From 47bfb47761d6c72d54eab6727de5e86cbf1da617 Mon Sep 17 00:00:00 2001 From: Dennis Eichhorn Date: Sat, 7 Jan 2023 18:57:49 +0100 Subject: [PATCH] update office and its dependencies --- Laminas/Escaper/Escaper.php | 412 ++ .../Escaper/Exception/ExceptionInterface.php | 11 + .../Exception/InvalidArgumentException.php | 13 + .../Escaper/Exception/RuntimeException.php | 13 + Laminas/Escaper/LICENSE.md | 26 + MyCLabs/Enum/Enum.php | 319 ++ MyCLabs/LICENSE | 18 + .../Common/Adapter/Zip/PclZipAdapter.php | 11 +- .../Common/Adapter/Zip/ZipArchiveAdapter.php | 3 +- PhpOffice/Common/Adapter/Zip/ZipInterface.php | 10 +- PhpOffice/Common/Autoloader.php | 11 +- PhpOffice/Common/COPYING | 674 ---- PhpOffice/Common/COPYING.LESSER | 165 - PhpOffice/Common/Drawing.php | 157 +- PhpOffice/Common/File.php | 42 +- PhpOffice/Common/Font.php | 50 +- PhpOffice/Common/LICENSE | 15 - PhpOffice/Common/Microsoft/OLERead.php | 160 +- .../Common/Microsoft/PasswordEncoder.php | 130 +- PhpOffice/Common/Text.php | 92 +- PhpOffice/Common/XMLReader.php | 90 +- PhpOffice/Common/XMLWriter.php | 38 +- PhpOffice/PhpPresentation/AbstractShape.php | 220 +- PhpOffice/PhpPresentation/Autoloader.php | 26 +- PhpOffice/PhpPresentation/COPYING | 674 ---- PhpOffice/PhpPresentation/COPYING.LESSER | 165 - .../PhpPresentation/ComparableInterface.php | 25 +- PhpOffice/PhpPresentation/DocumentLayout.php | 141 +- .../PhpPresentation/DocumentProperties.php | 222 +- .../Exception/DirectoryNotFoundException.php | 32 + .../FeatureNotImplementedException.php | 29 + .../Exception/FileCopyException.php | 33 + .../Exception/FileNotFoundException.php | 32 + .../Exception/FileRemoveException.php | 32 + .../Exception/InvalidClassException.php | 33 + .../Exception/InvalidFileFormatException.php | 42 + .../Exception/InvalidParameterException.php | 33 + .../Exception/OutOfBoundsException.php | 34 + .../PhpPresentationException.php} | 15 +- ...ShapeContainerAlreadyAssignedException.php | 32 + .../UnauthorizedMimetypeException.php | 37 + .../UndefinedChartTypeException.php} | 17 +- .../PhpPresentation/GeometryCalculator.php | 58 +- PhpOffice/PhpPresentation/HashTable.php | 115 +- PhpOffice/PhpPresentation/IOFactory.php | 90 +- PhpOffice/PhpPresentation/LICENSE | 15 - PhpOffice/PhpPresentation/PhpPresentation.php | 280 +- .../PresentationProperties.php | 186 +- .../PhpPresentation/Reader/ODPresentation.php | 594 ++- .../PhpPresentation/Reader/PowerPoint2007.php | 834 ++-- .../PhpPresentation/Reader/PowerPoint97.php | 2136 +++++----- .../Reader/ReaderInterface.php | 22 +- .../PhpPresentation/Reader/Serialized.php | 87 +- .../PhpPresentation/Shape/AbstractGraphic.php | 105 +- PhpOffice/PhpPresentation/Shape/AutoShape.php | 300 ++ PhpOffice/PhpPresentation/Shape/Chart.php | 132 +- .../PhpPresentation/Shape/Chart/Axis.php | 380 +- .../PhpPresentation/Shape/Chart/Gridlines.php | 20 +- .../PhpPresentation/Shape/Chart/Legend.php | 186 +- .../PhpPresentation/Shape/Chart/Marker.php | 116 +- .../PhpPresentation/Shape/Chart/PlotArea.php | 145 +- .../PhpPresentation/Shape/Chart/Series.php | 366 +- .../PhpPresentation/Shape/Chart/Title.php | 144 +- .../Shape/Chart/Type/AbstractType.php | 99 +- .../Shape/Chart/Type/AbstractTypeBar.php | 89 +- .../Shape/Chart/Type/AbstractTypeLine.php | 65 + .../Shape/Chart/Type/AbstractTypePie.php | 40 +- .../PhpPresentation/Shape/Chart/Type/Area.php | 12 +- .../PhpPresentation/Shape/Chart/Type/Bar.php | 11 +- .../Shape/Chart/Type/Bar3D.php | 11 +- .../Shape/Chart/Type/Doughnut.php | 19 +- .../PhpPresentation/Shape/Chart/Type/Line.php | 17 +- .../PhpPresentation/Shape/Chart/Type/Pie.php | 11 +- .../Shape/Chart/Type/Pie3D.php | 11 +- .../Shape/Chart/Type/Radar.php | 41 + .../Shape/Chart/Type/Scatter.php | 17 +- .../PhpPresentation/Shape/Chart/View3D.php | 97 +- PhpOffice/PhpPresentation/Shape/Comment.php | 47 +- .../PhpPresentation/Shape/Comment/Author.php | 28 +- .../Shape/Drawing/AbstractDrawingAdapter.php | 45 +- .../PhpPresentation/Shape/Drawing/Base64.php | 97 +- .../PhpPresentation/Shape/Drawing/File.php | 78 +- .../PhpPresentation/Shape/Drawing/Gd.php | 112 +- .../PhpPresentation/Shape/Drawing/ZipFile.php | 69 +- PhpOffice/PhpPresentation/Shape/Group.php | 210 +- PhpOffice/PhpPresentation/Shape/Hyperlink.php | 133 +- PhpOffice/PhpPresentation/Shape/Line.php | 13 +- PhpOffice/PhpPresentation/Shape/Media.php | 14 +- .../PhpPresentation/Shape/Placeholder.php | 61 +- PhpOffice/PhpPresentation/Shape/RichText.php | 463 ++- .../Shape/RichText/BreakElement.php | 43 +- .../Shape/RichText/Paragraph.php | 260 +- .../PhpPresentation/Shape/RichText/Run.php | 27 +- .../Shape/RichText/TextElement.php | 60 +- .../Shape/RichText/TextElementInterface.php | 21 +- PhpOffice/PhpPresentation/Shape/Table.php | 76 +- .../PhpPresentation/Shape/Table/Cell.php | 196 +- PhpOffice/PhpPresentation/Shape/Table/Row.php | 143 +- .../ShapeContainerInterface.php | 60 +- PhpOffice/PhpPresentation/Slide.php | 109 +- .../Slide/AbstractBackground.php | 19 +- .../PhpPresentation/Slide/AbstractSlide.php | 223 +- PhpOffice/PhpPresentation/Slide/Animation.php | 35 +- .../Slide/Background/Color.php | 32 +- .../Slide/Background/Image.php | 61 +- .../Slide/Background/SchemeColor.php | 29 +- PhpOffice/PhpPresentation/Slide/Iterator.php | 43 +- PhpOffice/PhpPresentation/Slide/Layout.php | 29 +- PhpOffice/PhpPresentation/Slide/Note.php | 125 +- .../PhpPresentation/Slide/SlideLayout.php | 42 +- .../PhpPresentation/Slide/SlideMaster.php | 77 +- .../PhpPresentation/Slide/Transition.php | 156 +- PhpOffice/PhpPresentation/Style/Alignment.php | 275 +- PhpOffice/PhpPresentation/Style/Border.php | 150 +- PhpOffice/PhpPresentation/Style/Borders.php | 69 +- PhpOffice/PhpPresentation/Style/Bullet.php | 198 +- PhpOffice/PhpPresentation/Style/Color.php | 100 +- PhpOffice/PhpPresentation/Style/ColorMap.php | 71 +- PhpOffice/PhpPresentation/Style/Fill.php | 163 +- PhpOffice/PhpPresentation/Style/Font.php | 377 +- PhpOffice/PhpPresentation/Style/Outline.php | 42 +- .../PhpPresentation/Style/SchemeColor.php | 20 +- PhpOffice/PhpPresentation/Style/Shadow.php | 182 +- PhpOffice/PhpPresentation/Style/TextStyle.php | 90 +- .../Writer/AbstractDecoratorWriter.php | 26 +- .../PhpPresentation/Writer/AbstractWriter.php | 90 +- .../PhpPresentation/Writer/ODPresentation.php | 89 +- .../AbstractDecoratorWriter.php | 20 + .../Writer/ODPresentation/Content.php | 461 +-- .../Writer/ODPresentation/Meta.php | 60 +- .../Writer/ODPresentation/MetaInfManifest.php | 35 +- .../Writer/ODPresentation/Mimetype.php | 24 +- .../Writer/ODPresentation/ObjectsChart.php | 518 ++- .../Writer/ODPresentation/Pictures.php | 28 +- .../Writer/ODPresentation/Styles.php | 107 +- .../ODPresentation/ThumbnailsThumbnail.php | 27 +- .../PhpPresentation/Writer/PowerPoint2007.php | 116 +- .../AbstractDecoratorWriter.php | 160 +- .../Writer/PowerPoint2007/AbstractSlide.php | 463 ++- .../Writer/PowerPoint2007/CommentAuthors.php | 29 +- .../Writer/PowerPoint2007/ContentTypes.php | 69 +- .../Writer/PowerPoint2007/DocPropsApp.php | 27 +- .../Writer/PowerPoint2007/DocPropsCore.php | 25 +- .../Writer/PowerPoint2007/DocPropsCustom.php | 62 +- .../PowerPoint2007/DocPropsThumbnail.php | 26 +- .../LayoutPack/AbstractLayoutPack.php | 225 -- .../PowerPoint2007/LayoutPack/PackDefault.php | 3281 --------------- .../LayoutPack/TemplateBased.php | 203 - .../Writer/PowerPoint2007/PptCharts.php | 761 ++-- .../Writer/PowerPoint2007/PptComments.php | 29 +- .../Writer/PowerPoint2007/PptMedia.php | 24 +- .../Writer/PowerPoint2007/PptPresProps.php | 36 +- .../Writer/PowerPoint2007/PptPresentation.php | 32 +- .../Writer/PowerPoint2007/PptSlideLayouts.php | 44 +- .../Writer/PowerPoint2007/PptSlideMasters.php | 69 +- .../Writer/PowerPoint2007/PptSlides.php | 208 +- .../Writer/PowerPoint2007/PptTableProps.php | 25 +- .../Writer/PowerPoint2007/PptTheme.php | 48 +- .../Writer/PowerPoint2007/PptViewProps.php | 29 +- .../Writer/PowerPoint2007/Relationships.php | 44 +- .../PhpPresentation/Writer/Serialized.php | 52 +- .../Writer/WriterInterface.php | 12 +- .../Calculation/ArrayEnabled.php | 133 + .../Calculation/BinaryComparison.php | 181 + .../Calculation/Calculation.php | 3539 +++++++++++------ .../PhpSpreadsheet/Calculation/Category.php | 2 + .../PhpSpreadsheet/Calculation/Database.php | 326 +- .../Calculation/Database/DAverage.php | 46 + .../Calculation/Database/DCount.php | 47 + .../Calculation/Database/DCountA.php | 46 + .../Calculation/Database/DGet.php | 51 + .../Calculation/Database/DMax.php | 47 + .../Calculation/Database/DMin.php | 47 + .../Calculation/Database/DProduct.php | 46 + .../Calculation/Database/DStDev.php | 47 + .../Calculation/Database/DStDevP.php | 47 + .../Calculation/Database/DSum.php | 46 + .../Calculation/Database/DVar.php | 47 + .../Calculation/Database/DVarP.php | 47 + .../Calculation/Database/DatabaseAbstract.php | 192 + .../PhpSpreadsheet/Calculation/DateTime.php | 1427 ++----- .../Calculation/DateTimeExcel/Constants.php | 38 + .../Calculation/DateTimeExcel/Current.php | 59 + .../Calculation/DateTimeExcel/Date.php | 172 + .../Calculation/DateTimeExcel/DateParts.php | 151 + .../Calculation/DateTimeExcel/DateValue.php | 157 + .../Calculation/DateTimeExcel/Days.php | 62 + .../Calculation/DateTimeExcel/Days360.php | 118 + .../Calculation/DateTimeExcel/Difference.php | 158 + .../Calculation/DateTimeExcel/Helpers.php | 307 ++ .../Calculation/DateTimeExcel/Month.php | 101 + .../Calculation/DateTimeExcel/NetworkDays.php | 119 + .../Calculation/DateTimeExcel/Time.php | 130 + .../Calculation/DateTimeExcel/TimeParts.php | 132 + .../Calculation/DateTimeExcel/TimeValue.php | 78 + .../Calculation/DateTimeExcel/Week.php | 278 ++ .../Calculation/DateTimeExcel/WorkDay.php | 201 + .../Calculation/DateTimeExcel/YearFrac.php | 133 + .../Engine/ArrayArgumentHelper.php | 209 + .../Engine/ArrayArgumentProcessor.php | 175 + .../Calculation/Engine/BranchPruner.php | 223 ++ .../Engine/CyclicReferenceStack.php | 4 +- .../Calculation/Engine/FormattedNumber.php | 126 + .../Calculation/Engine/Logger.php | 42 +- .../Calculation/Engine/Operands/Operand.php | 10 + .../Engine/Operands/StructuredReference.php | 332 ++ .../Calculation/Engineering.php | 2128 ++-------- .../Calculation/Engineering/BesselI.php | 152 + .../Calculation/Engineering/BesselJ.php | 180 + .../Calculation/Engineering/BesselK.php | 135 + .../Calculation/Engineering/BesselY.php | 141 + .../Calculation/Engineering/BitWise.php | 277 ++ .../Calculation/Engineering/Compare.php | 82 + .../Calculation/Engineering/Complex.php | 121 + .../Engineering/ComplexFunctions.php | 611 +++ .../Engineering/ComplexOperations.php | 134 + .../Calculation/Engineering/Constants.php | 11 + .../Calculation/Engineering/ConvertBase.php | 69 + .../Calculation/Engineering/ConvertBinary.php | 163 + .../Engineering/ConvertDecimal.php | 213 + .../Calculation/Engineering/ConvertHex.php | 175 + .../Calculation/Engineering/ConvertOctal.php | 174 + .../Calculation/Engineering/ConvertUOM.php | 694 ++++ .../Engineering/EngineeringValidations.php | 33 + .../Calculation/Engineering/Erf.php | 105 + .../Calculation/Engineering/ErfC.php | 77 + .../PhpSpreadsheet/Calculation/Exception.php | 4 +- .../Calculation/ExceptionHandler.php | 4 +- .../PhpSpreadsheet/Calculation/Financial.php | 1926 +++------ .../Calculation/Financial/Amortization.php | 214 + .../CashFlow/CashFlowValidations.php | 53 + .../Financial/CashFlow/Constant/Periodic.php | 200 + .../CashFlow/Constant/Periodic/Cumulative.php | 142 + .../CashFlow/Constant/Periodic/Interest.php | 219 + .../Periodic/InterestAndPrincipal.php | 44 + .../CashFlow/Constant/Periodic/Payments.php | 116 + .../Calculation/Financial/CashFlow/Single.php | 109 + .../CashFlow/Variable/NonPeriodic.php | 325 ++ .../Financial/CashFlow/Variable/Periodic.php | 168 + .../Calculation/Financial/Constants.php | 19 + .../Calculation/Financial/Coupons.php | 426 ++ .../Calculation/Financial/Depreciation.php | 270 ++ .../Calculation/Financial/Dollar.php | 132 + .../Financial/FinancialValidations.php | 158 + .../Calculation/Financial/Helpers.php | 58 + .../Calculation/Financial/InterestRate.php | 73 + .../Financial/Securities/AccruedInterest.php | 159 + .../Financial/Securities/Price.php | 284 ++ .../Financial/Securities/Rates.php | 138 + .../Securities/SecurityValidations.php | 42 + .../Financial/Securities/Yields.php | 153 + .../Calculation/Financial/TreasuryBill.php | 148 + .../Calculation/FormulaParser.php | 79 +- .../Calculation/FormulaToken.php | 20 +- .../PhpSpreadsheet/Calculation/Functions.php | 602 ++- .../Calculation/Information/ErrorValue.php | 71 + .../Calculation/Information/ExcelError.php | 171 + .../Calculation/Information/Value.php | 328 ++ .../Calculation/Internal/MakeMatrix.php | 11 + .../Calculation/Internal/WildcardMatch.php | 39 + .../PhpSpreadsheet/Calculation/Logical.php | 254 +- .../Calculation/Logical/Boolean.php | 36 + .../Calculation/Logical/Conditional.php | 211 + .../Calculation/Logical/Operations.php | 207 + .../PhpSpreadsheet/Calculation/LookupRef.php | 896 +---- .../Calculation/LookupRef/Address.php | 124 + .../Calculation/LookupRef/ExcelMatch.php | 282 ++ .../Calculation/LookupRef/Filter.php | 81 + .../Calculation/LookupRef/Formula.php | 43 + .../Calculation/LookupRef/HLookup.php | 121 + .../Calculation/LookupRef/Helpers.php | 74 + .../Calculation/LookupRef/Hyperlink.php | 41 + .../Calculation/LookupRef/Indirect.php | 130 + .../Calculation/LookupRef/Lookup.php | 110 + .../Calculation/LookupRef/LookupBase.php | 66 + .../LookupRef/LookupRefValidations.php | 40 + .../Calculation/LookupRef/Matrix.php | 142 + .../Calculation/LookupRef/Offset.php | 148 + .../LookupRef/RowColumnInformation.php | 209 + .../Calculation/LookupRef/Selection.php | 51 + .../Calculation/LookupRef/Sort.php | 342 ++ .../Calculation/LookupRef/Unique.php | 141 + .../Calculation/LookupRef/VLookup.php | 117 + .../PhpSpreadsheet/Calculation/MathTrig.php | 1866 ++++----- .../Calculation/MathTrig/Absolute.php | 37 + .../Calculation/MathTrig/Angle.php | 63 + .../Calculation/MathTrig/Arabic.php | 112 + .../Calculation/MathTrig/Base.php | 68 + .../Calculation/MathTrig/Ceiling.php | 167 + .../Calculation/MathTrig/Combinations.php | 91 + .../Calculation/MathTrig/Exp.php | 37 + .../Calculation/MathTrig/Factorial.php | 125 + .../Calculation/MathTrig/Floor.php | 195 + .../Calculation/MathTrig/Gcd.php | 70 + .../Calculation/MathTrig/Helpers.php | 130 + .../Calculation/MathTrig/IntClass.php | 40 + .../Calculation/MathTrig/Lcm.php | 111 + .../Calculation/MathTrig/Logarithms.php | 102 + .../Calculation/MathTrig/MatrixFunctions.php | 179 + .../Calculation/MathTrig/Operations.php | 162 + .../Calculation/MathTrig/Random.php | 99 + .../Calculation/MathTrig/Roman.php | 846 ++++ .../Calculation/MathTrig/Round.php | 218 + .../Calculation/MathTrig/SeriesSum.php | 53 + .../Calculation/MathTrig/Sign.php | 38 + .../Calculation/MathTrig/Sqrt.php | 64 + .../Calculation/MathTrig/Subtotal.php | 135 + .../Calculation/MathTrig/Sum.php | 118 + .../Calculation/MathTrig/SumSquares.php | 143 + .../Calculation/MathTrig/Trig/Cosecant.php | 64 + .../Calculation/MathTrig/Trig/Cosine.php | 116 + .../Calculation/MathTrig/Trig/Cotangent.php | 118 + .../Calculation/MathTrig/Trig/Secant.php | 64 + .../Calculation/MathTrig/Trig/Sine.php | 116 + .../Calculation/MathTrig/Trig/Tangent.php | 161 + .../Calculation/MathTrig/Trunc.php | 50 + .../Calculation/Statistical.php | 3172 +++------------ .../Calculation/Statistical/AggregateBase.php | 70 + .../Calculation/Statistical/Averages.php | 259 ++ .../Calculation/Statistical/Averages/Mean.php | 132 + .../Calculation/Statistical/Conditional.php | 310 ++ .../Calculation/Statistical/Confidence.php | 52 + .../Calculation/Statistical/Counts.php | 102 + .../Calculation/Statistical/Deviations.php | 142 + .../Statistical/Distributions/Beta.php | 283 ++ .../Statistical/Distributions/Binomial.php | 237 ++ .../Statistical/Distributions/ChiSquared.php | 337 ++ .../Distributions/DistributionValidations.php | 24 + .../Statistical/Distributions/Exponential.php | 55 + .../Statistical/Distributions/F.php | 64 + .../Statistical/Distributions/Fisher.php | 74 + .../Statistical/Distributions/Gamma.php | 151 + .../Statistical/Distributions/GammaBase.php | 380 ++ .../Distributions/HyperGeometric.php | 76 + .../Statistical/Distributions/LogNormal.php | 139 + .../Distributions/NewtonRaphson.php | 63 + .../Statistical/Distributions/Normal.php | 180 + .../Statistical/Distributions/Poisson.php | 66 + .../Distributions/StandardNormal.php | 151 + .../Statistical/Distributions/StudentT.php | 139 + .../Statistical/Distributions/Weibull.php | 57 + .../Calculation/Statistical/MaxMinBase.php | 17 + .../Calculation/Statistical/Maximum.php | 89 + .../Calculation/Statistical/Minimum.php | 89 + .../Calculation/Statistical/Percentiles.php | 206 + .../Calculation/Statistical/Permutations.php | 90 + .../Calculation/Statistical/Size.php | 97 + .../Statistical/StandardDeviations.php | 95 + .../Calculation/Statistical/Standardize.php | 49 + .../Statistical/StatisticalValidations.php | 45 + .../Calculation/Statistical/Trends.php | 430 ++ .../Calculation/Statistical/VarianceBase.php | 28 + .../Calculation/Statistical/Variances.php | 186 + .../PhpSpreadsheet/Calculation/TextData.php | 598 +-- .../Calculation/TextData/CaseConvert.php | 80 + .../Calculation/TextData/CharacterConvert.php | 81 + .../Calculation/TextData/Concatenate.php | 137 + .../Calculation/TextData/Extract.php | 280 ++ .../Calculation/TextData/Format.php | 314 ++ .../Calculation/TextData/Helpers.php | 91 + .../Calculation/TextData/Replace.php | 118 + .../Calculation/TextData/Search.php | 97 + .../Calculation/TextData/Text.php | 255 ++ .../Calculation/TextData/Trim.php | 52 + .../Calculation/Token/Stack.php | 96 +- PhpOffice/PhpSpreadsheet/Calculation/Web.php | 30 + .../Calculation/Web/Service.php | 75 + .../Calculation/functionlist.txt | 388 -- .../Calculation/locale/Translations.xlsx | Bin 0 -> 108747 bytes .../Calculation/locale/bg/config | 0 .../Calculation/locale/bg/functions | 0 .../Calculation/locale/cs/config | 31 +- .../Calculation/locale/cs/functions | 856 ++-- .../Calculation/locale/da/config | 33 +- .../Calculation/locale/da/functions | 874 ++-- .../Calculation/locale/de/config | 32 +- .../Calculation/locale/de/functions | 870 ++-- .../Calculation/locale/en/uk/config | 0 .../Calculation/locale/es/config | 32 +- .../Calculation/locale/es/functions | 874 ++-- .../Calculation/locale/fi/config | 32 +- .../Calculation/locale/fi/functions | 874 ++-- .../Calculation/locale/fr/config | 32 +- .../Calculation/locale/fr/functions | 861 ++-- .../Calculation/locale/hu/config | 31 +- .../Calculation/locale/hu/functions | 874 ++-- .../Calculation/locale/it/config | 32 +- .../Calculation/locale/it/functions | 873 ++-- .../Calculation/locale/nb/config | 20 + .../Calculation/locale/nb/functions | 539 +++ .../Calculation/locale/nl/config | 32 +- .../Calculation/locale/nl/functions | 873 ++-- .../Calculation/locale/no/config | 24 - .../Calculation/locale/no/functions | 416 -- .../Calculation/locale/pl/config | 32 +- .../Calculation/locale/pl/functions | 872 ++-- .../Calculation/locale/pt/br/config | 32 +- .../Calculation/locale/pt/br/functions | 864 ++-- .../Calculation/locale/pt/config | 32 +- .../Calculation/locale/pt/functions | 874 ++-- .../Calculation/locale/ru/config | 32 +- .../Calculation/locale/ru/functions | 891 +++-- .../Calculation/locale/sv/config | 32 +- .../Calculation/locale/sv/functions | 869 ++-- .../Calculation/locale/tr/config | 32 +- .../Calculation/locale/tr/functions | 873 ++-- .../PhpSpreadsheet/Cell/AddressHelper.php | 177 + .../PhpSpreadsheet/Cell/AddressRange.php | 24 + .../Cell/AdvancedValueBinder.php | 158 +- PhpOffice/PhpSpreadsheet/Cell/Cell.php | 435 +- PhpOffice/PhpSpreadsheet/Cell/CellAddress.php | 166 + PhpOffice/PhpSpreadsheet/Cell/CellRange.php | 136 + PhpOffice/PhpSpreadsheet/Cell/ColumnRange.php | 125 + PhpOffice/PhpSpreadsheet/Cell/Coordinate.php | 327 +- PhpOffice/PhpSpreadsheet/Cell/DataType.php | 36 +- .../PhpSpreadsheet/Cell/DataValidation.php | 120 +- .../PhpSpreadsheet/Cell/DataValidator.php | 7 +- .../Cell/DefaultValueBinder.php | 43 +- PhpOffice/PhpSpreadsheet/Cell/Hyperlink.php | 26 +- .../PhpSpreadsheet/Cell/IValueBinder.php | 0 PhpOffice/PhpSpreadsheet/Cell/RowRange.php | 93 + .../PhpSpreadsheet/Cell/StringValueBinder.php | 105 +- .../PhpSpreadsheet/CellReferenceHelper.php | 131 + PhpOffice/PhpSpreadsheet/Chart/Axis.php | 577 +-- PhpOffice/PhpSpreadsheet/Chart/Chart.php | 417 +- PhpOffice/PhpSpreadsheet/Chart/ChartColor.php | 177 + PhpOffice/PhpSpreadsheet/Chart/DataSeries.php | 75 +- .../PhpSpreadsheet/Chart/DataSeriesValues.php | 280 +- PhpOffice/PhpSpreadsheet/Chart/Exception.php | 0 PhpOffice/PhpSpreadsheet/Chart/GridLines.php | 442 -- PhpOffice/PhpSpreadsheet/Chart/Layout.php | 338 +- PhpOffice/PhpSpreadsheet/Chart/Legend.php | 30 +- PhpOffice/PhpSpreadsheet/Chart/PlotArea.php | 84 +- PhpOffice/PhpSpreadsheet/Chart/Properties.php | 1072 +++-- .../Chart/Renderer/IRenderer.php | 2 - .../PhpSpreadsheet/Chart/Renderer/JpGraph.php | 846 +--- .../Chart/Renderer/JpGraphRendererBase.php | 852 ++++ .../Chart/Renderer/MtJpGraphRenderer.php | 36 + .../Chart/Renderer/PHP Charting Libraries.txt | 7 +- .../Chart/Renderer/Polyfill.php | 9 - PhpOffice/PhpSpreadsheet/Chart/Title.php | 49 +- PhpOffice/PhpSpreadsheet/Chart/TrendLine.php | 226 ++ PhpOffice/PhpSpreadsheet/Collection/Cells.php | 348 +- .../Collection/CellsFactory.php | 9 +- .../{Memory.php => Memory/SimpleCache1.php} | 53 +- .../Collection/Memory/SimpleCache3.php | 109 + PhpOffice/PhpSpreadsheet/Comment.php | 203 +- PhpOffice/PhpSpreadsheet/DefinedName.php | 273 ++ .../PhpSpreadsheet/Document/Properties.php | 456 +-- .../PhpSpreadsheet/Document/Security.php | 125 +- PhpOffice/PhpSpreadsheet/Exception.php | 0 PhpOffice/PhpSpreadsheet/HashTable.php | 87 +- PhpOffice/PhpSpreadsheet/Helper/Dimension.php | 115 + PhpOffice/PhpSpreadsheet/Helper/Html.php | 189 +- PhpOffice/PhpSpreadsheet/Helper/Migrator.php | 333 -- PhpOffice/PhpSpreadsheet/Helper/Sample.php | 83 +- PhpOffice/PhpSpreadsheet/Helper/Size.php | 52 + PhpOffice/PhpSpreadsheet/Helper/TextGrid.php | 139 + PhpOffice/PhpSpreadsheet/IComparable.php | 0 PhpOffice/PhpSpreadsheet/IOFactory.php | 163 +- PhpOffice/PhpSpreadsheet/NamedFormula.php | 45 + PhpOffice/PhpSpreadsheet/NamedRange.php | 235 +- .../PhpSpreadsheet/Reader/BaseReader.php | 88 +- PhpOffice/PhpSpreadsheet/Reader/Csv.php | 636 +-- .../PhpSpreadsheet/Reader/Csv/Delimiter.php | 151 + .../Reader/DefaultReadFilter.php | 4 +- PhpOffice/PhpSpreadsheet/Reader/Exception.php | 0 PhpOffice/PhpSpreadsheet/Reader/Gnumeric.php | 1071 ++--- .../Reader/Gnumeric/PageSetup.php | 139 + .../Reader/Gnumeric/Properties.php | 164 + .../PhpSpreadsheet/Reader/Gnumeric/Styles.php | 281 ++ PhpOffice/PhpSpreadsheet/Reader/Html.php | 867 ++-- .../PhpSpreadsheet/Reader/IReadFilter.php | 4 +- PhpOffice/PhpSpreadsheet/Reader/IReader.php | 39 +- PhpOffice/PhpSpreadsheet/Reader/Ods.php | 452 ++- .../PhpSpreadsheet/Reader/Ods/AutoFilter.php | 45 + .../PhpSpreadsheet/Reader/Ods/BaseLoader.php | 27 + .../Reader/Ods/DefinedNames.php | 66 + .../Reader/Ods/FormulaTranslator.php | 97 + .../Reader/Ods/PageSettings.php | 186 + .../PhpSpreadsheet/Reader/Ods/Properties.php | 33 +- .../Reader/Security/XmlScanner.php | 45 +- PhpOffice/PhpSpreadsheet/Reader/Slk.php | 666 ++-- PhpOffice/PhpSpreadsheet/Reader/Xls.php | 979 +++-- PhpOffice/PhpSpreadsheet/Reader/Xls/Color.php | 2 +- .../PhpSpreadsheet/Reader/Xls/Color/BIFF5.php | 8 +- .../PhpSpreadsheet/Reader/Xls/Color/BIFF8.php | 8 +- .../Reader/Xls/Color/BuiltIn.php | 8 +- .../Reader/Xls/ConditionalFormatting.php | 49 + .../Reader/Xls/DataValidationHelper.php | 72 + .../PhpSpreadsheet/Reader/Xls/ErrorCode.php | 8 +- .../PhpSpreadsheet/Reader/Xls/Escher.php | 269 +- PhpOffice/PhpSpreadsheet/Reader/Xls/MD5.php | 70 +- PhpOffice/PhpSpreadsheet/Reader/Xls/RC4.php | 6 +- .../Reader/Xls/Style/Border.php | 19 +- .../Reader/Xls/Style/CellAlignment.php | 50 + .../Reader/Xls/Style/CellFont.php | 39 + .../Reader/Xls/Style/FillPattern.php | 9 +- PhpOffice/PhpSpreadsheet/Reader/Xlsx.php | 1961 ++++----- .../PhpSpreadsheet/Reader/Xlsx/AutoFilter.php | 59 +- .../Reader/Xlsx/BaseParserClass.php | 9 +- .../PhpSpreadsheet/Reader/Xlsx/Chart.php | 1133 +++++- .../Reader/Xlsx/ColumnAndRowAttributes.php | 125 +- .../Reader/Xlsx/ConditionalStyles.php | 238 +- .../Reader/Xlsx/DataValidations.php | 19 +- .../PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php | 42 +- .../PhpSpreadsheet/Reader/Xlsx/Namespaces.php | 118 + .../PhpSpreadsheet/Reader/Xlsx/PageSetup.php | 71 +- .../PhpSpreadsheet/Reader/Xlsx/Properties.php | 44 +- .../Reader/Xlsx/SheetViewOptions.php | 78 +- .../PhpSpreadsheet/Reader/Xlsx/SheetViews.php | 95 +- .../PhpSpreadsheet/Reader/Xlsx/Styles.php | 373 +- .../Reader/Xlsx/TableReader.php | 113 + .../PhpSpreadsheet/Reader/Xlsx/Theme.php | 41 +- .../Reader/Xlsx/WorkbookView.php | 153 + PhpOffice/PhpSpreadsheet/Reader/Xml.php | 675 +--- .../Reader/Xml/PageSettings.php | 135 + .../PhpSpreadsheet/Reader/Xml/Properties.php | 157 + PhpOffice/PhpSpreadsheet/Reader/Xml/Style.php | 83 + .../Reader/Xml/Style/Alignment.php | 58 + .../Reader/Xml/Style/Border.php | 98 + .../PhpSpreadsheet/Reader/Xml/Style/Fill.php | 63 + .../PhpSpreadsheet/Reader/Xml/Style/Font.php | 79 + .../Reader/Xml/Style/NumberFormat.php | 33 + .../Reader/Xml/Style/StyleBase.php | 32 + PhpOffice/PhpSpreadsheet/ReferenceHelper.php | 1144 ++++-- .../PhpSpreadsheet/RichText/ITextElement.php | 2 +- .../PhpSpreadsheet/RichText/RichText.php | 54 +- PhpOffice/PhpSpreadsheet/RichText/Run.php | 14 +- .../PhpSpreadsheet/RichText/TextElement.php | 25 +- PhpOffice/PhpSpreadsheet/Settings.php | 167 +- PhpOffice/PhpSpreadsheet/Shared/CodePage.php | 208 +- PhpOffice/PhpSpreadsheet/Shared/Date.php | 272 +- PhpOffice/PhpSpreadsheet/Shared/Drawing.php | 188 +- PhpOffice/PhpSpreadsheet/Shared/Escher.php | 0 .../Shared/Escher/DgContainer.php | 4 +- .../Escher/DgContainer/SpgrContainer.php | 12 +- .../DgContainer/SpgrContainer/SpContainer.php | 24 +- .../Shared/Escher/DggContainer.php | 16 +- .../Escher/DggContainer/BstoreContainer.php | 6 +- .../DggContainer/BstoreContainer/BSE.php | 14 +- .../DggContainer/BstoreContainer/BSE/Blip.php | 14 +- PhpOffice/PhpSpreadsheet/Shared/File.php | 125 +- PhpOffice/PhpSpreadsheet/Shared/Font.php | 631 ++- .../PhpSpreadsheet/Shared/IntOrFloat.php | 21 + .../PhpSpreadsheet/Shared/JAMA/CHANGELOG.TXT | 16 - .../Shared/JAMA/CholeskyDecomposition.php | 147 - .../Shared/JAMA/EigenvalueDecomposition.php | 861 ---- .../Shared/JAMA/LUDecomposition.php | 285 -- .../PhpSpreadsheet/Shared/JAMA/Matrix.php | 1233 ------ .../Shared/JAMA/QRDecomposition.php | 249 -- .../JAMA/SingularValueDecomposition.php | 528 --- .../Shared/JAMA/utils/Maths.php | 30 - PhpOffice/PhpSpreadsheet/Shared/OLE.php | 146 +- .../Shared/OLE/ChainedBlockStream.php | 17 +- PhpOffice/PhpSpreadsheet/Shared/OLE/PPS.php | 33 +- .../PhpSpreadsheet/Shared/OLE/PPS/File.php | 4 +- .../PhpSpreadsheet/Shared/OLE/PPS/Root.php | 113 +- PhpOffice/PhpSpreadsheet/Shared/OLERead.php | 30 +- .../PhpSpreadsheet/Shared/PasswordHasher.php | 104 +- .../PhpSpreadsheet/Shared/StringHelper.php | 258 +- PhpOffice/PhpSpreadsheet/Shared/TimeZone.php | 44 +- .../PhpSpreadsheet/Shared/Trend/BestFit.php | 109 +- .../Shared/Trend/ExponentialBestFit.php | 23 +- .../Shared/Trend/LinearBestFit.php | 5 +- .../Shared/Trend/LogarithmicBestFit.php | 23 +- .../Shared/Trend/PolynomialBestFit.php | 31 +- .../Shared/Trend/PowerBestFit.php | 39 +- .../PhpSpreadsheet/Shared/Trend/Trend.php | 18 +- PhpOffice/PhpSpreadsheet/Shared/XMLWriter.php | 31 +- PhpOffice/PhpSpreadsheet/Shared/Xls.php | 106 +- PhpOffice/PhpSpreadsheet/Spreadsheet.php | 671 ++-- PhpOffice/PhpSpreadsheet/Style/Alignment.php | 243 +- PhpOffice/PhpSpreadsheet/Style/Border.php | 95 +- PhpOffice/PhpSpreadsheet/Style/Borders.php | 101 +- PhpOffice/PhpSpreadsheet/Style/Color.php | 375 +- .../PhpSpreadsheet/Style/Conditional.php | 144 +- .../ConditionalFormatting/CellMatcher.php | 313 ++ .../CellStyleAssessor.php | 45 + .../ConditionalDataBar.php | 93 + .../ConditionalDataBarExtension.php | 290 ++ .../ConditionalFormatValueObject.php | 78 + .../ConditionalFormattingRuleExtension.php | 209 + .../ConditionalFormatting/StyleMerger.php | 118 + .../Style/ConditionalFormatting/Wizard.php | 95 + .../ConditionalFormatting/Wizard/Blanks.php | 99 + .../Wizard/CellValue.php | 200 + .../Wizard/DateValue.php | 111 + .../Wizard/Duplicates.php | 78 + .../ConditionalFormatting/Wizard/Errors.php | 95 + .../Wizard/Expression.php | 75 + .../Wizard/TextValue.php | 164 + .../Wizard/WizardAbstract.php | 199 + .../Wizard/WizardInterface.php | 25 + PhpOffice/PhpSpreadsheet/Style/Fill.php | 121 +- PhpOffice/PhpSpreadsheet/Style/Font.php | 487 ++- .../PhpSpreadsheet/Style/NumberFormat.php | 540 +-- .../Style/NumberFormat/BaseFormatter.php | 12 + .../Style/NumberFormat/DateFormatter.php | 182 + .../Style/NumberFormat/Formatter.php | 165 + .../Style/NumberFormat/FractionFormatter.php | 67 + .../Style/NumberFormat/NumberFormatter.php | 278 ++ .../NumberFormat/PercentageFormatter.php | 47 + PhpOffice/PhpSpreadsheet/Style/Protection.php | 54 +- PhpOffice/PhpSpreadsheet/Style/Style.php | 354 +- PhpOffice/PhpSpreadsheet/Style/Supervisor.php | 64 +- .../PhpSpreadsheet/Worksheet/AutoFilter.php | 809 ++-- .../Worksheet/AutoFilter/Column.php | 160 +- .../Worksheet/AutoFilter/Column/Rule.php | 197 +- .../PhpSpreadsheet/Worksheet/BaseDrawing.php | 434 +- .../PhpSpreadsheet/Worksheet/CellIterator.php | 37 +- PhpOffice/PhpSpreadsheet/Worksheet/Column.php | 66 +- .../Worksheet/ColumnCellIterator.php | 104 +- .../Worksheet/ColumnDimension.php | 80 +- .../Worksheet/ColumnIterator.php | 51 +- .../PhpSpreadsheet/Worksheet/Dimension.php | 65 +- .../PhpSpreadsheet/Worksheet/Drawing.php | 142 +- .../Worksheet/Drawing/Shadow.php | 60 +- .../PhpSpreadsheet/Worksheet/HeaderFooter.php | 87 +- .../Worksheet/HeaderFooterDrawing.php | 0 .../PhpSpreadsheet/Worksheet/Iterator.php | 29 +- .../Worksheet/MemoryDrawing.php | 213 +- .../PhpSpreadsheet/Worksheet/PageMargins.php | 78 +- .../PhpSpreadsheet/Worksheet/PageSetup.php | 246 +- .../PhpSpreadsheet/Worksheet/Protection.php | 569 ++- PhpOffice/PhpSpreadsheet/Worksheet/Row.php | 53 +- .../Worksheet/RowCellIterator.php | 84 +- .../PhpSpreadsheet/Worksheet/RowDimension.php | 53 +- .../PhpSpreadsheet/Worksheet/RowIterator.php | 52 +- .../PhpSpreadsheet/Worksheet/SheetView.php | 78 +- PhpOffice/PhpSpreadsheet/Worksheet/Table.php | 585 +++ .../PhpSpreadsheet/Worksheet/Table/Column.php | 254 ++ .../Worksheet/Table/TableStyle.php | 230 ++ .../PhpSpreadsheet/Worksheet/Validations.php | 115 + .../PhpSpreadsheet/Worksheet/Worksheet.php | 1941 +++++---- .../PhpSpreadsheet/Writer/BaseWriter.php | 84 +- PhpOffice/PhpSpreadsheet/Writer/Csv.php | 228 +- PhpOffice/PhpSpreadsheet/Writer/Exception.php | 0 PhpOffice/PhpSpreadsheet/Writer/Html.php | 1677 ++++---- PhpOffice/PhpSpreadsheet/Writer/IWriter.php | 31 +- PhpOffice/PhpSpreadsheet/Writer/Ods.php | 191 +- .../PhpSpreadsheet/Writer/Ods/AutoFilters.php | 66 + .../Writer/Ods/Cell/Comment.php | 5 +- .../PhpSpreadsheet/Writer/Ods/Cell/Style.php | 262 ++ .../PhpSpreadsheet/Writer/Ods/Content.php | 240 +- .../PhpSpreadsheet/Writer/Ods/Formula.php | 119 + PhpOffice/PhpSpreadsheet/Writer/Ods/Meta.php | 73 +- .../PhpSpreadsheet/Writer/Ods/MetaInf.php | 4 +- .../PhpSpreadsheet/Writer/Ods/Mimetype.php | 6 +- .../Writer/Ods/NamedExpressions.php | 140 + .../PhpSpreadsheet/Writer/Ods/Settings.php | 120 +- .../PhpSpreadsheet/Writer/Ods/Styles.php | 7 +- .../PhpSpreadsheet/Writer/Ods/Thumbnails.php | 6 +- .../PhpSpreadsheet/Writer/Ods/WriterPart.php | 4 +- PhpOffice/PhpSpreadsheet/Writer/Pdf.php | 82 +- .../PhpSpreadsheet/Writer/Pdf/Dompdf.php | 60 +- PhpOffice/PhpSpreadsheet/Writer/Pdf/Mpdf.php | 67 +- PhpOffice/PhpSpreadsheet/Writer/Pdf/Tcpdf.php | 64 +- PhpOffice/PhpSpreadsheet/Writer/Xls.php | 396 +- .../PhpSpreadsheet/Writer/Xls/BIFFwriter.php | 8 +- .../Writer/Xls/CellDataValidation.php | 78 + .../Writer/Xls/ConditionalHelper.php | 76 + .../PhpSpreadsheet/Writer/Xls/ErrorCode.php | 28 + .../PhpSpreadsheet/Writer/Xls/Escher.php | 10 +- PhpOffice/PhpSpreadsheet/Writer/Xls/Font.php | 27 +- .../PhpSpreadsheet/Writer/Xls/Parser.php | 181 +- .../Writer/Xls/Style/CellAlignment.php | 59 + .../Writer/Xls/Style/CellBorder.php | 39 + .../Writer/Xls/Style/CellFill.php | 46 + .../Writer/Xls/Style/ColorMap.php | 90 + .../PhpSpreadsheet/Writer/Xls/Workbook.php | 155 +- .../PhpSpreadsheet/Writer/Xls/Worksheet.php | 2081 ++-------- PhpOffice/PhpSpreadsheet/Writer/Xls/Xf.php | 304 +- PhpOffice/PhpSpreadsheet/Writer/Xlsx.php | 852 ++-- .../PhpSpreadsheet/Writer/Xlsx/AutoFilter.php | 125 + .../PhpSpreadsheet/Writer/Xlsx/Chart.php | 1564 ++++---- .../PhpSpreadsheet/Writer/Xlsx/Comments.php | 70 +- .../Writer/Xlsx/ContentTypes.php | 79 +- .../Writer/Xlsx/DefinedNames.php | 244 ++ .../PhpSpreadsheet/Writer/Xlsx/DocProps.php | 64 +- .../PhpSpreadsheet/Writer/Xlsx/Drawing.php | 260 +- .../Writer/Xlsx/FunctionPrefix.php | 194 + PhpOffice/PhpSpreadsheet/Writer/Xlsx/Rels.php | 246 +- .../PhpSpreadsheet/Writer/Xlsx/RelsRibbon.php | 9 +- .../PhpSpreadsheet/Writer/Xlsx/RelsVBA.php | 12 +- .../Writer/Xlsx/StringTable.php | 216 +- .../PhpSpreadsheet/Writer/Xlsx/Style.php | 368 +- .../PhpSpreadsheet/Writer/Xlsx/Table.php | 115 + .../PhpSpreadsheet/Writer/Xlsx/Theme.php | 35 +- .../PhpSpreadsheet/Writer/Xlsx/Workbook.php | 262 +- .../PhpSpreadsheet/Writer/Xlsx/Worksheet.php | 1233 +++--- .../PhpSpreadsheet/Writer/Xlsx/WriterPart.php | 6 +- PhpOffice/PhpWord/COPYING | 674 ---- PhpOffice/PhpWord/COPYING.LESSER | 165 - .../PhpWord/Collection/AbstractCollection.php | 20 +- PhpOffice/PhpWord/Collection/Bookmarks.php | 4 +- PhpOffice/PhpWord/Collection/Charts.php | 4 +- PhpOffice/PhpWord/Collection/Comments.php | 4 +- PhpOffice/PhpWord/Collection/Endnotes.php | 4 +- PhpOffice/PhpWord/Collection/Footnotes.php | 4 +- PhpOffice/PhpWord/Collection/Titles.php | 4 +- .../ComplexType/FootnoteProperties.php | 47 +- PhpOffice/PhpWord/ComplexType/ProofState.php | 30 +- PhpOffice/PhpWord/ComplexType/TblWidth.php | 2 +- .../PhpWord/ComplexType/TrackChangesView.php | 46 +- .../PhpWord/Element/AbstractContainer.php | 157 +- PhpOffice/PhpWord/Element/AbstractElement.php | 142 +- PhpOffice/PhpWord/Element/Bookmark.php | 16 +- PhpOffice/PhpWord/Element/Cell.php | 16 +- PhpOffice/PhpWord/Element/Chart.php | 38 +- PhpOffice/PhpWord/Element/CheckBox.php | 17 +- PhpOffice/PhpWord/Element/Comment.php | 33 +- PhpOffice/PhpWord/Element/Endnote.php | 8 +- PhpOffice/PhpWord/Element/Field.php | 158 +- PhpOffice/PhpWord/Element/Footer.php | 24 +- PhpOffice/PhpWord/Element/Footnote.php | 42 +- PhpOffice/PhpWord/Element/FormField.php | 61 +- PhpOffice/PhpWord/Element/Header.php | 7 +- PhpOffice/PhpWord/Element/Image.php | 161 +- PhpOffice/PhpWord/Element/Line.php | 10 +- PhpOffice/PhpWord/Element/Link.php | 84 +- PhpOffice/PhpWord/Element/ListItem.php | 29 +- PhpOffice/PhpWord/Element/ListItemRun.php | 38 +- PhpOffice/PhpWord/Element/OLEObject.php | 58 +- PhpOffice/PhpWord/Element/PageBreak.php | 6 +- PhpOffice/PhpWord/Element/PreserveText.php | 36 +- PhpOffice/PhpWord/Element/Row.php | 25 +- PhpOffice/PhpWord/Element/SDT.php | 51 +- PhpOffice/PhpWord/Element/Section.php | 134 +- PhpOffice/PhpWord/Element/Shape.php | 19 +- PhpOffice/PhpWord/Element/TOC.php | 42 +- PhpOffice/PhpWord/Element/Table.php | 40 +- PhpOffice/PhpWord/Element/Text.php | 49 +- PhpOffice/PhpWord/Element/TextBox.php | 10 +- PhpOffice/PhpWord/Element/TextBreak.php | 46 +- PhpOffice/PhpWord/Element/TextRun.php | 23 +- PhpOffice/PhpWord/Element/Title.php | 27 +- PhpOffice/PhpWord/Element/TrackChange.php | 29 +- PhpOffice/PhpWord/Escaper/AbstractEscaper.php | 2 +- .../PhpWord/Escaper/EscaperInterface.php | 2 +- PhpOffice/PhpWord/Escaper/RegExp.php | 2 +- PhpOffice/PhpWord/Escaper/Rtf.php | 7 +- PhpOffice/PhpWord/Escaper/Xml.php | 5 +- .../PhpWord/Exception/CopyFileException.php | 4 +- .../CreateTemporaryFileException.php | 4 +- PhpOffice/PhpWord/Exception/Exception.php | 4 +- .../Exception/InvalidImageException.php | 4 +- .../Exception/InvalidObjectException.php | 4 +- .../Exception/InvalidStyleException.php | 4 +- .../UnsupportedImageTypeException.php | 4 +- PhpOffice/PhpWord/IOFactory.php | 29 +- PhpOffice/PhpWord/LICENSE | 15 - PhpOffice/PhpWord/Media.php | 221 +- PhpOffice/PhpWord/Metadata/Compatibility.php | 12 +- PhpOffice/PhpWord/Metadata/DocInfo.php | 159 +- PhpOffice/PhpWord/Metadata/Protection.php | 46 +- PhpOffice/PhpWord/Metadata/Settings.php | 112 +- PhpOffice/PhpWord/PhpWord.php | 201 +- PhpOffice/PhpWord/Reader/AbstractReader.php | 28 +- PhpOffice/PhpWord/Reader/HTML.php | 11 +- PhpOffice/PhpWord/Reader/MsDoc.php | 410 +- PhpOffice/PhpWord/Reader/ODText.php | 25 +- .../PhpWord/Reader/ODText/AbstractPart.php | 5 +- PhpOffice/PhpWord/Reader/ODText/Content.php | 24 +- PhpOffice/PhpWord/Reader/ODText/Meta.php | 25 +- PhpOffice/PhpWord/Reader/RTF.php | 11 +- PhpOffice/PhpWord/Reader/RTF/Document.php | 130 +- PhpOffice/PhpWord/Reader/ReaderInterface.php | 7 +- PhpOffice/PhpWord/Reader/Word2007.php | 55 +- .../PhpWord/Reader/Word2007/AbstractPart.php | 336 +- .../PhpWord/Reader/Word2007/DocPropsApp.php | 12 +- .../PhpWord/Reader/Word2007/DocPropsCore.php | 36 +- .../Reader/Word2007/DocPropsCustom.php | 10 +- .../PhpWord/Reader/Word2007/Document.php | 80 +- .../PhpWord/Reader/Word2007/Endnotes.php | 8 +- .../PhpWord/Reader/Word2007/Footnotes.php | 22 +- .../PhpWord/Reader/Word2007/Numbering.php | 30 +- .../PhpWord/Reader/Word2007/Settings.php | 69 +- PhpOffice/PhpWord/Reader/Word2007/Styles.php | 17 +- PhpOffice/PhpWord/Settings.php | 135 +- PhpOffice/PhpWord/Shared/AbstractEnum.php | 28 +- PhpOffice/PhpWord/Shared/Converter.php | 105 +- PhpOffice/PhpWord/Shared/Css.php | 80 + PhpOffice/PhpWord/Shared/Drawing.php | 263 ++ PhpOffice/PhpWord/Shared/Html.php | 742 +++- .../Shared/Microsoft/PasswordEncoder.php | 243 ++ PhpOffice/PhpWord/Shared/OLERead.php | 82 +- .../PhpWord/Shared/PCLZip/pclzip.lib.php | 23 - PhpOffice/PhpWord/Shared/Text.php | 252 ++ PhpOffice/PhpWord/Shared/XMLReader.php | 231 ++ PhpOffice/PhpWord/Shared/XMLWriter.php | 187 + PhpOffice/PhpWord/Shared/ZipArchive.php | 92 +- PhpOffice/PhpWord/SimpleType/Border.php | 57 + PhpOffice/PhpWord/SimpleType/DocProtect.php | 15 +- PhpOffice/PhpWord/SimpleType/Jc.php | 3 +- PhpOffice/PhpWord/SimpleType/JcTable.php | 2 +- .../PhpWord/SimpleType/LineSpacingRule.php | 11 +- PhpOffice/PhpWord/SimpleType/NumberFormat.php | 3 +- PhpOffice/PhpWord/SimpleType/TblWidth.php | 5 +- .../PhpWord/SimpleType/TextAlignment.php | 5 +- PhpOffice/PhpWord/SimpleType/VerticalJc.php | 2 +- PhpOffice/PhpWord/SimpleType/Zoom.php | 5 +- PhpOffice/PhpWord/Style.php | 49 +- PhpOffice/PhpWord/Style/AbstractStyle.php | 116 +- PhpOffice/PhpWord/Style/Border.php | 143 +- PhpOffice/PhpWord/Style/Cell.php | 107 +- PhpOffice/PhpWord/Style/Chart.php | 179 +- PhpOffice/PhpWord/Style/Extrusion.php | 26 +- PhpOffice/PhpWord/Style/Fill.php | 15 +- PhpOffice/PhpWord/Style/Font.php | 374 +- PhpOffice/PhpWord/Style/Frame.php | 204 +- PhpOffice/PhpWord/Style/Image.php | 64 +- PhpOffice/PhpWord/Style/Indentation.php | 60 +- PhpOffice/PhpWord/Style/Language.php | 64 +- PhpOffice/PhpWord/Style/Line.php | 75 +- PhpOffice/PhpWord/Style/LineNumbering.php | 45 +- PhpOffice/PhpWord/Style/ListItem.php | 108 +- PhpOffice/PhpWord/Style/Numbering.php | 31 +- PhpOffice/PhpWord/Style/NumberingLevel.php | 127 +- PhpOffice/PhpWord/Style/Outline.php | 91 +- PhpOffice/PhpWord/Style/Paper.php | 43 +- PhpOffice/PhpWord/Style/Paragraph.php | 308 +- PhpOffice/PhpWord/Style/Row.php | 63 +- PhpOffice/PhpWord/Style/Section.php | 205 +- PhpOffice/PhpWord/Style/Shading.php | 37 +- PhpOffice/PhpWord/Style/Shadow.php | 22 +- PhpOffice/PhpWord/Style/Shape.php | 64 +- PhpOffice/PhpWord/Style/Spacing.php | 81 +- PhpOffice/PhpWord/Style/TOC.php | 45 +- PhpOffice/PhpWord/Style/Tab.php | 51 +- PhpOffice/PhpWord/Style/Table.php | 214 +- PhpOffice/PhpWord/Style/TablePosition.php | 122 +- PhpOffice/PhpWord/Style/TextBox.php | 58 +- PhpOffice/PhpWord/Template.php | 27 - PhpOffice/PhpWord/TemplateProcessor.php | 472 ++- PhpOffice/PhpWord/Writer/AbstractWriter.php | 102 +- PhpOffice/PhpWord/Writer/HTML.php | 42 +- .../Writer/HTML/Element/AbstractElement.php | 22 +- .../PhpWord/Writer/HTML/Element/Bookmark.php | 6 +- .../PhpWord/Writer/HTML/Element/Container.php | 10 +- .../PhpWord/Writer/HTML/Element/Endnote.php | 6 +- .../PhpWord/Writer/HTML/Element/Footnote.php | 8 +- .../PhpWord/Writer/HTML/Element/Image.php | 6 +- .../PhpWord/Writer/HTML/Element/Link.php | 6 +- .../PhpWord/Writer/HTML/Element/ListItem.php | 6 +- .../Writer/HTML/Element/ListItemRun.php | 6 +- .../PhpWord/Writer/HTML/Element/PageBreak.php | 6 +- .../PhpWord/Writer/HTML/Element/Table.php | 28 +- .../PhpWord/Writer/HTML/Element/Text.php | 32 +- .../PhpWord/Writer/HTML/Element/TextBreak.php | 6 +- .../PhpWord/Writer/HTML/Element/TextRun.php | 6 +- .../PhpWord/Writer/HTML/Element/Title.php | 6 +- .../PhpWord/Writer/HTML/Part/AbstractPart.php | 11 +- PhpOffice/PhpWord/Writer/HTML/Part/Body.php | 10 +- PhpOffice/PhpWord/Writer/HTML/Part/Head.php | 66 +- .../Writer/HTML/Style/AbstractStyle.php | 26 +- PhpOffice/PhpWord/Writer/HTML/Style/Font.php | 10 +- .../PhpWord/Writer/HTML/Style/Generic.php | 8 +- PhpOffice/PhpWord/Writer/HTML/Style/Image.php | 8 +- .../PhpWord/Writer/HTML/Style/Paragraph.php | 18 +- PhpOffice/PhpWord/Writer/ODText.php | 30 +- .../Writer/ODText/Element/AbstractElement.php | 4 +- .../Writer/ODText/Element/Container.php | 6 +- .../PhpWord/Writer/ODText/Element/Field.php | 85 + .../PhpWord/Writer/ODText/Element/Image.php | 10 +- .../PhpWord/Writer/ODText/Element/Link.php | 10 +- .../Writer/ODText/Element/PageBreak.php | 10 +- .../PhpWord/Writer/ODText/Element/Table.php | 24 +- .../PhpWord/Writer/ODText/Element/Text.php | 69 +- .../Writer/ODText/Element/TextBreak.php | 8 +- .../PhpWord/Writer/ODText/Element/TextRun.php | 16 +- .../PhpWord/Writer/ODText/Element/Title.php | 39 +- .../Writer/ODText/Part/AbstractPart.php | 18 +- .../PhpWord/Writer/ODText/Part/Content.php | 217 +- .../PhpWord/Writer/ODText/Part/Manifest.php | 8 +- PhpOffice/PhpWord/Writer/ODText/Part/Meta.php | 15 +- .../PhpWord/Writer/ODText/Part/Mimetype.php | 6 +- .../PhpWord/Writer/ODText/Part/Styles.php | 156 +- .../Writer/ODText/Style/AbstractStyle.php | 4 +- .../PhpWord/Writer/ODText/Style/Font.php | 25 +- .../PhpWord/Writer/ODText/Style/Image.php | 6 +- .../PhpWord/Writer/ODText/Style/Paragraph.php | 118 +- .../PhpWord/Writer/ODText/Style/Section.php | 6 +- .../PhpWord/Writer/ODText/Style/Table.php | 8 +- PhpOffice/PhpWord/Writer/PDF.php | 21 +- .../PhpWord/Writer/PDF/AbstractRenderer.php | 50 +- PhpOffice/PhpWord/Writer/PDF/DomPDF.php | 22 +- PhpOffice/PhpWord/Writer/PDF/MPDF.php | 27 +- PhpOffice/PhpWord/Writer/PDF/TCPDF.php | 25 +- PhpOffice/PhpWord/Writer/RTF.php | 22 +- .../Writer/RTF/Element/AbstractElement.php | 23 +- .../PhpWord/Writer/RTF/Element/Container.php | 6 +- .../PhpWord/Writer/RTF/Element/Field.php | 4 +- .../PhpWord/Writer/RTF/Element/Image.php | 6 +- PhpOffice/PhpWord/Writer/RTF/Element/Link.php | 6 +- .../PhpWord/Writer/RTF/Element/ListItem.php | 4 +- .../PhpWord/Writer/RTF/Element/PageBreak.php | 6 +- .../PhpWord/Writer/RTF/Element/Table.php | 23 +- PhpOffice/PhpWord/Writer/RTF/Element/Text.php | 8 +- .../PhpWord/Writer/RTF/Element/TextBreak.php | 6 +- .../PhpWord/Writer/RTF/Element/TextRun.php | 6 +- .../PhpWord/Writer/RTF/Element/Title.php | 12 +- .../PhpWord/Writer/RTF/Part/AbstractPart.php | 6 +- .../PhpWord/Writer/RTF/Part/Document.php | 40 +- PhpOffice/PhpWord/Writer/RTF/Part/Header.php | 34 +- .../Writer/RTF/Style/AbstractStyle.php | 4 +- PhpOffice/PhpWord/Writer/RTF/Style/Border.php | 25 +- PhpOffice/PhpWord/Writer/RTF/Style/Font.php | 11 +- .../PhpWord/Writer/RTF/Style/Indentation.php | 6 +- .../PhpWord/Writer/RTF/Style/Paragraph.php | 30 +- .../PhpWord/Writer/RTF/Style/Section.php | 6 +- PhpOffice/PhpWord/Writer/RTF/Style/Tab.php | 12 +- PhpOffice/PhpWord/Writer/Word2007.php | 110 +- .../Word2007/Element/AbstractElement.php | 64 +- .../Writer/Word2007/Element/Bookmark.php | 8 +- .../PhpWord/Writer/Word2007/Element/Chart.php | 10 +- .../Writer/Word2007/Element/CheckBox.php | 6 +- .../Writer/Word2007/Element/Container.php | 17 +- .../Writer/Word2007/Element/Endnote.php | 6 +- .../PhpWord/Writer/Word2007/Element/Field.php | 24 +- .../Writer/Word2007/Element/Footnote.php | 8 +- .../Writer/Word2007/Element/FormField.php | 23 +- .../PhpWord/Writer/Word2007/Element/Image.php | 14 +- .../PhpWord/Writer/Word2007/Element/Line.php | 6 +- .../PhpWord/Writer/Word2007/Element/Link.php | 6 +- .../Writer/Word2007/Element/ListItem.php | 6 +- .../Writer/Word2007/Element/ListItemRun.php | 20 +- .../Writer/Word2007/Element/OLEObject.php | 6 +- .../Writer/Word2007/Element/PageBreak.php | 6 +- .../Word2007/Element/ParagraphAlignment.php | 4 +- .../Writer/Word2007/Element/PreserveText.php | 8 +- .../PhpWord/Writer/Word2007/Element/SDT.php | 28 +- .../PhpWord/Writer/Word2007/Element/Shape.php | 49 +- .../PhpWord/Writer/Word2007/Element/TOC.php | 31 +- .../PhpWord/Writer/Word2007/Element/Table.php | 25 +- .../Word2007/Element/TableAlignment.php | 4 +- .../PhpWord/Writer/Word2007/Element/Text.php | 14 +- .../Writer/Word2007/Element/TextBox.php | 6 +- .../Writer/Word2007/Element/TextBreak.php | 6 +- .../Writer/Word2007/Element/TextRun.php | 6 +- .../PhpWord/Writer/Word2007/Element/Title.php | 6 +- .../Writer/Word2007/Part/AbstractPart.php | 27 +- .../PhpWord/Writer/Word2007/Part/Chart.php | 139 +- .../PhpWord/Writer/Word2007/Part/Comments.php | 18 +- .../Writer/Word2007/Part/ContentTypes.php | 38 +- .../Writer/Word2007/Part/DocPropsApp.php | 6 +- .../Writer/Word2007/Part/DocPropsCore.php | 6 +- .../Writer/Word2007/Part/DocPropsCustom.php | 15 +- .../PhpWord/Writer/Word2007/Part/Document.php | 20 +- .../PhpWord/Writer/Word2007/Part/Endnotes.php | 12 +- .../Writer/Word2007/Part/FontTable.php | 14 +- .../PhpWord/Writer/Word2007/Part/Footer.php | 13 +- .../Writer/Word2007/Part/Footnotes.php | 30 +- .../PhpWord/Writer/Word2007/Part/Header.php | 6 +- .../Writer/Word2007/Part/Numbering.php | 44 +- .../PhpWord/Writer/Word2007/Part/Rels.php | 37 +- .../Writer/Word2007/Part/RelsDocument.php | 20 +- .../PhpWord/Writer/Word2007/Part/RelsPart.php | 15 +- .../PhpWord/Writer/Word2007/Part/Settings.php | 204 +- .../PhpWord/Writer/Word2007/Part/Styles.php | 36 +- .../PhpWord/Writer/Word2007/Part/Theme.php | 21 +- .../Writer/Word2007/Part/WebSettings.php | 10 +- .../Writer/Word2007/Style/AbstractStyle.php | 61 +- .../PhpWord/Writer/Word2007/Style/Cell.php | 24 +- .../Writer/Word2007/Style/Extrusion.php | 6 +- .../PhpWord/Writer/Word2007/Style/Fill.php | 6 +- .../PhpWord/Writer/Word2007/Style/Font.php | 16 +- .../PhpWord/Writer/Word2007/Style/Frame.php | 58 +- .../PhpWord/Writer/Word2007/Style/Image.php | 4 +- .../Writer/Word2007/Style/Indentation.php | 10 +- .../PhpWord/Writer/Word2007/Style/Line.php | 23 +- .../Writer/Word2007/Style/LineNumbering.php | 8 +- .../Writer/Word2007/Style/MarginBorder.php | 39 +- .../PhpWord/Writer/Word2007/Style/Outline.php | 6 +- .../Writer/Word2007/Style/Paragraph.php | 27 +- .../PhpWord/Writer/Word2007/Style/Row.php | 8 +- .../PhpWord/Writer/Word2007/Style/Section.php | 34 +- .../PhpWord/Writer/Word2007/Style/Shading.php | 12 +- .../PhpWord/Writer/Word2007/Style/Shadow.php | 6 +- .../PhpWord/Writer/Word2007/Style/Shape.php | 8 +- .../PhpWord/Writer/Word2007/Style/Spacing.php | 14 +- .../PhpWord/Writer/Word2007/Style/Tab.php | 6 +- .../PhpWord/Writer/Word2007/Style/Table.php | 53 +- .../Writer/Word2007/Style/TablePosition.php | 32 +- .../PhpWord/Writer/Word2007/Style/TextBox.php | 8 +- PhpOffice/PhpWord/Writer/WriterInterface.php | 6 +- PhpOffice/PhpWord/resources/doc.png | Bin PhpOffice/PhpWord/resources/ppt.png | Bin PhpOffice/PhpWord/resources/xls.png | Bin Psr/SimpleCache/CacheException.php | 10 + Psr/SimpleCache/CacheInterface.php | 114 + Psr/SimpleCache/InvalidArgumentException.php | 13 + Psr/SimpleCache/LICENSE.md | 21 + ZipStream/Bigint.php | 174 + ZipStream/DeflateStream.php | 27 + ZipStream/Exception.php | 12 + ZipStream/Exception/EncodingException.php | 14 + ZipStream/Exception/FileNotFoundException.php | 23 + .../Exception/FileNotReadableException.php | 23 + .../IncompatibleOptionsException.php | 14 + ZipStream/Exception/OverflowException.php | 18 + .../Exception/StreamNotReadableException.php | 23 + ZipStream/File.php | 470 +++ .../PhpSpreadsheet => ZipStream}/LICENSE | 5 +- ZipStream/Option/Archive.php | 276 ++ ZipStream/Option/File.php | 122 + ZipStream/Option/Method.php | 23 + ZipStream/Option/Version.php | 27 + ZipStream/Stream.php | 265 ++ ZipStream/ZipStream.php | 608 +++ 1010 files changed, 94009 insertions(+), 61354 deletions(-) create mode 100644 Laminas/Escaper/Escaper.php create mode 100644 Laminas/Escaper/Exception/ExceptionInterface.php create mode 100644 Laminas/Escaper/Exception/InvalidArgumentException.php create mode 100644 Laminas/Escaper/Exception/RuntimeException.php create mode 100644 Laminas/Escaper/LICENSE.md create mode 100644 MyCLabs/Enum/Enum.php create mode 100644 MyCLabs/LICENSE mode change 100755 => 100644 PhpOffice/Common/Adapter/Zip/PclZipAdapter.php mode change 100755 => 100644 PhpOffice/Common/Adapter/Zip/ZipArchiveAdapter.php mode change 100755 => 100644 PhpOffice/Common/Adapter/Zip/ZipInterface.php mode change 100755 => 100644 PhpOffice/Common/Autoloader.php delete mode 100755 PhpOffice/Common/COPYING delete mode 100755 PhpOffice/Common/COPYING.LESSER mode change 100755 => 100644 PhpOffice/Common/Drawing.php mode change 100755 => 100644 PhpOffice/Common/File.php mode change 100755 => 100644 PhpOffice/Common/Font.php delete mode 100755 PhpOffice/Common/LICENSE mode change 100755 => 100644 PhpOffice/Common/Microsoft/OLERead.php mode change 100755 => 100644 PhpOffice/Common/Microsoft/PasswordEncoder.php mode change 100755 => 100644 PhpOffice/Common/Text.php mode change 100755 => 100644 PhpOffice/Common/XMLReader.php mode change 100755 => 100644 PhpOffice/Common/XMLWriter.php mode change 100755 => 100644 PhpOffice/PhpPresentation/AbstractShape.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Autoloader.php delete mode 100755 PhpOffice/PhpPresentation/COPYING delete mode 100755 PhpOffice/PhpPresentation/COPYING.LESSER mode change 100755 => 100644 PhpOffice/PhpPresentation/ComparableInterface.php mode change 100755 => 100644 PhpOffice/PhpPresentation/DocumentLayout.php mode change 100755 => 100644 PhpOffice/PhpPresentation/DocumentProperties.php create mode 100644 PhpOffice/PhpPresentation/Exception/DirectoryNotFoundException.php create mode 100644 PhpOffice/PhpPresentation/Exception/FeatureNotImplementedException.php create mode 100644 PhpOffice/PhpPresentation/Exception/FileCopyException.php create mode 100644 PhpOffice/PhpPresentation/Exception/FileNotFoundException.php create mode 100644 PhpOffice/PhpPresentation/Exception/FileRemoveException.php create mode 100644 PhpOffice/PhpPresentation/Exception/InvalidClassException.php create mode 100644 PhpOffice/PhpPresentation/Exception/InvalidFileFormatException.php create mode 100644 PhpOffice/PhpPresentation/Exception/InvalidParameterException.php create mode 100644 PhpOffice/PhpPresentation/Exception/OutOfBoundsException.php rename PhpOffice/PhpPresentation/{Shape/Drawing.php => Exception/PhpPresentationException.php} (73%) mode change 100755 => 100644 create mode 100644 PhpOffice/PhpPresentation/Exception/ShapeContainerAlreadyAssignedException.php create mode 100644 PhpOffice/PhpPresentation/Exception/UnauthorizedMimetypeException.php rename PhpOffice/PhpPresentation/{Shape/MemoryDrawing.php => Exception/UndefinedChartTypeException.php} (67%) mode change 100755 => 100644 mode change 100755 => 100644 PhpOffice/PhpPresentation/GeometryCalculator.php mode change 100755 => 100644 PhpOffice/PhpPresentation/HashTable.php mode change 100755 => 100644 PhpOffice/PhpPresentation/IOFactory.php delete mode 100755 PhpOffice/PhpPresentation/LICENSE mode change 100755 => 100644 PhpOffice/PhpPresentation/PhpPresentation.php mode change 100755 => 100644 PhpOffice/PhpPresentation/PresentationProperties.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Reader/ODPresentation.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Reader/PowerPoint2007.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Reader/PowerPoint97.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Reader/ReaderInterface.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Reader/Serialized.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/AbstractGraphic.php create mode 100644 PhpOffice/PhpPresentation/Shape/AutoShape.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Chart.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Chart/Axis.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Chart/Gridlines.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Chart/Legend.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Chart/Marker.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Chart/PlotArea.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Chart/Series.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Chart/Title.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractType.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractTypeBar.php create mode 100644 PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractTypeLine.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractTypePie.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Chart/Type/Area.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Chart/Type/Bar.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Chart/Type/Bar3D.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Chart/Type/Doughnut.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Chart/Type/Line.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Chart/Type/Pie.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Chart/Type/Pie3D.php create mode 100644 PhpOffice/PhpPresentation/Shape/Chart/Type/Radar.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Chart/Type/Scatter.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Chart/View3D.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Comment.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Comment/Author.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Drawing/AbstractDrawingAdapter.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Drawing/Base64.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Drawing/File.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Drawing/Gd.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Drawing/ZipFile.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Group.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Hyperlink.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Line.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Media.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Placeholder.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/RichText.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/RichText/BreakElement.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/RichText/Paragraph.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/RichText/Run.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/RichText/TextElement.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/RichText/TextElementInterface.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Table.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Table/Cell.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Shape/Table/Row.php mode change 100755 => 100644 PhpOffice/PhpPresentation/ShapeContainerInterface.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Slide.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Slide/AbstractBackground.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Slide/AbstractSlide.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Slide/Animation.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Slide/Background/Color.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Slide/Background/Image.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Slide/Background/SchemeColor.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Slide/Iterator.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Slide/Layout.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Slide/Note.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Slide/SlideLayout.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Slide/SlideMaster.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Slide/Transition.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Style/Alignment.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Style/Border.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Style/Borders.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Style/Bullet.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Style/Color.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Style/ColorMap.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Style/Fill.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Style/Font.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Style/Outline.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Style/SchemeColor.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Style/Shadow.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Style/TextStyle.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/AbstractDecoratorWriter.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/AbstractWriter.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/ODPresentation.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/ODPresentation/AbstractDecoratorWriter.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/ODPresentation/Content.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/ODPresentation/Meta.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/ODPresentation/MetaInfManifest.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/ODPresentation/Mimetype.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/ODPresentation/ObjectsChart.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/ODPresentation/Pictures.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/ODPresentation/Styles.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/ODPresentation/ThumbnailsThumbnail.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/AbstractDecoratorWriter.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/AbstractSlide.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/CommentAuthors.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/ContentTypes.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsApp.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsCore.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsCustom.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsThumbnail.php delete mode 100755 PhpOffice/PhpPresentation/Writer/PowerPoint2007/LayoutPack/AbstractLayoutPack.php delete mode 100755 PhpOffice/PhpPresentation/Writer/PowerPoint2007/LayoutPack/PackDefault.php delete mode 100755 PhpOffice/PhpPresentation/Writer/PowerPoint2007/LayoutPack/TemplateBased.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptCharts.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptComments.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptMedia.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptPresProps.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptPresentation.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptSlideLayouts.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptSlideMasters.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptSlides.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptTableProps.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptTheme.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptViewProps.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/PowerPoint2007/Relationships.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/Serialized.php mode change 100755 => 100644 PhpOffice/PhpPresentation/Writer/WriterInterface.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/ArrayEnabled.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/BinaryComparison.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/Calculation.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/Category.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/Database.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Database/DAverage.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Database/DCount.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Database/DCountA.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Database/DGet.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Database/DMax.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Database/DMin.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Database/DProduct.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Database/DStDev.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Database/DStDevP.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Database/DSum.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Database/DVar.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Database/DVarP.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/DateTime.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Constants.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Current.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Date.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/DateParts.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Days.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Days360.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Difference.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Month.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Time.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/TimeParts.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Week.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Engine/ArrayArgumentHelper.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Engine/ArrayArgumentProcessor.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Engine/BranchPruner.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/Engine/CyclicReferenceStack.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Engine/FormattedNumber.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/Engine/Logger.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Engine/Operands/Operand.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Engine/Operands/StructuredReference.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/Engineering.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Engineering/BesselI.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Engineering/BesselJ.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Engineering/BesselK.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Engineering/BesselY.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Engineering/BitWise.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Engineering/Compare.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Engineering/Complex.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Engineering/ComplexFunctions.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Engineering/ComplexOperations.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Engineering/Constants.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Engineering/ConvertBase.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Engineering/ConvertBinary.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Engineering/ConvertDecimal.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Engineering/ConvertHex.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Engineering/ConvertOctal.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Engineering/EngineeringValidations.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Engineering/Erf.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Engineering/ErfC.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/Exception.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/ExceptionHandler.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/Financial.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Financial/Amortization.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/CashFlowValidations.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Cumulative.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/InterestAndPrincipal.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Payments.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/Single.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/Periodic.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Financial/Constants.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Financial/Coupons.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Financial/Depreciation.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Financial/Dollar.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Financial/FinancialValidations.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Financial/Helpers.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Financial/InterestRate.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Financial/Securities/AccruedInterest.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Financial/Securities/Price.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Financial/Securities/Rates.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Financial/Securities/SecurityValidations.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Financial/Securities/Yields.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Financial/TreasuryBill.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/FormulaParser.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/FormulaToken.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/Functions.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Information/ErrorValue.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Information/ExcelError.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Information/Value.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Internal/MakeMatrix.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Internal/WildcardMatch.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/Logical.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Logical/Boolean.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Logical/Conditional.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Logical/Operations.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/LookupRef.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Address.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Filter.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Formula.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/LookupRef/HLookup.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Helpers.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Hyperlink.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Indirect.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Lookup.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/LookupRef/LookupRefValidations.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Matrix.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Offset.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Selection.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Sort.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Unique.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/LookupRef/VLookup.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Absolute.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Angle.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Arabic.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Base.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Ceiling.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Combinations.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Exp.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Factorial.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Floor.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Gcd.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Helpers.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/IntClass.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Lcm.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Logarithms.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Operations.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Random.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Roman.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Round.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/SeriesSum.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Sign.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Sqrt.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Sum.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/SumSquares.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Trig/Cosecant.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Trig/Cosine.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Trig/Cotangent.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Trig/Secant.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Trig/Sine.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Trig/Tangent.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Trunc.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/AggregateBase.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Averages.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Averages/Mean.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Conditional.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Confidence.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Counts.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Deviations.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Beta.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Binomial.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/DistributionValidations.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Exponential.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/F.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Fisher.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Gamma.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/HyperGeometric.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/LogNormal.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/NewtonRaphson.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Normal.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Poisson.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/StandardNormal.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/StudentT.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Weibull.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/MaxMinBase.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Maximum.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Minimum.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Percentiles.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Permutations.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Size.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/StandardDeviations.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Standardize.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/StatisticalValidations.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Trends.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/VarianceBase.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Statistical/Variances.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/TextData.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/TextData/CaseConvert.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/TextData/CharacterConvert.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/TextData/Concatenate.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/TextData/Extract.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/TextData/Format.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/TextData/Helpers.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/TextData/Replace.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/TextData/Search.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/TextData/Text.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/TextData/Trim.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/Token/Stack.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Web.php create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/Web/Service.php delete mode 100755 PhpOffice/PhpSpreadsheet/Calculation/functionlist.txt create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/Translations.xlsx mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/bg/config mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/bg/functions mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/cs/config mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/cs/functions mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/da/config mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/da/functions mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/de/config mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/de/functions mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/en/uk/config mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/es/config mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/es/functions mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/fi/config mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/fi/functions mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/fr/config mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/fr/functions mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/hu/config mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/hu/functions mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/it/config mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/it/functions create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/nb/config create mode 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/nb/functions mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/nl/config mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/nl/functions delete mode 100755 PhpOffice/PhpSpreadsheet/Calculation/locale/no/config delete mode 100755 PhpOffice/PhpSpreadsheet/Calculation/locale/no/functions mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/pl/config mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/pl/functions mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/pt/br/config mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/pt/br/functions mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/pt/config mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/pt/functions mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/ru/config mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/ru/functions mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/sv/config mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/sv/functions mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/tr/config mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Calculation/locale/tr/functions create mode 100644 PhpOffice/PhpSpreadsheet/Cell/AddressHelper.php create mode 100644 PhpOffice/PhpSpreadsheet/Cell/AddressRange.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Cell/AdvancedValueBinder.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Cell/Cell.php create mode 100644 PhpOffice/PhpSpreadsheet/Cell/CellAddress.php create mode 100644 PhpOffice/PhpSpreadsheet/Cell/CellRange.php create mode 100644 PhpOffice/PhpSpreadsheet/Cell/ColumnRange.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Cell/Coordinate.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Cell/DataType.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Cell/DataValidation.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Cell/DataValidator.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Cell/DefaultValueBinder.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Cell/Hyperlink.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Cell/IValueBinder.php create mode 100644 PhpOffice/PhpSpreadsheet/Cell/RowRange.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Cell/StringValueBinder.php create mode 100644 PhpOffice/PhpSpreadsheet/CellReferenceHelper.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Chart/Axis.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Chart/Chart.php create mode 100644 PhpOffice/PhpSpreadsheet/Chart/ChartColor.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Chart/DataSeries.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Chart/DataSeriesValues.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Chart/Exception.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Chart/GridLines.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Chart/Layout.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Chart/Legend.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Chart/PlotArea.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Chart/Properties.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Chart/Renderer/IRenderer.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Chart/Renderer/JpGraph.php create mode 100644 PhpOffice/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php create mode 100644 PhpOffice/PhpSpreadsheet/Chart/Renderer/MtJpGraphRenderer.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Chart/Renderer/PHP Charting Libraries.txt delete mode 100755 PhpOffice/PhpSpreadsheet/Chart/Renderer/Polyfill.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Chart/Title.php create mode 100644 PhpOffice/PhpSpreadsheet/Chart/TrendLine.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Collection/Cells.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Collection/CellsFactory.php rename PhpOffice/PhpSpreadsheet/Collection/{Memory.php => Memory/SimpleCache1.php} (58%) mode change 100755 => 100644 create mode 100644 PhpOffice/PhpSpreadsheet/Collection/Memory/SimpleCache3.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Comment.php create mode 100644 PhpOffice/PhpSpreadsheet/DefinedName.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Document/Properties.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Document/Security.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Exception.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/HashTable.php create mode 100644 PhpOffice/PhpSpreadsheet/Helper/Dimension.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Helper/Html.php delete mode 100755 PhpOffice/PhpSpreadsheet/Helper/Migrator.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Helper/Sample.php create mode 100644 PhpOffice/PhpSpreadsheet/Helper/Size.php create mode 100644 PhpOffice/PhpSpreadsheet/Helper/TextGrid.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/IComparable.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/IOFactory.php create mode 100644 PhpOffice/PhpSpreadsheet/NamedFormula.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/NamedRange.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/BaseReader.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Csv.php create mode 100644 PhpOffice/PhpSpreadsheet/Reader/Csv/Delimiter.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/DefaultReadFilter.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Exception.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Gnumeric.php create mode 100644 PhpOffice/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php create mode 100644 PhpOffice/PhpSpreadsheet/Reader/Gnumeric/Properties.php create mode 100644 PhpOffice/PhpSpreadsheet/Reader/Gnumeric/Styles.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Html.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/IReadFilter.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/IReader.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Ods.php create mode 100644 PhpOffice/PhpSpreadsheet/Reader/Ods/AutoFilter.php create mode 100644 PhpOffice/PhpSpreadsheet/Reader/Ods/BaseLoader.php create mode 100644 PhpOffice/PhpSpreadsheet/Reader/Ods/DefinedNames.php create mode 100644 PhpOffice/PhpSpreadsheet/Reader/Ods/FormulaTranslator.php create mode 100644 PhpOffice/PhpSpreadsheet/Reader/Ods/PageSettings.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Ods/Properties.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Security/XmlScanner.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Slk.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Xls.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Xls/Color.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Xls/Color/BIFF5.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Xls/Color/BIFF8.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Xls/Color/BuiltIn.php create mode 100644 PhpOffice/PhpSpreadsheet/Reader/Xls/ConditionalFormatting.php create mode 100644 PhpOffice/PhpSpreadsheet/Reader/Xls/DataValidationHelper.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Xls/ErrorCode.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Xls/Escher.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Xls/MD5.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Xls/RC4.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Xls/Style/Border.php create mode 100644 PhpOffice/PhpSpreadsheet/Reader/Xls/Style/CellAlignment.php create mode 100644 PhpOffice/PhpSpreadsheet/Reader/Xls/Style/CellFont.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Xls/Style/FillPattern.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Xlsx.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Xlsx/Chart.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Xlsx/DataValidations.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php create mode 100644 PhpOffice/PhpSpreadsheet/Reader/Xlsx/Namespaces.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Xlsx/PageSetup.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Xlsx/Properties.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Xlsx/SheetViews.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Xlsx/Styles.php create mode 100644 PhpOffice/PhpSpreadsheet/Reader/Xlsx/TableReader.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Xlsx/Theme.php create mode 100644 PhpOffice/PhpSpreadsheet/Reader/Xlsx/WorkbookView.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Reader/Xml.php create mode 100644 PhpOffice/PhpSpreadsheet/Reader/Xml/PageSettings.php create mode 100644 PhpOffice/PhpSpreadsheet/Reader/Xml/Properties.php create mode 100644 PhpOffice/PhpSpreadsheet/Reader/Xml/Style.php create mode 100644 PhpOffice/PhpSpreadsheet/Reader/Xml/Style/Alignment.php create mode 100644 PhpOffice/PhpSpreadsheet/Reader/Xml/Style/Border.php create mode 100644 PhpOffice/PhpSpreadsheet/Reader/Xml/Style/Fill.php create mode 100644 PhpOffice/PhpSpreadsheet/Reader/Xml/Style/Font.php create mode 100644 PhpOffice/PhpSpreadsheet/Reader/Xml/Style/NumberFormat.php create mode 100644 PhpOffice/PhpSpreadsheet/Reader/Xml/Style/StyleBase.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/ReferenceHelper.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/RichText/ITextElement.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/RichText/RichText.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/RichText/Run.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/RichText/TextElement.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Settings.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/CodePage.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/Date.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/Drawing.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/Escher.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/Escher/DgContainer.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer/SpContainer.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/Escher/DggContainer.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE/Blip.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/File.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/Font.php create mode 100644 PhpOffice/PhpSpreadsheet/Shared/IntOrFloat.php delete mode 100755 PhpOffice/PhpSpreadsheet/Shared/JAMA/CHANGELOG.TXT delete mode 100755 PhpOffice/PhpSpreadsheet/Shared/JAMA/CholeskyDecomposition.php delete mode 100755 PhpOffice/PhpSpreadsheet/Shared/JAMA/EigenvalueDecomposition.php delete mode 100755 PhpOffice/PhpSpreadsheet/Shared/JAMA/LUDecomposition.php delete mode 100755 PhpOffice/PhpSpreadsheet/Shared/JAMA/Matrix.php delete mode 100755 PhpOffice/PhpSpreadsheet/Shared/JAMA/QRDecomposition.php delete mode 100755 PhpOffice/PhpSpreadsheet/Shared/JAMA/SingularValueDecomposition.php delete mode 100755 PhpOffice/PhpSpreadsheet/Shared/JAMA/utils/Maths.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/OLE.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/OLE/PPS.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/OLE/PPS/File.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/OLE/PPS/Root.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/OLERead.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/PasswordHasher.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/StringHelper.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/TimeZone.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/Trend/BestFit.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/Trend/ExponentialBestFit.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/Trend/LinearBestFit.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/Trend/LogarithmicBestFit.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/Trend/PowerBestFit.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/Trend/Trend.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/XMLWriter.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Shared/Xls.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Spreadsheet.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Style/Alignment.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Style/Border.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Style/Borders.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Style/Color.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Style/Conditional.php create mode 100644 PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/CellMatcher.php create mode 100644 PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/CellStyleAssessor.php create mode 100644 PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php create mode 100644 PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBarExtension.php create mode 100644 PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php create mode 100644 PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php create mode 100644 PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/StyleMerger.php create mode 100644 PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard.php create mode 100644 PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Blanks.php create mode 100644 PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/CellValue.php create mode 100644 PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/DateValue.php create mode 100644 PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Duplicates.php create mode 100644 PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Errors.php create mode 100644 PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Expression.php create mode 100644 PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/TextValue.php create mode 100644 PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardAbstract.php create mode 100644 PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardInterface.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Style/Fill.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Style/Font.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Style/NumberFormat.php create mode 100644 PhpOffice/PhpSpreadsheet/Style/NumberFormat/BaseFormatter.php create mode 100644 PhpOffice/PhpSpreadsheet/Style/NumberFormat/DateFormatter.php create mode 100644 PhpOffice/PhpSpreadsheet/Style/NumberFormat/Formatter.php create mode 100644 PhpOffice/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php create mode 100644 PhpOffice/PhpSpreadsheet/Style/NumberFormat/NumberFormatter.php create mode 100644 PhpOffice/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Style/Protection.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Style/Style.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Style/Supervisor.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Worksheet/AutoFilter.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Worksheet/AutoFilter/Column.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Worksheet/BaseDrawing.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Worksheet/CellIterator.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Worksheet/Column.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Worksheet/ColumnCellIterator.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Worksheet/ColumnDimension.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Worksheet/ColumnIterator.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Worksheet/Dimension.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Worksheet/Drawing.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Worksheet/Drawing/Shadow.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Worksheet/HeaderFooter.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Worksheet/HeaderFooterDrawing.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Worksheet/Iterator.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Worksheet/MemoryDrawing.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Worksheet/PageMargins.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Worksheet/PageSetup.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Worksheet/Protection.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Worksheet/Row.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Worksheet/RowCellIterator.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Worksheet/RowDimension.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Worksheet/RowIterator.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Worksheet/SheetView.php create mode 100644 PhpOffice/PhpSpreadsheet/Worksheet/Table.php create mode 100644 PhpOffice/PhpSpreadsheet/Worksheet/Table/Column.php create mode 100644 PhpOffice/PhpSpreadsheet/Worksheet/Table/TableStyle.php create mode 100644 PhpOffice/PhpSpreadsheet/Worksheet/Validations.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Worksheet/Worksheet.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/BaseWriter.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Csv.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Exception.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Html.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/IWriter.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Ods.php create mode 100644 PhpOffice/PhpSpreadsheet/Writer/Ods/AutoFilters.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Ods/Cell/Comment.php create mode 100644 PhpOffice/PhpSpreadsheet/Writer/Ods/Cell/Style.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Ods/Content.php create mode 100644 PhpOffice/PhpSpreadsheet/Writer/Ods/Formula.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Ods/Meta.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Ods/MetaInf.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Ods/Mimetype.php create mode 100644 PhpOffice/PhpSpreadsheet/Writer/Ods/NamedExpressions.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Ods/Settings.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Ods/Styles.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Ods/Thumbnails.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Ods/WriterPart.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Pdf.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Pdf/Dompdf.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Pdf/Mpdf.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Pdf/Tcpdf.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Xls.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Xls/BIFFwriter.php create mode 100644 PhpOffice/PhpSpreadsheet/Writer/Xls/CellDataValidation.php create mode 100644 PhpOffice/PhpSpreadsheet/Writer/Xls/ConditionalHelper.php create mode 100644 PhpOffice/PhpSpreadsheet/Writer/Xls/ErrorCode.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Xls/Escher.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Xls/Font.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Xls/Parser.php create mode 100644 PhpOffice/PhpSpreadsheet/Writer/Xls/Style/CellAlignment.php create mode 100644 PhpOffice/PhpSpreadsheet/Writer/Xls/Style/CellBorder.php create mode 100644 PhpOffice/PhpSpreadsheet/Writer/Xls/Style/CellFill.php create mode 100644 PhpOffice/PhpSpreadsheet/Writer/Xls/Style/ColorMap.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Xls/Workbook.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Xls/Worksheet.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Xls/Xf.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Xlsx.php create mode 100644 PhpOffice/PhpSpreadsheet/Writer/Xlsx/AutoFilter.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Xlsx/Chart.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Xlsx/Comments.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php create mode 100644 PhpOffice/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Xlsx/DocProps.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Xlsx/Drawing.php create mode 100644 PhpOffice/PhpSpreadsheet/Writer/Xlsx/FunctionPrefix.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Xlsx/Rels.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Xlsx/RelsRibbon.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Xlsx/RelsVBA.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Xlsx/StringTable.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Xlsx/Style.php create mode 100644 PhpOffice/PhpSpreadsheet/Writer/Xlsx/Table.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Xlsx/Theme.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Xlsx/Workbook.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Xlsx/Worksheet.php mode change 100755 => 100644 PhpOffice/PhpSpreadsheet/Writer/Xlsx/WriterPart.php delete mode 100755 PhpOffice/PhpWord/COPYING delete mode 100755 PhpOffice/PhpWord/COPYING.LESSER mode change 100755 => 100644 PhpOffice/PhpWord/Collection/AbstractCollection.php mode change 100755 => 100644 PhpOffice/PhpWord/Collection/Bookmarks.php mode change 100755 => 100644 PhpOffice/PhpWord/Collection/Charts.php mode change 100755 => 100644 PhpOffice/PhpWord/Collection/Comments.php mode change 100755 => 100644 PhpOffice/PhpWord/Collection/Endnotes.php mode change 100755 => 100644 PhpOffice/PhpWord/Collection/Footnotes.php mode change 100755 => 100644 PhpOffice/PhpWord/Collection/Titles.php mode change 100755 => 100644 PhpOffice/PhpWord/ComplexType/FootnoteProperties.php mode change 100755 => 100644 PhpOffice/PhpWord/ComplexType/ProofState.php mode change 100755 => 100644 PhpOffice/PhpWord/ComplexType/TblWidth.php mode change 100755 => 100644 PhpOffice/PhpWord/ComplexType/TrackChangesView.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/AbstractContainer.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/AbstractElement.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/Bookmark.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/Cell.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/Chart.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/CheckBox.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/Comment.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/Endnote.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/Field.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/Footer.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/Footnote.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/FormField.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/Header.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/Image.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/Line.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/Link.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/ListItem.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/ListItemRun.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/OLEObject.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/PageBreak.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/PreserveText.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/Row.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/SDT.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/Section.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/Shape.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/TOC.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/Table.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/Text.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/TextBox.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/TextBreak.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/TextRun.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/Title.php mode change 100755 => 100644 PhpOffice/PhpWord/Element/TrackChange.php mode change 100755 => 100644 PhpOffice/PhpWord/Escaper/AbstractEscaper.php mode change 100755 => 100644 PhpOffice/PhpWord/Escaper/EscaperInterface.php mode change 100755 => 100644 PhpOffice/PhpWord/Escaper/RegExp.php mode change 100755 => 100644 PhpOffice/PhpWord/Escaper/Rtf.php mode change 100755 => 100644 PhpOffice/PhpWord/Escaper/Xml.php mode change 100755 => 100644 PhpOffice/PhpWord/Exception/CopyFileException.php mode change 100755 => 100644 PhpOffice/PhpWord/Exception/CreateTemporaryFileException.php mode change 100755 => 100644 PhpOffice/PhpWord/Exception/Exception.php mode change 100755 => 100644 PhpOffice/PhpWord/Exception/InvalidImageException.php mode change 100755 => 100644 PhpOffice/PhpWord/Exception/InvalidObjectException.php mode change 100755 => 100644 PhpOffice/PhpWord/Exception/InvalidStyleException.php mode change 100755 => 100644 PhpOffice/PhpWord/Exception/UnsupportedImageTypeException.php mode change 100755 => 100644 PhpOffice/PhpWord/IOFactory.php delete mode 100755 PhpOffice/PhpWord/LICENSE mode change 100755 => 100644 PhpOffice/PhpWord/Media.php mode change 100755 => 100644 PhpOffice/PhpWord/Metadata/Compatibility.php mode change 100755 => 100644 PhpOffice/PhpWord/Metadata/DocInfo.php mode change 100755 => 100644 PhpOffice/PhpWord/Metadata/Protection.php mode change 100755 => 100644 PhpOffice/PhpWord/Metadata/Settings.php mode change 100755 => 100644 PhpOffice/PhpWord/PhpWord.php mode change 100755 => 100644 PhpOffice/PhpWord/Reader/AbstractReader.php mode change 100755 => 100644 PhpOffice/PhpWord/Reader/HTML.php mode change 100755 => 100644 PhpOffice/PhpWord/Reader/MsDoc.php mode change 100755 => 100644 PhpOffice/PhpWord/Reader/ODText.php mode change 100755 => 100644 PhpOffice/PhpWord/Reader/ODText/AbstractPart.php mode change 100755 => 100644 PhpOffice/PhpWord/Reader/ODText/Content.php mode change 100755 => 100644 PhpOffice/PhpWord/Reader/ODText/Meta.php mode change 100755 => 100644 PhpOffice/PhpWord/Reader/RTF.php mode change 100755 => 100644 PhpOffice/PhpWord/Reader/RTF/Document.php mode change 100755 => 100644 PhpOffice/PhpWord/Reader/ReaderInterface.php mode change 100755 => 100644 PhpOffice/PhpWord/Reader/Word2007.php mode change 100755 => 100644 PhpOffice/PhpWord/Reader/Word2007/AbstractPart.php mode change 100755 => 100644 PhpOffice/PhpWord/Reader/Word2007/DocPropsApp.php mode change 100755 => 100644 PhpOffice/PhpWord/Reader/Word2007/DocPropsCore.php mode change 100755 => 100644 PhpOffice/PhpWord/Reader/Word2007/DocPropsCustom.php mode change 100755 => 100644 PhpOffice/PhpWord/Reader/Word2007/Document.php mode change 100755 => 100644 PhpOffice/PhpWord/Reader/Word2007/Endnotes.php mode change 100755 => 100644 PhpOffice/PhpWord/Reader/Word2007/Footnotes.php mode change 100755 => 100644 PhpOffice/PhpWord/Reader/Word2007/Numbering.php mode change 100755 => 100644 PhpOffice/PhpWord/Reader/Word2007/Settings.php mode change 100755 => 100644 PhpOffice/PhpWord/Reader/Word2007/Styles.php mode change 100755 => 100644 PhpOffice/PhpWord/Settings.php mode change 100755 => 100644 PhpOffice/PhpWord/Shared/AbstractEnum.php mode change 100755 => 100644 PhpOffice/PhpWord/Shared/Converter.php create mode 100644 PhpOffice/PhpWord/Shared/Css.php create mode 100644 PhpOffice/PhpWord/Shared/Drawing.php mode change 100755 => 100644 PhpOffice/PhpWord/Shared/Html.php create mode 100644 PhpOffice/PhpWord/Shared/Microsoft/PasswordEncoder.php mode change 100755 => 100644 PhpOffice/PhpWord/Shared/OLERead.php mode change 100755 => 100644 PhpOffice/PhpWord/Shared/PCLZip/pclzip.lib.php create mode 100644 PhpOffice/PhpWord/Shared/Text.php create mode 100644 PhpOffice/PhpWord/Shared/XMLReader.php create mode 100644 PhpOffice/PhpWord/Shared/XMLWriter.php mode change 100755 => 100644 PhpOffice/PhpWord/Shared/ZipArchive.php create mode 100644 PhpOffice/PhpWord/SimpleType/Border.php mode change 100755 => 100644 PhpOffice/PhpWord/SimpleType/DocProtect.php mode change 100755 => 100644 PhpOffice/PhpWord/SimpleType/Jc.php mode change 100755 => 100644 PhpOffice/PhpWord/SimpleType/JcTable.php mode change 100755 => 100644 PhpOffice/PhpWord/SimpleType/LineSpacingRule.php mode change 100755 => 100644 PhpOffice/PhpWord/SimpleType/NumberFormat.php mode change 100755 => 100644 PhpOffice/PhpWord/SimpleType/TblWidth.php mode change 100755 => 100644 PhpOffice/PhpWord/SimpleType/TextAlignment.php mode change 100755 => 100644 PhpOffice/PhpWord/SimpleType/VerticalJc.php mode change 100755 => 100644 PhpOffice/PhpWord/SimpleType/Zoom.php mode change 100755 => 100644 PhpOffice/PhpWord/Style.php mode change 100755 => 100644 PhpOffice/PhpWord/Style/AbstractStyle.php mode change 100755 => 100644 PhpOffice/PhpWord/Style/Border.php mode change 100755 => 100644 PhpOffice/PhpWord/Style/Cell.php mode change 100755 => 100644 PhpOffice/PhpWord/Style/Chart.php mode change 100755 => 100644 PhpOffice/PhpWord/Style/Extrusion.php mode change 100755 => 100644 PhpOffice/PhpWord/Style/Fill.php mode change 100755 => 100644 PhpOffice/PhpWord/Style/Font.php mode change 100755 => 100644 PhpOffice/PhpWord/Style/Frame.php mode change 100755 => 100644 PhpOffice/PhpWord/Style/Image.php mode change 100755 => 100644 PhpOffice/PhpWord/Style/Indentation.php mode change 100755 => 100644 PhpOffice/PhpWord/Style/Language.php mode change 100755 => 100644 PhpOffice/PhpWord/Style/Line.php mode change 100755 => 100644 PhpOffice/PhpWord/Style/LineNumbering.php mode change 100755 => 100644 PhpOffice/PhpWord/Style/ListItem.php mode change 100755 => 100644 PhpOffice/PhpWord/Style/Numbering.php mode change 100755 => 100644 PhpOffice/PhpWord/Style/NumberingLevel.php mode change 100755 => 100644 PhpOffice/PhpWord/Style/Outline.php mode change 100755 => 100644 PhpOffice/PhpWord/Style/Paper.php mode change 100755 => 100644 PhpOffice/PhpWord/Style/Paragraph.php mode change 100755 => 100644 PhpOffice/PhpWord/Style/Row.php mode change 100755 => 100644 PhpOffice/PhpWord/Style/Section.php mode change 100755 => 100644 PhpOffice/PhpWord/Style/Shading.php mode change 100755 => 100644 PhpOffice/PhpWord/Style/Shadow.php mode change 100755 => 100644 PhpOffice/PhpWord/Style/Shape.php mode change 100755 => 100644 PhpOffice/PhpWord/Style/Spacing.php mode change 100755 => 100644 PhpOffice/PhpWord/Style/TOC.php mode change 100755 => 100644 PhpOffice/PhpWord/Style/Tab.php mode change 100755 => 100644 PhpOffice/PhpWord/Style/Table.php mode change 100755 => 100644 PhpOffice/PhpWord/Style/TablePosition.php mode change 100755 => 100644 PhpOffice/PhpWord/Style/TextBox.php delete mode 100755 PhpOffice/PhpWord/Template.php mode change 100755 => 100644 PhpOffice/PhpWord/TemplateProcessor.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/AbstractWriter.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/HTML.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/HTML/Element/AbstractElement.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/HTML/Element/Bookmark.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/HTML/Element/Container.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/HTML/Element/Endnote.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/HTML/Element/Footnote.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/HTML/Element/Image.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/HTML/Element/Link.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/HTML/Element/ListItem.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/HTML/Element/ListItemRun.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/HTML/Element/PageBreak.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/HTML/Element/Table.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/HTML/Element/Text.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/HTML/Element/TextBreak.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/HTML/Element/TextRun.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/HTML/Element/Title.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/HTML/Part/AbstractPart.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/HTML/Part/Body.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/HTML/Part/Head.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/HTML/Style/AbstractStyle.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/HTML/Style/Font.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/HTML/Style/Generic.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/HTML/Style/Image.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/HTML/Style/Paragraph.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/ODText.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/ODText/Element/AbstractElement.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/ODText/Element/Container.php create mode 100644 PhpOffice/PhpWord/Writer/ODText/Element/Field.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/ODText/Element/Image.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/ODText/Element/Link.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/ODText/Element/PageBreak.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/ODText/Element/Table.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/ODText/Element/Text.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/ODText/Element/TextBreak.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/ODText/Element/TextRun.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/ODText/Element/Title.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/ODText/Part/AbstractPart.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/ODText/Part/Content.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/ODText/Part/Manifest.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/ODText/Part/Meta.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/ODText/Part/Mimetype.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/ODText/Part/Styles.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/ODText/Style/AbstractStyle.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/ODText/Style/Font.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/ODText/Style/Image.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/ODText/Style/Paragraph.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/ODText/Style/Section.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/ODText/Style/Table.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/PDF.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/PDF/AbstractRenderer.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/PDF/DomPDF.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/PDF/MPDF.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/PDF/TCPDF.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/RTF.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/RTF/Element/AbstractElement.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/RTF/Element/Container.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/RTF/Element/Field.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/RTF/Element/Image.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/RTF/Element/Link.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/RTF/Element/ListItem.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/RTF/Element/PageBreak.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/RTF/Element/Table.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/RTF/Element/Text.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/RTF/Element/TextBreak.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/RTF/Element/TextRun.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/RTF/Element/Title.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/RTF/Part/AbstractPart.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/RTF/Part/Document.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/RTF/Part/Header.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/RTF/Style/AbstractStyle.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/RTF/Style/Border.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/RTF/Style/Font.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/RTF/Style/Indentation.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/RTF/Style/Paragraph.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/RTF/Style/Section.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/RTF/Style/Tab.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Element/AbstractElement.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Element/Bookmark.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Element/Chart.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Element/CheckBox.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Element/Container.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Element/Endnote.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Element/Field.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Element/Footnote.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Element/FormField.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Element/Image.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Element/Line.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Element/Link.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Element/ListItem.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Element/ListItemRun.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Element/OLEObject.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Element/PageBreak.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Element/ParagraphAlignment.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Element/PreserveText.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Element/SDT.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Element/Shape.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Element/TOC.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Element/Table.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Element/TableAlignment.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Element/Text.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Element/TextBox.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Element/TextBreak.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Element/TextRun.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Element/Title.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Part/AbstractPart.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Part/Chart.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Part/Comments.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Part/ContentTypes.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Part/DocPropsApp.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Part/DocPropsCore.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Part/DocPropsCustom.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Part/Document.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Part/Endnotes.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Part/FontTable.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Part/Footer.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Part/Footnotes.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Part/Header.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Part/Numbering.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Part/Rels.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Part/RelsDocument.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Part/RelsPart.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Part/Settings.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Part/Styles.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Part/Theme.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Part/WebSettings.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Style/AbstractStyle.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Cell.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Extrusion.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Fill.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Font.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Frame.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Image.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Indentation.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Line.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Style/LineNumbering.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Style/MarginBorder.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Outline.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Paragraph.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Row.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Section.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Shading.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Shadow.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Shape.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Spacing.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Tab.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Style/Table.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Style/TablePosition.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/Word2007/Style/TextBox.php mode change 100755 => 100644 PhpOffice/PhpWord/Writer/WriterInterface.php mode change 100755 => 100644 PhpOffice/PhpWord/resources/doc.png mode change 100755 => 100644 PhpOffice/PhpWord/resources/ppt.png mode change 100755 => 100644 PhpOffice/PhpWord/resources/xls.png create mode 100644 Psr/SimpleCache/CacheException.php create mode 100644 Psr/SimpleCache/CacheInterface.php create mode 100644 Psr/SimpleCache/InvalidArgumentException.php create mode 100644 Psr/SimpleCache/LICENSE.md create mode 100644 ZipStream/Bigint.php create mode 100644 ZipStream/DeflateStream.php create mode 100644 ZipStream/Exception.php create mode 100644 ZipStream/Exception/EncodingException.php create mode 100644 ZipStream/Exception/FileNotFoundException.php create mode 100644 ZipStream/Exception/FileNotReadableException.php create mode 100644 ZipStream/Exception/IncompatibleOptionsException.php create mode 100644 ZipStream/Exception/OverflowException.php create mode 100644 ZipStream/Exception/StreamNotReadableException.php create mode 100644 ZipStream/File.php rename {PhpOffice/PhpSpreadsheet => ZipStream}/LICENSE (81%) mode change 100755 => 100644 create mode 100644 ZipStream/Option/Archive.php create mode 100644 ZipStream/Option/File.php create mode 100644 ZipStream/Option/Method.php create mode 100644 ZipStream/Option/Version.php create mode 100644 ZipStream/Stream.php create mode 100644 ZipStream/ZipStream.php diff --git a/Laminas/Escaper/Escaper.php b/Laminas/Escaper/Escaper.php new file mode 100644 index 0000000..d6a02e1 --- /dev/null +++ b/Laminas/Escaper/Escaper.php @@ -0,0 +1,412 @@ + + */ + protected static $htmlNamedEntityMap = [ + 34 => 'quot', // quotation mark + 38 => 'amp', // ampersand + 60 => 'lt', // less-than sign + 62 => 'gt', // greater-than sign + ]; + + /** + * Current encoding for escaping. If not UTF-8, we convert strings from this encoding + * pre-escaping and back to this encoding post-escaping. + * + * @var string + */ + protected $encoding = 'utf-8'; + + /** + * Holds the value of the special flags passed as second parameter to + * htmlspecialchars(). + * + * @var int + */ + protected $htmlSpecialCharsFlags; + + /** + * Static Matcher which escapes characters for HTML Attribute contexts + * + * @var callable + * @psalm-var callable(array):string + */ + protected $htmlAttrMatcher; + + /** + * Static Matcher which escapes characters for Javascript contexts + * + * @var callable + * @psalm-var callable(array):string + */ + protected $jsMatcher; + + /** + * Static Matcher which escapes characters for CSS Attribute contexts + * + * @var callable + * @psalm-var callable(array):string + */ + protected $cssMatcher; + + /** + * List of all encoding supported by this class + * + * @var array + */ + protected $supportedEncodings = [ + 'iso-8859-1', + 'iso8859-1', + 'iso-8859-5', + 'iso8859-5', + 'iso-8859-15', + 'iso8859-15', + 'utf-8', + 'cp866', + 'ibm866', + '866', + 'cp1251', + 'windows-1251', + 'win-1251', + '1251', + 'cp1252', + 'windows-1252', + '1252', + 'koi8-r', + 'koi8-ru', + 'koi8r', + 'big5', + '950', + 'gb2312', + '936', + 'big5-hkscs', + 'shift_jis', + 'sjis', + 'sjis-win', + 'cp932', + '932', + 'euc-jp', + 'eucjp', + 'eucjp-win', + 'macroman', + ]; + + /** + * Constructor: Single parameter allows setting of global encoding for use by + * the current object. + * + * @throws Exception\InvalidArgumentException + */ + public function __construct(?string $encoding = null) + { + if ($encoding !== null) { + if ($encoding === '') { + throw new Exception\InvalidArgumentException( + static::class . ' constructor parameter does not allow a blank value' + ); + } + + $encoding = strtolower($encoding); + if (! in_array($encoding, $this->supportedEncodings)) { + throw new Exception\InvalidArgumentException( + 'Value of \'' . $encoding . '\' passed to ' . static::class + . ' constructor parameter is invalid. Provide an encoding supported by htmlspecialchars()' + ); + } + + $this->encoding = $encoding; + } + + // We take advantage of ENT_SUBSTITUTE flag to correctly deal with invalid UTF-8 sequences. + $this->htmlSpecialCharsFlags = ENT_QUOTES | ENT_SUBSTITUTE; + + // set matcher callbacks + $this->htmlAttrMatcher = [$this, 'htmlAttrMatcher']; + $this->jsMatcher = [$this, 'jsMatcher']; + $this->cssMatcher = [$this, 'cssMatcher']; + } + + /** + * Return the encoding that all output/input is expected to be encoded in. + * + * @return string + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * Escape a string for the HTML Body context where there are very few characters + * of special meaning. Internally this will use htmlspecialchars(). + * + * @return string + */ + public function escapeHtml(string $string) + { + return htmlspecialchars($string, $this->htmlSpecialCharsFlags, $this->encoding); + } + + /** + * Escape a string for the HTML Attribute context. We use an extended set of characters + * to escape that are not covered by htmlspecialchars() to cover cases where an attribute + * might be unquoted or quoted illegally (e.g. backticks are valid quotes for IE). + * + * @return string + */ + public function escapeHtmlAttr(string $string) + { + $string = $this->toUtf8($string); + if ($string === '' || ctype_digit($string)) { + return $string; + } + + $result = preg_replace_callback('/[^a-z0-9,\.\-_]/iSu', $this->htmlAttrMatcher, $string); + return $this->fromUtf8($result); + } + + /** + * Escape a string for the Javascript context. This does not use json_encode(). An extended + * set of characters are escaped beyond ECMAScript's rules for Javascript literal string + * escaping in order to prevent misinterpretation of Javascript as HTML leading to the + * injection of special characters and entities. The escaping used should be tolerant + * of cases where HTML escaping was not applied on top of Javascript escaping correctly. + * Backslash escaping is not used as it still leaves the escaped character as-is and so + * is not useful in a HTML context. + * + * @return string + */ + public function escapeJs(string $string) + { + $string = $this->toUtf8($string); + if ($string === '' || ctype_digit($string)) { + return $string; + } + + $result = preg_replace_callback('/[^a-z0-9,\._]/iSu', $this->jsMatcher, $string); + return $this->fromUtf8($result); + } + + /** + * Escape a string for the URI or Parameter contexts. This should not be used to escape + * an entire URI - only a subcomponent being inserted. The function is a simple proxy + * to rawurlencode() which now implements RFC 3986 since PHP 5.3 completely. + * + * @return string + */ + public function escapeUrl(string $string) + { + return rawurlencode($string); + } + + /** + * Escape a string for the CSS context. CSS escaping can be applied to any string being + * inserted into CSS and escapes everything except alphanumerics. + * + * @return string + */ + public function escapeCss(string $string) + { + $string = $this->toUtf8($string); + if ($string === '' || ctype_digit($string)) { + return $string; + } + + $result = preg_replace_callback('/[^a-z0-9]/iSu', $this->cssMatcher, $string); + return $this->fromUtf8($result); + } + + /** + * Callback function for preg_replace_callback that applies HTML Attribute + * escaping to all matches. + * + * @param array $matches + * @return string + */ + protected function htmlAttrMatcher($matches) + { + $chr = $matches[0]; + $ord = ord($chr); + + /** + * The following replaces characters undefined in HTML with the + * hex entity for the Unicode replacement character. + */ + if ( + ($ord <= 0x1f && $chr !== "\t" && $chr !== "\n" && $chr !== "\r") + || ($ord >= 0x7f && $ord <= 0x9f) + ) { + return '�'; + } + + /** + * Check if the current character to escape has a name entity we should + * replace it with while grabbing the integer value of the character. + */ + if (strlen($chr) > 1) { + $chr = $this->convertEncoding($chr, 'UTF-32BE', 'UTF-8'); + } + + $hex = bin2hex($chr); + $ord = hexdec($hex); + if (isset(static::$htmlNamedEntityMap[$ord])) { + return '&' . static::$htmlNamedEntityMap[$ord] . ';'; + } + + /** + * Per OWASP recommendations, we'll use upper hex entities + * for any other characters where a named entity does not exist. + */ + if ($ord > 255) { + return sprintf('&#x%04X;', $ord); + } + return sprintf('&#x%02X;', $ord); + } + + /** + * Callback function for preg_replace_callback that applies Javascript + * escaping to all matches. + * + * @param array $matches + * @return string + */ + protected function jsMatcher($matches) + { + $chr = $matches[0]; + if (strlen($chr) === 1) { + return sprintf('\\x%02X', ord($chr)); + } + $chr = $this->convertEncoding($chr, 'UTF-16BE', 'UTF-8'); + $hex = strtoupper(bin2hex($chr)); + if (strlen($hex) <= 4) { + return sprintf('\\u%04s', $hex); + } + $highSurrogate = substr($hex, 0, 4); + $lowSurrogate = substr($hex, 4, 4); + return sprintf('\\u%04s\\u%04s', $highSurrogate, $lowSurrogate); + } + + /** + * Callback function for preg_replace_callback that applies CSS + * escaping to all matches. + * + * @param array $matches + * @return string + */ + protected function cssMatcher($matches) + { + $chr = $matches[0]; + if (strlen($chr) === 1) { + $ord = ord($chr); + } else { + $chr = $this->convertEncoding($chr, 'UTF-32BE', 'UTF-8'); + $ord = hexdec(bin2hex($chr)); + } + return sprintf('\\%X ', $ord); + } + + /** + * Converts a string to UTF-8 from the base encoding. The base encoding is set via this + * + * @param string $string + * @throws Exception\RuntimeException + * @return string + */ + protected function toUtf8($string) + { + if ($this->getEncoding() === 'utf-8') { + $result = $string; + } else { + $result = $this->convertEncoding($string, 'UTF-8', $this->getEncoding()); + } + + if (! $this->isUtf8($result)) { + throw new Exception\RuntimeException( + sprintf('String to be escaped was not valid UTF-8 or could not be converted: %s', $result) + ); + } + + return $result; + } + + /** + * Converts a string from UTF-8 to the base encoding. The base encoding is set via this + * + * @param string $string + * @return string + */ + protected function fromUtf8($string) + { + if ($this->getEncoding() === 'utf-8') { + return $string; + } + + return $this->convertEncoding($string, $this->getEncoding(), 'UTF-8'); + } + + /** + * Checks if a given string appears to be valid UTF-8 or not. + * + * @param string $string + * @return bool + */ + protected function isUtf8($string) + { + return $string === '' || preg_match('/^./su', $string); + } + + /** + * Encoding conversion helper which wraps mb_convert_encoding + * + * @param string $string + * @param string $to + * @param array|string $from + * @return string + */ + protected function convertEncoding($string, $to, $from) + { + $result = mb_convert_encoding($string, $to, $from); + + if ($result === false) { + return ''; // return non-fatal blank string on encoding errors from users + } + + return $result; + } +} diff --git a/Laminas/Escaper/Exception/ExceptionInterface.php b/Laminas/Escaper/Exception/ExceptionInterface.php new file mode 100644 index 0000000..8f5fd89 --- /dev/null +++ b/Laminas/Escaper/Exception/ExceptionInterface.php @@ -0,0 +1,11 @@ + + * @author Daniel Costa + * @author Mirosław Filip + * + * @psalm-template T + * @psalm-immutable + * @psalm-consistent-constructor + */ +abstract class Enum implements \JsonSerializable, \Stringable +{ + /** + * Enum value + * + * @var mixed + * @psalm-var T + */ + protected $value; + + /** + * Enum key, the constant name + * + * @var string + */ + private $key; + + /** + * Store existing constants in a static cache per object. + * + * + * @var array + * @psalm-var array> + */ + protected static $cache = []; + + /** + * Cache of instances of the Enum class + * + * @var array + * @psalm-var array> + */ + protected static $instances = []; + + /** + * Creates a new value of some type + * + * @psalm-pure + * @param mixed $value + * + * @psalm-param T $value + * @throws \UnexpectedValueException if incompatible type is given. + */ + public function __construct($value) + { + if ($value instanceof static) { + /** @psalm-var T */ + $value = $value->getValue(); + } + + /** @psalm-suppress ImplicitToStringCast assertValidValueReturningKey returns always a string but psalm has currently an issue here */ + $this->key = static::assertValidValueReturningKey($value); + + /** @psalm-var T */ + $this->value = $value; + } + + /** + * This method exists only for the compatibility reason when deserializing a previously serialized version + * that didn't had the key property + */ + public function __wakeup() + { + /** @psalm-suppress DocblockTypeContradiction key can be null when deserializing an enum without the key */ + if ($this->key === null) { + /** + * @psalm-suppress InaccessibleProperty key is not readonly as marked by psalm + * @psalm-suppress PossiblyFalsePropertyAssignmentValue deserializing a case that was removed + */ + $this->key = static::search($this->value); + } + } + + /** + * @param mixed $value + * @return static + */ + public static function from($value): self + { + $key = static::assertValidValueReturningKey($value); + + return self::__callStatic($key, []); + } + + /** + * @psalm-pure + * @return mixed + * @psalm-return T + */ + public function getValue() + { + return $this->value; + } + + /** + * Returns the enum key (i.e. the constant name). + * + * @psalm-pure + * @return string + */ + public function getKey() + { + return $this->key; + } + + /** + * @psalm-pure + * @psalm-suppress InvalidCast + * @return string + */ + public function __toString() + { + return (string)$this->value; + } + + /** + * Determines if Enum should be considered equal with the variable passed as a parameter. + * Returns false if an argument is an object of different class or not an object. + * + * This method is final, for more information read https://github.com/myclabs/php-enum/issues/4 + * + * @psalm-pure + * @psalm-param mixed $variable + * @return bool + */ + final public function equals($variable = null): bool + { + return $variable instanceof self + && $this->getValue() === $variable->getValue() + && static::class === \get_class($variable); + } + + /** + * Returns the names (keys) of all constants in the Enum class + * + * @psalm-pure + * @psalm-return list + * @return array + */ + public static function keys() + { + return \array_keys(static::toArray()); + } + + /** + * Returns instances of the Enum class of all Enum constants + * + * @psalm-pure + * @psalm-return array + * @return static[] Constant name in key, Enum instance in value + */ + public static function values() + { + $values = array(); + + /** @psalm-var T $value */ + foreach (static::toArray() as $key => $value) { + /** @psalm-suppress UnsafeGenericInstantiation */ + $values[$key] = new static($value); + } + + return $values; + } + + /** + * Returns all possible values as an array + * + * @psalm-pure + * @psalm-suppress ImpureStaticProperty + * + * @psalm-return array + * @return array Constant name in key, constant value in value + */ + public static function toArray() + { + $class = static::class; + + if (!isset(static::$cache[$class])) { + /** @psalm-suppress ImpureMethodCall this reflection API usage has no side-effects here */ + $reflection = new \ReflectionClass($class); + /** @psalm-suppress ImpureMethodCall this reflection API usage has no side-effects here */ + static::$cache[$class] = $reflection->getConstants(); + } + + return static::$cache[$class]; + } + + /** + * Check if is valid enum value + * + * @param $value + * @psalm-param mixed $value + * @psalm-pure + * @psalm-assert-if-true T $value + * @return bool + */ + public static function isValid($value) + { + return \in_array($value, static::toArray(), true); + } + + /** + * Asserts valid enum value + * + * @psalm-pure + * @psalm-assert T $value + * @param mixed $value + */ + public static function assertValidValue($value): void + { + self::assertValidValueReturningKey($value); + } + + /** + * Asserts valid enum value + * + * @psalm-pure + * @psalm-assert T $value + * @param mixed $value + * @return string + */ + private static function assertValidValueReturningKey($value): string + { + if (false === ($key = static::search($value))) { + throw new \UnexpectedValueException("Value '$value' is not part of the enum " . static::class); + } + + return $key; + } + + /** + * Check if is valid enum key + * + * @param $key + * @psalm-param string $key + * @psalm-pure + * @return bool + */ + public static function isValidKey($key) + { + $array = static::toArray(); + + return isset($array[$key]) || \array_key_exists($key, $array); + } + + /** + * Return key for value + * + * @param mixed $value + * + * @psalm-param mixed $value + * @psalm-pure + * @return string|false + */ + public static function search($value) + { + return \array_search($value, static::toArray(), true); + } + + /** + * Returns a value when called statically like so: MyEnum::SOME_VALUE() given SOME_VALUE is a class constant + * + * @param string $name + * @param array $arguments + * + * @return static + * @throws \BadMethodCallException + * + * @psalm-pure + */ + public static function __callStatic($name, $arguments) + { + $class = static::class; + if (!isset(self::$instances[$class][$name])) { + $array = static::toArray(); + if (!isset($array[$name]) && !\array_key_exists($name, $array)) { + $message = "No static method or enum constant '$name' in class " . static::class; + throw new \BadMethodCallException($message); + } + /** @psalm-suppress UnsafeGenericInstantiation */ + return self::$instances[$class][$name] = new static($array[$name]); + } + return clone self::$instances[$class][$name]; + } + + /** + * Specify data which should be serialized to JSON. This method returns data that can be serialized by json_encode() + * natively. + * + * @return mixed + * @link http://php.net/manual/en/jsonserializable.jsonserialize.php + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->getValue(); + } +} diff --git a/MyCLabs/LICENSE b/MyCLabs/LICENSE new file mode 100644 index 0000000..2a8cf22 --- /dev/null +++ b/MyCLabs/LICENSE @@ -0,0 +1,18 @@ +The MIT License (MIT) + +Copyright (c) 2015 My C-Labs + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/PhpOffice/Common/Adapter/Zip/PclZipAdapter.php b/PhpOffice/Common/Adapter/Zip/PclZipAdapter.php old mode 100755 new mode 100644 index 053531f..9d31f1a --- a/PhpOffice/Common/Adapter/Zip/PclZipAdapter.php +++ b/PhpOffice/Common/Adapter/Zip/PclZipAdapter.php @@ -1,4 +1,5 @@ oPclZip = new PclZip($filename); $this->tmpDir = sys_get_temp_dir(); + return $this; } @@ -31,15 +33,16 @@ class PclZipAdapter implements ZipInterface { $pathData = pathinfo($localname); - $hFile = fopen($this->tmpDir.'/'.$pathData['basename'], "wb"); + $hFile = fopen($this->tmpDir . '/' . $pathData['basename'], 'wb'); fwrite($hFile, $contents); fclose($hFile); - $res = $this->oPclZip->add($this->tmpDir.'/'.$pathData['basename'], PCLZIP_OPT_REMOVE_PATH, $this->tmpDir, PCLZIP_OPT_ADD_PATH, $pathData['dirname']); + $res = $this->oPclZip->add($this->tmpDir . '/' . $pathData['basename'], PCLZIP_OPT_REMOVE_PATH, $this->tmpDir, PCLZIP_OPT_ADD_PATH, $pathData['dirname']); if ($res == 0) { - throw new \Exception("Error zipping files : " . $this->oPclZip->errorInfo(true)); + throw new \Exception('Error zipping files : ' . $this->oPclZip->errorInfo(true)); } - unlink($this->tmpDir.'/'.$pathData['basename']); + unlink($this->tmpDir . '/' . $pathData['basename']); + return $this; } } diff --git a/PhpOffice/Common/Adapter/Zip/ZipArchiveAdapter.php b/PhpOffice/Common/Adapter/Zip/ZipArchiveAdapter.php old mode 100755 new mode 100644 index da2c346..a8728eb --- a/PhpOffice/Common/Adapter/Zip/ZipArchiveAdapter.php +++ b/PhpOffice/Common/Adapter/Zip/ZipArchiveAdapter.php @@ -35,13 +35,14 @@ class ZipArchiveAdapter implements ZipInterface if ($this->oZipArchive->close() === false) { throw new \Exception("Could not close zip file $this->filename."); } + return $this; } public function addFromString($localname, $contents) { if ($this->oZipArchive->addFromString($localname, $contents) === false) { - throw new \Exception("Error zipping files : " . $localname); + throw new \Exception('Error zipping files : ' . $localname); } return $this; diff --git a/PhpOffice/Common/Adapter/Zip/ZipInterface.php b/PhpOffice/Common/Adapter/Zip/ZipInterface.php old mode 100755 new mode 100644 index e8dbc8c..1e22790 --- a/PhpOffice/Common/Adapter/Zip/ZipInterface.php +++ b/PhpOffice/Common/Adapter/Zip/ZipInterface.php @@ -6,24 +6,32 @@ interface ZipInterface { /** * Open a ZIP file archive + * * @param string $filename + * * @return $this + * * @throws \Exception */ public function open($filename); /** * Close the active archive (opened or newly created) + * * @return $this + * * @throws \Exception */ public function close(); /** * Add a file to a ZIP archive using its contents - * @param string $localname The name of the entry to create. + * + * @param string $localname the name of the entry to create * @param string $contents The contents to use to create the entry. It is used in a binary safe mode. + * * @return $this + * * @throws \Exception */ public function addFromString($localname, $contents); diff --git a/PhpOffice/Common/Autoloader.php b/PhpOffice/Common/Autoloader.php old mode 100755 new mode 100644 index 8b976c9..9269232 --- a/PhpOffice/Common/Autoloader.php +++ b/PhpOffice/Common/Autoloader.php @@ -9,7 +9,8 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/Common/contributors. * - * @link https://github.com/PHPOffice/Common + * @see https://github.com/PHPOffice/Common + * * @copyright 2009-2016 PHPOffice Common contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -22,16 +23,16 @@ namespace PhpOffice\Common; class Autoloader { /** @const string */ - const NAMESPACE_PREFIX = 'PhpOffice\\Common\\'; + public const NAMESPACE_PREFIX = 'PhpOffice\\Common\\'; /** * Register * * @return void */ - public static function register() + public static function register(): void { - spl_autoload_register(array(new self, 'autoload')); + spl_autoload_register([new self(), 'autoload']); } /** @@ -39,7 +40,7 @@ class Autoloader * * @param string $class */ - public static function autoload($class) + public static function autoload(string $class): void { $prefixLength = strlen(self::NAMESPACE_PREFIX); if (0 === strncmp(self::NAMESPACE_PREFIX, $class, $prefixLength)) { diff --git a/PhpOffice/Common/COPYING b/PhpOffice/Common/COPYING deleted file mode 100755 index 94a9ed0..0000000 --- a/PhpOffice/Common/COPYING +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/PhpOffice/Common/COPYING.LESSER b/PhpOffice/Common/COPYING.LESSER deleted file mode 100755 index 65c5ca8..0000000 --- a/PhpOffice/Common/COPYING.LESSER +++ /dev/null @@ -1,165 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. diff --git a/PhpOffice/Common/Drawing.php b/PhpOffice/Common/Drawing.php old mode 100755 new mode 100644 index d36c544..2f71865 --- a/PhpOffice/Common/Drawing.php +++ b/PhpOffice/Common/Drawing.php @@ -9,27 +9,26 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * - * @link https://github.com/PHPOffice/Common + * @see https://github.com/PHPOffice/Common + * * @copyright 2009-2016 PHPOffice Common contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\Common; -/** - * \PhpOffice\Common\Drawing - */ class Drawing { - const DPI_96 = 96; + public const DPI_96 = 96; /** * Convert pixels to EMU * - * @param int $pValue Value in pixels - * @return int + * @param float $pValue Value in pixels + * + * @return float */ - public static function pixelsToEmu($pValue = 0) + public static function pixelsToEmu(float $pValue = 0): float { return round($pValue * 9525); } @@ -37,89 +36,116 @@ class Drawing /** * Convert EMU to pixels * - * @param int $pValue Value in EMU + * @param int $pValue Value in EMU + * * @return int */ - public static function emuToPixels($pValue = 0) + public static function emuToPixels(int $pValue = 0): int { if ($pValue == 0) { return 0; } - return round($pValue / 9525); + + return (int) round($pValue / 9525); } /** * Convert pixels to points * - * @param int $pValue Value in pixels + * @param int $pValue Value in pixels + * * @return float */ - public static function pixelsToPoints($pValue = 0) + public static function pixelsToPoints(int $pValue = 0): float { - return $pValue * 0.67777777; + return $pValue * 0.75; } /** * Convert points width to centimeters * - * @param int $pValue Value in points + * @param float $pValue Value in points + * * @return float */ - public static function pointsToCentimeters($pValue = 0) + public static function pointsToCentimeters(float $pValue = 0): float { if ($pValue == 0) { return 0; } - return ((($pValue * 1.333333333) / self::DPI_96) * 2.54); + + return (($pValue / 0.75) / self::DPI_96) * 2.54; } - + + /** + * Convert centimeters width to points + * + * @param float $pValue Value in centimeters + * + * @return float + */ + public static function centimetersToPoints(float $pValue = 0): float + { + if ($pValue == 0) { + return 0; + } + + return ($pValue / 2.54) * self::DPI_96 * 0.75; + } + /** * Convert points width to pixels * - * @param int $pValue Value in points + * @param float $pValue Value in points + * * @return float */ - public static function pointsToPixels($pValue = 0) + public static function pointsToPixels(float $pValue = 0): float { if ($pValue == 0) { return 0; } - return $pValue * 1.333333333; + + return $pValue / 0.75; } /** * Convert pixels to centimeters * - * @param int $pValue Value in pixels + * @param int $pValue Value in pixels + * * @return float */ - public static function pixelsToCentimeters($pValue = 0) + public static function pixelsToCentimeters(int $pValue = 0): float { //return $pValue * 0.028; - return (($pValue / self::DPI_96) * 2.54); + return ($pValue / self::DPI_96) * 2.54; } /** * Convert centimeters width to pixels * - * @param int $pValue Value in centimeters - * @return float + * @param float $pValue Value in centimeters + * + * @return int */ - public static function centimetersToPixels($pValue = 0) + public static function centimetersToPixels(float $pValue = 0): int { if ($pValue == 0) { return 0; } - return ($pValue / 2.54) * self::DPI_96; + + return (int) round((($pValue / 2.54) * self::DPI_96)); } /** * Convert degrees to angle * - * @param int $pValue Degrees + * @param int $pValue Degrees + * * @return int */ - public static function degreesToAngle($pValue = 0) + public static function degreesToAngle(int $pValue = 0): int { return (int) round($pValue * 60000); } @@ -127,111 +153,140 @@ class Drawing /** * Convert angle to degrees * - * @param int $pValue Angle - * @return int + * @param int $pValue Angle + * + * @return float */ - public static function angleToDegrees($pValue = 0) + public static function angleToDegrees(int $pValue = 0): float { if ($pValue == 0) { return 0; } + return round($pValue / 60000); } /** * Convert centimeters width to twips * - * @param integer $pValue + * @param int $pValue + * * @return float */ - public static function centimetersToTwips($pValue = 0) + public static function centimetersToTwips(int $pValue = 0): float { if ($pValue == 0) { return 0; } + return $pValue * 566.928; } /** * Convert twips width to centimeters * - * @param integer $pValue + * @param int $pValue + * * @return float */ - public static function twipsToCentimeters($pValue = 0) + public static function twipsToCentimeters(int $pValue = 0): float { if ($pValue == 0) { return 0; } + return $pValue / 566.928; } /** * Convert inches width to twips * - * @param integer $pValue - * @return float + * @param int $pValue + * + * @return int */ - public static function inchesToTwips($pValue = 0) + public static function inchesToTwips(int $pValue = 0): int { if ($pValue == 0) { return 0; } + return $pValue * 1440; } /** * Convert twips width to inches * - * @param integer $pValue + * @param int $pValue + * * @return float */ - public static function twipsToInches($pValue = 0) + public static function twipsToInches(int $pValue = 0): float { if ($pValue == 0) { return 0; } + return $pValue / 1440; } /** * Convert twips width to pixels * - * @param integer $pValue + * @param int $pValue + * * @return float */ - public static function twipsToPixels($pValue = 0) + public static function twipsToPixels(int $pValue = 0): float { if ($pValue == 0) { return 0; } - return round($pValue / 15.873984); + + return round($pValue / 15); + } + + /** + * Convert points to emu + * + * @param float $pValue + * + * @return int + */ + public static function pointsToEmu(float $pValue = 0): int + { + if ($pValue == 0) { + return 0; + } + + return (int) round(($pValue / 0.75) / 9525); } /** * Convert HTML hexadecimal to RGB * * @param string $pValue HTML Color in hexadecimal - * @return array|false Value in RGB + * + * @return array|null Value in RGB */ - public static function htmlToRGB($pValue) + public static function htmlToRGB(string $pValue): ?array { if ($pValue[0] == '#') { $pValue = substr($pValue, 1); } if (strlen($pValue) == 6) { - list($colorR, $colorG, $colorB) = array($pValue[0] . $pValue[1], $pValue[2] . $pValue[3], $pValue[4] . $pValue[5]); + list($colorR, $colorG, $colorB) = [$pValue[0] . $pValue[1], $pValue[2] . $pValue[3], $pValue[4] . $pValue[5]]; } elseif (strlen($pValue) == 3) { - list($colorR, $colorG, $colorB) = array($pValue[0] . $pValue[0], $pValue[1] . $pValue[1], $pValue[2] . $pValue[2]); + list($colorR, $colorG, $colorB) = [$pValue[0] . $pValue[0], $pValue[1] . $pValue[1], $pValue[2] . $pValue[2]]; } else { - return false; + return null; } $colorR = hexdec($colorR); $colorG = hexdec($colorG); $colorB = hexdec($colorB); - return array($colorR, $colorG, $colorB); + return [$colorR, $colorG, $colorB]; } } diff --git a/PhpOffice/Common/File.php b/PhpOffice/Common/File.php old mode 100755 new mode 100644 index 8cb5907..8ab931d --- a/PhpOffice/Common/File.php +++ b/PhpOffice/Common/File.php @@ -9,35 +9,36 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * - * @link https://github.com/PHPOffice/Common + * @see https://github.com/PHPOffice/Common + * * @copyright 2009-2016 PHPOffice Common contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\Common; -/** - * Drawing - */ +use ZipArchive; + class File { /** * Verify if a file exists * - * @param string $pFilename Filename + * @param string $pFilename Filename + * * @return bool */ - public static function fileExists($pFilename) + public static function fileExists(string $pFilename): bool { // Sick construction, but it seems that // file_exists returns strange values when // doing the original file_exists on ZIP archives... if (strtolower(substr($pFilename, 0, 3)) == 'zip') { // Open ZIP file and verify if the file exists - $zipFile = substr($pFilename, 6, strpos($pFilename, '#') - 6); + $zipFile = substr($pFilename, 6, strpos($pFilename, '#') - 6); $archiveFile = substr($pFilename, strpos($pFilename, '#') + 1); - $zip = new \ZipArchive(); + $zip = new ZipArchive(); if ($zip->open($zipFile) === true) { $returnValue = ($zip->getFromName($archiveFile) !== false); $zip->close(); @@ -51,29 +52,33 @@ class File // Regular file_exists return file_exists($pFilename); } + /** * Returns the content of a file * - * @param string $pFilename Filename - * @return string + * @param string $pFilename Filename + * + * @return string|null */ - public static function fileGetContents($pFilename) + public static function fileGetContents(string $pFilename): ?string { if (!self::fileExists($pFilename)) { - return false; + return null; } if (strtolower(substr($pFilename, 0, 3)) == 'zip') { // Open ZIP file and verify if the file exists - $zipFile = substr($pFilename, 6, strpos($pFilename, '#') - 6); + $zipFile = substr($pFilename, 6, strpos($pFilename, '#') - 6); $archiveFile = substr($pFilename, strpos($pFilename, '#') + 1); - $zip = new \ZipArchive(); + $zip = new ZipArchive(); if ($zip->open($zipFile) === true) { $returnValue = $zip->getFromName($archiveFile); $zip->close(); + return $returnValue; } - return false; + + return null; } // Regular file contents return file_get_contents($pFilename); @@ -82,16 +87,17 @@ class File /** * Returns canonicalized absolute pathname, also for ZIP archives * - * @param string $pFilename + * @param string $pFilename + * * @return string */ - public static function realpath($pFilename) + public static function realpath(string $pFilename): string { // Try using realpath() $returnValue = realpath($pFilename); // Found something? - if ($returnValue == '' || is_null($returnValue)) { + if (empty($returnValue)) { $pathArray = explode('/', $pFilename); while (in_array('..', $pathArray) && $pathArray[0] != '..') { $numPathArray = count($pathArray); diff --git a/PhpOffice/Common/Font.php b/PhpOffice/Common/Font.php old mode 100755 new mode 100644 index 03a8e73..38bc81d --- a/PhpOffice/Common/Font.php +++ b/PhpOffice/Common/Font.php @@ -9,7 +9,8 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/Common/contributors. * - * @link https://github.com/PHPOffice/Common + * @see https://github.com/PHPOffice/Common + * * @copyright 2009-2016 PHPOffice Common contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -24,43 +25,47 @@ class Font /** * Calculate an (approximate) pixel size, based on a font points size * - * @param int $fontSizeInPoints Font size (in points) - * @return int Font size (in pixels) + * @param int $fontSizeInPoints Font size (in points) + * + * @return float Font size (in pixels) */ - public static function fontSizeToPixels($fontSizeInPoints = 12) + public static function fontSizeToPixels(int $fontSizeInPoints = 12): float { - return ((16 / 12) * $fontSizeInPoints); + return (16 / 12) * $fontSizeInPoints; } /** * Calculate an (approximate) pixel size, based on inch size * - * @param int $sizeInInch Font size (in inch) + * @param int $sizeInInch Font size (in inch) + * * @return int Size (in pixels) */ - public static function inchSizeToPixels($sizeInInch = 1) + public static function inchSizeToPixels(int $sizeInInch = 1): int { - return ($sizeInInch * 96); + return $sizeInInch * 96; } /** * Calculate an (approximate) pixel size, based on centimeter size * - * @param int $sizeInCm Font size (in centimeters) - * @return int Size (in pixels) + * @param int $sizeInCm Font size (in centimeters) + * + * @return float Size (in pixels) */ - public static function centimeterSizeToPixels($sizeInCm = 1) + public static function centimeterSizeToPixels(int $sizeInCm = 1): float { - return ($sizeInCm * 37.795275591); + return $sizeInCm * 37.795275591; } /** * Convert centimeter to twip * * @param int $sizeInCm - * @return double + * + * @return float */ - public static function centimeterSizeToTwips($sizeInCm = 1) + public static function centimeterSizeToTwips(int $sizeInCm = 1): float { return $sizeInCm / 2.54 * 1440; } @@ -69,9 +74,10 @@ class Font * Convert inch to twip * * @param int $sizeInInch - * @return double + * + * @return int */ - public static function inchSizeToTwips($sizeInInch = 1) + public static function inchSizeToTwips(int $sizeInInch = 1): int { return $sizeInInch * 1440; } @@ -80,9 +86,10 @@ class Font * Convert pixel to twip * * @param int $sizeInPixel - * @return double + * + * @return float */ - public static function pixelSizeToTwips($sizeInPixel = 1) + public static function pixelSizeToTwips(int $sizeInPixel = 1): float { return $sizeInPixel / 96 * 1440; } @@ -90,10 +97,11 @@ class Font /** * Calculate twip based on point size, used mainly for paragraph spacing * - * @param integer $sizeInPoint Size in point - * @return integer Size (in twips) + * @param int $sizeInPoint Size in point + * + * @return float Size (in twips) */ - public static function pointSizeToTwips($sizeInPoint = 1) + public static function pointSizeToTwips(int $sizeInPoint = 1): float { return $sizeInPoint / 72 * 1440; } diff --git a/PhpOffice/Common/LICENSE b/PhpOffice/Common/LICENSE deleted file mode 100755 index dba7f0e..0000000 --- a/PhpOffice/Common/LICENSE +++ /dev/null @@ -1,15 +0,0 @@ -PHPOffice Common, a shared PHP library for PHPOffice Libraries - -Copyright (c) 2015-2015 PHPOffice. - -PHPOffice Common is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License version 3 as published by -the Free Software Foundation. - -PHPOffice Common is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License version 3 for more details. - -You should have received a copy of the GNU Lesser General Public License version 3 -along with PHPOffice Common. If not, see . diff --git a/PhpOffice/Common/Microsoft/OLERead.php b/PhpOffice/Common/Microsoft/OLERead.php old mode 100755 new mode 100644 index fa1df12..0bdf7f0 --- a/PhpOffice/Common/Microsoft/OLERead.php +++ b/PhpOffice/Common/Microsoft/OLERead.php @@ -9,7 +9,8 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/Common/contributors. * - * @link https://github.com/PHPOffice/Common + * @see https://github.com/PHPOffice/Common + * * @copyright 2009-2016 PHPOffice Common contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -17,64 +18,98 @@ namespace PhpOffice\Common\Microsoft; if (!defined('IDENTIFIER_OLE')) { - define('IDENTIFIER_OLE', pack('CCCCCCCC', 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1)); + define('IDENTIFIER_OLE', pack('CCCCCCCC', 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1)); } class OLERead { + /** + * @var string + */ private $data = ''; // OLE identifier - const IDENTIFIER_OLE = IDENTIFIER_OLE; + public const IDENTIFIER_OLE = IDENTIFIER_OLE; // Size of a sector = 512 bytes - const BIG_BLOCK_SIZE = 0x200; + public const BIG_BLOCK_SIZE = 0x200; // Size of a short sector = 64 bytes - const SMALL_BLOCK_SIZE = 0x40; + public const SMALL_BLOCK_SIZE = 0x40; // Size of a directory entry always = 128 bytes - const PROPERTY_STORAGE_BLOCK_SIZE = 0x80; + public const PROPERTY_STORAGE_BLOCK_SIZE = 0x80; // Minimum size of a standard stream = 4096 bytes, streams smaller than this are stored as short streams - const SMALL_BLOCK_THRESHOLD = 0x1000; + public const SMALL_BLOCK_THRESHOLD = 0x1000; // header offsets - const NUM_BIG_BLOCK_DEPOT_BLOCKS_POS = 0x2c; - const ROOT_START_BLOCK_POS = 0x30; - const SMALL_BLOCK_DEPOT_BLOCK_POS = 0x3c; - const EXTENSION_BLOCK_POS = 0x44; - const NUM_EXTENSION_BLOCK_POS = 0x48; - const BIG_BLOCK_DEPOT_BLOCKS_POS = 0x4c; + public const NUM_BIG_BLOCK_DEPOT_BLOCKS_POS = 0x2C; + public const ROOT_START_BLOCK_POS = 0x30; + public const SMALL_BLOCK_DEPOT_BLOCK_POS = 0x3C; + public const EXTENSION_BLOCK_POS = 0x44; + public const NUM_EXTENSION_BLOCK_POS = 0x48; + public const BIG_BLOCK_DEPOT_BLOCKS_POS = 0x4C; // property storage offsets (directory offsets) - const SIZE_OF_NAME_POS = 0x40; - const TYPE_POS = 0x42; - const START_BLOCK_POS = 0x74; - const SIZE_POS = 0x78; + public const SIZE_OF_NAME_POS = 0x40; + public const TYPE_POS = 0x42; + public const START_BLOCK_POS = 0x74; + public const SIZE_POS = 0x78; - public $summaryInformation = null; - public $docSummaryInfos = null; - public $powerpointDocument = null; - public $currentUser = null; - public $pictures = null; - public $rootEntry = null; - public $props = array(); - public $smallBlockChain = null; - public $bigBlockChain = null; - public $entry = null; + /** + * @var int|null + */ + public $summaryInformation; + /** + * @var int|null + */ + public $docSummaryInfos; + /** + * @var int|null + */ + public $powerpointDocument; + /** + * @var int|null + */ + public $currentUser; + /** + * @var int|null + */ + public $pictures; + /** + * @var int|null + */ + public $rootEntry; + /** + * @var array> + */ + public $props = []; + /** + * @var string|null + */ + public $smallBlockChain; + /** + * @var string|null + */ + public $bigBlockChain; + /** + * @var string|null + */ + public $entry; /** * Read the file * - * @param $sFileName string Filename + * @param string $sFileName Filename + * * @throws \Exception */ - public function read($sFileName) + public function read(string $sFileName): void { // Check if file exists and is readable if (!is_readable($sFileName)) { - throw new \Exception("Could not open " . $sFileName . " for reading! File does not exist, or it is not readable."); + throw new \Exception('Could not open ' . $sFileName . ' for reading! File does not exist, or it is not readable.'); } // Get the file identifier @@ -104,18 +139,18 @@ class OLERead // Total number of sectors used by MSAT $numExtensionBlocks = self::getInt4d($this->data, self::NUM_EXTENSION_BLOCK_POS); - $bigBlockDepotBlocks = array(); + $bigBlockDepotBlocks = []; $pos = self::BIG_BLOCK_DEPOT_BLOCKS_POS; $bbdBlocks = $numBigBlkDepotBlks; if ($numExtensionBlocks != 0) { - $bbdBlocks = (self::BIG_BLOCK_SIZE - self::BIG_BLOCK_DEPOT_BLOCKS_POS)/4; + $bbdBlocks = (self::BIG_BLOCK_SIZE - self::BIG_BLOCK_DEPOT_BLOCKS_POS) / 4; } for ($i = 0; $i < $bbdBlocks; ++$i) { - $bigBlockDepotBlocks[$i] = self::getInt4d($this->data, $pos); - $pos += 4; + $bigBlockDepotBlocks[$i] = self::getInt4d($this->data, $pos); + $pos += 4; } for ($j = 0; $j < $numExtensionBlocks; ++$j) { @@ -138,8 +173,8 @@ class OLERead for ($i = 0; $i < $numBigBlkDepotBlks; ++$i) { $pos = ($bigBlockDepotBlocks[$i] + 1) * self::BIG_BLOCK_SIZE; - $this->bigBlockChain .= substr($this->data, $pos, 4*$bbs); - $pos += 4*$bbs; + $this->bigBlockChain .= substr($this->data, $pos, 4 * $bbs); + $pos += 4 * $bbs; } $sbdBlock = $sbdStartBlock; @@ -147,10 +182,10 @@ class OLERead while ($sbdBlock != -2) { $pos = ($sbdBlock + 1) * self::BIG_BLOCK_SIZE; - $this->smallBlockChain .= substr($this->data, $pos, 4*$bbs); - $pos += 4*$bbs; + $this->smallBlockChain .= substr($this->data, $pos, 4 * $bbs); + $pos += 4 * $bbs; - $sbdBlock = self::getInt4d($this->bigBlockChain, $sbdBlock*4); + $sbdBlock = self::getInt4d($this->bigBlockChain, $sbdBlock * 4); } // read the directory stream @@ -165,12 +200,8 @@ class OLERead * * @return string */ - public function getStream($stream) + public function getStream(int $stream): ?string { - if ($stream === null) { - return null; - } - $streamData = ''; if ($this->props[$stream]['size'] < self::SMALL_BLOCK_THRESHOLD) { @@ -179,10 +210,10 @@ class OLERead $block = $this->props[$stream]['startBlock']; while ($block != -2) { - $pos = $block * self::SMALL_BLOCK_SIZE; + $pos = $block * self::SMALL_BLOCK_SIZE; $streamData .= substr($rootdata, $pos, self::SMALL_BLOCK_SIZE); - $block = self::getInt4d($this->smallBlockChain, $block*4); + $block = self::getInt4d($this->smallBlockChain, $block * 4); } return $streamData; @@ -202,7 +233,7 @@ class OLERead while ($block != -2) { $pos = ($block + 1) * self::BIG_BLOCK_SIZE; $streamData .= substr($this->data, $pos, self::BIG_BLOCK_SIZE); - $block = self::getInt4d($this->bigBlockChain, $block*4); + $block = self::getInt4d($this->bigBlockChain, $block * 4); } return $streamData; @@ -212,9 +243,10 @@ class OLERead * Read a standard stream (by joining sectors using information from SAT) * * @param int $blID Sector ID where the stream starts + * * @return string Data for standard stream */ - private function readData($blID) + private function readData(int $blID): string { $block = $blID; $data = ''; @@ -222,15 +254,16 @@ class OLERead while ($block != -2) { $pos = ($block + 1) * self::BIG_BLOCK_SIZE; $data .= substr($this->data, $pos, self::BIG_BLOCK_SIZE); - $block = self::getInt4d($this->bigBlockChain, $block*4); + $block = self::getInt4d($this->bigBlockChain, $block * 4); } + return $data; } /** * Read entries in the directory stream. */ - private function readPropertySets() + private function readPropertySets(): void { $offset = 0; @@ -241,7 +274,7 @@ class OLERead $data = substr($this->entry, $offset, self::PROPERTY_STORAGE_BLOCK_SIZE); // size in bytes of name - $nameSize = ord($data[self::SIZE_OF_NAME_POS]) | (ord($data[self::SIZE_OF_NAME_POS+1]) << 8); + $nameSize = ord($data[self::SIZE_OF_NAME_POS]) | (ord($data[self::SIZE_OF_NAME_POS + 1]) << 8); // type of entry $type = ord($data[self::TYPE_POS]); @@ -252,13 +285,14 @@ class OLERead $size = self::getInt4d($data, self::SIZE_POS); - $name = str_replace("\x00", "", substr($data, 0, $nameSize)); + $name = str_replace("\x00", '', substr($data, 0, $nameSize)); if ($size > 0) { - $this->props[] = array ( - 'name' => $name, - 'type' => $type, - 'startBlock' => $startBlock, - 'size' => $size); + $this->props[] = [ + 'name' => $name, + 'type' => $type, + 'startBlock' => $startBlock, + 'size' => $size, + ]; // tmp helper to simplify checks $upName = strtoupper($name); @@ -268,14 +302,14 @@ class OLERead case 'R': $this->rootEntry = count($this->props) - 1; break; - case chr(1).'COMPOBJ': + case chr(1) . 'COMPOBJ': break; - case chr(1).'OLE': + case chr(1) . 'OLE': break; - case chr(5).'SUMMARYINFORMATION': + case chr(5) . 'SUMMARYINFORMATION': $this->summaryInformation = count($this->props) - 1; break; - case chr(5).'DOCUMENTSUMMARYINFORMATION': + case chr(5) . 'DOCUMENTSUMMARYINFORMATION': $this->docSummaryInfos = count($this->props) - 1; break; case 'CURRENT USER': @@ -288,7 +322,7 @@ class OLERead $this->powerpointDocument = count($this->props) - 1; break; default: - throw new \Exception('OLE Block Not defined: $upName : '.$upName. ' - $name : "'.$name.'"'); + throw new \Exception('OLE Block Not defined: $upName : ' . $upName . ' - $name : "' . $name . '"'); } } @@ -301,6 +335,7 @@ class OLERead * * @param string $data * @param int $pos + * * @return int */ private static function getInt4d($data, $pos) @@ -315,6 +350,7 @@ class OLERead } else { $ord24 = ($or24 & 127) << 24; } + return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $ord24; } } diff --git a/PhpOffice/Common/Microsoft/PasswordEncoder.php b/PhpOffice/Common/Microsoft/PasswordEncoder.php old mode 100755 new mode 100644 index f620f4c..097060b --- a/PhpOffice/Common/Microsoft/PasswordEncoder.php +++ b/PhpOffice/Common/Microsoft/PasswordEncoder.php @@ -9,7 +9,8 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/Common/contributors. * - * @link https://github.com/PHPOffice/Common + * @see https://github.com/PHPOffice/Common + * * @copyright 2009-2016 PHPOffice Common contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -21,39 +22,43 @@ namespace PhpOffice\Common\Microsoft; */ class PasswordEncoder { - const ALGORITHM_MD2 = 'MD2'; - const ALGORITHM_MD4 = 'MD4'; - const ALGORITHM_MD5 = 'MD5'; - const ALGORITHM_SHA_1 = 'SHA-1'; - const ALGORITHM_SHA_256 = 'SHA-256'; - const ALGORITHM_SHA_384 = 'SHA-384'; - const ALGORITHM_SHA_512 = 'SHA-512'; - const ALGORITHM_RIPEMD = 'RIPEMD'; - const ALGORITHM_RIPEMD_160 = 'RIPEMD-160'; - const ALGORITHM_MAC = 'MAC'; - const ALGORITHM_HMAC = 'HMAC'; + public const ALGORITHM_MD2 = 'MD2'; + public const ALGORITHM_MD4 = 'MD4'; + public const ALGORITHM_MD5 = 'MD5'; + public const ALGORITHM_SHA_1 = 'SHA-1'; + public const ALGORITHM_SHA_256 = 'SHA-256'; + public const ALGORITHM_SHA_384 = 'SHA-384'; + public const ALGORITHM_SHA_512 = 'SHA-512'; + public const ALGORITHM_RIPEMD = 'RIPEMD'; + public const ALGORITHM_RIPEMD_160 = 'RIPEMD-160'; + public const ALGORITHM_MAC = 'MAC'; + public const ALGORITHM_HMAC = 'HMAC'; /** * Mapping between algorithm name and algorithm ID * - * @var array + * @var array> + * * @see https://msdn.microsoft.com/en-us/library/documentformat.openxml.wordprocessing.writeprotection.cryptographicalgorithmsid(v=office.14).aspx */ - private static $algorithmMapping = array( - self::ALGORITHM_MD2 => array(1, 'md2'), - self::ALGORITHM_MD4 => array(2, 'md4'), - self::ALGORITHM_MD5 => array(3, 'md5'), - self::ALGORITHM_SHA_1 => array(4, 'sha1'), - self::ALGORITHM_MAC => array(5, ''), // 'mac' -> not possible with hash() - self::ALGORITHM_RIPEMD => array(6, 'ripemd'), - self::ALGORITHM_RIPEMD_160 => array(7, 'ripemd160'), - self::ALGORITHM_HMAC => array(9, ''), //'hmac' -> not possible with hash() - self::ALGORITHM_SHA_256 => array(12, 'sha256'), - self::ALGORITHM_SHA_384 => array(13, 'sha384'), - self::ALGORITHM_SHA_512 => array(14, 'sha512'), - ); + private static $algorithmMapping = [ + self::ALGORITHM_MD2 => [1, 'md2'], + self::ALGORITHM_MD4 => [2, 'md4'], + self::ALGORITHM_MD5 => [3, 'md5'], + self::ALGORITHM_SHA_1 => [4, 'sha1'], + self::ALGORITHM_MAC => [5, ''], // 'mac' -> not possible with hash() + self::ALGORITHM_RIPEMD => [6, 'ripemd'], + self::ALGORITHM_RIPEMD_160 => [7, 'ripemd160'], + self::ALGORITHM_HMAC => [9, ''], //'hmac' -> not possible with hash() + self::ALGORITHM_SHA_256 => [12, 'sha256'], + self::ALGORITHM_SHA_384 => [13, 'sha384'], + self::ALGORITHM_SHA_512 => [14, 'sha512'], + ]; - private static $initialCodeArray = array( + /** + * @var array + */ + private static $initialCodeArray = [ 0xE1F0, 0x1D0F, 0xCC9C, @@ -69,39 +74,47 @@ class PasswordEncoder 0x280C, 0xA96A, 0x4EC3, - ); + ]; - private static $encryptionMatrix = array( - array(0xAEFC, 0x4DD9, 0x9BB2, 0x2745, 0x4E8A, 0x9D14, 0x2A09), - array(0x7B61, 0xF6C2, 0xFDA5, 0xEB6B, 0xC6F7, 0x9DCF, 0x2BBF), - array(0x4563, 0x8AC6, 0x05AD, 0x0B5A, 0x16B4, 0x2D68, 0x5AD0), - array(0x0375, 0x06EA, 0x0DD4, 0x1BA8, 0x3750, 0x6EA0, 0xDD40), - array(0xD849, 0xA0B3, 0x5147, 0xA28E, 0x553D, 0xAA7A, 0x44D5), - array(0x6F45, 0xDE8A, 0xAD35, 0x4A4B, 0x9496, 0x390D, 0x721A), - array(0xEB23, 0xC667, 0x9CEF, 0x29FF, 0x53FE, 0xA7FC, 0x5FD9), - array(0x47D3, 0x8FA6, 0x0F6D, 0x1EDA, 0x3DB4, 0x7B68, 0xF6D0), - array(0xB861, 0x60E3, 0xC1C6, 0x93AD, 0x377B, 0x6EF6, 0xDDEC), - array(0x45A0, 0x8B40, 0x06A1, 0x0D42, 0x1A84, 0x3508, 0x6A10), - array(0xAA51, 0x4483, 0x8906, 0x022D, 0x045A, 0x08B4, 0x1168), - array(0x76B4, 0xED68, 0xCAF1, 0x85C3, 0x1BA7, 0x374E, 0x6E9C), - array(0x3730, 0x6E60, 0xDCC0, 0xA9A1, 0x4363, 0x86C6, 0x1DAD), - array(0x3331, 0x6662, 0xCCC4, 0x89A9, 0x0373, 0x06E6, 0x0DCC), - array(0x1021, 0x2042, 0x4084, 0x8108, 0x1231, 0x2462, 0x48C4), - ); + /** + * @var array> + */ + private static $encryptionMatrix = [ + [0xAEFC, 0x4DD9, 0x9BB2, 0x2745, 0x4E8A, 0x9D14, 0x2A09], + [0x7B61, 0xF6C2, 0xFDA5, 0xEB6B, 0xC6F7, 0x9DCF, 0x2BBF], + [0x4563, 0x8AC6, 0x05AD, 0x0B5A, 0x16B4, 0x2D68, 0x5AD0], + [0x0375, 0x06EA, 0x0DD4, 0x1BA8, 0x3750, 0x6EA0, 0xDD40], + [0xD849, 0xA0B3, 0x5147, 0xA28E, 0x553D, 0xAA7A, 0x44D5], + [0x6F45, 0xDE8A, 0xAD35, 0x4A4B, 0x9496, 0x390D, 0x721A], + [0xEB23, 0xC667, 0x9CEF, 0x29FF, 0x53FE, 0xA7FC, 0x5FD9], + [0x47D3, 0x8FA6, 0x0F6D, 0x1EDA, 0x3DB4, 0x7B68, 0xF6D0], + [0xB861, 0x60E3, 0xC1C6, 0x93AD, 0x377B, 0x6EF6, 0xDDEC], + [0x45A0, 0x8B40, 0x06A1, 0x0D42, 0x1A84, 0x3508, 0x6A10], + [0xAA51, 0x4483, 0x8906, 0x022D, 0x045A, 0x08B4, 0x1168], + [0x76B4, 0xED68, 0xCAF1, 0x85C3, 0x1BA7, 0x374E, 0x6E9C], + [0x3730, 0x6E60, 0xDCC0, 0xA9A1, 0x4363, 0x86C6, 0x1DAD], + [0x3331, 0x6662, 0xCCC4, 0x89A9, 0x0373, 0x06E6, 0x0DCC], + [0x1021, 0x2042, 0x4084, 0x8108, 0x1231, 0x2462, 0x48C4], + ]; + /** + * @var int + */ private static $passwordMaxLength = 15; /** * Create a hashed password that MS Word will be able to work with + * * @see https://blogs.msdn.microsoft.com/vsod/2010/04/05/how-to-set-the-editing-restrictions-in-word-using-open-xml-sdk-2-0/ * * @param string $password * @param string $algorithmName * @param string $salt * @param int $spinCount + * * @return string */ - public static function hashPassword($password, $algorithmName = self::ALGORITHM_SHA_1, $salt = null, $spinCount = 10000) + public static function hashPassword(string $password, string $algorithmName = self::ALGORITHM_SHA_1, string $salt = null, int $spinCount = 10000) { $origEncoding = mb_internal_encoding(); mb_internal_encoding('UTF-8'); @@ -111,9 +124,9 @@ class PasswordEncoder // Get the single-byte values by iterating through the Unicode characters of the truncated password. // For each character, if the low byte is not equal to 0, take it. Otherwise, take the high byte. $passUtf8 = mb_convert_encoding($password, 'UCS-2LE', 'UTF-8'); - $byteChars = array(); + $byteChars = []; - for ($i = 0; $i < mb_strlen($password); $i++) { + for ($i = 0; $i < mb_strlen($password); ++$i) { $byteChars[$i] = ord(substr($passUtf8, $i * 2, 1)); if ($byteChars[$i] == 0) { @@ -135,7 +148,7 @@ class PasswordEncoder $algorithm = self::getAlgorithm($algorithmName); $generatedKey = hash($algorithm, $salt . $generatedKey, true); - for ($i = 0; $i < $spinCount; $i++) { + for ($i = 0; $i < $spinCount; ++$i) { $generatedKey = hash($algorithm, $generatedKey . pack('CCCC', $i, $i >> 8, $i >> 16, $i >> 24), true); } $generatedKey = base64_encode($generatedKey); @@ -149,9 +162,10 @@ class PasswordEncoder * Get algorithm from self::$algorithmMapping * * @param string $algorithmName + * * @return string */ - private static function getAlgorithm($algorithmName) + private static function getAlgorithm(string $algorithmName): string { $algorithm = self::$algorithmMapping[$algorithmName][1]; if ($algorithm == '') { @@ -165,9 +179,10 @@ class PasswordEncoder * Returns the algorithm ID * * @param string $algorithmName + * * @return int */ - public static function getAlgorithmId($algorithmName) + public static function getAlgorithmId(string $algorithmName): int { return self::$algorithmMapping[$algorithmName][0]; } @@ -175,10 +190,11 @@ class PasswordEncoder /** * Build combined key from low-order word and high-order word * - * @param array $byteChars byte array representation of password + * @param array $byteChars byte array representation of password + * * @return int */ - private static function buildCombinedKey($byteChars) + private static function buildCombinedKey(array $byteChars): int { $byteCharsLength = count($byteChars); // Compute the high-order word @@ -189,10 +205,10 @@ class PasswordEncoder // For every bit in the character, starting with the least significant and progressing to (but excluding) // the most significant, if the bit is set, XOR the key’s high-order word with the corresponding word from // the Encryption Matrix - for ($i = 0; $i < $byteCharsLength; $i++) { + for ($i = 0; $i < $byteCharsLength; ++$i) { $tmp = self::$passwordMaxLength - $byteCharsLength + $i; $matrixRow = self::$encryptionMatrix[$tmp]; - for ($intBit = 0; $intBit < 7; $intBit++) { + for ($intBit = 0; $intBit < 7; ++$intBit) { if (($byteChars[$i] & (0x0001 << $intBit)) != 0) { $highOrderWord = ($highOrderWord ^ $matrixRow[$intBit]); } @@ -203,7 +219,7 @@ class PasswordEncoder // Initialize with 0 $lowOrderWord = 0; // For each character in the password, going backwards - for ($i = $byteCharsLength - 1; $i >= 0; $i--) { + for ($i = $byteCharsLength - 1; $i >= 0; --$i) { // low-order word = (((low-order word SHR 14) AND 0x0001) OR (low-order word SHL 1) AND 0x7FFF)) XOR character $lowOrderWord = (((($lowOrderWord >> 14) & 0x0001) | (($lowOrderWord << 1) & 0x7FFF)) ^ $byteChars[$i]); } @@ -218,7 +234,9 @@ class PasswordEncoder * Simulate behaviour of (signed) int32 * * @codeCoverageIgnore + * * @param int $value + * * @return int */ private static function int32($value) diff --git a/PhpOffice/Common/Text.php b/PhpOffice/Common/Text.php old mode 100755 new mode 100644 index 969b6c1..1410f79 --- a/PhpOffice/Common/Text.php +++ b/PhpOffice/Common/Text.php @@ -9,7 +9,8 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/Common/contributors. * - * @link https://github.com/PHPOffice/Common + * @see https://github.com/PHPOffice/Common + * * @copyright 2009-2016 PHPOffice Common contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -26,17 +27,17 @@ class Text * * @var string[] */ - private static $controlCharacters = array(); + private static $controlCharacters = []; /** * Build control characters array */ - private static function buildControlCharacters() + private static function buildControlCharacters(): void { for ($i = 0; $i <= 19; ++$i) { if ($i != 9 && $i != 10 && $i != 13) { - $find = '_x' . sprintf('%04s', strtoupper(dechex($i))) . '_'; - $replace = chr($i); + $find = '_x' . sprintf('%04s', strtoupper(dechex($i))) . '_'; + $replace = chr($i); self::$controlCharacters[$find] = $replace; } } @@ -53,10 +54,11 @@ class Text * So you could end up with something like _x0008_ in a string (either in a cell value () * element or in the shared string element. * - * @param string $value Value to escape + * @param string $value Value to escape + * * @return string */ - public static function controlCharacterPHP2OOXML($value = '') + public static function controlCharacterPHP2OOXML(string $value = ''): string { if (empty(self::$controlCharacters)) { self::buildControlCharacters(); @@ -67,35 +69,41 @@ class Text /** * Return a number formatted for being integrated in xml files + * * @param float $number - * @param integer $decimals + * @param int $decimals + * * @return string */ - public static function numberFormat($number, $decimals) + public static function numberFormat(float $number, int $decimals): string { return number_format($number, $decimals, '.', ''); } /** * @param int $dec - * @link http://stackoverflow.com/a/7153133/2235790 + * + * @see http://stackoverflow.com/a/7153133/2235790 + * * @author velcrow + * * @return string */ - public static function chr($dec) + public static function chr(int $dec): string { - if ($dec<=0x7F) { + if ($dec <= 0x7F) { return chr($dec); } - if ($dec<=0x7FF) { - return chr(($dec>>6)+192).chr(($dec&63)+128); + if ($dec <= 0x7FF) { + return chr(($dec >> 6) + 192) . chr(($dec & 63) + 128); } - if ($dec<=0xFFFF) { - return chr(($dec>>12)+224).chr((($dec>>6)&63)+128).chr(($dec&63)+128); + if ($dec <= 0xFFFF) { + return chr(($dec >> 12) + 224) . chr((($dec >> 6) & 63) + 128) . chr(($dec & 63) + 128); } - if ($dec<=0x1FFFFF) { - return chr(($dec>>18)+240).chr((($dec>>12)&63)+128).chr((($dec>>6)&63)+128).chr(($dec&63)+128); + if ($dec <= 0x1FFFFF) { + return chr(($dec >> 18) + 240) . chr((($dec >> 12) & 63) + 128) . chr((($dec >> 6) & 63) + 128) . chr(($dec & 63) + 128); } + return ''; } @@ -103,9 +111,10 @@ class Text * Convert from OpenXML escaped control character to PHP control character * * @param string $value Value to unescape + * * @return string */ - public static function controlCharacterOOXML2PHP($value = '') + public static function controlCharacterOOXML2PHP(string $value = ''): string { if (empty(self::$controlCharacters)) { self::buildControlCharacters(); @@ -118,9 +127,10 @@ class Text * Check if a string contains UTF-8 data * * @param string $value - * @return boolean + * + * @return bool */ - public static function isUTF8($value = '') + public static function isUTF8(string $value = ''): bool { return is_string($value) && ($value === '' || preg_match('/^./su', $value) == 1); } @@ -128,10 +138,11 @@ class Text /** * Return UTF8 encoded value * - * @param string $value - * @return string + * @param string|null $value + * + * @return string|null */ - public static function toUTF8($value = '') + public static function toUTF8(?string $value = ''): ?string { if (!is_null($value) && !self::isUTF8($value)) { $value = utf8_encode($value); @@ -146,10 +157,12 @@ class Text * The function is splitted to reduce cyclomatic complexity * * @param string $text UTF8 text + * * @return string Unicode text + * * @since 0.11.0 */ - public static function toUnicode($text) + public static function toUnicode(string $text): string { return self::unicodeToEntities(self::utf8ToUnicode($text)); } @@ -158,18 +171,20 @@ class Text * Returns unicode array from UTF8 text * * @param string $text UTF8 text - * @return array + * + * @return array + * * @since 0.11.0 - * @link http://www.randomchaos.com/documents/?source=php_and_unicode + * @see http://www.randomchaos.com/documents/?source=php_and_unicode */ - public static function utf8ToUnicode($text) + public static function utf8ToUnicode(string $text): array { - $unicode = array(); - $values = array(); + $unicode = []; + $values = []; $lookingFor = 1; // Gets unicode for each character - for ($i = 0; $i < strlen($text); $i++) { + for ($i = 0; $i < strlen($text); ++$i) { $thisValue = ord($text[$i]); if ($thisValue < 128) { $unicode[] = $thisValue; @@ -185,7 +200,7 @@ class Text $number = (($values[0] % 32) * 64) + ($values[1] % 64); } $unicode[] = $number; - $values = array(); + $values = []; $lookingFor = 1; } } @@ -197,12 +212,14 @@ class Text /** * Returns entites from unicode array * - * @param array $unicode + * @param array $unicode + * * @return string + * * @since 0.11.0 - * @link http://www.randomchaos.com/documents/?source=php_and_unicode + * @see http://www.randomchaos.com/documents/?source=php_and_unicode */ - private static function unicodeToEntities($unicode) + private static function unicodeToEntities(array $unicode): string { $entities = ''; @@ -218,10 +235,11 @@ class Text /** * Return name without underscore for < 0.10.0 variable name compatibility * - * @param string $value + * @param string|null $value + * * @return string */ - public static function removeUnderscorePrefix($value) + public static function removeUnderscorePrefix(?string $value): string { if (!is_null($value)) { if (substr($value, 0, 1) == '_') { diff --git a/PhpOffice/Common/XMLReader.php b/PhpOffice/Common/XMLReader.php old mode 100755 new mode 100644 index 5a2fa98..5d9fe81 --- a/PhpOffice/Common/XMLReader.php +++ b/PhpOffice/Common/XMLReader.php @@ -9,13 +9,20 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/Common/contributors. * - * @link https://github.com/PHPOffice/Common + * @see https://github.com/PHPOffice/Common + * * @copyright 2009-2016 PHPOffice Common contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\Common; +use DOMDocument; +use DOMElement; +use DOMNodeList; +use DOMXpath; +use ZipArchive; + /** * XML Reader wrapper * @@ -26,14 +33,14 @@ class XMLReader /** * DOMDocument object * - * @var \DOMDocument + * @var DOMDocument */ private $dom = null; /** * DOMXpath object * - * @var \DOMXpath + * @var DOMXpath */ private $xpath = null; @@ -42,16 +49,18 @@ class XMLReader * * @param string $zipFile * @param string $xmlFile - * @return \DOMDocument|false + * + * @return DOMDocument|false + * * @throws \Exception */ - public function getDomFromZip($zipFile, $xmlFile) + public function getDomFromZip(string $zipFile, string $xmlFile) { if (file_exists($zipFile) === false) { throw new \Exception('Cannot find archive file.'); } - $zip = new \ZipArchive(); + $zip = new ZipArchive(); $zip->open($zipFile); $content = $zip->getFromName($xmlFile); $zip->close(); @@ -67,14 +76,22 @@ class XMLReader * Get DOMDocument from content string * * @param string $content - * @return \DOMDocument + * + * @return DOMDocument */ - public function getDomFromString($content) + public function getDomFromString(string $content) { - //$originalLibXMLEntityValue = libxml_disable_entity_loader(true); - $this->dom = new \DOMDocument(); + $originalLibXMLEntityValue = false; + if (\PHP_VERSION_ID < 80000) { + $originalLibXMLEntityValue = libxml_disable_entity_loader(true); + } + + $this->dom = new DOMDocument(); $this->dom->loadXML($content); - //libxml_disable_entity_loader($originalLibXMLEntityValue); + + if (\PHP_VERSION_ID < 80000) { + libxml_disable_entity_loader($originalLibXMLEntityValue); + } return $this->dom; } @@ -83,16 +100,17 @@ class XMLReader * Get elements * * @param string $path - * @param \DOMElement $contextNode - * @return \DOMNodeList + * @param DOMElement $contextNode + * + * @return DOMNodeList */ - public function getElements($path, \DOMElement $contextNode = null) + public function getElements(string $path, DOMElement $contextNode = null) { if ($this->dom === null) { - return array(); + return new DOMNodeList(); } if ($this->xpath === null) { - $this->xpath = new \DOMXpath($this->dom); + $this->xpath = new DOMXpath($this->dom); } if (is_null($contextNode)) { @@ -107,7 +125,9 @@ class XMLReader * * @param string $prefix The prefix * @param string $namespaceURI The URI of the namespace + * * @return bool true on success or false on failure + * * @throws \InvalidArgumentException If called before having loaded the DOM document */ public function registerNamespace($prefix, $namespaceURI) @@ -116,8 +136,9 @@ class XMLReader throw new \InvalidArgumentException('Dom needs to be loaded before registering a namespace'); } if ($this->xpath === null) { - $this->xpath = new \DOMXpath($this->dom); + $this->xpath = new DOMXpath($this->dom); } + return $this->xpath->registerNamespace($prefix, $namespaceURI); } @@ -125,14 +146,15 @@ class XMLReader * Get element * * @param string $path - * @param \DOMElement $contextNode - * @return \DOMElement|null + * @param DOMElement $contextNode + * + * @return DOMElement|null */ - public function getElement($path, \DOMElement $contextNode = null) + public function getElement($path, DOMElement $contextNode = null): ?DOMElement { $elements = $this->getElements($path, $contextNode); if ($elements->length > 0) { - return $elements->item(0); + return $elements->item(0) instanceof DOMElement ? $elements->item(0) : null; } return null; @@ -142,17 +164,18 @@ class XMLReader * Get element attribute * * @param string $attribute - * @param \DOMElement $contextNode + * @param DOMElement $contextNode * @param string $path + * * @return string|null */ - public function getAttribute($attribute, \DOMElement $contextNode = null, $path = null) + public function getAttribute($attribute, DOMElement $contextNode = null, $path = null) { $return = null; if ($path !== null) { $elements = $this->getElements($path, $contextNode); if ($elements->length > 0) { - /** @var \DOMElement $node Type hint */ + /** @var DOMElement $node Type hint */ $node = $elements->item(0); $return = $node->getAttribute($attribute); } @@ -169,10 +192,11 @@ class XMLReader * Get element value * * @param string $path - * @param \DOMElement $contextNode + * @param DOMElement $contextNode + * * @return string|null */ - public function getValue($path, \DOMElement $contextNode = null) + public function getValue($path, DOMElement $contextNode = null) { $elements = $this->getElements($path, $contextNode); if ($elements->length > 0) { @@ -186,10 +210,11 @@ class XMLReader * Count elements * * @param string $path - * @param \DOMElement $contextNode - * @return integer + * @param DOMElement $contextNode + * + * @return int */ - public function countElements($path, \DOMElement $contextNode = null) + public function countElements($path, DOMElement $contextNode = null) { $elements = $this->getElements($path, $contextNode); @@ -200,10 +225,11 @@ class XMLReader * Element exists * * @param string $path - * @param \DOMElement $contextNode - * @return boolean + * @param DOMElement $contextNode + * + * @return bool */ - public function elementExists($path, \DOMElement $contextNode = null) + public function elementExists($path, DOMElement $contextNode = null) { return $this->getElements($path, $contextNode)->length > 0; } diff --git a/PhpOffice/Common/XMLWriter.php b/PhpOffice/Common/XMLWriter.php old mode 100755 new mode 100644 index 4440d65..4c886d6 --- a/PhpOffice/Common/XMLWriter.php +++ b/PhpOffice/Common/XMLWriter.php @@ -9,7 +9,8 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/Common/contributors. * - * @link https://github.com/PHPOffice/Common + * @see https://github.com/PHPOffice/Common + * * @copyright 2009-2016 PHPOffice Common contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -35,8 +36,8 @@ namespace PhpOffice\Common; class XMLWriter extends \XMLWriter { /** Temporary storage method */ - const STORAGE_MEMORY = 1; - const STORAGE_DISK = 2; + public const STORAGE_MEMORY = 1; + public const STORAGE_DISK = 2; /** * Temporary filename @@ -58,7 +59,7 @@ class XMLWriter extends \XMLWriter if ($pTemporaryStorage == self::STORAGE_MEMORY) { $this->openMemory(); } else { - if (!is_dir($pTemporaryStorageDir)) { + if ($pTemporaryStorageDir && !is_dir($pTemporaryStorageDir)) { $pTemporaryStorageDir = sys_get_temp_dir(); } // Create temporary filename @@ -87,7 +88,7 @@ class XMLWriter extends \XMLWriter return; } if (PHP_OS != 'WINNT' && @unlink($this->tempFileName) === false) { - throw new \Exception('The file '.$this->tempFileName.' could not be deleted.'); + throw new \Exception('The file ' . $this->tempFileName . ' could not be deleted.'); } } @@ -103,10 +104,10 @@ class XMLWriter extends \XMLWriter } $this->flush(); + return file_get_contents($this->tempFileName); } - /** * Write simple element and attribute(s) block * @@ -115,15 +116,16 @@ class XMLWriter extends \XMLWriter * 2. If not, then it's a simple attribute-value pair * * @param string $element - * @param string|array $attributes + * @param string|array $attributes * @param string $value + * * @return void */ - public function writeElementBlock($element, $attributes, $value = null) + public function writeElementBlock(string $element, $attributes, string $value = null) { $this->startElement($element); if (!is_array($attributes)) { - $attributes = array($attributes => $value); + $attributes = [$attributes => $value]; } foreach ($attributes as $attribute => $value) { $this->writeAttribute($attribute, $value); @@ -136,13 +138,14 @@ class XMLWriter extends \XMLWriter * * @param bool $condition * @param string $element - * @param string $attribute + * @param string|null $attribute * @param mixed $value + * * @return void */ - public function writeElementIf($condition, $element, $attribute = null, $value = null) + public function writeElementIf(bool $condition, string $element, ?string $attribute = null, $value = null) { - if ($condition == true) { + if ($condition) { if (is_null($attribute)) { $this->writeElement($element, $value); } else { @@ -159,11 +162,12 @@ class XMLWriter extends \XMLWriter * @param bool $condition * @param string $attribute * @param mixed $value + * * @return void */ - public function writeAttributeIf($condition, $attribute, $value) + public function writeAttributeIf(bool $condition, string $attribute, $value) { - if ($condition == true) { + if ($condition) { $this->writeAttribute($attribute, $value); } } @@ -171,13 +175,15 @@ class XMLWriter extends \XMLWriter /** * @param string $name * @param mixed $value + * * @return bool */ - public function writeAttribute($name, $value) + public function writeAttribute($name, $value): bool { if (is_float($value)) { $value = json_encode($value); } - return parent::writeAttribute($name, $value); + + return parent::writeAttribute($name, $value ?? ''); } } diff --git a/PhpOffice/PhpPresentation/AbstractShape.php b/PhpOffice/PhpPresentation/AbstractShape.php old mode 100755 new mode 100644 index 9c588df..c8d3df0 --- a/PhpOffice/PhpPresentation/AbstractShape.php +++ b/PhpOffice/PhpPresentation/AbstractShape.php @@ -10,127 +10,120 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation; +use PhpOffice\PhpPresentation\Exception\ShapeContainerAlreadyAssignedException; use PhpOffice\PhpPresentation\Shape\Hyperlink; use PhpOffice\PhpPresentation\Shape\Placeholder; +use PhpOffice\PhpPresentation\Style\Border; use PhpOffice\PhpPresentation\Style\Fill; use PhpOffice\PhpPresentation\Style\Shadow; /** - * Abstract shape + * Abstract shape. */ abstract class AbstractShape implements ComparableInterface { /** - * Container + * Container. * - * @var \PhpOffice\PhpPresentation\ShapeContainerInterface + * @var ShapeContainerInterface|null */ protected $container; /** - * Offset X + * Offset X. * * @var int */ protected $offsetX; /** - * Offset Y + * Offset Y. * * @var int */ protected $offsetY; /** - * Width + * Width. * * @var int */ protected $width; /** - * Height + * Height. * * @var int */ protected $height; /** - * Fill - * - * @var \PhpOffice\PhpPresentation\Style\Fill + * @var Fill|null */ private $fill; /** - * Border + * Border. * - * @var \PhpOffice\PhpPresentation\Style\Border + * @var Border */ private $border; /** - * Rotation + * Rotation. * * @var int */ protected $rotation; /** - * Shadow + * Shadow. * - * @var \PhpOffice\PhpPresentation\Style\Shadow + * @var Shadow|null */ protected $shadow; /** - * Hyperlink - * - * @var \PhpOffice\PhpPresentation\Shape\Hyperlink + * @var Hyperlink|null */ protected $hyperlink; /** - * PlaceHolder - * @var \PhpOffice\PhpPresentation\Shape\Placeholder + * @var Placeholder|null */ protected $placeholder; /** - * Hash index + * Hash index. * - * @var string + * @var int */ private $hashIndex; /** - * Create a new self + * Create a new self. */ public function __construct() { - // Initialise values - $this->container = null; - $this->offsetX = 0; - $this->offsetY = 0; - $this->width = 0; - $this->height = 0; - $this->rotation = 0; - $this->fill = new Style\Fill(); - $this->border = new Style\Border(); - $this->shadow = new Style\Shadow(); - + $this->offsetX = $this->offsetY = $this->width = $this->height = $this->rotation = 0; + $this->fill = new Fill(); + $this->shadow = new Shadow(); + $this->border = new Border(); $this->border->setLineStyle(Style\Border::LINE_NONE); } /** - * Magic Method : clone + * Magic Method : clone. */ public function __clone() { @@ -141,34 +134,34 @@ abstract class AbstractShape implements ComparableInterface } /** - * Get Container, Slide or Group - * - * @return \PhpOffice\PhpPresentation\ShapeContainerInterface + * Get Container, Slide or Group. */ - public function getContainer() + public function getContainer(): ?ShapeContainerInterface { return $this->container; } /** - * Set Container, Slide or Group + * Set Container, Slide or Group. + * + * @param ShapeContainerInterface $pValue + * @param bool $pOverrideOld If a Slide has already been assigned, overwrite it and remove image from old Slide? + * + * @throws ShapeContainerAlreadyAssignedException * - * @param \PhpOffice\PhpPresentation\ShapeContainerInterface $pValue - * @param bool $pOverrideOld If a Slide has already been assigned, overwrite it and remove image from old Slide? - * @throws \Exception * @return $this */ public function setContainer(ShapeContainerInterface $pValue = null, $pOverrideOld = false) { if (is_null($this->container)) { - // Add drawing to \PhpOffice\PhpPresentation\ShapeContainerInterface + // Add drawing to ShapeContainerInterface $this->container = $pValue; if (!is_null($this->container)) { $this->container->getShapeCollection()->append($this); } } else { if ($pOverrideOld) { - // Remove drawing from old \PhpOffice\PhpPresentation\ShapeContainerInterface + // Remove drawing from old ShapeContainerInterface $iterator = $this->container->getShapeCollection()->getIterator(); while ($iterator->valid()) { @@ -183,7 +176,7 @@ abstract class AbstractShape implements ComparableInterface // Set new \PhpOffice\PhpPresentation\Slide $this->setContainer($pValue); } else { - throw new \Exception("A \PhpOffice\PhpPresentation\ShapeContainerInterface has already been assigned. Shapes can only exist on one \PhpOffice\PhpPresentation\ShapeContainerInterface."); + throw new ShapeContainerAlreadyAssignedException(self::class); } } @@ -191,22 +184,19 @@ abstract class AbstractShape implements ComparableInterface } /** - * Get OffsetX - * - * @return int + * Get OffsetX. */ - public function getOffsetX() + public function getOffsetX(): int { return $this->offsetX; } /** - * Set OffsetX + * Set OffsetX. * - * @param int $pValue * @return $this */ - public function setOffsetX($pValue = 0) + public function setOffsetX(int $pValue = 0) { $this->offsetX = $pValue; @@ -214,7 +204,7 @@ abstract class AbstractShape implements ComparableInterface } /** - * Get OffsetY + * Get OffsetY. * * @return int */ @@ -224,12 +214,11 @@ abstract class AbstractShape implements ComparableInterface } /** - * Set OffsetY + * Set OffsetY. * - * @param int $pValue * @return $this */ - public function setOffsetY($pValue = 0) + public function setOffsetY(int $pValue = 0) { $this->offsetY = $pValue; @@ -237,7 +226,7 @@ abstract class AbstractShape implements ComparableInterface } /** - * Get Width + * Get Width. * * @return int */ @@ -247,19 +236,19 @@ abstract class AbstractShape implements ComparableInterface } /** - * Set Width + * Set Width. * - * @param int $pValue * @return $this */ - public function setWidth($pValue = 0) + public function setWidth(int $pValue = 0) { $this->width = $pValue; + return $this; } /** - * Get Height + * Get Height. * * @return int */ @@ -269,34 +258,32 @@ abstract class AbstractShape implements ComparableInterface } /** - * Set Height + * Set Height. * - * @param int $pValue * @return $this */ - public function setHeight($pValue = 0) + public function setHeight(int $pValue = 0) { $this->height = $pValue; + return $this; } /** - * Set width and height with proportional resize + * Set width and height with proportional resize. * - * @param int $width - * @param int $height - * @example $objDrawing->setWidthAndHeight(160,120); - * @return $this + * @return self */ - public function setWidthAndHeight($width = 0, $height = 0) + public function setWidthAndHeight(int $width = 0, int $height = 0) { $this->width = $width; $this->height = $height; + return $this; } /** - * Get Rotation + * Get Rotation. * * @return int */ @@ -306,75 +293,55 @@ abstract class AbstractShape implements ComparableInterface } /** - * Set Rotation + * Set Rotation. + * + * @param int $pValue * - * @param int $pValue * @return $this */ public function setRotation($pValue = 0) { $this->rotation = $pValue; + return $this; } - /** - * Get Fill - * - * @return \PhpOffice\PhpPresentation\Style\Fill - */ - public function getFill() + public function getFill(): ?Fill { return $this->fill; } - /** - * Set Fill - * @param \PhpOffice\PhpPresentation\Style\Fill $pValue - * @return \PhpOffice\PhpPresentation\AbstractShape - */ - public function setFill(Fill $pValue = null) + public function setFill(Fill $pValue = null): self { $this->fill = $pValue; + return $this; } - /** - * Get Border - * - * @return \PhpOffice\PhpPresentation\Style\Border - */ - public function getBorder() + public function getBorder(): Border { return $this->border; } - /** - * Get Shadow - * - * @return \PhpOffice\PhpPresentation\Style\Shadow - */ - public function getShadow() + public function getShadow(): ?Shadow { return $this->shadow; } /** - * Set Shadow - * - * @param \PhpOffice\PhpPresentation\Style\Shadow $pValue - * @throws \Exception * @return $this */ public function setShadow(Shadow $pValue = null) { $this->shadow = $pValue; + return $this; } /** * Has Hyperlink? * - * @return boolean + * @return bool */ public function hasHyperlink() { @@ -383,87 +350,84 @@ abstract class AbstractShape implements ComparableInterface /** * Get Hyperlink - * - * @return \PhpOffice\PhpPresentation\Shape\Hyperlink - * @throws \Exception */ - public function getHyperlink() + public function getHyperlink(): Hyperlink { if (is_null($this->hyperlink)) { $this->hyperlink = new Hyperlink(); } + return $this->hyperlink; } /** * Set Hyperlink - * - * @param \PhpOffice\PhpPresentation\Shape\Hyperlink $pHyperlink - * @throws \Exception - * @return $this */ - public function setHyperlink(Hyperlink $pHyperlink = null) + public function setHyperlink(Hyperlink $pHyperlink = null): self { $this->hyperlink = $pHyperlink; + return $this; } /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { return md5((is_object($this->container) ? $this->container->getHashCode() : '') . $this->offsetX . $this->offsetY . $this->width . $this->height . $this->rotation . (is_null($this->getFill()) ? '' : $this->getFill()->getHashCode()) . (is_null($this->shadow) ? '' : $this->shadow->getHashCode()) . (is_null($this->hyperlink) ? '' : $this->hyperlink->getHashCode()) . __CLASS__); } /** - * Get hash index + * Get hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @return string Hash index + * @return int|null Hash index */ - public function getHashIndex() + public function getHashIndex(): ?int { return $this->hashIndex; } /** - * Set hash index + * Set hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @param string $value Hash index + * @param int $value Hash index + * + * @return $this */ - public function setHashIndex($value) + public function setHashIndex(int $value) { $this->hashIndex = $value; + + return $this; } - public function isPlaceholder() + public function isPlaceholder(): bool { return !is_null($this->placeholder); } - public function getPlaceholder() + public function getPlaceholder(): ?Placeholder { if (!$this->isPlaceholder()) { return null; } + return $this->placeholder; } - /** - * @param \PhpOffice\PhpPresentation\Shape\Placeholder $placeholder - * @return $this - */ - public function setPlaceHolder(Placeholder $placeholder) + public function setPlaceHolder(Placeholder $placeholder): self { $this->placeholder = $placeholder; + return $this; } } diff --git a/PhpOffice/PhpPresentation/Autoloader.php b/PhpOffice/PhpPresentation/Autoloader.php old mode 100755 new mode 100644 index 605981b..6c7211f --- a/PhpOffice/PhpPresentation/Autoloader.php +++ b/PhpOffice/PhpPresentation/Autoloader.php @@ -10,42 +10,44 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation; /** - * Autoloader + * Autoloader. */ class Autoloader { /** @const string */ - const NAMESPACE_PREFIX = 'PhpOffice\\PhpPresentation\\'; + public const NAMESPACE_PREFIX = 'PhpOffice\\PhpPresentation\\'; /** - * Register - * - * @return void + * Register. */ - public static function register() + public static function register(): void { - spl_autoload_register(array(new self, 'autoload')); + spl_autoload_register([new self(), 'autoload']); } /** - * Autoload - * - * @param string $class + * Autoload. */ - public static function autoload($class) + public static function autoload(string $class): void { $prefixLength = strlen(self::NAMESPACE_PREFIX); if (0 === strncmp(self::NAMESPACE_PREFIX, $class, $prefixLength)) { $file = str_replace('\\', DIRECTORY_SEPARATOR, substr($class, $prefixLength)); $file = realpath(__DIR__ . (empty($file) ? '' : DIRECTORY_SEPARATOR) . $file . '.php'); + if (!$file) { + return; + } if (file_exists($file)) { /** @noinspection PhpIncludeInspection Dynamic includes */ require_once $file; diff --git a/PhpOffice/PhpPresentation/COPYING b/PhpOffice/PhpPresentation/COPYING deleted file mode 100755 index 94a9ed0..0000000 --- a/PhpOffice/PhpPresentation/COPYING +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/PhpOffice/PhpPresentation/COPYING.LESSER b/PhpOffice/PhpPresentation/COPYING.LESSER deleted file mode 100755 index 65c5ca8..0000000 --- a/PhpOffice/PhpPresentation/COPYING.LESSER +++ /dev/null @@ -1,165 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. diff --git a/PhpOffice/PhpPresentation/ComparableInterface.php b/PhpOffice/PhpPresentation/ComparableInterface.php old mode 100755 new mode 100644 index 87eae2c..7fefbda --- a/PhpOffice/PhpPresentation/ComparableInterface.php +++ b/PhpOffice/PhpPresentation/ComparableInterface.php @@ -10,42 +10,47 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation; /** - * PhpOffice\PhpPresentation\ComparableInterface + * PhpOffice\PhpPresentation\ComparableInterface. */ interface ComparableInterface { /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode(); + public function getHashCode(): string; /** - * Get hash index + * Get hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @return string Hash index + * @return int|null Hash index */ - public function getHashIndex(); + public function getHashIndex(): ?int; /** - * Set hash index + * Set hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @param string $value Hash index + * @param int $value Hash index + * + * @return $this */ - public function setHashIndex($value); + public function setHashIndex(int $value); } diff --git a/PhpOffice/PhpPresentation/DocumentLayout.php b/PhpOffice/PhpPresentation/DocumentLayout.php old mode 100755 new mode 100644 index c8cf385..bf79104 --- a/PhpOffice/PhpPresentation/DocumentLayout.php +++ b/PhpOffice/PhpPresentation/DocumentLayout.php @@ -10,83 +10,90 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation; use PhpOffice\Common\Drawing; /** - * \PhpOffice\PhpPresentation\DocumentLayout + * \PhpOffice\PhpPresentation\DocumentLayout. */ class DocumentLayout { - const LAYOUT_CUSTOM = ''; - const LAYOUT_SCREEN_4X3 = 'screen4x3'; - const LAYOUT_SCREEN_16X10 = 'screen16x10'; - const LAYOUT_SCREEN_16X9 = 'screen16x9'; - const LAYOUT_35MM = '35mm'; - const LAYOUT_A3 = 'A3'; - const LAYOUT_A4 = 'A4'; - const LAYOUT_B4ISO = 'B4ISO'; - const LAYOUT_B5ISO = 'B5ISO'; - const LAYOUT_BANNER = 'banner'; - const LAYOUT_LETTER = 'letter'; - const LAYOUT_OVERHEAD = 'overhead'; + public const LAYOUT_CUSTOM = ''; + public const LAYOUT_SCREEN_4X3 = 'screen4x3'; + public const LAYOUT_SCREEN_16X10 = 'screen16x10'; + public const LAYOUT_SCREEN_16X9 = 'screen16x9'; + public const LAYOUT_35MM = '35mm'; + public const LAYOUT_A3 = 'A3'; + public const LAYOUT_A4 = 'A4'; + public const LAYOUT_B4ISO = 'B4ISO'; + public const LAYOUT_B5ISO = 'B5ISO'; + public const LAYOUT_BANNER = 'banner'; + public const LAYOUT_LETTER = 'letter'; + public const LAYOUT_OVERHEAD = 'overhead'; - const UNIT_EMU = 'emu'; - const UNIT_CENTIMETER = 'cm'; - const UNIT_INCH = 'in'; - const UNIT_MILLIMETER = 'mm'; - const UNIT_PIXEL = 'px'; - const UNIT_POINT = 'pt'; + public const UNIT_EMU = 'emu'; + public const UNIT_CENTIMETER = 'cm'; + public const UNIT_INCH = 'in'; + public const UNIT_MILLIMETER = 'mm'; + public const UNIT_PIXEL = 'px'; + public const UNIT_POINT = 'pt'; /** - * Dimension types + * Dimension types. * * 1 px = 9525 EMU @ 96dpi (which is seems to be the default) * Absolute distances are specified in English Metric Units (EMUs), * occasionally referred to as A units; there are 360000 EMUs per * centimeter, 914400 EMUs per inch, 12700 EMUs per point. + * + * @var array> */ - private $dimension = array( - self::LAYOUT_SCREEN_4X3 => array('cx' => 9144000, 'cy' => 6858000), - self::LAYOUT_SCREEN_16X10 => array('cx' => 9144000, 'cy' => 5715000), - self::LAYOUT_SCREEN_16X9 => array('cx' => 9144000, 'cy' => 5143500), - self::LAYOUT_35MM => array('cx' => 10287000, 'cy' => 6858000), - self::LAYOUT_A3 => array('cx' => 15120000, 'cy' => 10692000), - self::LAYOUT_A4 => array('cx' => 10692000, 'cy' => 7560000), - self::LAYOUT_B4ISO => array('cx' => 10826750, 'cy' => 8120063), - self::LAYOUT_B5ISO => array('cx' => 7169150, 'cy' => 5376863), - self::LAYOUT_BANNER => array('cx' => 7315200, 'cy' => 914400), - self::LAYOUT_LETTER => array('cx' => 9144000, 'cy' => 6858000), - self::LAYOUT_OVERHEAD => array('cx' => 9144000, 'cy' => 6858000), - ); + private $dimension = [ + self::LAYOUT_SCREEN_4X3 => ['cx' => 9144000, 'cy' => 6858000], + self::LAYOUT_SCREEN_16X10 => ['cx' => 9144000, 'cy' => 5715000], + self::LAYOUT_SCREEN_16X9 => ['cx' => 9144000, 'cy' => 5143500], + self::LAYOUT_35MM => ['cx' => 10287000, 'cy' => 6858000], + self::LAYOUT_A3 => ['cx' => 15120000, 'cy' => 10692000], + self::LAYOUT_A4 => ['cx' => 10692000, 'cy' => 7560000], + self::LAYOUT_B4ISO => ['cx' => 10826750, 'cy' => 8120063], + self::LAYOUT_B5ISO => ['cx' => 7169150, 'cy' => 5376863], + self::LAYOUT_BANNER => ['cx' => 7315200, 'cy' => 914400], + self::LAYOUT_LETTER => ['cx' => 9144000, 'cy' => 6858000], + self::LAYOUT_OVERHEAD => ['cx' => 9144000, 'cy' => 6858000], + ]; /** - * Layout name + * Layout name. * * @var string */ private $layout; /** - * Layout X dimension + * Layout X dimension. + * * @var float */ private $dimensionX; /** - * Layout Y dimension + * Layout Y dimension. + * * @var float */ private $dimensionY; /** - * Create a new \PhpOffice\PhpPresentation\DocumentLayout + * Create a new \PhpOffice\PhpPresentation\DocumentLayout. */ public function __construct() { @@ -94,23 +101,20 @@ class DocumentLayout } /** - * Get Document Layout - * - * @return string + * Get Document Layout. */ - public function getDocumentLayout() + public function getDocumentLayout(): string { return $this->layout; } /** - * Set Document Layout + * Set Document Layout. * - * @param array|string $pValue - * @param boolean $isLandscape - * @return \PhpOffice\PhpPresentation\DocumentLayout + * @param array|string $pValue + * @param bool $isLandscape */ - public function setDocumentLayout($pValue = self::LAYOUT_SCREEN_4X3, $isLandscape = true) + public function setDocumentLayout($pValue = self::LAYOUT_SCREEN_4X3, $isLandscape = true): self { switch ($pValue) { case self::LAYOUT_SCREEN_4X3: @@ -146,63 +150,47 @@ class DocumentLayout } /** - * Get Document Layout cx - * - * @param string $unit - * @return integer + * Get Document Layout cx. */ - public function getCX($unit = self::UNIT_EMU) + public function getCX(string $unit = self::UNIT_EMU): float { return $this->convertUnit($this->dimensionX, self::UNIT_EMU, $unit); } /** - * Get Document Layout cy - * - * @param string $unit - * @return integer + * Get Document Layout cy. */ - public function getCY($unit = self::UNIT_EMU) + public function getCY(string $unit = self::UNIT_EMU): float { return $this->convertUnit($this->dimensionY, self::UNIT_EMU, $unit); } /** - * Get Document Layout cx - * - * @param float $value - * @param string $unit - * @return DocumentLayout + * Get Document Layout cx. */ - public function setCX($value, $unit = self::UNIT_EMU) + public function setCX(float $value, string $unit = self::UNIT_EMU): self { $this->layout = self::LAYOUT_CUSTOM; $this->dimensionX = $this->convertUnit($value, $unit, self::UNIT_EMU); + return $this; } /** - * Get Document Layout cy - * - * @param float $value - * @param string $unit - * @return DocumentLayout + * Get Document Layout cy. */ - public function setCY($value, $unit = self::UNIT_EMU) + public function setCY(float $value, string $unit = self::UNIT_EMU): self { $this->layout = self::LAYOUT_CUSTOM; $this->dimensionY = $this->convertUnit($value, $unit, self::UNIT_EMU); + return $this; } /** - * Convert EMUs to differents units - * @param float $value - * @param string $fromUnit - * @param string $toUnit - * @return float + * Convert EMUs to differents units. */ - protected function convertUnit($value, $fromUnit, $toUnit) + protected function convertUnit(float $value, string $fromUnit, string $toUnit): float { // Convert from $fromUnit to EMU switch ($fromUnit) { @@ -238,7 +226,7 @@ class DocumentLayout $value /= 914400; break; case self::UNIT_PIXEL: - $value = Drawing::emuToPixels($value); + $value = Drawing::emuToPixels((int) $value); break; case self::UNIT_POINT: $value /= 12700; @@ -247,6 +235,7 @@ class DocumentLayout default: // no changes } + return $value; } } diff --git a/PhpOffice/PhpPresentation/DocumentProperties.php b/PhpOffice/PhpPresentation/DocumentProperties.php old mode 100755 new mode 100644 index c9344fe..ab45420 --- a/PhpOffice/PhpPresentation/DocumentProperties.php +++ b/PhpOffice/PhpPresentation/DocumentProperties.php @@ -10,108 +10,125 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation; /** - * \PhpOffice\PhpPresentation\DocumentProperties + * \PhpOffice\PhpPresentation\DocumentProperties. */ class DocumentProperties { + public const PROPERTY_TYPE_BOOLEAN = 'b'; + public const PROPERTY_TYPE_INTEGER = 'i'; + public const PROPERTY_TYPE_FLOAT = 'f'; + public const PROPERTY_TYPE_DATE = 'd'; + public const PROPERTY_TYPE_STRING = 's'; + public const PROPERTY_TYPE_UNKNOWN = 'u'; + /** - * Creator + * Creator. * * @var string */ private $creator; /** - * LastModifiedBy + * LastModifiedBy. * * @var string */ private $lastModifiedBy; /** - * Created + * Created. * * @var int */ private $created; /** - * Modified + * Modified. * * @var int */ private $modified; /** - * Title + * Title. * * @var string */ private $title; /** - * Description + * Description. * * @var string */ private $description; /** - * Subject + * Subject. * * @var string */ private $subject; /** - * Keywords + * Keywords. * * @var string */ private $keywords; /** - * Category + * Category. * * @var string */ private $category; /** - * Company + * Company. * * @var string */ private $company; + /** + * Custom Properties. + * + * @var array> + */ + private $customProperties = []; + /** * Create a new \PhpOffice\PhpPresentation\DocumentProperties */ public function __construct() { // Initialise values - $this->creator = 'Unknown Creator'; + $this->creator = 'Unknown Creator'; $this->lastModifiedBy = $this->creator; - $this->created = time(); - $this->modified = time(); - $this->title = "Untitled Presentation"; - $this->subject = ''; - $this->description = ''; - $this->keywords = ''; - $this->category = ''; - $this->company = 'Microsoft Corporation'; + $this->created = time(); + $this->modified = time(); + $this->title = 'Untitled Presentation'; + $this->subject = ''; + $this->description = ''; + $this->keywords = ''; + $this->category = ''; + $this->company = 'Microsoft Corporation'; } /** - * Get Creator + * Get Creator. * * @return string */ @@ -121,9 +138,10 @@ class DocumentProperties } /** - * Set Creator + * Set Creator. + * + * @param string $pValue * - * @param string $pValue * @return \PhpOffice\PhpPresentation\DocumentProperties */ public function setCreator($pValue = '') @@ -134,7 +152,7 @@ class DocumentProperties } /** - * Get Last Modified By + * Get Last Modified By. * * @return string */ @@ -144,9 +162,10 @@ class DocumentProperties } /** - * Set Last Modified By + * Set Last Modified By. + * + * @param string $pValue * - * @param string $pValue * @return \PhpOffice\PhpPresentation\DocumentProperties */ public function setLastModifiedBy($pValue = '') @@ -157,7 +176,7 @@ class DocumentProperties } /** - * Get Created + * Get Created. * * @return int */ @@ -167,9 +186,10 @@ class DocumentProperties } /** - * Set Created + * Set Created. * * @param int $pValue + * * @return \PhpOffice\PhpPresentation\DocumentProperties */ public function setCreated($pValue = null) @@ -183,7 +203,7 @@ class DocumentProperties } /** - * Get Modified + * Get Modified. * * @return int */ @@ -193,9 +213,10 @@ class DocumentProperties } /** - * Set Modified + * Set Modified. + * + * @param int $pValue * - * @param int $pValue * @return \PhpOffice\PhpPresentation\DocumentProperties */ public function setModified($pValue = null) @@ -209,7 +230,7 @@ class DocumentProperties } /** - * Get Title + * Get Title. * * @return string */ @@ -219,9 +240,10 @@ class DocumentProperties } /** - * Set Title + * Set Title. + * + * @param string $pValue * - * @param string $pValue * @return \PhpOffice\PhpPresentation\DocumentProperties */ public function setTitle($pValue = '') @@ -232,7 +254,7 @@ class DocumentProperties } /** - * Get Description + * Get Description. * * @return string */ @@ -242,9 +264,10 @@ class DocumentProperties } /** - * Set Description + * Set Description. + * + * @param string $pValue * - * @param string $pValue * @return \PhpOffice\PhpPresentation\DocumentProperties */ public function setDescription($pValue = '') @@ -255,7 +278,7 @@ class DocumentProperties } /** - * Get Subject + * Get Subject. * * @return string */ @@ -265,9 +288,10 @@ class DocumentProperties } /** - * Set Subject + * Set Subject. + * + * @param string $pValue * - * @param string $pValue * @return \PhpOffice\PhpPresentation\DocumentProperties */ public function setSubject($pValue = '') @@ -278,7 +302,7 @@ class DocumentProperties } /** - * Get Keywords + * Get Keywords. * * @return string */ @@ -288,9 +312,10 @@ class DocumentProperties } /** - * Set Keywords + * Set Keywords. + * + * @param string $pValue * - * @param string $pValue * @return \PhpOffice\PhpPresentation\DocumentProperties */ public function setKeywords($pValue = '') @@ -301,7 +326,7 @@ class DocumentProperties } /** - * Get Category + * Get Category. * * @return string */ @@ -311,9 +336,10 @@ class DocumentProperties } /** - * Set Category + * Set Category. + * + * @param string $pValue * - * @param string $pValue * @return \PhpOffice\PhpPresentation\DocumentProperties */ public function setCategory($pValue = '') @@ -324,7 +350,7 @@ class DocumentProperties } /** - * Get Company + * Get Company. * * @return string */ @@ -334,9 +360,10 @@ class DocumentProperties } /** - * Set Company + * Set Company. + * + * @param string $pValue * - * @param string $pValue * @return \PhpOffice\PhpPresentation\DocumentProperties */ public function setCompany($pValue = '') @@ -345,4 +372,99 @@ class DocumentProperties return $this; } + + /** + * Get a List of Custom Property Names. + * + * @return array + */ + public function getCustomProperties(): array + { + return array_keys($this->customProperties); + } + + /** + * Check if a Custom Property is defined. + * + * @param string $propertyName + * + * @return bool + */ + public function isCustomPropertySet(string $propertyName): bool + { + return isset($this->customProperties[$propertyName]); + } + + /** + * Get a Custom Property Value. + * + * @param string $propertyName + * + * @return mixed|null + */ + public function getCustomPropertyValue(string $propertyName) + { + if ($this->isCustomPropertySet($propertyName)) { + return $this->customProperties[$propertyName]['value']; + } + + return null; + } + + /** + * Get a Custom Property Type. + * + * @param string $propertyName + * + * @return string|null + */ + public function getCustomPropertyType(string $propertyName): ?string + { + if ($this->isCustomPropertySet($propertyName)) { + return $this->customProperties[$propertyName]['type']; + } + + return null; + } + + /** + * Set a Custom Property. + * + * @param string $propertyName + * @param mixed $propertyValue + * @param string|null $propertyType + * 'i' : Integer + * 'f' : Floating Point + * 's' : String + * 'd' : Date/Time + * 'b' : Boolean + * + * @return self + */ + public function setCustomProperty(string $propertyName, $propertyValue = '', ?string $propertyType = null): self + { + if (!in_array($propertyType, [ + self::PROPERTY_TYPE_INTEGER, + self::PROPERTY_TYPE_FLOAT, + self::PROPERTY_TYPE_STRING, + self::PROPERTY_TYPE_DATE, + self::PROPERTY_TYPE_BOOLEAN, + ])) { + if (is_float($propertyValue)) { + $propertyType = self::PROPERTY_TYPE_FLOAT; + } elseif (is_int($propertyValue)) { + $propertyType = self::PROPERTY_TYPE_INTEGER; + } elseif (is_bool($propertyValue)) { + $propertyType = self::PROPERTY_TYPE_BOOLEAN; + } else { + $propertyType = self::PROPERTY_TYPE_STRING; + } + } + $this->customProperties[$propertyName] = [ + 'value' => $propertyValue, + 'type' => $propertyType, + ]; + + return $this; + } } diff --git a/PhpOffice/PhpPresentation/Exception/DirectoryNotFoundException.php b/PhpOffice/PhpPresentation/Exception/DirectoryNotFoundException.php new file mode 100644 index 0000000..bfd444c --- /dev/null +++ b/PhpOffice/PhpPresentation/Exception/DirectoryNotFoundException.php @@ -0,0 +1,32 @@ + $authorizedMimetypes + */ + public function __construct(string $expectedMimetype, array $authorizedMimetypes) + { + parent::__construct(sprintf( + 'The mime type %s is not found in autorized values (%s)', + $expectedMimetype, + implode(', ', $authorizedMimetypes) + )); + } +} diff --git a/PhpOffice/PhpPresentation/Shape/MemoryDrawing.php b/PhpOffice/PhpPresentation/Exception/UndefinedChartTypeException.php old mode 100755 new mode 100644 similarity index 67% rename from PhpOffice/PhpPresentation/Shape/MemoryDrawing.php rename to PhpOffice/PhpPresentation/Exception/UndefinedChartTypeException.php index e4b5c7a..5e77181 --- a/PhpOffice/PhpPresentation/Shape/MemoryDrawing.php +++ b/PhpOffice/PhpPresentation/Exception/UndefinedChartTypeException.php @@ -10,19 +10,20 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ -namespace PhpOffice\PhpPresentation\Shape; +declare(strict_types=1); -use PhpOffice\PhpPresentation\Shape\Drawing\Gd; +namespace PhpOffice\PhpPresentation\Exception; -/** - * Memory drawing shape - * @deprecated Drawing\Gd - */ -class MemoryDrawing extends Gd +class UndefinedChartTypeException extends PhpPresentationException { + public function __construct() + { + parent::__construct('The chart type has not been defined'); + } } diff --git a/PhpOffice/PhpPresentation/GeometryCalculator.php b/PhpOffice/PhpPresentation/GeometryCalculator.php old mode 100755 new mode 100644 index 987a7ba..7eccbf8 --- a/PhpOffice/PhpPresentation/GeometryCalculator.php +++ b/PhpOffice/PhpPresentation/GeometryCalculator.php @@ -10,40 +10,42 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation; /** - * PhpOffice\PhpPresentation\GeometryCalculator + * PhpOffice\PhpPresentation\GeometryCalculator. */ class GeometryCalculator { - const X = 'X'; - const Y = 'Y'; + public const X = 'X'; + public const Y = 'Y'; /** - * Calculate X and Y offsets for a set of shapes within a container such as a slide or group. - * - * @param \PhpOffice\PhpPresentation\ShapeContainerInterface $container - * @return array - */ - public static function calculateOffsets(ShapeContainerInterface $container) + * Calculate X and Y offsets for a set of shapes within a container such as a slide or group. + * + * @return array + */ + public static function calculateOffsets(ShapeContainerInterface $container): array { - $offsets = array(self::X => 0, self::Y => 0); + $offsets = [self::X => 0, self::Y => 0]; - if ($container !== null && count($container->getShapeCollection()) != 0) { + if (null !== $container && 0 != count($container->getShapeCollection())) { $shapes = $container->getShapeCollection(); - if ($shapes[0] !== null) { + if (null !== $shapes[0]) { $offsets[self::X] = $shapes[0]->getOffsetX(); $offsets[self::Y] = $shapes[0]->getOffsetY(); } foreach ($shapes as $shape) { - if ($shape !== null) { + if (null !== $shape) { if ($shape->getOffsetX() < $offsets[self::X]) { $offsets[self::X] = $shape->getOffsetX(); } @@ -59,26 +61,26 @@ class GeometryCalculator } /** - * Calculate X and Y extents for a set of shapes within a container such as a slide or group. - * - * @param \PhpOffice\PhpPresentation\ShapeContainerInterface $container - * @return array - */ - public static function calculateExtents(ShapeContainerInterface $container) + * Calculate X and Y extents for a set of shapes within a container such as a slide or group. + * + * @return array + */ + public static function calculateExtents(ShapeContainerInterface $container): array { - $extents = array(self::X => 0, self::Y => 0); + /** @var array $extents */ + $extents = [self::X => 0, self::Y => 0]; - if ($container !== null && count($container->getShapeCollection()) != 0) { + if (null !== $container && 0 != count($container->getShapeCollection())) { $shapes = $container->getShapeCollection(); - if ($shapes[0] !== null) { - $extents[self::X] = $shapes[0]->getOffsetX() + $shapes[0]->getWidth(); - $extents[self::Y] = $shapes[0]->getOffsetY() + $shapes[0]->getHeight(); + if (null !== $shapes[0]) { + $extents[self::X] = (int) ($shapes[0]->getOffsetX() + $shapes[0]->getWidth()); + $extents[self::Y] = (int) ($shapes[0]->getOffsetY() + $shapes[0]->getHeight()); } foreach ($shapes as $shape) { - if ($shape !== null) { - $extentX = $shape->getOffsetX() + $shape->getWidth(); - $extentY = $shape->getOffsetY() + $shape->getHeight(); + if (null !== $shape) { + $extentX = (int) ($shape->getOffsetX() + $shape->getWidth()); + $extentY = (int) ($shape->getOffsetY() + $shape->getHeight()); if ($extentX > $extents[self::X]) { $extents[self::X] = $extentX; diff --git a/PhpOffice/PhpPresentation/HashTable.php b/PhpOffice/PhpPresentation/HashTable.php old mode 100755 new mode 100644 index 54eec91..df5e5a2 --- a/PhpOffice/PhpPresentation/HashTable.php +++ b/PhpOffice/PhpPresentation/HashTable.php @@ -10,88 +10,76 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation; /** - * \PhpOffice\PhpPresentation\HashTable + * \PhpOffice\PhpPresentation\HashTable. */ class HashTable { /** - * HashTable elements + * HashTable elements. * - * @var array + * @var array */ - public $items = array(); + public $items = []; /** - * HashTable key map + * HashTable key map. * - * @var array + * @var array */ - public $keyMap = array(); + public $keyMap = []; /** - * Create a new \PhpOffice\PhpPresentation\HashTable + * Create a new \PhpOffice\PhpPresentation\HashTable. * - * @param \PhpOffice\PhpPresentation\ComparableInterface[] $pSource Optional source array to create HashTable from - * @throws \Exception + * @param array $pSource Optional source array to create HashTable from */ - public function __construct(array $pSource = null) + public function __construct(array $pSource = []) { - if (!is_null($pSource)) { - // Create HashTable - $this->addFromSource($pSource); - } + $this->addFromSource($pSource); } /** - * Add HashTable items from source + * Add HashTable items from source. * - * @param \PhpOffice\PhpPresentation\ComparableInterface[] $pSource Source array to create HashTable from - * @throws \Exception + * @param array $pSource Source array to create HashTable from */ - public function addFromSource($pSource = null) + public function addFromSource(array $pSource = []): void { - // Check if an array was passed - if ($pSource == null) { - return; - } elseif (!is_array($pSource)) { - throw new \Exception('Invalid array parameter passed.'); - } - foreach ($pSource as $item) { $this->add($item); } } /** - * Add HashTable item + * Add HashTable item. * - * @param \PhpOffice\PhpPresentation\ComparableInterface $pSource Item to add + * @param ComparableInterface $pSource Item to add */ - public function add(ComparableInterface $pSource) + public function add(ComparableInterface $pSource): void { // Determine hashcode $hashIndex = $pSource->getHashIndex(); $hashCode = $pSource->getHashCode(); - - if (is_null($hashIndex)) { - $hashCode = $pSource->getHashCode(); - } elseif (isset($this->keyMap[$hashIndex])) { + if (isset($this->keyMap[$hashIndex])) { $hashCode = $this->keyMap[$hashIndex]; } // Add value if (!isset($this->items[$hashCode])) { $this->items[$hashCode] = $pSource; - $index = count($this->items) - 1; - $this->keyMap[$index] = $hashCode; + $index = count($this->items) - 1; + $this->keyMap[$index] = $hashCode; $pSource->setHashIndex($index); } else { $pSource->setHashIndex($this->items[$hashCode]->getHashIndex()); @@ -99,12 +87,11 @@ class HashTable } /** - * Remove HashTable item + * Remove HashTable item. * - * @param \PhpOffice\PhpPresentation\ComparableInterface $pSource Item to remove - * @throws \Exception + * @param ComparableInterface $pSource Item to remove */ - public function remove(ComparableInterface $pSource) + public function remove(ComparableInterface $pSource): void { if (isset($this->items[$pSource->getHashCode()])) { unset($this->items[$pSource->getHashCode()]); @@ -124,44 +111,38 @@ class HashTable } /** - * Clear HashTable - * + * Clear HashTable. */ - public function clear() + public function clear(): void { - $this->items = array(); - $this->keyMap = array(); + $this->items = []; + $this->keyMap = []; } /** - * Count - * - * @return int + * Count. */ - public function count() + public function count(): int { return count($this->items); } /** - * Get index for hash code + * Get index for hash code. * - * @param string $pHashCode - * @return int Index + * @return int Index (-1 if not found) */ - public function getIndexForHashCode($pHashCode = '') + public function getIndexForHashCode(string $pHashCode = ''): int { - return array_search($pHashCode, $this->keyMap); + $index = array_search($pHashCode, $this->keyMap); + + return false === $index ? -1 : $index; } /** - * Get by index - * - * @param int $pIndex - * @return \PhpOffice\PhpPresentation\ComparableInterface - * + * Get by index. */ - public function getByIndex($pIndex = 0) + public function getByIndex(int $pIndex = 0): ?ComparableInterface { if (isset($this->keyMap[$pIndex])) { return $this->getByHashCode($this->keyMap[$pIndex]); @@ -171,13 +152,9 @@ class HashTable } /** - * Get by hashcode - * - * @param string $pHashCode - * @return \PhpOffice\PhpPresentation\ComparableInterface - * + * Get by hashcode. */ - public function getByHashCode($pHashCode = '') + public function getByHashCode(string $pHashCode = ''): ?ComparableInterface { if (isset($this->items[$pHashCode])) { return $this->items[$pHashCode]; @@ -187,11 +164,11 @@ class HashTable } /** - * HashTable to array + * HashTable to array. * - * @return \PhpOffice\PhpPresentation\ComparableInterface[] + * @return array */ - public function toArray() + public function toArray(): array { return $this->items; } diff --git a/PhpOffice/PhpPresentation/IOFactory.php b/PhpOffice/PhpPresentation/IOFactory.php old mode 100755 new mode 100644 index 84cb375..0d7f90b --- a/PhpOffice/PhpPresentation/IOFactory.php +++ b/PhpOffice/PhpPresentation/IOFactory.php @@ -10,60 +10,61 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation; +use PhpOffice\PhpPresentation\Exception\InvalidClassException; +use PhpOffice\PhpPresentation\Exception\InvalidFileFormatException; +use PhpOffice\PhpPresentation\Reader\ReaderInterface; +use PhpOffice\PhpPresentation\Writer\WriterInterface; +use ReflectionClass; + /** - * IOFactory + * IOFactory. */ class IOFactory { /** - * Autoresolve classes + * Autoresolve classes. * - * @var array + * @var array */ - private static $autoResolveClasses = array('Serialized', 'ODPresentation', 'PowerPoint97', 'PowerPoint2007'); + private static $autoResolveClasses = ['Serialized', 'ODPresentation', 'PowerPoint97', 'PowerPoint2007']; /** - * Create writer + * Create writer. * * @param PhpPresentation $phpPresentation * @param string $name - * @return \PhpOffice\PhpPresentation\Writer\WriterInterface - * @throws \Exception */ - public static function createWriter(PhpPresentation $phpPresentation, $name = 'PowerPoint2007') + public static function createWriter(PhpPresentation $phpPresentation, string $name = 'PowerPoint2007'): WriterInterface { - $class = 'PhpOffice\\PhpPresentation\\Writer\\' . $name; - return self::loadClass($class, $name, 'writer', $phpPresentation); + return self::loadClass('PhpOffice\\PhpPresentation\\Writer\\' . $name, 'Writer', $phpPresentation); } /** - * Create reader + * Create reader. * - * @param string $name - * @return \PhpOffice\PhpPresentation\Reader\ReaderInterface - * @throws \Exception + * @param string $name */ - public static function createReader($name = '') + public static function createReader(string $name): ReaderInterface { - $class = 'PhpOffice\\PhpPresentation\\Reader\\' . $name; - return self::loadClass($class, $name, 'reader'); + return self::loadClass('PhpOffice\\PhpPresentation\\Reader\\' . $name, 'Reader'); } /** - * Loads PhpPresentation from file using automatic \PhpOffice\PhpPresentation\Reader\ReaderInterface resolution + * Loads PhpPresentation from file using automatic ReaderInterface resolution. * - * @param string $pFilename - * @return PhpPresentation - * @throws \Exception + * @throws InvalidFileFormatException */ - public static function load($pFilename) + public static function load(string $pFilename): PhpPresentation { // Try loading using self::$autoResolveClasses foreach (self::$autoResolveClasses as $autoResolveClass) { @@ -73,42 +74,45 @@ class IOFactory } } - throw new \Exception("Could not automatically determine \PhpOffice\PhpPresentation\Reader\ReaderInterface for file."); + throw new InvalidFileFormatException( + $pFilename, + IOFactory::class, + 'Could not automatically determine the good ' . ReaderInterface::class + ); } /** * Load class * * @param string $class - * @param string $name * @param string $type - * @param \PhpOffice\PhpPresentation\PhpPresentation $phpPresentation - * @return mixed - * @throws \ReflectionException + * @param PhpPresentation|null $phpPresentation + * + * @return object + * + * @throws InvalidClassException */ - private static function loadClass($class, $name, $type, PhpPresentation $phpPresentation = null) + private static function loadClass(string $class, string $type, PhpPresentation $phpPresentation = null) { - if (class_exists($class) && self::isConcreteClass($class)) { - if (is_null($phpPresentation)) { - return new $class(); - } else { - return new $class($phpPresentation); - } - } else { - throw new \Exception('"'.$name.'" is not a valid '.$type.'.'); + if (!class_exists($class)) { + throw new InvalidClassException($class, $type . ': The class doesn\'t exist'); } + if (!self::isConcreteClass($class)) { + throw new InvalidClassException($class, $type . ': The class is an abstract class or an interface'); + } + if (is_null($phpPresentation)) { + return new $class(); + } + + return new $class($phpPresentation); } /** * Is it a concrete class? - * - * @param string $class - * @return bool - * @throws \ReflectionException */ - private static function isConcreteClass($class) + private static function isConcreteClass(string $class): bool { - $reflection = new \ReflectionClass($class); + $reflection = new ReflectionClass($class); return !$reflection->isAbstract() && !$reflection->isInterface(); } diff --git a/PhpOffice/PhpPresentation/LICENSE b/PhpOffice/PhpPresentation/LICENSE deleted file mode 100755 index e67de5a..0000000 --- a/PhpOffice/PhpPresentation/LICENSE +++ /dev/null @@ -1,15 +0,0 @@ -PHPPresentation, a pure PHP library for writing presentations files. - -Copyright (c) 2010-2015 PHPPresentation. - -PHPPresentation is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License version 3 as published by -the Free Software Foundation. - -PHPPresentation is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License version 3 for more details. - -You should have received a copy of the GNU Lesser General Public License version 3 -along with PHPPresentation. If not, see . diff --git a/PhpOffice/PhpPresentation/PhpPresentation.php b/PhpOffice/PhpPresentation/PhpPresentation.php old mode 100755 new mode 100644 index 172babe..e2db811 --- a/PhpOffice/PhpPresentation/PhpPresentation.php +++ b/PhpOffice/PhpPresentation/PhpPresentation.php @@ -10,65 +10,70 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation; -use PhpOffice\PhpPresentation\Slide; +use ArrayObject; +use PhpOffice\PhpPresentation\Exception\OutOfBoundsException; use PhpOffice\PhpPresentation\Slide\Iterator; use PhpOffice\PhpPresentation\Slide\SlideMaster; /** - * PhpPresentation + * PhpPresentation. */ class PhpPresentation { /** - * Document properties + * Document properties. * - * @var \PhpOffice\PhpPresentation\DocumentProperties + * @var DocumentProperties */ protected $documentProperties; /** - * Presentation properties + * Presentation properties. * - * @var \PhpOffice\PhpPresentation\PresentationProperties + * @var PresentationProperties */ protected $presentationProps; /** - * Document layout + * Document layout. * - * @var \PhpOffice\PhpPresentation\DocumentLayout + * @var DocumentLayout */ protected $layout; /** - * Collection of Slide objects + * Collection of Slide objects. * - * @var \PhpOffice\PhpPresentation\Slide[] + * @var array */ - protected $slideCollection = array(); + protected $slideCollection = []; /** - * Active slide index + * Active slide index. * * @var int */ protected $activeSlideIndex = 0; /** - * Collection of Master Slides - * @var \ArrayObject|\PhpOffice\PhpPresentation\Slide\SlideMaster[] + * Collection of Master Slides. + * + * @var array|ArrayObject */ protected $slideMasters; /** - * Create a new PhpPresentation with one Slide + * Create a new PhpPresentation with one Slide. */ public function __construct() { @@ -86,45 +91,17 @@ class PhpPresentation } /** - * Get properties - * - * @return \PhpOffice\PhpPresentation\DocumentProperties - * @deprecated for getDocumentProperties + * Get properties. */ - public function getProperties() - { - return $this->getDocumentProperties(); - } - - /** - * Set properties - * - * @param \PhpOffice\PhpPresentation\DocumentProperties $value - * @deprecated for setDocumentProperties - * @return PhpPresentation - */ - public function setProperties(DocumentProperties $value) - { - return $this->setDocumentProperties($value); - } - - /** - * Get properties - * - * @return \PhpOffice\PhpPresentation\DocumentProperties - */ - public function getDocumentProperties() + public function getDocumentProperties(): DocumentProperties { return $this->documentProperties; } /** - * Set properties - * - * @param \PhpOffice\PhpPresentation\DocumentProperties $value - * @return PhpPresentation + * Set properties. */ - public function setDocumentProperties(DocumentProperties $value) + public function setDocumentProperties(DocumentProperties $value): self { $this->documentProperties = $value; @@ -132,44 +109,37 @@ class PhpPresentation } /** - * Get presentation properties - * - * @return \PhpOffice\PhpPresentation\PresentationProperties + * Get presentation properties. */ - public function getPresentationProperties() + public function getPresentationProperties(): PresentationProperties { return $this->presentationProps; } /** - * Set presentation properties + * Set presentation properties. * - * @param \PhpOffice\PhpPresentation\PresentationProperties $value * @return PhpPresentation */ - public function setPresentationProperties(PresentationProperties $value) + public function setPresentationProperties(PresentationProperties $value): self { $this->presentationProps = $value; + return $this; } /** - * Get layout - * - * @return \PhpOffice\PhpPresentation\DocumentLayout + * Get layout. */ - public function getLayout() + public function getLayout(): DocumentLayout { return $this->layout; } /** - * Set layout - * - * @param \PhpOffice\PhpPresentation\DocumentLayout $value - * @return PhpPresentation + * Set layout. */ - public function setLayout(DocumentLayout $value) + public function setLayout(DocumentLayout $value): self { $this->layout = $value; @@ -177,36 +147,28 @@ class PhpPresentation } /** - * Get active slide - * - * @return \PhpOffice\PhpPresentation\Slide + * Get active slide. */ - public function getActiveSlide() + public function getActiveSlide(): Slide { return $this->slideCollection[$this->activeSlideIndex]; } /** - * Create slide and add it to this presentation - * - * @return \PhpOffice\PhpPresentation\Slide - * @throws \Exception + * Create slide and add it to this presentation. */ - public function createSlide() + public function createSlide(): Slide { $newSlide = new Slide($this); $this->addSlide($newSlide); + return $newSlide; } /** - * Add slide - * - * @param \PhpOffice\PhpPresentation\Slide $slide - * @throws \Exception - * @return \PhpOffice\PhpPresentation\Slide + * Add slide. */ - public function addSlide(Slide $slide = null) + public function addSlide(Slide $slide): Slide { $this->slideCollection[] = $slide; @@ -214,16 +176,16 @@ class PhpPresentation } /** - * Remove slide by index + * Remove slide by index. * - * @param int $index Slide index - * @throws \Exception - * @return PhpPresentation + * @param int $index Slide index + * + * @throws OutOfBoundsException */ - public function removeSlideByIndex($index = 0) + public function removeSlideByIndex(int $index = 0): self { if ($index > count($this->slideCollection) - 1) { - throw new \Exception("Slide index is out of bounds."); + throw new OutOfBoundsException(0, count($this->slideCollection) - 1, $index); } array_splice($this->slideCollection, $index, 1); @@ -231,80 +193,77 @@ class PhpPresentation } /** - * Get slide by index + * Get slide by index. * - * @param int $index Slide index - * @return \PhpOffice\PhpPresentation\Slide - * @throws \Exception + * @param int $index Slide index + * + * @throws OutOfBoundsException */ - public function getSlide($index = 0) + public function getSlide(int $index = 0): Slide { if ($index > count($this->slideCollection) - 1) { - throw new \Exception("Slide index is out of bounds."); + throw new OutOfBoundsException(0, count($this->slideCollection) - 1, $index); } + return $this->slideCollection[$index]; } /** - * Get all slides + * Get all slides. * - * @return \PhpOffice\PhpPresentation\Slide[] + * @return array */ - public function getAllSlides() + public function getAllSlides(): array { return $this->slideCollection; } /** - * Get index for slide - * - * @param \PhpOffice\PhpPresentation\Slide\AbstractSlide $slide - * @return int - * @throws \Exception + * Get index for slide. */ - public function getIndex(Slide\AbstractSlide $slide) + public function getIndex(Slide\AbstractSlide $slide): ?int { - $index = null; + if (empty($this->slideCollection)) { + return null; + } foreach ($this->slideCollection as $key => $value) { if ($value->getHashCode() == $slide->getHashCode()) { - $index = $key; - break; + return $key; } } - return $index; + + return null; } /** - * Get slide count - * - * @return int + * Get slide count. */ - public function getSlideCount() + public function getSlideCount(): int { return count($this->slideCollection); } /** - * Get active slide index + * Get active slide index. * * @return int Active slide index */ - public function getActiveSlideIndex() + public function getActiveSlideIndex(): int { return $this->activeSlideIndex; } /** - * Set active slide index + * Set active slide index. * - * @param int $index Active slide index - * @throws \Exception - * @return \PhpOffice\PhpPresentation\Slide + * @param int $index Active slide index + * + * @throws OutOfBoundsException */ - public function setActiveSlideIndex($index = 0) + public function setActiveSlideIndex(int $index = 0): Slide { if ($index > count($this->slideCollection) - 1) { - throw new \Exception("Active slide index is out of bounds."); + throw new OutOfBoundsException(0, count($this->slideCollection) - 1, $index); } $this->activeSlideIndex = $index; @@ -312,13 +271,11 @@ class PhpPresentation } /** - * Add external slide + * Add external slide. * - * @param \PhpOffice\PhpPresentation\Slide $slide External slide to add - * @throws \Exception - * @return \PhpOffice\PhpPresentation\Slide + * @param Slide $slide External slide to add */ - public function addExternalSlide(Slide $slide) + public function addExternalSlide(Slide $slide): Slide { $slide->rebindParent($this); @@ -328,36 +285,28 @@ class PhpPresentation } /** - * Get slide iterator - * - * @return \PhpOffice\PhpPresentation\Slide\Iterator + * Get slide iterator. */ - public function getSlideIterator() + public function getSlideIterator(): Iterator { return new Iterator($this); } /** - * Create a masterslide and add it to this presentation - * - * @return \PhpOffice\PhpPresentation\Slide\SlideMaster - * @throws \Exception + * Create a masterslide and add it to this presentation. */ - public function createMasterSlide() + public function createMasterSlide(): SlideMaster { $newMasterSlide = new SlideMaster($this); $this->addMasterSlide($newMasterSlide); + return $newMasterSlide; } /** - * Add masterslide - * - * @param \PhpOffice\PhpPresentation\Slide\SlideMaster $slide - * @return \PhpOffice\PhpPresentation\Slide\SlideMaster - * @throws \Exception + * Add masterslide. */ - public function addMasterSlide(SlideMaster $slide = null) + public function addMasterSlide(SlideMaster $slide): SlideMaster { $this->slideMasters[] = $slide; @@ -365,12 +314,9 @@ class PhpPresentation } /** - * Copy presentation (!= clone!) - * - * @return PhpPresentation - * @throws \Exception + * Copy presentation (!= clone!). */ - public function copy() + public function copy(): PhpPresentation { $copied = clone $this; @@ -384,49 +330,7 @@ class PhpPresentation } /** - * Mark a document as final - * @param bool $state - * @return PresentationProperties - * @deprecated for getPresentationProperties()->markAsFinal() - */ - public function markAsFinal($state = true) - { - return $this->getPresentationProperties()->markAsFinal($state); - } - - /** - * Return if this document is marked as final - * @return bool - * @deprecated for getPresentationProperties()->isMarkedAsFinal() - */ - public function isMarkedAsFinal() - { - return $this->getPresentationProperties()->isMarkedAsFinal(); - } - - /** - * Set the zoom of the document (in percentage) - * @param float $zoom - * @return PresentationProperties - * @deprecated for getPresentationProperties()->setZoom() - */ - public function setZoom($zoom = 1.0) - { - return $this->getPresentationProperties()->setZoom($zoom); - } - - /** - * Return the zoom (in percentage) - * @return float - * @deprecated for getPresentationProperties()->getZoom() - */ - public function getZoom() - { - return $this->getPresentationProperties()->getZoom(); - } - - /** - * @return \ArrayObject|Slide\SlideMaster[] + * @return array|ArrayObject */ public function getAllMasterSlides() { @@ -434,14 +338,14 @@ class PhpPresentation } /** - * @param \ArrayObject|Slide\SlideMaster[] $slideMasters - * @return $this + * @param array|ArrayObject $slideMasters */ - public function setAllMasterSlides($slideMasters = array()) + public function setAllMasterSlides($slideMasters = []): self { - if ($slideMasters instanceof \ArrayObject || is_array($slideMasters)) { + if ($slideMasters instanceof ArrayObject || is_array($slideMasters)) { $this->slideMasters = $slideMasters; } + return $this; } } diff --git a/PhpOffice/PhpPresentation/PresentationProperties.php b/PhpOffice/PhpPresentation/PresentationProperties.php old mode 100755 new mode 100644 index 05b6dbf..a8ee8ef --- a/PhpOffice/PhpPresentation/PresentationProperties.php +++ b/PhpOffice/PhpPresentation/PresentationProperties.php @@ -10,27 +10,31 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ + +declare(strict_types=1); + namespace PhpOffice\PhpPresentation; -/** - * \PhpOffice\PhpPresentation\PresentationProperties - */ class PresentationProperties { - const VIEW_HANDOUT = 'handoutView'; - const VIEW_NOTES = 'notesView'; - const VIEW_NOTES_MASTER = 'notesMasterView'; - const VIEW_OUTLINE = 'outlineView'; - const VIEW_SLIDE = 'sldView'; - const VIEW_SLIDE_MASTER = 'sldMasterView'; - const VIEW_SLIDE_SORTER = 'sldSorterView'; - const VIEW_SLIDE_THUMBNAIL = 'sldThumbnailView'; + public const VIEW_HANDOUT = 'handoutView'; + public const VIEW_NOTES = 'notesView'; + public const VIEW_NOTES_MASTER = 'notesMasterView'; + public const VIEW_OUTLINE = 'outlineView'; + public const VIEW_SLIDE = 'sldView'; + public const VIEW_SLIDE_MASTER = 'sldMasterView'; + public const VIEW_SLIDE_SORTER = 'sldSorterView'; + public const VIEW_SLIDE_THUMBNAIL = 'sldThumbnailView'; - protected $arrayView = array( + /** + * @var array + */ + protected $arrayView = [ self::VIEW_HANDOUT, self::VIEW_NOTES, self::VIEW_NOTES_MASTER, @@ -39,163 +43,189 @@ class PresentationProperties self::VIEW_SLIDE_MASTER, self::VIEW_SLIDE_SORTER, self::VIEW_SLIDE_THUMBNAIL, - ); + ]; - /* - * @var boolean + public const SLIDESHOW_TYPE_PRESENT = 'present'; + public const SLIDESHOW_TYPE_BROWSE = 'browse'; + public const SLIDESHOW_TYPE_KIOSK = 'kiosk'; + + /** + * @var array + */ + protected $arraySlideshowTypes = [ + self::SLIDESHOW_TYPE_PRESENT, + self::SLIDESHOW_TYPE_BROWSE, + self::SLIDESHOW_TYPE_KIOSK, + ]; + + /** + * @var bool */ protected $isLoopUntilEsc = false; /** - * Mark as final + * Mark as final. + * * @var bool */ protected $markAsFinal = false; - /* - * @var string + /** + * @var string|null */ protected $thumbnail; /** - * Zoom + * Zoom. + * * @var float */ - protected $zoom = 1; + protected $zoom = 1.0; - /* + /** * @var string */ protected $lastView = self::VIEW_SLIDE; - /* - * @var boolean + /** + * @var string + */ + protected $slideshowType = self::SLIDESHOW_TYPE_PRESENT; + + /** + * @var bool */ protected $isCommentVisible = false; - - /** - * @return bool - */ - public function isLoopContinuouslyUntilEsc() + + public function isLoopContinuouslyUntilEsc(): bool { return $this->isLoopUntilEsc; } - - /** - * @param bool $value - * @return \PhpOffice\PhpPresentation\PresentationProperties - */ - public function setLoopContinuouslyUntilEsc($value = false) + + public function setLoopContinuouslyUntilEsc(bool $value = false): self { - if (is_bool($value)) { - $this->isLoopUntilEsc = $value; - } + $this->isLoopUntilEsc = $value; + return $this; } - + /** - * Return the thumbnail file path - * @return string + * Return the thumbnail file path. + * + * @return string|null */ - public function getThumbnailPath() + public function getThumbnailPath(): ?string { return $this->thumbnail; } - + /** - * Define the path for the thumbnail file / preview picture + * Define the path for the thumbnail file / preview picture. + * * @param string $path - * @return \PhpOffice\PhpPresentation\PresentationProperties + * + * @return self */ - public function setThumbnailPath($path = '') + public function setThumbnailPath(string $path = ''): self { if (file_exists($path)) { $this->thumbnail = $path; } + return $this; } /** - * Mark a document as final - * @param bool $state - * @return PresentationProperties + * Mark a document as final. */ - public function markAsFinal($state = true) + public function markAsFinal(bool $state = true): self { - if (is_bool($state)) { - $this->markAsFinal = $state; - } + $this->markAsFinal = $state; + return $this; } /** - * Return if this document is marked as final + * Return if this document is marked as final. + * * @return bool */ - public function isMarkedAsFinal() + public function isMarkedAsFinal(): bool { return $this->markAsFinal; } /** - * Set the zoom of the document (in percentage) - * @param float $zoom - * @return PresentationProperties + * Set the zoom of the document (in percentage). */ - public function setZoom($zoom = 1.0) + public function setZoom(float $zoom = 1.0): self { - if (is_numeric($zoom)) { - $this->zoom = (float)$zoom; - } + $this->zoom = $zoom; + return $this; } /** - * Return the zoom (in percentage) - * @return float + * Return the zoom (in percentage). */ - public function getZoom() + public function getZoom(): float { return $this->zoom; } /** * @param string $value - * @return $this + * + * @return self */ - public function setLastView($value = self::VIEW_SLIDE) + public function setLastView(string $value = self::VIEW_SLIDE): self { if (in_array($value, $this->arrayView)) { $this->lastView = $value; } + return $this; } /** * @return string */ - public function getLastView() + public function getLastView(): string { return $this->lastView; } - /** - * @param bool $value - * @return $this - */ - public function setCommentVisible($value = false) + public function setCommentVisible(bool $value = false): self { - if (is_bool($value)) { - $this->isCommentVisible = $value; - } + $this->isCommentVisible = $value; + return $this; } + public function isCommentVisible(): bool + { + return $this->isCommentVisible; + } + /** * @return string */ - public function isCommentVisible() + public function getSlideshowType(): string { - return $this->isCommentVisible; + return $this->slideshowType; + } + + /** + * @param string $value + * + * @return self + */ + public function setSlideshowType(string $value = self::SLIDESHOW_TYPE_PRESENT): self + { + if (in_array($value, $this->arraySlideshowTypes)) { + $this->slideshowType = $value; + } + + return $this; } } diff --git a/PhpOffice/PhpPresentation/Reader/ODPresentation.php b/PhpOffice/PhpPresentation/Reader/ODPresentation.php old mode 100755 new mode 100644 index d658322..d4fd67c --- a/PhpOffice/PhpPresentation/Reader/ODPresentation.php +++ b/PhpOffice/PhpPresentation/Reader/ODPresentation.php @@ -10,64 +10,76 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Reader; -use ZipArchive; -use PhpOffice\Common\XMLReader; +use DateTime; +use DOMElement; use PhpOffice\Common\Drawing as CommonDrawing; +use PhpOffice\Common\XMLReader; +use PhpOffice\PhpPresentation\DocumentProperties; +use PhpOffice\PhpPresentation\Exception\FileNotFoundException; +use PhpOffice\PhpPresentation\Exception\InvalidFileFormatException; use PhpOffice\PhpPresentation\PhpPresentation; +use PhpOffice\PhpPresentation\PresentationProperties; +use PhpOffice\PhpPresentation\Shape\Drawing\Base64; use PhpOffice\PhpPresentation\Shape\Drawing\Gd; use PhpOffice\PhpPresentation\Shape\RichText; use PhpOffice\PhpPresentation\Shape\RichText\Paragraph; use PhpOffice\PhpPresentation\Slide\Background\Image; +use PhpOffice\PhpPresentation\Style\Alignment; use PhpOffice\PhpPresentation\Style\Bullet; use PhpOffice\PhpPresentation\Style\Color; use PhpOffice\PhpPresentation\Style\Fill; use PhpOffice\PhpPresentation\Style\Font; use PhpOffice\PhpPresentation\Style\Shadow; -use PhpOffice\PhpPresentation\Style\Alignment; +use ZipArchive; /** - * Serialized format reader + * Serialized format reader. */ class ODPresentation implements ReaderInterface { /** - * Output Object + * Output Object. + * * @var PhpPresentation */ protected $oPhpPresentation; /** - * Output Object + * Output Object. + * * @var \ZipArchive */ protected $oZip; /** - * @var array[] + * @var array */ - protected $arrayStyles = array(); + protected $arrayStyles = []; /** - * @var array[] + * @var array> */ - protected $arrayCommonStyles = array(); + protected $arrayCommonStyles = []; /** * @var \PhpOffice\Common\XMLReader */ protected $oXMLReader; + /** + * @var int + */ + protected $levelParagraph = 0; /** * Can the current \PhpOffice\PhpPresentation\Reader\ReaderInterface read the file? - * - * @param string $pFilename - * @throws \Exception - * @return boolean */ - public function canRead($pFilename) + public function canRead(string $pFilename): bool { return $this->fileSupportsUnserializePhpPresentation($pFilename); } @@ -75,83 +87,81 @@ class ODPresentation implements ReaderInterface /** * Does a file support UnserializePhpPresentation ? * - * @param string $pFilename - * @throws \Exception - * @return boolean + * @throws FileNotFoundException */ - public function fileSupportsUnserializePhpPresentation($pFilename = '') + public function fileSupportsUnserializePhpPresentation(string $pFilename = ''): bool { // Check if file exists if (!file_exists($pFilename)) { - throw new \Exception("Could not open " . $pFilename . " for reading! File does not exist."); + throw new FileNotFoundException($pFilename); } - + $oZip = new ZipArchive(); // Is it a zip ? - if ($oZip->open($pFilename) === true) { + if (true === $oZip->open($pFilename)) { // Is it an OpenXML Document ? // Is it a Presentation ? - if (is_array($oZip->statName('META-INF/manifest.xml')) && is_array($oZip->statName('mimetype')) && $oZip->getFromName('mimetype') == 'application/vnd.oasis.opendocument.presentation') { + if (is_array($oZip->statName('META-INF/manifest.xml')) && is_array($oZip->statName('mimetype')) && 'application/vnd.oasis.opendocument.presentation' == $oZip->getFromName('mimetype')) { return true; } } + return false; } /** - * Loads PhpPresentation Serialized file + * Loads PhpPresentation Serialized file. * - * @param string $pFilename - * @return \PhpOffice\PhpPresentation\PhpPresentation - * @throws \Exception + * @throws InvalidFileFormatException */ - public function load($pFilename) + public function load(string $pFilename): PhpPresentation { // Unserialize... First make sure the file supports it! if (!$this->fileSupportsUnserializePhpPresentation($pFilename)) { - throw new \Exception("Invalid file format for PhpOffice\PhpPresentation\Reader\ODPresentation: " . $pFilename . "."); + throw new InvalidFileFormatException($pFilename, ODPresentation::class); } return $this->loadFile($pFilename); } /** - * Load PhpPresentation Serialized file + * Load PhpPresentation Serialized file. * - * @param string $pFilename - * @return \PhpOffice\PhpPresentation\PhpPresentation - * @throws \Exception + * @param string $pFilename + * + * @return PhpPresentation */ protected function loadFile($pFilename) { $this->oPhpPresentation = new PhpPresentation(); $this->oPhpPresentation->removeSlideByIndex(); - + $this->oZip = new ZipArchive(); $this->oZip->open($pFilename); - + $this->oXMLReader = new XMLReader(); - if ($this->oXMLReader->getDomFromZip($pFilename, 'meta.xml') !== false) { + if (false !== $this->oXMLReader->getDomFromZip($pFilename, 'meta.xml')) { $this->loadDocumentProperties(); } $this->oXMLReader = new XMLReader(); - if ($this->oXMLReader->getDomFromZip($pFilename, 'styles.xml') !== false) { + if (false !== $this->oXMLReader->getDomFromZip($pFilename, 'styles.xml')) { $this->loadStylesFile(); } $this->oXMLReader = new XMLReader(); - if ($this->oXMLReader->getDomFromZip($pFilename, 'content.xml') !== false) { + if (false !== $this->oXMLReader->getDomFromZip($pFilename, 'content.xml')) { $this->loadSlides(); + $this->loadPresentationProperties(); } return $this->oPhpPresentation; } - + /** * Read Document Properties */ - protected function loadDocumentProperties() + protected function loadDocumentProperties(): void { - $arrayProperties = array( + $arrayProperties = [ '/office:document-meta/office:meta/meta:initial-creator' => 'setCreator', '/office:document-meta/office:meta/dc:creator' => 'setLastModifiedBy', '/office:document-meta/office:meta/dc:title' => 'setTitle', @@ -160,62 +170,99 @@ class ODPresentation implements ReaderInterface '/office:document-meta/office:meta/meta:keyword' => 'setKeywords', '/office:document-meta/office:meta/meta:creation-date' => 'setCreated', '/office:document-meta/office:meta/dc:date' => 'setModified', - ); - $oProperties = $this->oPhpPresentation->getDocumentProperties(); + ]; + $properties = $this->oPhpPresentation->getDocumentProperties(); foreach ($arrayProperties as $path => $property) { $oElement = $this->oXMLReader->getElement($path); - if ($oElement instanceof \DOMElement) { - if (in_array($property, array('setCreated', 'setModified'))) { - $oDateTime = new \DateTime(); - $oDateTime->createFromFormat(\DateTime::W3C, $oElement->nodeValue); - $oProperties->{$property}($oDateTime->getTimestamp()); - } else { - $oProperties->{$property}($oElement->nodeValue); + if ($oElement instanceof DOMElement) { + $value = $oElement->nodeValue; + if (in_array($property, ['setCreated', 'setModified'])) { + $dateTime = DateTime::createFromFormat(DateTime::W3C, $value); + if (!$dateTime) { + $dateTime = new DateTime(); + } + $value = $dateTime->getTimestamp(); } + $properties->{$property}($value); } } + + foreach ($this->oXMLReader->getElements('/office:document-meta/office:meta/meta:user-defined') as $element) { + if (!($element instanceof DOMElement) + || !$element->hasAttribute('meta:name')) { + continue; + } + $propertyName = $element->getAttribute('meta:name'); + $propertyValue = (string) $element->nodeValue; + $propertyType = $element->getAttribute('meta:value-type'); + switch ($propertyType) { + case 'boolean': + $propertyType = DocumentProperties::PROPERTY_TYPE_BOOLEAN; + break; + case 'float': + $propertyType = filter_var($propertyValue, FILTER_VALIDATE_INT) === false + ? DocumentProperties::PROPERTY_TYPE_FLOAT + : DocumentProperties::PROPERTY_TYPE_INTEGER; + break; + case 'date': + $propertyType = DocumentProperties::PROPERTY_TYPE_DATE; + break; + case 'string': + default: + $propertyType = DocumentProperties::PROPERTY_TYPE_STRING; + break; + } + $properties->setCustomProperty($propertyName, $propertyValue, $propertyType); + } } - + /** * Extract all slides */ - protected function loadSlides() + protected function loadSlides(): void { foreach ($this->oXMLReader->getElements('/office:document-content/office:automatic-styles/*') as $oElement) { - if ($oElement->hasAttribute('style:name')) { + if ($oElement instanceof DOMElement && $oElement->hasAttribute('style:name')) { $this->loadStyle($oElement); } } foreach ($this->oXMLReader->getElements('/office:document-content/office:body/office:presentation/draw:page') as $oElement) { - if ($oElement->nodeName == 'draw:page') { + if ($oElement instanceof DOMElement && 'draw:page' == $oElement->nodeName) { $this->loadSlide($oElement); } } } + protected function loadPresentationProperties(): void + { + $element = $this->oXMLReader->getElement('/office:document-content/office:body/office:presentation/presentation:settings'); + if ($element instanceof DOMElement) { + if ($element->getAttribute('presentation:full-screen') === 'false') { + $this->oPhpPresentation->getPresentationProperties()->setSlideshowType(PresentationProperties::SLIDESHOW_TYPE_BROWSE); + } + } + } + /** * Extract style - * @param \DOMElement $nodeStyle - * @return bool - * @throws \Exception */ - protected function loadStyle(\DOMElement $nodeStyle) + protected function loadStyle(DOMElement $nodeStyle): bool { $keyStyle = $nodeStyle->getAttribute('style:name'); $nodeDrawingPageProps = $this->oXMLReader->getElement('style:drawing-page-properties', $nodeStyle); - if ($nodeDrawingPageProps instanceof \DOMElement) { + if ($nodeDrawingPageProps instanceof DOMElement) { // Read Background Color - if ($nodeDrawingPageProps->hasAttribute('draw:fill-color') && $nodeDrawingPageProps->getAttribute('draw:fill') == 'solid') { + if ($nodeDrawingPageProps->hasAttribute('draw:fill-color') && 'solid' == $nodeDrawingPageProps->getAttribute('draw:fill')) { $oBackground = new \PhpOffice\PhpPresentation\Slide\Background\Color(); $oColor = new Color(); $oColor->setRGB(substr($nodeDrawingPageProps->getAttribute('draw:fill-color'), -6)); $oBackground->setColor($oColor); } // Read Background Image - if ($nodeDrawingPageProps->getAttribute('draw:fill') == 'bitmap' && $nodeDrawingPageProps->hasAttribute('draw:fill-image-name')) { + if ('bitmap' == $nodeDrawingPageProps->getAttribute('draw:fill') && $nodeDrawingPageProps->hasAttribute('draw:fill-image-name')) { $nameStyle = $nodeDrawingPageProps->getAttribute('draw:fill-image-name'); - if (!empty($this->arrayCommonStyles[$nameStyle]) && $this->arrayCommonStyles[$nameStyle]['type'] == 'image' && !empty($this->arrayCommonStyles[$nameStyle]['path'])) { + if (!empty($this->arrayCommonStyles[$nameStyle]) && 'image' == $this->arrayCommonStyles[$nameStyle]['type'] && !empty($this->arrayCommonStyles[$nameStyle]['path'])) { $tmpBkgImg = tempnam(sys_get_temp_dir(), 'PhpPresentationReaderODPBkg'); $contentImg = $this->oZip->getFromName($this->arrayCommonStyles[$nameStyle]['path']); file_put_contents($tmpBkgImg, $contentImg); @@ -227,27 +274,27 @@ class ODPresentation implements ReaderInterface } $nodeGraphicProps = $this->oXMLReader->getElement('style:graphic-properties', $nodeStyle); - if ($nodeGraphicProps instanceof \DOMElement) { + if ($nodeGraphicProps instanceof DOMElement) { // Read Shadow - if ($nodeGraphicProps->hasAttribute('draw:shadow') && $nodeGraphicProps->getAttribute('draw:shadow') == 'visible') { + if ($nodeGraphicProps->hasAttribute('draw:shadow') && 'visible' == $nodeGraphicProps->getAttribute('draw:shadow')) { $oShadow = new Shadow(); $oShadow->setVisible(true); if ($nodeGraphicProps->hasAttribute('draw:shadow-color')) { $oShadow->getColor()->setRGB(substr($nodeGraphicProps->getAttribute('draw:shadow-color'), -6)); } if ($nodeGraphicProps->hasAttribute('draw:shadow-opacity')) { - $oShadow->setAlpha(100 - (int)substr($nodeGraphicProps->getAttribute('draw:shadow-opacity'), 0, -1)); + $oShadow->setAlpha(100 - (int) substr($nodeGraphicProps->getAttribute('draw:shadow-opacity'), 0, -1)); } if ($nodeGraphicProps->hasAttribute('draw:shadow-offset-x') && $nodeGraphicProps->hasAttribute('draw:shadow-offset-y')) { - $offsetX = substr($nodeGraphicProps->getAttribute('draw:shadow-offset-x'), 0, -2); - $offsetY = substr($nodeGraphicProps->getAttribute('draw:shadow-offset-y'), 0, -2); + $offsetX = (float) substr($nodeGraphicProps->getAttribute('draw:shadow-offset-x'), 0, -2); + $offsetY = (float) substr($nodeGraphicProps->getAttribute('draw:shadow-offset-y'), 0, -2); $distance = 0; - if ($offsetX != 0) { + if (0 != $offsetX) { $distance = ($offsetX < 0 ? $offsetX * -1 : $offsetX); - } elseif ($offsetY != 0) { + } elseif (0 != $offsetY) { $distance = ($offsetY < 0 ? $offsetY * -1 : $offsetY); } - $oShadow->setDirection(rad2deg(atan2($offsetY, $offsetX))); + $oShadow->setDirection((int) rad2deg(atan2($offsetY, $offsetX))); $oShadow->setDistance(CommonDrawing::centimetersToPixels($distance)); } } @@ -272,91 +319,177 @@ class ODPresentation implements ReaderInterface } } } - + $nodeTextProperties = $this->oXMLReader->getElement('style:text-properties', $nodeStyle); - if ($nodeTextProperties instanceof \DOMElement) { + if ($nodeTextProperties instanceof DOMElement) { $oFont = new Font(); if ($nodeTextProperties->hasAttribute('fo:color')) { $oFont->getColor()->setRGB(substr($nodeTextProperties->getAttribute('fo:color'), -6)); } + // Font Latin if ($nodeTextProperties->hasAttribute('fo:font-family')) { - $oFont->setName($nodeTextProperties->getAttribute('fo:font-family')); + $oFont + ->setName($nodeTextProperties->getAttribute('fo:font-family')) + ->setFormat(Font::FORMAT_LATIN); } - if ($nodeTextProperties->hasAttribute('fo:font-weight') && $nodeTextProperties->getAttribute('fo:font-weight') == 'bold') { - $oFont->setBold(true); + if ($nodeTextProperties->hasAttribute('fo:font-weight') && 'bold' == $nodeTextProperties->getAttribute('fo:font-weight')) { + $oFont + ->setBold(true) + ->setFormat(Font::FORMAT_LATIN); } if ($nodeTextProperties->hasAttribute('fo:font-size')) { - $oFont->setSize(substr($nodeTextProperties->getAttribute('fo:font-size'), 0, -2)); + $oFont + ->setSize((int) substr($nodeTextProperties->getAttribute('fo:font-size'), 0, -2)) + ->setFormat(Font::FORMAT_LATIN); + } + // Font East Asian + if ($nodeTextProperties->hasAttribute('style:font-family-asian')) { + $oFont + ->setName($nodeTextProperties->getAttribute('style:font-family-asian')) + ->setFormat(Font::FORMAT_EAST_ASIAN); + } + if ($nodeTextProperties->hasAttribute('style:font-weight-asian') && 'bold' == $nodeTextProperties->getAttribute('style:font-weight-asian')) { + $oFont + ->setBold(true) + ->setFormat(Font::FORMAT_EAST_ASIAN); + } + if ($nodeTextProperties->hasAttribute('style:font-size-asian')) { + $oFont + ->setSize((int) substr($nodeTextProperties->getAttribute('style:font-size-asian'), 0, -2)) + ->setFormat(Font::FORMAT_EAST_ASIAN); + } + // Font Complex Script + if ($nodeTextProperties->hasAttribute('style:font-family-complex')) { + $oFont + ->setName($nodeTextProperties->getAttribute('style:font-family-complex')) + ->setFormat(Font::FORMAT_COMPLEX_SCRIPT); + } + if ($nodeTextProperties->hasAttribute('style:font-weight-complex') && 'bold' == $nodeTextProperties->getAttribute('style:font-weight-complex')) { + $oFont + ->setBold(true) + ->setFormat(Font::FORMAT_COMPLEX_SCRIPT); + } + if ($nodeTextProperties->hasAttribute('style:font-size-complex')) { + $oFont + ->setSize((int) substr($nodeTextProperties->getAttribute('style:font-size-complex'), 0, -2)) + ->setFormat(Font::FORMAT_COMPLEX_SCRIPT); + } + if ($nodeTextProperties->hasAttribute('style:script-type')) { + switch ($nodeTextProperties->getAttribute('style:script-type')) { + case 'latin': + $oFont->setFormat(Font::FORMAT_LATIN); + break; + case 'asian': + $oFont->setFormat(Font::FORMAT_EAST_ASIAN); + break; + case 'complex': + $oFont->setFormat(Font::FORMAT_COMPLEX_SCRIPT); + break; + } } } $nodeParagraphProps = $this->oXMLReader->getElement('style:paragraph-properties', $nodeStyle); - if ($nodeParagraphProps instanceof \DOMElement) { + if ($nodeParagraphProps instanceof DOMElement) { + if ($nodeParagraphProps->hasAttribute('fo:line-height')) { + $lineHeightUnit = $this->getExpressionUnit($nodeParagraphProps->getAttribute('fo:margin-bottom')); + $lineSpacingMode = $lineHeightUnit == '%' ? Paragraph::LINE_SPACING_MODE_PERCENT : Paragraph::LINE_SPACING_MODE_POINT; + $lineSpacing = $this->getExpressionValue($nodeParagraphProps->getAttribute('fo:margin-bottom')); + } + if ($nodeParagraphProps->hasAttribute('fo:margin-bottom')) { + $spacingAfter = (float) substr($nodeParagraphProps->getAttribute('fo:margin-bottom'), 0, -2); + $spacingAfter = CommonDrawing::centimetersToPoints($spacingAfter); + } + if ($nodeParagraphProps->hasAttribute('fo:margin-top')) { + $spacingBefore = (float) substr($nodeParagraphProps->getAttribute('fo:margin-top'), 0, -2); + $spacingBefore = CommonDrawing::centimetersToPoints($spacingBefore); + } $oAlignment = new Alignment(); if ($nodeParagraphProps->hasAttribute('fo:text-align')) { $oAlignment->setHorizontal($nodeParagraphProps->getAttribute('fo:text-align')); } + if ($nodeParagraphProps->hasAttribute('style:writing-mode')) { + switch ($nodeParagraphProps->getAttribute('style:writing-mode')) { + case 'lr-tb': + case 'tb-lr': + case 'lr': + $oAlignment->setIsRTL(false); + break; + case 'rl-tb': + case 'tb-rl': + case 'rl': + $oAlignment->setIsRTL(false); + break; + case 'tb': + case 'page': + default: + break; + } + } } - - if ($nodeStyle->nodeName == 'text:list-style') { - $arrayListStyle = array(); + + if ('text:list-style' == $nodeStyle->nodeName) { + $arrayListStyle = []; foreach ($this->oXMLReader->getElements('text:list-level-style-bullet', $nodeStyle) as $oNodeListLevel) { $oAlignment = new Alignment(); $oBullet = new Bullet(); $oBullet->setBulletType(Bullet::TYPE_NONE); - if ($oNodeListLevel->hasAttribute('text:level')) { - $oAlignment->setLevel((int) $oNodeListLevel->getAttribute('text:level') - 1); - } - if ($oNodeListLevel->hasAttribute('text:bullet-char')) { - $oBullet->setBulletChar($oNodeListLevel->getAttribute('text:bullet-char')); - $oBullet->setBulletType(Bullet::TYPE_BULLET); - } - - $oNodeListProperties = $this->oXMLReader->getElement('style:list-level-properties', $oNodeListLevel); - if ($oNodeListProperties instanceof \DOMElement) { - if ($oNodeListProperties->hasAttribute('text:min-label-width')) { - $oAlignment->setIndent((int)round(CommonDrawing::centimetersToPixels(substr($oNodeListProperties->getAttribute('text:min-label-width'), 0, -2)))); + if ($oNodeListLevel instanceof DOMElement) { + if ($oNodeListLevel->hasAttribute('text:level')) { + $oAlignment->setLevel((int) $oNodeListLevel->getAttribute('text:level') - 1); } - if ($oNodeListProperties->hasAttribute('text:space-before')) { - $iSpaceBefore = CommonDrawing::centimetersToPixels(substr($oNodeListProperties->getAttribute('text:space-before'), 0, -2)); - $iMarginLeft = $iSpaceBefore + $oAlignment->getIndent(); - $oAlignment->setMarginLeft($iMarginLeft); + if ($oNodeListLevel->hasAttribute('text:bullet-char')) { + $oBullet->setBulletChar($oNodeListLevel->getAttribute('text:bullet-char')); + $oBullet->setBulletType(Bullet::TYPE_BULLET); + } + + $oNodeListProperties = $this->oXMLReader->getElement('style:list-level-properties', $oNodeListLevel); + if ($oNodeListProperties instanceof DOMElement) { + if ($oNodeListProperties->hasAttribute('text:min-label-width')) { + $oAlignment->setIndent(CommonDrawing::centimetersToPixels((float) substr($oNodeListProperties->getAttribute('text:min-label-width'), 0, -2))); + } + if ($oNodeListProperties->hasAttribute('text:space-before')) { + $iSpaceBefore = CommonDrawing::centimetersToPixels((float) substr($oNodeListProperties->getAttribute('text:space-before'), 0, -2)); + $iMarginLeft = $iSpaceBefore + $oAlignment->getIndent(); + $oAlignment->setMarginLeft($iMarginLeft); + } + } + + $oNodeTextProperties = $this->oXMLReader->getElement('style:text-properties', $oNodeListLevel); + if ($oNodeTextProperties instanceof DOMElement) { + if ($oNodeTextProperties->hasAttribute('fo:font-family')) { + $oBullet->setBulletFont($oNodeTextProperties->getAttribute('fo:font-family')); + } } } - $oNodeTextProperties = $this->oXMLReader->getElement('style:text-properties', $oNodeListLevel); - if ($oNodeTextProperties instanceof \DOMElement) { - if ($oNodeTextProperties->hasAttribute('fo:font-family')) { - $oBullet->setBulletFont($oNodeTextProperties->getAttribute('fo:font-family')); - } - } - - $arrayListStyle[$oAlignment->getLevel()] = array( + + $arrayListStyle[$oAlignment->getLevel()] = [ 'alignment' => $oAlignment, 'bullet' => $oBullet, - ); + ]; } } - - $this->arrayStyles[$keyStyle] = array( - 'alignment' => isset($oAlignment) ? $oAlignment : null, - 'background' => isset($oBackground) ? $oBackground : null, - 'fill' => isset($oFill) ? $oFill : null, - 'font' => isset($oFont) ? $oFont : null, - 'shadow' => isset($oShadow) ? $oShadow : null, - 'listStyle' => isset($arrayListStyle) ? $arrayListStyle : null, - ); - + + $this->arrayStyles[$keyStyle] = [ + 'alignment' => $oAlignment ?? null, + 'background' => $oBackground ?? null, + 'fill' => $oFill ?? null, + 'font' => $oFont ?? null, + 'shadow' => $oShadow ?? null, + 'listStyle' => $arrayListStyle ?? null, + 'spacingAfter' => $spacingAfter ?? null, + 'spacingBefore' => $spacingBefore ?? null, + 'lineSpacingMode' => $lineSpacingMode ?? null, + 'lineSpacing' => $lineSpacing ?? null, + ]; + return true; } /** * Read Slide - * - * @param \DOMElement $nodeSlide - * @return bool - * @throws \Exception */ - protected function loadSlide(\DOMElement $nodeSlide) + protected function loadSlide(DOMElement $nodeSlide): bool { // Core $this->oPhpPresentation->createSlide(); @@ -371,128 +504,151 @@ class ODPresentation implements ReaderInterface } } foreach ($this->oXMLReader->getElements('draw:frame', $nodeSlide) as $oNodeFrame) { - if ($this->oXMLReader->getElement('draw:image', $oNodeFrame)) { - $this->loadShapeDrawing($oNodeFrame); - continue; - } - if ($this->oXMLReader->getElement('draw:text-box', $oNodeFrame)) { - $this->loadShapeRichText($oNodeFrame); - continue; + if ($oNodeFrame instanceof DOMElement) { + if ($this->oXMLReader->getElement('draw:image', $oNodeFrame)) { + $this->loadShapeDrawing($oNodeFrame); + continue; + } + if ($this->oXMLReader->getElement('draw:text-box', $oNodeFrame)) { + $this->loadShapeRichText($oNodeFrame); + continue; + } } } + return true; } /** * Read Shape Drawing - * - * @param \DOMElement $oNodeFrame - * @throws \Exception */ - protected function loadShapeDrawing(\DOMElement $oNodeFrame) + protected function loadShapeDrawing(DOMElement $oNodeFrame): void { // Core - $oShape = new Gd(); - $oShape->getShadow()->setVisible(false); + $mimetype = ''; $oNodeImage = $this->oXMLReader->getElement('draw:image', $oNodeFrame); - if ($oNodeImage instanceof \DOMElement) { + if ($oNodeImage instanceof DOMElement) { + if ($oNodeImage->hasAttribute('loext:mime-type')) { + $mimetype = $oNodeImage->getAttribute('loext:mime-type'); + } if ($oNodeImage->hasAttribute('xlink:href')) { $sFilename = $oNodeImage->getAttribute('xlink:href'); // svm = StarView Metafile - if (pathinfo($sFilename, PATHINFO_EXTENSION) == 'svm') { + if ('svm' == pathinfo($sFilename, PATHINFO_EXTENSION)) { return; } $imageFile = $this->oZip->getFromName($sFilename); - if (!empty($imageFile)) { - $oShape->setImageResource(imagecreatefromstring($imageFile)); - } } } - - $oShape->setName($oNodeFrame->hasAttribute('draw:name') ? $oNodeFrame->getAttribute('draw:name') : ''); - $oShape->setDescription($oNodeFrame->hasAttribute('draw:name') ? $oNodeFrame->getAttribute('draw:name') : ''); - $oShape->setResizeProportional(false); - $oShape->setWidth($oNodeFrame->hasAttribute('svg:width') ? (int)round(CommonDrawing::centimetersToPixels(substr($oNodeFrame->getAttribute('svg:width'), 0, -2))) : ''); - $oShape->setHeight($oNodeFrame->hasAttribute('svg:height') ? (int)round(CommonDrawing::centimetersToPixels(substr($oNodeFrame->getAttribute('svg:height'), 0, -2))) : ''); - $oShape->setResizeProportional(true); - $oShape->setOffsetX($oNodeFrame->hasAttribute('svg:x') ? (int)round(CommonDrawing::centimetersToPixels(substr($oNodeFrame->getAttribute('svg:x'), 0, -2))) : ''); - $oShape->setOffsetY($oNodeFrame->hasAttribute('svg:y') ? (int)round(CommonDrawing::centimetersToPixels(substr($oNodeFrame->getAttribute('svg:y'), 0, -2))) : ''); - + + if (empty($imageFile)) { + return; + } + + // Contents of file + if (empty($mimetype)) { + $shape = new Gd(); + $shape->setImageResource(imagecreatefromstring($imageFile)); + } else { + $shape = new Base64(); + $shape->setData('data:' . $mimetype . ';base64,' . base64_encode($imageFile)); + } + + $shape->getShadow()->setVisible(false); + $shape->setName($oNodeFrame->hasAttribute('draw:name') ? $oNodeFrame->getAttribute('draw:name') : ''); + $shape->setDescription($oNodeFrame->hasAttribute('draw:name') ? $oNodeFrame->getAttribute('draw:name') : ''); + $shape->setResizeProportional(false); + $shape->setWidth($oNodeFrame->hasAttribute('svg:width') ? CommonDrawing::centimetersToPixels((float) substr($oNodeFrame->getAttribute('svg:width'), 0, -2)) : 0); + $shape->setHeight($oNodeFrame->hasAttribute('svg:height') ? CommonDrawing::centimetersToPixels((float) substr($oNodeFrame->getAttribute('svg:height'), 0, -2)) : 0); + $shape->setResizeProportional(true); + $shape->setOffsetX($oNodeFrame->hasAttribute('svg:x') ? CommonDrawing::centimetersToPixels((float) substr($oNodeFrame->getAttribute('svg:x'), 0, -2)) : 0); + $shape->setOffsetY($oNodeFrame->hasAttribute('svg:y') ? CommonDrawing::centimetersToPixels((float) substr($oNodeFrame->getAttribute('svg:y'), 0, -2)) : 0); + if ($oNodeFrame->hasAttribute('draw:style-name')) { $keyStyle = $oNodeFrame->getAttribute('draw:style-name'); if (isset($this->arrayStyles[$keyStyle])) { - $oShape->setShadow($this->arrayStyles[$keyStyle]['shadow']); - $oShape->setFill($this->arrayStyles[$keyStyle]['fill']); + $shape->setShadow($this->arrayStyles[$keyStyle]['shadow']); + $shape->setFill($this->arrayStyles[$keyStyle]['fill']); } } - - $this->oPhpPresentation->getActiveSlide()->addShape($oShape); + + $this->oPhpPresentation->getActiveSlide()->addShape($shape); } /** * Read Shape RichText - * - * @param \DOMElement $oNodeFrame - * @throws \Exception */ - protected function loadShapeRichText(\DOMElement $oNodeFrame) + protected function loadShapeRichText(DOMElement $oNodeFrame): void { // Core $oShape = $this->oPhpPresentation->getActiveSlide()->createRichTextShape(); - $oShape->setParagraphs(array()); - - $oShape->setWidth($oNodeFrame->hasAttribute('svg:width') ? (int)round(CommonDrawing::centimetersToPixels(substr($oNodeFrame->getAttribute('svg:width'), 0, -2))) : ''); - $oShape->setHeight($oNodeFrame->hasAttribute('svg:height') ? (int)round(CommonDrawing::centimetersToPixels(substr($oNodeFrame->getAttribute('svg:height'), 0, -2))) : ''); - $oShape->setOffsetX($oNodeFrame->hasAttribute('svg:x') ? (int)round(CommonDrawing::centimetersToPixels(substr($oNodeFrame->getAttribute('svg:x'), 0, -2))) : ''); - $oShape->setOffsetY($oNodeFrame->hasAttribute('svg:y') ? (int)round(CommonDrawing::centimetersToPixels(substr($oNodeFrame->getAttribute('svg:y'), 0, -2))) : ''); - + $oShape->setParagraphs([]); + + $oShape->setWidth($oNodeFrame->hasAttribute('svg:width') ? CommonDrawing::centimetersToPixels((float) substr($oNodeFrame->getAttribute('svg:width'), 0, -2)) : 0); + $oShape->setHeight($oNodeFrame->hasAttribute('svg:height') ? CommonDrawing::centimetersToPixels((float) substr($oNodeFrame->getAttribute('svg:height'), 0, -2)) : 0); + $oShape->setOffsetX($oNodeFrame->hasAttribute('svg:x') ? CommonDrawing::centimetersToPixels((float) substr($oNodeFrame->getAttribute('svg:x'), 0, -2)) : 0); + $oShape->setOffsetY($oNodeFrame->hasAttribute('svg:y') ? CommonDrawing::centimetersToPixels((float) substr($oNodeFrame->getAttribute('svg:y'), 0, -2)) : 0); + foreach ($this->oXMLReader->getElements('draw:text-box/*', $oNodeFrame) as $oNodeParagraph) { $this->levelParagraph = 0; - if ($oNodeParagraph->nodeName == 'text:p') { - $this->readParagraph($oShape, $oNodeParagraph); - } - if ($oNodeParagraph->nodeName == 'text:list') { - $this->readList($oShape, $oNodeParagraph); + if ($oNodeParagraph instanceof DOMElement) { + if ('text:p' == $oNodeParagraph->nodeName) { + $this->readParagraph($oShape, $oNodeParagraph); + } + if ('text:list' == $oNodeParagraph->nodeName) { + $this->readList($oShape, $oNodeParagraph); + } } } - + if (count($oShape->getParagraphs()) > 0) { $oShape->setActiveParagraph(0); } } - - protected $levelParagraph = 0; /** * Read Paragraph - * @param RichText $oShape - * @param \DOMElement $oNodeParent - * @throws \Exception */ - protected function readParagraph(RichText $oShape, \DOMElement $oNodeParent) + protected function readParagraph(RichText $oShape, DOMElement $oNodeParent): void { $oParagraph = $oShape->createParagraph(); + if ($oNodeParent->hasAttribute('text:style-name')) { + $keyStyle = $oNodeParent->getAttribute('text:style-name'); + if (isset($this->arrayStyles[$keyStyle])) { + if (!empty($this->arrayStyles[$keyStyle]['spacingAfter'])) { + $oParagraph->setSpacingAfter($this->arrayStyles[$keyStyle]['spacingAfter']); + } + if (!empty($this->arrayStyles[$keyStyle]['spacingBefore'])) { + $oParagraph->setSpacingBefore($this->arrayStyles[$keyStyle]['spacingBefore']); + } + if (!empty($this->arrayStyles[$keyStyle]['lineSpacingMode'])) { + $oParagraph->setLineSpacingMode($this->arrayStyles[$keyStyle]['lineSpacingMode']); + } + if (!empty($this->arrayStyles[$keyStyle]['lineSpacing'])) { + $oParagraph->setLineSpacing($this->arrayStyles[$keyStyle]['lineSpacing']); + } + } + } $oDomList = $this->oXMLReader->getElements('text:span', $oNodeParent); $oDomTextNodes = $this->oXMLReader->getElements('text()', $oNodeParent); foreach ($oDomTextNodes as $oDomTextNode) { - if (trim($oDomTextNode->nodeValue) != '') { + if ('' != trim($oDomTextNode->nodeValue)) { $oTextRun = $oParagraph->createTextRun(); $oTextRun->setText(trim($oDomTextNode->nodeValue)); } } foreach ($oDomList as $oNodeRichTextElement) { - $this->readParagraphItem($oParagraph, $oNodeRichTextElement); + if ($oNodeRichTextElement instanceof DOMElement) { + $this->readParagraphItem($oParagraph, $oNodeRichTextElement); + } } } /** * Read Paragraph Item - * @param Paragraph $oParagraph - * @param \DOMElement $oNodeParent - * @throws \Exception */ - protected function readParagraphItem(Paragraph $oParagraph, \DOMElement $oNodeParent) + protected function readParagraphItem(Paragraph $oParagraph, DOMElement $oNodeParent): void { if ($this->oXMLReader->elementExists('text:line-break', $oNodeParent)) { $oParagraph->createBreak(); @@ -505,7 +661,7 @@ class ODPresentation implements ReaderInterface } } $oTextRunLink = $this->oXMLReader->getElement('text:a', $oNodeParent); - if ($oTextRunLink instanceof \DOMElement) { + if ($oTextRunLink instanceof DOMElement) { $oTextRun->setText($oTextRunLink->nodeValue); if ($oTextRunLink->hasAttribute('xlink:href')) { $oTextRun->getHyperlink()->setUrl($oTextRunLink->getAttribute('xlink:href')); @@ -518,33 +674,27 @@ class ODPresentation implements ReaderInterface /** * Read List - * - * @param RichText $oShape - * @param \DOMElement $oNodeParent - * @throws \Exception */ - protected function readList(RichText $oShape, \DOMElement $oNodeParent) + protected function readList(RichText $oShape, DOMElement $oNodeParent): void { foreach ($this->oXMLReader->getElements('text:list-item/*', $oNodeParent) as $oNodeListItem) { - if ($oNodeListItem->nodeName == 'text:p') { - $this->readListItem($oShape, $oNodeListItem, $oNodeParent); - } - if ($oNodeListItem->nodeName == 'text:list') { - $this->levelParagraph++; - $this->readList($oShape, $oNodeListItem); - $this->levelParagraph--; + if ($oNodeListItem instanceof DOMElement) { + if ('text:p' == $oNodeListItem->nodeName) { + $this->readListItem($oShape, $oNodeListItem, $oNodeParent); + } + if ('text:list' == $oNodeListItem->nodeName) { + ++$this->levelParagraph; + $this->readList($oShape, $oNodeListItem); + --$this->levelParagraph; + } } } } /** * Read List Item - * @param RichText $oShape - * @param \DOMElement $oNodeParent - * @param \DOMElement $oNodeParagraph - * @throws \Exception */ - protected function readListItem(RichText $oShape, \DOMElement $oNodeParent, \DOMElement $oNodeParagraph) + protected function readListItem(RichText $oShape, DOMElement $oNodeParent, DOMElement $oNodeParagraph): void { $oParagraph = $oShape->createParagraph(); if ($oNodeParagraph->hasAttribute('text:style-name')) { @@ -555,22 +705,52 @@ class ODPresentation implements ReaderInterface } } foreach ($this->oXMLReader->getElements('text:span', $oNodeParent) as $oNodeRichTextElement) { - $this->readParagraphItem($oParagraph, $oNodeRichTextElement); + if ($oNodeRichTextElement instanceof DOMElement) { + $this->readParagraphItem($oParagraph, $oNodeRichTextElement); + } } } /** - * Load file 'styles.xml' + * Load file 'styles.xml'. */ - protected function loadStylesFile() + protected function loadStylesFile(): void { foreach ($this->oXMLReader->getElements('/office:document-styles/office:styles/*') as $oElement) { - if ($oElement->nodeName == 'draw:fill-image') { - $this->arrayCommonStyles[$oElement->getAttribute('draw:name')] = array( + if ($oElement instanceof DOMElement && 'draw:fill-image' == $oElement->nodeName) { + $this->arrayCommonStyles[$oElement->getAttribute('draw:name')] = [ 'type' => 'image', - 'path' => $oElement->hasAttribute('xlink:href') ? $oElement->getAttribute('xlink:href') : null - ); + 'path' => $oElement->hasAttribute('xlink:href') ? $oElement->getAttribute('xlink:href') : null, + ]; } } } + + /** + * @param string $expr + * + * @return string + */ + private function getExpressionUnit(string $expr): string + { + if (substr($expr, -1) == '%') { + return '%'; + } + + return substr($expr, -2); + } + + /** + * @param string $expr + * + * @return string + */ + private function getExpressionValue(string $expr): string + { + if (substr($expr, -1) == '%') { + return substr($expr, 0, -1); + } + + return substr($expr, 0, -2); + } } diff --git a/PhpOffice/PhpPresentation/Reader/PowerPoint2007.php b/PhpOffice/PhpPresentation/Reader/PowerPoint2007.php old mode 100755 new mode 100644 index aa3b847..a0a8957 --- a/PhpOffice/PhpPresentation/Reader/PowerPoint2007.php +++ b/PhpOffice/PhpPresentation/Reader/PowerPoint2007.php @@ -10,75 +10,89 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Reader; +use DateTime; +use DOMElement; +use DOMNode; +use DOMNodeList; +use PhpOffice\Common\Drawing as CommonDrawing; +use PhpOffice\Common\XMLReader; use PhpOffice\PhpPresentation\DocumentLayout; +use PhpOffice\PhpPresentation\DocumentProperties; +use PhpOffice\PhpPresentation\Exception\FeatureNotImplementedException; +use PhpOffice\PhpPresentation\Exception\FileNotFoundException; +use PhpOffice\PhpPresentation\Exception\InvalidFileFormatException; use PhpOffice\PhpPresentation\PhpPresentation; +use PhpOffice\PhpPresentation\PresentationProperties; +use PhpOffice\PhpPresentation\Shape\Drawing\Base64; +use PhpOffice\PhpPresentation\Shape\Drawing\Gd; +use PhpOffice\PhpPresentation\Shape\Hyperlink; use PhpOffice\PhpPresentation\Shape\Placeholder; use PhpOffice\PhpPresentation\Shape\RichText; use PhpOffice\PhpPresentation\Shape\RichText\Paragraph; use PhpOffice\PhpPresentation\Shape\Table\Cell; use PhpOffice\PhpPresentation\Slide; use PhpOffice\PhpPresentation\Slide\AbstractSlide; +use PhpOffice\PhpPresentation\Slide\Note; use PhpOffice\PhpPresentation\Slide\SlideLayout; use PhpOffice\PhpPresentation\Slide\SlideMaster; -use PhpOffice\PhpPresentation\Shape\Drawing\Gd; -use PhpOffice\PhpPresentation\Style\Bullet; use PhpOffice\PhpPresentation\Style\Border; use PhpOffice\PhpPresentation\Style\Borders; +use PhpOffice\PhpPresentation\Style\Bullet; use PhpOffice\PhpPresentation\Style\Color; use PhpOffice\PhpPresentation\Style\Fill; +use PhpOffice\PhpPresentation\Style\Font; use PhpOffice\PhpPresentation\Style\SchemeColor; use PhpOffice\PhpPresentation\Style\TextStyle; -use PhpOffice\Common\XMLReader; -use PhpOffice\Common\Drawing as CommonDrawing; use ZipArchive; /** - * Serialized format reader + * Serialized format reader. */ class PowerPoint2007 implements ReaderInterface { /** - * Output Object + * Output Object. + * * @var PhpPresentation */ protected $oPhpPresentation; /** - * Output Object + * Output Object. + * * @var \ZipArchive */ protected $oZip; /** - * @var array[] + * @var array>> */ - protected $arrayRels = array(); + protected $arrayRels = []; /** * @var SlideLayout[] */ - protected $arraySlideLayouts = array(); - /* + protected $arraySlideLayouts = []; + /** * @var string */ protected $filename; - /* + /** * @var string */ protected $fileRels; /** * Can the current \PhpOffice\PhpPresentation\Reader\ReaderInterface read the file? - * - * @param string $pFilename - * @throws \Exception - * @return boolean */ - public function canRead($pFilename) + public function canRead(string $pFilename): bool { return $this->fileSupportsUnserializePhpPresentation($pFilename); } @@ -86,103 +100,101 @@ class PowerPoint2007 implements ReaderInterface /** * Does a file support UnserializePhpPresentation ? * - * @param string $pFilename - * @throws \Exception - * @return boolean + * @throws FileNotFoundException */ - public function fileSupportsUnserializePhpPresentation($pFilename = '') + public function fileSupportsUnserializePhpPresentation(string $pFilename = ''): bool { // Check if file exists if (!file_exists($pFilename)) { - throw new \Exception("Could not open " . $pFilename . " for reading! File does not exist."); + throw new FileNotFoundException($pFilename); } $oZip = new ZipArchive(); // Is it a zip ? - if ($oZip->open($pFilename) === true) { + if (true === $oZip->open($pFilename)) { // Is it an OpenXML Document ? // Is it a Presentation ? if (is_array($oZip->statName('[Content_Types].xml')) && is_array($oZip->statName('ppt/presentation.xml'))) { return true; } } + return false; } /** - * Loads PhpPresentation Serialized file + * Loads PhpPresentation Serialized file. * - * @param string $pFilename - * @return \PhpOffice\PhpPresentation\PhpPresentation - * @throws \Exception + * @throws InvalidFileFormatException */ - public function load($pFilename) + public function load(string $pFilename): PhpPresentation { // Unserialize... First make sure the file supports it! if (!$this->fileSupportsUnserializePhpPresentation($pFilename)) { - throw new \Exception("Invalid file format for PhpOffice\PhpPresentation\Reader\PowerPoint2007: " . $pFilename . "."); + throw new InvalidFileFormatException($pFilename, PowerPoint2007::class); } return $this->loadFile($pFilename); } /** - * Load PhpPresentation Serialized file - * - * @param string $pFilename - * @return \PhpOffice\PhpPresentation\PhpPresentation - * @throws \Exception + * Load PhpPresentation Serialized file. */ - protected function loadFile($pFilename) + protected function loadFile(string $pFilename): PhpPresentation { $this->oPhpPresentation = new PhpPresentation(); $this->oPhpPresentation->removeSlideByIndex(); - $this->oPhpPresentation->setAllMasterSlides(array()); + $this->oPhpPresentation->setAllMasterSlides([]); $this->filename = $pFilename; $this->oZip = new ZipArchive(); $this->oZip->open($this->filename); $docPropsCore = $this->oZip->getFromName('docProps/core.xml'); - if ($docPropsCore !== false) { + if (false !== $docPropsCore) { $this->loadDocumentProperties($docPropsCore); } $docPropsCustom = $this->oZip->getFromName('docProps/custom.xml'); - if ($docPropsCustom !== false) { + if (false !== $docPropsCustom) { $this->loadCustomProperties($docPropsCustom); } $pptViewProps = $this->oZip->getFromName('ppt/viewProps.xml'); - if ($pptViewProps !== false) { + if (false !== $pptViewProps) { $this->loadViewProperties($pptViewProps); } $pptPresentation = $this->oZip->getFromName('ppt/presentation.xml'); - if ($pptPresentation !== false) { + if (false !== $pptPresentation) { $this->loadDocumentLayout($pptPresentation); $this->loadSlides($pptPresentation); } + $pptPresProps = $this->oZip->getFromName('ppt/presProps.xml'); + if (false !== $pptPresProps) { + $this->loadPresentationProperties($pptPresentation); + } + return $this->oPhpPresentation; } /** - * Read Document Layout - * @param $sPart + * Read Document Layout. */ - protected function loadDocumentLayout($sPart) + protected function loadDocumentLayout(string $sPart): void { $xmlReader = new XMLReader(); + /* @phpstan-ignore-next-line */ if ($xmlReader->getDomFromString($sPart)) { foreach ($xmlReader->getElements('/p:presentation/p:sldSz') as $oElement) { - if (!($oElement instanceof \DOMElement)) { + if (!($oElement instanceof DOMElement)) { continue; } $type = $oElement->getAttribute('type'); $oLayout = $this->oPhpPresentation->getLayout(); - if ($type == DocumentLayout::LAYOUT_CUSTOM) { - $oLayout->setCX($oElement->getAttribute('cx')); - $oLayout->setCY($oElement->getAttribute('cy')); + if (DocumentLayout::LAYOUT_CUSTOM == $type) { + $oLayout->setCX((float) $oElement->getAttribute('cx')); + $oLayout->setCY((float) $oElement->getAttribute('cy')); } else { $oLayout->setDocumentLayout($type, true); if ($oElement->getAttribute('cx') < $oElement->getAttribute('cy')) { @@ -194,14 +206,14 @@ class PowerPoint2007 implements ReaderInterface } /** - * Read Document Properties - * @param string $sPart + * Read Document Properties. */ - protected function loadDocumentProperties($sPart) + protected function loadDocumentProperties(string $sPart): void { $xmlReader = new XMLReader(); + /* @phpstan-ignore-next-line */ if ($xmlReader->getDomFromString($sPart)) { - $arrayProperties = array( + $arrayProperties = [ '/cp:coreProperties/dc:creator' => 'setCreator', '/cp:coreProperties/cp:lastModifiedBy' => 'setLastModifiedBy', '/cp:coreProperties/dc:title' => 'setTitle', @@ -211,15 +223,14 @@ class PowerPoint2007 implements ReaderInterface '/cp:coreProperties/cp:category' => 'setCategory', '/cp:coreProperties/dcterms:created' => 'setCreated', '/cp:coreProperties/dcterms:modified' => 'setModified', - ); + ]; $oProperties = $this->oPhpPresentation->getDocumentProperties(); foreach ($arrayProperties as $path => $property) { $oElement = $xmlReader->getElement($path); - if ($oElement instanceof \DOMElement) { - if ($oElement->hasAttribute('xsi:type') && $oElement->getAttribute('xsi:type') == 'dcterms:W3CDTF') { - $oDateTime = new \DateTime(); - $oDateTime->createFromFormat(\DateTime::W3C, $oElement->nodeValue); - $oProperties->{$property}($oDateTime->getTimestamp()); + if ($oElement instanceof DOMElement) { + if ($oElement->hasAttribute('xsi:type') && 'dcterms:W3CDTF' == $oElement->getAttribute('xsi:type')) { + $dateTime = DateTime::createFromFormat(DateTime::W3C, $oElement->nodeValue); + $oProperties->{$property}($dateTime->getTimestamp()); } else { $oProperties->{$property}($oElement->nodeValue); } @@ -229,34 +240,99 @@ class PowerPoint2007 implements ReaderInterface } /** - * Read Custom Properties - * @param string $sPart + * Read Custom Properties. */ - protected function loadCustomProperties($sPart) + protected function loadCustomProperties(string $sPart): void { $xmlReader = new XMLReader(); $sPart = str_replace(' xmlns="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties"', '', $sPart); + /* @phpstan-ignore-next-line */ if ($xmlReader->getDomFromString($sPart)) { - $pathMarkAsFinal = '/Properties/property[@pid="2"][@fmtid="{D5CDD505-2E9C-101B-9397-08002B2CF9AE}"][@name="_MarkAsFinal"]/vt:bool'; - if (is_object($oElement = $xmlReader->getElement($pathMarkAsFinal))) { - if ($oElement->nodeValue == 'true') { - $this->oPhpPresentation->getPresentationProperties()->markAsFinal(true); + foreach ($xmlReader->getElements('/Properties/property[@fmtid="{D5CDD505-2E9C-101B-9397-08002B2CF9AE}"]') as $element) { + if (!$element->hasAttribute('name')) { + continue; + } + $propertyName = $element->getAttribute('name'); + if ($propertyName == '_MarkAsFinal') { + $attributeElement = $xmlReader->getElement('vt:bool', $element); + if ($attributeElement && 'true' == $attributeElement->nodeValue) { + $this->oPhpPresentation->getPresentationProperties()->markAsFinal(true); + } + } else { + $attributeTypeInt = $xmlReader->getElement('vt:i4', $element); + $attributeTypeFloat = $xmlReader->getElement('vt:r8', $element); + $attributeTypeBoolean = $xmlReader->getElement('vt:bool', $element); + $attributeTypeDate = $xmlReader->getElement('vt:filetime', $element); + $attributeTypeString = $xmlReader->getElement('vt:lpwstr', $element); + + if ($attributeTypeInt) { + $propertyType = DocumentProperties::PROPERTY_TYPE_INTEGER; + $propertyValue = (int) $attributeTypeInt->nodeValue; + } elseif ($attributeTypeFloat) { + $propertyType = DocumentProperties::PROPERTY_TYPE_FLOAT; + $propertyValue = (float) $attributeTypeFloat->nodeValue; + } elseif ($attributeTypeBoolean) { + $propertyType = DocumentProperties::PROPERTY_TYPE_BOOLEAN; + $propertyValue = $attributeTypeBoolean->nodeValue == 'true' ? true : false; + } elseif ($attributeTypeDate) { + $propertyType = DocumentProperties::PROPERTY_TYPE_DATE; + $propertyValue = strtotime($attributeTypeDate->nodeValue); + } else { + $propertyType = DocumentProperties::PROPERTY_TYPE_STRING; + $propertyValue = $attributeTypeString->nodeValue; + } + + $this->oPhpPresentation->getDocumentProperties()->setCustomProperty($propertyName, $propertyValue, $propertyType); } } } } /** - * Read View Properties - * @param string $sPart + * Read Presentation Properties */ - protected function loadViewProperties($sPart) + protected function loadPresentationProperties(string $sPart): void { $xmlReader = new XMLReader(); + /* @phpstan-ignore-next-line */ + if ($xmlReader->getDomFromString($sPart)) { + $element = $xmlReader->getElement('/p:presentationPr/p:showPr'); + if ($element instanceof DOMElement) { + if ($element->hasAttribute('loop')) { + $this->oPhpPresentation->getPresentationProperties()->setLoopContinuouslyUntilEsc( + (bool) $element->getAttribute('loop') + ); + } + if (null !== $xmlReader->getElement('p:present', $element)) { + $this->oPhpPresentation->getPresentationProperties()->setSlideshowType( + PresentationProperties::SLIDESHOW_TYPE_PRESENT + ); + } + if (null !== $xmlReader->getElement('p:browse', $element)) { + $this->oPhpPresentation->getPresentationProperties()->setSlideshowType( + PresentationProperties::SLIDESHOW_TYPE_BROWSE + ); + } + if (null !== $xmlReader->getElement('p:kiosk', $element)) { + $this->oPhpPresentation->getPresentationProperties()->setSlideshowType( + PresentationProperties::SLIDESHOW_TYPE_KIOSK + ); + } + } + } + } + + /** + * Read View Properties. + */ + protected function loadViewProperties(string $sPart): void + { + $xmlReader = new XMLReader(); + /* @phpstan-ignore-next-line */ if ($xmlReader->getDomFromString($sPart)) { $pathZoom = '/p:viewPr/p:slideViewPr/p:cSldViewPr/p:cViewPr/p:scale/a:sx'; $oElement = $xmlReader->getElement($pathZoom); - if ($oElement instanceof \DOMElement) { + if ($oElement instanceof DOMElement) { if ($oElement->hasAttribute('d') && $oElement->hasAttribute('n')) { $this->oPhpPresentation->getPresentationProperties()->setZoom($oElement->getAttribute('n') / $oElement->getAttribute('d')); } @@ -266,12 +342,11 @@ class PowerPoint2007 implements ReaderInterface /** * Extract all slides - * @param $sPart - * @throws \Exception */ - protected function loadSlides($sPart) + protected function loadSlides(string $sPart): void { $xmlReader = new XMLReader(); + /* @phpstan-ignore-next-line */ if ($xmlReader->getDomFromString($sPart)) { $fileRels = 'ppt/_rels/presentation.xml.rels'; $this->loadRels($fileRels); @@ -279,19 +354,19 @@ class PowerPoint2007 implements ReaderInterface $this->loadMasterSlides($xmlReader, $fileRels); // Continue with loading the slides foreach ($xmlReader->getElements('/p:presentation/p:sldIdLst/p:sldId') as $oElement) { - if (!($oElement instanceof \DOMElement)) { + if (!($oElement instanceof DOMElement)) { continue; } $rId = $oElement->getAttribute('r:id'); $pathSlide = isset($this->arrayRels[$fileRels][$rId]) ? $this->arrayRels[$fileRels][$rId]['Target'] : ''; if (!empty($pathSlide)) { $pptSlide = $this->oZip->getFromName('ppt/' . $pathSlide); - if ($pptSlide !== false) { + if (false !== $pptSlide) { $slideRels = 'ppt/slides/_rels/' . basename($pathSlide) . '.rels'; $this->loadRels($slideRels); $this->loadSlide($pptSlide, basename($pathSlide)); foreach ($this->arrayRels[$slideRels] as $rel) { - if ($rel['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide') { + if ('http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide' == $rel['Type']) { $this->loadSlideNote(basename($rel['Target']), $this->oPhpPresentation->getActiveSlide()); } } @@ -303,15 +378,12 @@ class PowerPoint2007 implements ReaderInterface /** * Extract all MasterSlides - * @param XMLReader $xmlReader - * @param string $fileRels - * @throws \Exception */ - protected function loadMasterSlides(XMLReader $xmlReader, $fileRels) + protected function loadMasterSlides(XMLReader $xmlReader, string $fileRels): void { // Get all the MasterSlide Id's from the presentation.xml file foreach ($xmlReader->getElements('/p:presentation/p:sldMasterIdLst/p:sldMasterId') as $oElement) { - if (!($oElement instanceof \DOMElement)) { + if (!($oElement instanceof DOMElement)) { continue; } $rId = $oElement->getAttribute('r:id'); @@ -320,7 +392,7 @@ class PowerPoint2007 implements ReaderInterface $this->arrayRels[$fileRels][$rId]['Target'] : ''; if (!empty($pathMasterSlide)) { $pptMasterSlide = $this->oZip->getFromName('ppt/' . $pathMasterSlide); - if ($pptMasterSlide !== false) { + if (false !== $pptMasterSlide) { $this->loadRels('ppt/slideMasters/_rels/' . basename($pathMasterSlide) . '.rels'); $this->loadMasterSlide($pptMasterSlide, basename($pathMasterSlide)); } @@ -330,13 +402,11 @@ class PowerPoint2007 implements ReaderInterface /** * Extract data from slide - * @param string $sPart - * @param string $baseFile - * @throws \Exception */ - protected function loadSlide($sPart, $baseFile) + protected function loadSlide(string $sPart, string $baseFile): void { $xmlReader = new XMLReader(); + /* @phpstan-ignore-next-line */ if ($xmlReader->getDomFromString($sPart)) { // Core $oSlide = $this->oPhpPresentation->createSlide(); @@ -345,9 +415,9 @@ class PowerPoint2007 implements ReaderInterface // Background $oElement = $xmlReader->getElement('/p:sld/p:cSld/p:bg/p:bgPr'); - if ($oElement instanceof \DOMElement) { + if ($oElement instanceof DOMElement) { $oElementColor = $xmlReader->getElement('a:solidFill/a:srgbClr', $oElement); - if ($oElementColor instanceof \DOMElement) { + if ($oElementColor instanceof DOMElement) { // Color $oColor = new Color(); $oColor->setRGB($oElementColor->hasAttribute('val') ? $oElementColor->getAttribute('val') : null); @@ -359,7 +429,7 @@ class PowerPoint2007 implements ReaderInterface $oSlide->setBackground($oBackground); } $oElementColor = $xmlReader->getElement('a:solidFill/a:schemeClr', $oElement); - if ($oElementColor instanceof \DOMElement) { + if ($oElementColor instanceof DOMElement) { // Color $oColor = new SchemeColor(); $oColor->setValue($oElementColor->hasAttribute('val') ? $oElementColor->getAttribute('val') : null); @@ -371,14 +441,14 @@ class PowerPoint2007 implements ReaderInterface $oSlide->setBackground($oBackground); } $oElementImage = $xmlReader->getElement('a:blipFill/a:blip', $oElement); - if ($oElementImage instanceof \DOMElement) { + if ($oElementImage instanceof DOMElement) { $relImg = $this->arrayRels['ppt/slides/_rels/' . $baseFile . '.rels'][$oElementImage->getAttribute('r:embed')]; if (is_array($relImg)) { // File $pathImage = 'ppt/slides/' . $relImg['Target']; $pathImage = explode('/', $pathImage); foreach ($pathImage as $key => $partPath) { - if ($partPath == '..') { + if ('..' == $partPath) { unset($pathImage[$key - 1]); unset($pathImage[$key]); } @@ -400,14 +470,12 @@ class PowerPoint2007 implements ReaderInterface // Shapes $arrayElements = $xmlReader->getElements('/p:sld/p:cSld/p:spTree/*'); - if ($arrayElements) { - $this->loadSlideShapes($oSlide, $arrayElements, $xmlReader); - } + $this->loadSlideShapes($oSlide, $arrayElements, $xmlReader); // Layout $oSlide = $this->oPhpPresentation->getActiveSlide(); foreach ($this->arrayRels['ppt/slides/_rels/' . $baseFile . '.rels'] as $valueRel) { - if ($valueRel['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout') { + if ('http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout' == $valueRel['Type']) { $layoutBasename = basename($valueRel['Target']); if (array_key_exists($layoutBasename, $this->arraySlideLayouts)) { $oSlide->setSlideLayout($this->arraySlideLayouts[$layoutBasename]); @@ -418,14 +486,10 @@ class PowerPoint2007 implements ReaderInterface } } - /** - * @param string $sPart - * @param string $baseFile - * @throws \Exception - */ - protected function loadMasterSlide($sPart, $baseFile) + protected function loadMasterSlide(string $sPart, string $baseFile): void { $xmlReader = new XMLReader(); + /* @phpstan-ignore-next-line */ if ($xmlReader->getDomFromString($sPart)) { // Core $oSlideMaster = $this->oPhpPresentation->createMasterSlide(); @@ -434,19 +498,18 @@ class PowerPoint2007 implements ReaderInterface // Background $oElement = $xmlReader->getElement('/p:sldMaster/p:cSld/p:bg'); - if ($oElement instanceof \DOMElement) { + if ($oElement instanceof DOMElement) { $this->loadSlideBackground($xmlReader, $oElement, $oSlideMaster); } // Shapes $arrayElements = $xmlReader->getElements('/p:sldMaster/p:cSld/p:spTree/*'); - if ($arrayElements) { - $this->loadSlideShapes($oSlideMaster, $arrayElements, $xmlReader); - } + $this->loadSlideShapes($oSlideMaster, $arrayElements, $xmlReader); + // Header & Footer // ColorMapping - $colorMap = array(); + $colorMap = []; $oElement = $xmlReader->getElement('/p:sldMaster/p:clrMap'); if ($oElement->hasAttributes()) { foreach ($oElement->attributes as $attr) { @@ -457,81 +520,80 @@ class PowerPoint2007 implements ReaderInterface // TextStyles $arrayElementTxStyles = $xmlReader->getElements('/p:sldMaster/p:txStyles/*'); - if ($arrayElementTxStyles) { - foreach ($arrayElementTxStyles as $oElementTxStyle) { - $arrayElementsLvl = $xmlReader->getElements('/p:sldMaster/p:txStyles/' . $oElementTxStyle->nodeName . '/*'); - foreach ($arrayElementsLvl as $oElementLvl) { - if (!($oElementLvl instanceof \DOMElement) || $oElementLvl->nodeName == 'a:extLst') { - continue; - } - $oRTParagraph = new Paragraph(); + foreach ($arrayElementTxStyles as $oElementTxStyle) { + $arrayElementsLvl = $xmlReader->getElements('/p:sldMaster/p:txStyles/' . $oElementTxStyle->nodeName . '/*'); + foreach ($arrayElementsLvl as $oElementLvl) { + if (!($oElementLvl instanceof DOMElement) || 'a:extLst' == $oElementLvl->nodeName) { + continue; + } + $oRTParagraph = new Paragraph(); - if ($oElementLvl->nodeName == 'a:defPPr') { - $level = 0; - } else { - $level = str_replace('a:lvl', '', $oElementLvl->nodeName); - $level = str_replace('pPr', '', $level); - } + if ('a:defPPr' == $oElementLvl->nodeName) { + $level = 0; + } else { + $level = str_replace('a:lvl', '', $oElementLvl->nodeName); + $level = str_replace('pPr', '', $level); + $level = intval($level); + } - if ($oElementLvl->hasAttribute('algn')) { - $oRTParagraph->getAlignment()->setHorizontal($oElementLvl->getAttribute('algn')); + if ($oElementLvl->hasAttribute('algn')) { + $oRTParagraph->getAlignment()->setHorizontal($oElementLvl->getAttribute('algn')); + } + if ($oElementLvl->hasAttribute('marL')) { + $val = (int) $oElementLvl->getAttribute('marL'); + $val = CommonDrawing::emuToPixels((int) $val); + $oRTParagraph->getAlignment()->setMarginLeft($val); + } + if ($oElementLvl->hasAttribute('marR')) { + $val = (int) $oElementLvl->getAttribute('marR'); + $val = CommonDrawing::emuToPixels((int) $val); + $oRTParagraph->getAlignment()->setMarginRight($val); + } + if ($oElementLvl->hasAttribute('indent')) { + $val = (int) $oElementLvl->getAttribute('indent'); + $val = CommonDrawing::emuToPixels((int) $val); + $oRTParagraph->getAlignment()->setIndent($val); + } + $oElementLvlDefRPR = $xmlReader->getElement('a:defRPr', $oElementLvl); + if ($oElementLvlDefRPR instanceof DOMElement) { + if ($oElementLvlDefRPR->hasAttribute('sz')) { + $oRTParagraph->getFont()->setSize($oElementLvlDefRPR->getAttribute('sz') / 100); } - if ($oElementLvl->hasAttribute('marL')) { - $val = $oElementLvl->getAttribute('marL'); - $val = CommonDrawing::emuToPixels($val); - $oRTParagraph->getAlignment()->setMarginLeft($val); + if ($oElementLvlDefRPR->hasAttribute('b') && 1 == $oElementLvlDefRPR->getAttribute('b')) { + $oRTParagraph->getFont()->setBold(true); } - if ($oElementLvl->hasAttribute('marR')) { - $val = $oElementLvl->getAttribute('marR'); - $val = CommonDrawing::emuToPixels($val); - $oRTParagraph->getAlignment()->setMarginRight($val); + if ($oElementLvlDefRPR->hasAttribute('i') && 1 == $oElementLvlDefRPR->getAttribute('i')) { + $oRTParagraph->getFont()->setItalic(true); } - if ($oElementLvl->hasAttribute('indent')) { - $val = $oElementLvl->getAttribute('indent'); - $val = CommonDrawing::emuToPixels($val); - $oRTParagraph->getAlignment()->setIndent($val); - } - $oElementLvlDefRPR = $xmlReader->getElement('a:defRPr', $oElementLvl); - if ($oElementLvlDefRPR instanceof \DOMElement) { - if ($oElementLvlDefRPR->hasAttribute('sz')) { - $oRTParagraph->getFont()->setSize($oElementLvlDefRPR->getAttribute('sz') / 100); - } - if ($oElementLvlDefRPR->hasAttribute('b') && $oElementLvlDefRPR->getAttribute('b') == 1) { - $oRTParagraph->getFont()->setBold(true); - } - if ($oElementLvlDefRPR->hasAttribute('i') && $oElementLvlDefRPR->getAttribute('i') == 1) { - $oRTParagraph->getFont()->setItalic(true); - } - } - $oElementSchemeColor = $xmlReader->getElement('a:defRPr/a:solidFill/a:schemeClr', $oElementLvl); - if ($oElementSchemeColor instanceof \DOMElement) { - if ($oElementSchemeColor->hasAttribute('val')) { - $oSchemeColor = new SchemeColor(); - $oSchemeColor->setValue($oElementSchemeColor->getAttribute('val')); - $oRTParagraph->getFont()->setColor($oSchemeColor); - } + } + $oElementSchemeColor = $xmlReader->getElement('a:defRPr/a:solidFill/a:schemeClr', $oElementLvl); + if ($oElementSchemeColor instanceof DOMElement) { + if ($oElementSchemeColor->hasAttribute('val')) { + $oSchemeColor = new SchemeColor(); + $oSchemeColor->setValue($oElementSchemeColor->getAttribute('val')); + $oRTParagraph->getFont()->setColor($oSchemeColor); } + } - switch ($oElementTxStyle->nodeName) { - case 'p:bodyStyle': - $oSlideMaster->getTextStyles()->setBodyStyleAtLvl($oRTParagraph, $level); - break; - case 'p:otherStyle': - $oSlideMaster->getTextStyles()->setOtherStyleAtLvl($oRTParagraph, $level); - break; - case 'p:titleStyle': - $oSlideMaster->getTextStyles()->setTitleStyleAtLvl($oRTParagraph, $level); - break; - } + switch ($oElementTxStyle->nodeName) { + case 'p:bodyStyle': + $oSlideMaster->getTextStyles()->setBodyStyleAtLvl($oRTParagraph, $level); + break; + case 'p:otherStyle': + $oSlideMaster->getTextStyles()->setOtherStyleAtLvl($oRTParagraph, $level); + break; + case 'p:titleStyle': + $oSlideMaster->getTextStyles()->setTitleStyleAtLvl($oRTParagraph, $level); + break; } } } // Load the theme foreach ($this->arrayRels[$oSlideMaster->getRelsIndex()] as $arrayRel) { - if ($arrayRel['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme') { + if ('http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme' == $arrayRel['Type']) { $pptTheme = $this->oZip->getFromName('ppt/' . substr($arrayRel['Target'], strrpos($arrayRel['Target'], '../') + 3)); - if ($pptTheme !== false) { + if (false !== $pptTheme) { $this->loadTheme($pptTheme, $oSlideMaster); } break; @@ -540,7 +602,7 @@ class PowerPoint2007 implements ReaderInterface // Load the Layoutslide foreach ($xmlReader->getElements('/p:sldMaster/p:sldLayoutIdLst/p:sldLayoutId') as $oElement) { - if (!($oElement instanceof \DOMElement)) { + if (!($oElement instanceof DOMElement)) { continue; } $rId = $oElement->getAttribute('r:id'); @@ -549,7 +611,7 @@ class PowerPoint2007 implements ReaderInterface $this->arrayRels[$oSlideMaster->getRelsIndex()][$rId]['Target'] : ''; if (!empty($pathLayoutSlide)) { $pptLayoutSlide = $this->oZip->getFromName('ppt/' . substr($pathLayoutSlide, strrpos($pathLayoutSlide, '../') + 3)); - if ($pptLayoutSlide !== false) { + if (false !== $pptLayoutSlide) { $this->loadRels('ppt/slideLayouts/_rels/' . basename($pathLayoutSlide) . '.rels'); $oSlideMaster->addSlideLayout( $this->loadLayoutSlide($pptLayoutSlide, basename($pathLayoutSlide), $oSlideMaster) @@ -560,16 +622,10 @@ class PowerPoint2007 implements ReaderInterface } } - /** - * @param string $sPart - * @param string $baseFile - * @param SlideMaster $oSlideMaster - * @return SlideLayout|null - * @throws \Exception - */ - protected function loadLayoutSlide($sPart, $baseFile, SlideMaster $oSlideMaster) + protected function loadLayoutSlide(string $sPart, string $baseFile, SlideMaster $oSlideMaster): ?SlideLayout { $xmlReader = new XMLReader(); + /* @phpstan-ignore-next-line */ if ($xmlReader->getDomFromString($sPart)) { // Core $oSlideLayout = new SlideLayout($oSlideMaster); @@ -577,20 +633,20 @@ class PowerPoint2007 implements ReaderInterface // Name $oElement = $xmlReader->getElement('/p:sldLayout/p:cSld'); - if ($oElement instanceof \DOMElement && $oElement->hasAttribute('name')) { + if ($oElement instanceof DOMElement && $oElement->hasAttribute('name')) { $oSlideLayout->setLayoutName($oElement->getAttribute('name')); } // Background $oElement = $xmlReader->getElement('/p:sldLayout/p:cSld/p:bg'); - if ($oElement instanceof \DOMElement) { + if ($oElement instanceof DOMElement) { $this->loadSlideBackground($xmlReader, $oElement, $oSlideLayout); } // ColorMapping $oElement = $xmlReader->getElement('/p:sldLayout/p:clrMapOvr/a:overrideClrMapping'); - if ($oElement instanceof \DOMElement && $oElement->hasAttributes()) { - $colorMap = array(); + if ($oElement instanceof DOMElement && $oElement->hasAttributes()) { + $colorMap = []; foreach ($oElement->attributes as $attr) { $colorMap[$attr->nodeName] = $attr->nodeValue; } @@ -599,30 +655,27 @@ class PowerPoint2007 implements ReaderInterface // Shapes $oElements = $xmlReader->getElements('/p:sldLayout/p:cSld/p:spTree/*'); - if ($oElements) { - $this->loadSlideShapes($oSlideLayout, $oElements, $xmlReader); - } + $this->loadSlideShapes($oSlideLayout, $oElements, $xmlReader); $this->arraySlideLayouts[$baseFile] = &$oSlideLayout; + return $oSlideLayout; } + /* @phpstan-ignore-next-line */ return null; } - /** - * @param string $sPart - * @param SlideMaster $oSlideMaster - */ - protected function loadTheme($sPart, SlideMaster $oSlideMaster) + protected function loadTheme(string $sPart, SlideMaster $oSlideMaster): void { $xmlReader = new XMLReader(); + /* @phpstan-ignore-next-line */ if ($xmlReader->getDomFromString($sPart)) { $oElements = $xmlReader->getElements('/a:theme/a:themeElements/a:clrScheme/*'); - if ($oElements) { - foreach ($oElements as $oElement) { + foreach ($oElements as $oElement) { + if ($oElement instanceof DOMElement) { $oSchemeColor = new SchemeColor(); $oSchemeColor->setValue(str_replace('a:', '', $oElement->tagName)); $colorElement = $xmlReader->getElement('*', $oElement); - if ($colorElement instanceof \DOMElement) { + if ($colorElement instanceof DOMElement) { if ($colorElement->hasAttribute('lastClr')) { $oSchemeColor->setRGB($colorElement->getAttribute('lastClr')); } elseif ($colorElement->hasAttribute('val')) { @@ -635,17 +688,11 @@ class PowerPoint2007 implements ReaderInterface } } - /** - * @param XMLReader $xmlReader - * @param \DOMElement $oElement - * @param AbstractSlide $oSlide - * @throws \Exception - */ - protected function loadSlideBackground(XMLReader $xmlReader, \DOMElement $oElement, AbstractSlide $oSlide) + protected function loadSlideBackground(XMLReader $xmlReader, DOMElement $oElement, AbstractSlide $oSlide): void { // Background color $oElementColor = $xmlReader->getElement('p:bgPr/a:solidFill/a:srgbClr', $oElement); - if ($oElementColor instanceof \DOMElement) { + if ($oElementColor instanceof DOMElement) { // Color $oColor = new Color(); $oColor->setRGB($oElementColor->hasAttribute('val') ? $oElementColor->getAttribute('val') : null); @@ -658,7 +705,7 @@ class PowerPoint2007 implements ReaderInterface // Background scheme color $oElementSchemeColor = $xmlReader->getElement('p:bgRef/a:schemeClr', $oElement); - if ($oElementSchemeColor instanceof \DOMElement) { + if ($oElementSchemeColor instanceof DOMElement) { // Color $oColor = new SchemeColor(); $oColor->setValue($oElementSchemeColor->hasAttribute('val') ? $oElementSchemeColor->getAttribute('val') : null); @@ -671,14 +718,14 @@ class PowerPoint2007 implements ReaderInterface // Background image $oElementImage = $xmlReader->getElement('p:bgPr/a:blipFill/a:blip', $oElement); - if ($oElementImage instanceof \DOMElement) { + if ($oElementImage instanceof DOMElement) { $relImg = $this->arrayRels[$oSlide->getRelsIndex()][$oElementImage->getAttribute('r:embed')]; if (is_array($relImg)) { // File $pathImage = 'ppt/slides/' . $relImg['Target']; $pathImage = explode('/', $pathImage); foreach ($pathImage as $key => $partPath) { - if ($partPath == '..') { + if ('..' == $partPath) { unset($pathImage[$key - 1]); unset($pathImage[$key]); } @@ -697,63 +744,53 @@ class PowerPoint2007 implements ReaderInterface } } - /** - * @param string $baseFile - * @param Slide $oSlide - * @throws \Exception - */ - protected function loadSlideNote($baseFile, Slide $oSlide) + protected function loadSlideNote(string $baseFile, Slide $oSlide): void { $sPart = $this->oZip->getFromName('ppt/notesSlides/' . $baseFile); $xmlReader = new XMLReader(); + /* @phpstan-ignore-next-line */ if ($xmlReader->getDomFromString($sPart)) { $oNote = $oSlide->getNote(); $arrayElements = $xmlReader->getElements('/p:notes/p:cSld/p:spTree/*'); - if ($arrayElements) { - $this->loadSlideShapes($oNote, $arrayElements, $xmlReader); - } + $this->loadSlideShapes($oNote, $arrayElements, $xmlReader); } } - /** - * @param XMLReader $document - * @param \DOMElement $node - * @param AbstractSlide $oSlide - * @throws \Exception - */ - protected function loadShapeDrawing(XMLReader $document, \DOMElement $node, AbstractSlide $oSlide) + protected function loadShapeDrawing(XMLReader $document, DOMElement $node, AbstractSlide $oSlide): void { // Core - $oShape = new Gd(); + $document->registerNamespace('asvg', 'http://schemas.microsoft.com/office/drawing/2016/SVG/main'); + if ($document->getElement('p:blipFill/a:blip/a:extLst/a:ext/asvg:svgBlip', $node)) { + $oShape = new Base64(); + } else { + $oShape = new Gd(); + } $oShape->getShadow()->setVisible(false); // Variables $fileRels = $oSlide->getRelsIndex(); $oElement = $document->getElement('p:nvPicPr/p:cNvPr', $node); - if ($oElement instanceof \DOMElement) { + if ($oElement instanceof DOMElement) { $oShape->setName($oElement->hasAttribute('name') ? $oElement->getAttribute('name') : ''); $oShape->setDescription($oElement->hasAttribute('descr') ? $oElement->getAttribute('descr') : ''); // Hyperlink $oElementHlinkClick = $document->getElement('a:hlinkClick', $oElement); if (is_object($oElementHlinkClick)) { - if ($oElementHlinkClick->hasAttribute('tooltip')) { - $oShape->getHyperlink()->setTooltip($oElementHlinkClick->getAttribute('tooltip')); - } - if ($oElementHlinkClick->hasAttribute('r:id') && isset($this->arrayRels[$fileRels][$oElementHlinkClick->getAttribute('r:id')]['Target'])) { - $oShape->getHyperlink()->setUrl($this->arrayRels[$fileRels][$oElementHlinkClick->getAttribute('r:id')]['Target']); - } + $oShape->setHyperlink( + $this->loadHyperlink($document, $oElementHlinkClick, $oShape->getHyperlink()) + ); } } $oElement = $document->getElement('p:blipFill/a:blip', $node); - if ($oElement instanceof \DOMElement) { + if ($oElement instanceof DOMElement) { if ($oElement->hasAttribute('r:embed') && isset($this->arrayRels[$fileRels][$oElement->getAttribute('r:embed')]['Target'])) { $pathImage = 'ppt/slides/' . $this->arrayRels[$fileRels][$oElement->getAttribute('r:embed')]['Target']; $pathImage = explode('/', $pathImage); foreach ($pathImage as $key => $partPath) { - if ($partPath == '..') { + if ('..' == $partPath) { unset($pathImage[$key - 1]); unset($pathImage[$key]); } @@ -761,61 +798,65 @@ class PowerPoint2007 implements ReaderInterface $pathImage = implode('/', $pathImage); $imageFile = $this->oZip->getFromName($pathImage); if (!empty($imageFile)) { - $info = getimagesizefromstring($imageFile); - $oShape->setMimeType($info['mime']); - $oShape->setRenderingFunction(str_replace('/', '', $info['mime'])); - $oShape->setImageResource(imagecreatefromstring($imageFile)); + if ($oShape instanceof Gd) { + $info = getimagesizefromstring($imageFile); + $oShape->setMimeType($info['mime']); + $oShape->setRenderingFunction(str_replace('/', '', $info['mime'])); + $oShape->setImageResource(imagecreatefromstring($imageFile)); + } elseif ($oShape instanceof Base64) { + $oShape->setData('data:image/svg+xml;base64,' . base64_encode($imageFile)); + } } } } $oElement = $document->getElement('p:spPr', $node); - if ($oElement instanceof \DOMElement) { + if ($oElement instanceof DOMElement) { $oFill = $this->loadStyleFill($document, $oElement); $oShape->setFill($oFill); } $oElement = $document->getElement('p:spPr/a:xfrm', $node); - if ($oElement instanceof \DOMElement) { + if ($oElement instanceof DOMElement) { if ($oElement->hasAttribute('rot')) { - $oShape->setRotation(CommonDrawing::angleToDegrees($oElement->getAttribute('rot'))); + $oShape->setRotation((int) CommonDrawing::angleToDegrees((int) $oElement->getAttribute('rot'))); } } $oElement = $document->getElement('p:spPr/a:xfrm/a:off', $node); - if ($oElement instanceof \DOMElement) { + if ($oElement instanceof DOMElement) { if ($oElement->hasAttribute('x')) { - $oShape->setOffsetX(CommonDrawing::emuToPixels($oElement->getAttribute('x'))); + $oShape->setOffsetX(CommonDrawing::emuToPixels((int) $oElement->getAttribute('x'))); } if ($oElement->hasAttribute('y')) { - $oShape->setOffsetY(CommonDrawing::emuToPixels($oElement->getAttribute('y'))); + $oShape->setOffsetY(CommonDrawing::emuToPixels((int) $oElement->getAttribute('y'))); } } $oElement = $document->getElement('p:spPr/a:xfrm/a:ext', $node); - if ($oElement instanceof \DOMElement) { + if ($oElement instanceof DOMElement) { if ($oElement->hasAttribute('cx')) { - $oShape->setWidth(CommonDrawing::emuToPixels($oElement->getAttribute('cx'))); + $oShape->setWidth(CommonDrawing::emuToPixels((int) $oElement->getAttribute('cx'))); } if ($oElement->hasAttribute('cy')) { - $oShape->setHeight(CommonDrawing::emuToPixels($oElement->getAttribute('cy'))); + $oShape->setHeight(CommonDrawing::emuToPixels((int) $oElement->getAttribute('cy'))); } } $oElement = $document->getElement('p:spPr/a:effectLst', $node); - if ($oElement instanceof \DOMElement) { + if ($oElement instanceof DOMElement) { $oShape->getShadow()->setVisible(true); $oSubElement = $document->getElement('a:outerShdw', $oElement); - if ($oSubElement instanceof \DOMElement) { + if ($oSubElement instanceof DOMElement) { if ($oSubElement->hasAttribute('blurRad')) { - $oShape->getShadow()->setBlurRadius(CommonDrawing::emuToPixels($oSubElement->getAttribute('blurRad'))); + $oShape->getShadow()->setBlurRadius(CommonDrawing::emuToPixels((int) $oSubElement->getAttribute('blurRad'))); } if ($oSubElement->hasAttribute('dist')) { - $oShape->getShadow()->setDistance(CommonDrawing::emuToPixels($oSubElement->getAttribute('dist'))); + $oShape->getShadow()->setDistance(CommonDrawing::emuToPixels((int) $oSubElement->getAttribute('dist'))); } if ($oSubElement->hasAttribute('dir')) { - $oShape->getShadow()->setDirection(CommonDrawing::angleToDegrees($oSubElement->getAttribute('dir'))); + $oShape->getShadow()->setDirection((int) CommonDrawing::angleToDegrees((int) $oSubElement->getAttribute('dir'))); } if ($oSubElement->hasAttribute('algn')) { $oShape->getShadow()->setAlignment($oSubElement->getAttribute('algn')); @@ -823,7 +864,7 @@ class PowerPoint2007 implements ReaderInterface } $oSubElement = $document->getElement('a:outerShdw/a:srgbClr', $oElement); - if ($oSubElement instanceof \DOMElement) { + if ($oSubElement instanceof DOMElement) { if ($oSubElement->hasAttribute('val')) { $oColor = new Color(); $oColor->setRGB($oSubElement->getAttribute('val')); @@ -832,9 +873,9 @@ class PowerPoint2007 implements ReaderInterface } $oSubElement = $document->getElement('a:outerShdw/a:srgbClr/a:alpha', $oElement); - if ($oSubElement instanceof \DOMElement) { + if ($oSubElement instanceof DOMElement) { if ($oSubElement->hasAttribute('val')) { - $oShape->getShadow()->setAlpha((int)$oSubElement->getAttribute('val') / 1000); + $oShape->getShadow()->setAlpha((int) $oSubElement->getAttribute('val') / 1000); } } } @@ -842,52 +883,46 @@ class PowerPoint2007 implements ReaderInterface $oSlide->addShape($oShape); } - /** - * @param XMLReader $document - * @param \DOMElement $node - * @param AbstractSlide $oSlide - * @throws \Exception - */ - protected function loadShapeRichText(XMLReader $document, \DOMElement $node, $oSlide) + protected function loadShapeRichText(XMLReader $document, DOMElement $node, $oSlide): void { - if (!$document->elementExists('p:txBody/a:p/a:r', $node)) { + if (!$document->elementExists('p:txBody/a:p/a:r', $node) || !$oSlide instanceof AbstractSlide) { return; } // Core $oShape = $oSlide->createRichTextShape(); - $oShape->setParagraphs(array()); + $oShape->setParagraphs([]); // Variables if ($oSlide instanceof AbstractSlide) { $this->fileRels = $oSlide->getRelsIndex(); } $oElement = $document->getElement('p:spPr/a:xfrm', $node); - if ($oElement instanceof \DOMElement && $oElement->hasAttribute('rot')) { - $oShape->setRotation(CommonDrawing::angleToDegrees($oElement->getAttribute('rot'))); + if ($oElement instanceof DOMElement && $oElement->hasAttribute('rot')) { + $oShape->setRotation((int) CommonDrawing::angleToDegrees((int) $oElement->getAttribute('rot'))); } $oElement = $document->getElement('p:spPr/a:xfrm/a:off', $node); - if ($oElement instanceof \DOMElement) { + if ($oElement instanceof DOMElement) { if ($oElement->hasAttribute('x')) { - $oShape->setOffsetX(CommonDrawing::emuToPixels($oElement->getAttribute('x'))); + $oShape->setOffsetX(CommonDrawing::emuToPixels((int) $oElement->getAttribute('x'))); } if ($oElement->hasAttribute('y')) { - $oShape->setOffsetY(CommonDrawing::emuToPixels($oElement->getAttribute('y'))); + $oShape->setOffsetY(CommonDrawing::emuToPixels((int) $oElement->getAttribute('y'))); } } $oElement = $document->getElement('p:spPr/a:xfrm/a:ext', $node); - if ($oElement instanceof \DOMElement) { + if ($oElement instanceof DOMElement) { if ($oElement->hasAttribute('cx')) { - $oShape->setWidth(CommonDrawing::emuToPixels($oElement->getAttribute('cx'))); + $oShape->setWidth(CommonDrawing::emuToPixels((int) $oElement->getAttribute('cx'))); } if ($oElement->hasAttribute('cy')) { - $oShape->setHeight(CommonDrawing::emuToPixels($oElement->getAttribute('cy'))); + $oShape->setHeight(CommonDrawing::emuToPixels((int) $oElement->getAttribute('cy'))); } } $oElement = $document->getElement('p:nvSpPr/p:nvPr/p:ph', $node); - if ($oElement instanceof \DOMElement) { + if ($oElement instanceof DOMElement) { if ($oElement->hasAttribute('type')) { $placeholder = new Placeholder($oElement->getAttribute('type')); $oShape->setPlaceHolder($placeholder); @@ -896,7 +931,9 @@ class PowerPoint2007 implements ReaderInterface $arrayElements = $document->getElements('p:txBody/a:p', $node); foreach ($arrayElements as $oElement) { - $this->loadParagraph($document, $oElement, $oShape); + if ($oElement instanceof DOMElement) { + $this->loadParagraph($document, $oElement, $oShape); + } } if (count($oShape->getParagraphs()) > 0) { @@ -904,20 +941,14 @@ class PowerPoint2007 implements ReaderInterface } } - /** - * @param XMLReader $document - * @param \DOMElement $node - * @param AbstractSlide $oSlide - * @throws \Exception - */ - protected function loadShapeTable(XMLReader $document, \DOMElement $node, AbstractSlide $oSlide) + protected function loadShapeTable(XMLReader $document, DOMElement $node, AbstractSlide $oSlide): void { $this->fileRels = $oSlide->getRelsIndex(); $oShape = $oSlide->createTableShape(); $oElement = $document->getElement('p:cNvPr', $node); - if ($oElement instanceof \DOMElement) { + if ($oElement instanceof DOMElement) { if ($oElement->hasAttribute('name')) { $oShape->setName($oElement->getAttribute('name')); } @@ -927,22 +958,22 @@ class PowerPoint2007 implements ReaderInterface } $oElement = $document->getElement('p:xfrm/a:off', $node); - if ($oElement instanceof \DOMElement) { + if ($oElement instanceof DOMElement) { if ($oElement->hasAttribute('x')) { - $oShape->setOffsetX(CommonDrawing::emuToPixels($oElement->getAttribute('x'))); + $oShape->setOffsetX(CommonDrawing::emuToPixels((int) $oElement->getAttribute('x'))); } if ($oElement->hasAttribute('y')) { - $oShape->setOffsetY(CommonDrawing::emuToPixels($oElement->getAttribute('y'))); + $oShape->setOffsetY(CommonDrawing::emuToPixels((int) $oElement->getAttribute('y'))); } } $oElement = $document->getElement('p:xfrm/a:ext', $node); - if ($oElement instanceof \DOMElement) { + if ($oElement instanceof DOMElement) { if ($oElement->hasAttribute('cx')) { - $oShape->setWidth(CommonDrawing::emuToPixels($oElement->getAttribute('cx'))); + $oShape->setWidth(CommonDrawing::emuToPixels((int) $oElement->getAttribute('cx'))); } if ($oElement->hasAttribute('cy')) { - $oShape->setHeight(CommonDrawing::emuToPixels($oElement->getAttribute('cy'))); + $oShape->setHeight(CommonDrawing::emuToPixels((int) $oElement->getAttribute('cy'))); } } @@ -950,43 +981,46 @@ class PowerPoint2007 implements ReaderInterface $oShape->setNumColumns($arrayElements->length); $oShape->createRow(); foreach ($arrayElements as $key => $oElement) { - if ($oElement instanceof \DOMElement && $oElement->getAttribute('w')) { - $oShape->getRow(0)->getCell($key)->setWidth(CommonDrawing::emuToPixels($oElement->getAttribute('w'))); + if ($oElement instanceof DOMElement && $oElement->getAttribute('w')) { + $oShape->getRow(0)->getCell($key)->setWidth(CommonDrawing::emuToPixels((int) $oElement->getAttribute('w'))); } } $arrayElements = $document->getElements('a:graphic/a:graphicData/a:tbl/a:tr', $node); foreach ($arrayElements as $keyRow => $oElementRow) { - if (!($oElementRow instanceof \DOMElement)) { + if (!($oElementRow instanceof DOMElement)) { continue; } - $oRow = $oShape->getRow($keyRow, true); - if (is_null($oRow)) { + if ($oShape->hasRow($keyRow)) { + $oRow = $oShape->getRow($keyRow); + } else { $oRow = $oShape->createRow(); } if ($oElementRow->hasAttribute('h')) { - $oRow->setHeight(CommonDrawing::emuToPixels($oElementRow->getAttribute('h'))); + $oRow->setHeight(CommonDrawing::emuToPixels((int) $oElementRow->getAttribute('h'))); } $arrayElementsCell = $document->getElements('a:tc', $oElementRow); foreach ($arrayElementsCell as $keyCell => $oElementCell) { - if (!($oElementCell instanceof \DOMElement)) { + if (!($oElementCell instanceof DOMElement)) { continue; } $oCell = $oRow->getCell($keyCell); - $oCell->setParagraphs(array()); + $oCell->setParagraphs([]); if ($oElementCell->hasAttribute('gridSpan')) { - $oCell->setColSpan($oElementCell->getAttribute('gridSpan')); + $oCell->setColSpan((int) $oElementCell->getAttribute('gridSpan')); } if ($oElementCell->hasAttribute('rowSpan')) { - $oCell->setRowSpan($oElementCell->getAttribute('rowSpan')); + $oCell->setRowSpan((int) $oElementCell->getAttribute('rowSpan')); } foreach ($document->getElements('a:txBody/a:p', $oElementCell) as $oElementPara) { - $this->loadParagraph($document, $oElementPara, $oCell); + if ($oElementPara instanceof DOMElement) { + $this->loadParagraph($document, $oElementPara, $oCell); + } } $oElementTcPr = $document->getElement('a:tcPr', $oElementCell); - if ($oElementTcPr instanceof \DOMElement) { + if ($oElementTcPr instanceof DOMElement) { $numParagraphs = count($oCell->getParagraphs()); if ($numParagraphs > 0) { if ($oElementTcPr->hasAttribute('vert')) { @@ -996,16 +1030,16 @@ class PowerPoint2007 implements ReaderInterface $oCell->getParagraph(0)->getAlignment()->setVertical($oElementTcPr->getAttribute('anchor')); } if ($oElementTcPr->hasAttribute('marB')) { - $oCell->getParagraph(0)->getAlignment()->setMarginBottom($oElementTcPr->getAttribute('marB')); + $oCell->getParagraph(0)->getAlignment()->setMarginBottom((int) $oElementTcPr->getAttribute('marB')); } if ($oElementTcPr->hasAttribute('marL')) { - $oCell->getParagraph(0)->getAlignment()->setMarginLeft($oElementTcPr->getAttribute('marL')); + $oCell->getParagraph(0)->getAlignment()->setMarginLeft((int) $oElementTcPr->getAttribute('marL')); } if ($oElementTcPr->hasAttribute('marR')) { - $oCell->getParagraph(0)->getAlignment()->setMarginRight($oElementTcPr->getAttribute('marR')); + $oCell->getParagraph(0)->getAlignment()->setMarginRight((int) $oElementTcPr->getAttribute('marR')); } if ($oElementTcPr->hasAttribute('marT')) { - $oCell->getParagraph(0)->getAlignment()->setMarginTop($oElementTcPr->getAttribute('marT')); + $oCell->getParagraph(0)->getAlignment()->setMarginTop((int) $oElementTcPr->getAttribute('marT')); } } @@ -1016,27 +1050,27 @@ class PowerPoint2007 implements ReaderInterface $oBorders = new Borders(); $oElementBorderL = $document->getElement('a:lnL', $oElementTcPr); - if ($oElementBorderL instanceof \DOMElement) { + if ($oElementBorderL instanceof DOMElement) { $this->loadStyleBorder($document, $oElementBorderL, $oBorders->getLeft()); } $oElementBorderR = $document->getElement('a:lnR', $oElementTcPr); - if ($oElementBorderR instanceof \DOMElement) { + if ($oElementBorderR instanceof DOMElement) { $this->loadStyleBorder($document, $oElementBorderR, $oBorders->getRight()); } $oElementBorderT = $document->getElement('a:lnT', $oElementTcPr); - if ($oElementBorderT instanceof \DOMElement) { + if ($oElementBorderT instanceof DOMElement) { $this->loadStyleBorder($document, $oElementBorderT, $oBorders->getTop()); } $oElementBorderB = $document->getElement('a:lnB', $oElementTcPr); - if ($oElementBorderB instanceof \DOMElement) { + if ($oElementBorderB instanceof DOMElement) { $this->loadStyleBorder($document, $oElementBorderB, $oBorders->getBottom()); } $oElementBorderDiagDown = $document->getElement('a:lnTlToBr', $oElementTcPr); - if ($oElementBorderDiagDown instanceof \DOMElement) { + if ($oElementBorderDiagDown instanceof DOMElement) { $this->loadStyleBorder($document, $oElementBorderDiagDown, $oBorders->getDiagonalDown()); } $oElementBorderDiagUp = $document->getElement('a:lnBlToTr', $oElementTcPr); - if ($oElementBorderDiagUp instanceof \DOMElement) { + if ($oElementBorderDiagUp instanceof DOMElement) { $this->loadStyleBorder($document, $oElementBorderDiagUp, $oBorders->getDiagonalUp()); } $oCell->setBorders($oBorders); @@ -1046,19 +1080,16 @@ class PowerPoint2007 implements ReaderInterface } /** - * @param XMLReader $document - * @param \DOMElement $oElement * @param Cell|RichText $oShape - * @throws \Exception */ - protected function loadParagraph(XMLReader $document, \DOMElement $oElement, $oShape) + protected function loadParagraph(XMLReader $document, DOMElement $oElement, $oShape): void { // Core $oParagraph = $oShape->createParagraph(); - $oParagraph->setRichTextElements(array()); + $oParagraph->setRichTextElements([]); $oSubElement = $document->getElement('a:pPr', $oElement); - if ($oSubElement instanceof \DOMElement) { + if ($oSubElement instanceof DOMElement) { if ($oSubElement->hasAttribute('algn')) { $oParagraph->getAlignment()->setHorizontal($oSubElement->getAttribute('algn')); } @@ -1066,51 +1097,73 @@ class PowerPoint2007 implements ReaderInterface $oParagraph->getAlignment()->setVertical($oSubElement->getAttribute('fontAlgn')); } if ($oSubElement->hasAttribute('marL')) { - $oParagraph->getAlignment()->setMarginLeft(CommonDrawing::emuToPixels($oSubElement->getAttribute('marL'))); + $oParagraph->getAlignment()->setMarginLeft(CommonDrawing::emuToPixels((int) $oSubElement->getAttribute('marL'))); } if ($oSubElement->hasAttribute('marR')) { - $oParagraph->getAlignment()->setMarginRight(CommonDrawing::emuToPixels($oSubElement->getAttribute('marR'))); + $oParagraph->getAlignment()->setMarginRight(CommonDrawing::emuToPixels((int) $oSubElement->getAttribute('marR'))); } if ($oSubElement->hasAttribute('indent')) { - $oParagraph->getAlignment()->setIndent(CommonDrawing::emuToPixels($oSubElement->getAttribute('indent'))); + $oParagraph->getAlignment()->setIndent(CommonDrawing::emuToPixels((int) $oSubElement->getAttribute('indent'))); } if ($oSubElement->hasAttribute('lvl')) { - $oParagraph->getAlignment()->setLevel($oSubElement->getAttribute('lvl')); + $oParagraph->getAlignment()->setLevel((int) $oSubElement->getAttribute('lvl')); + } + if ($oSubElement->hasAttribute('rtl')) { + $oParagraph->getAlignment()->setIsRTL((bool) $oSubElement->getAttribute('rtl')); + } + + $oElementLineSpacingPoints = $document->getElement('a:lnSpc/a:spcPts', $oSubElement); + if ($oElementLineSpacingPoints instanceof DOMElement) { + $oParagraph->setLineSpacingMode(Paragraph::LINE_SPACING_MODE_POINT); + $oParagraph->setLineSpacing($oElementLineSpacingPoints->getAttribute('val') / 100); + } + $oElementLineSpacingPercent = $document->getElement('a:lnSpc/a:spcPct', $oSubElement); + if ($oElementLineSpacingPercent instanceof DOMElement) { + $oParagraph->setLineSpacingMode(Paragraph::LINE_SPACING_MODE_PERCENT); + $oParagraph->setLineSpacing($oElementLineSpacingPercent->getAttribute('val') / 1000); + } + $oElementSpacingBefore = $document->getElement('a:spcBef/a:spcPts', $oSubElement); + if ($oElementSpacingBefore instanceof DOMElement) { + $oParagraph->setSpacingBefore($oElementSpacingBefore->getAttribute('val') / 100); + } + $oElementSpacingAfter = $document->getElement('a:spcAft/a:spcPts', $oSubElement); + if ($oElementSpacingAfter instanceof DOMElement) { + $oParagraph->setSpacingAfter($oElementSpacingAfter->getAttribute('val') / 100); } $oParagraph->getBulletStyle()->setBulletType(Bullet::TYPE_NONE); $oElementBuFont = $document->getElement('a:buFont', $oSubElement); - if ($oElementBuFont instanceof \DOMElement) { + if ($oElementBuFont instanceof DOMElement) { if ($oElementBuFont->hasAttribute('typeface')) { $oParagraph->getBulletStyle()->setBulletFont($oElementBuFont->getAttribute('typeface')); } } $oElementBuChar = $document->getElement('a:buChar', $oSubElement); - if ($oElementBuChar instanceof \DOMElement) { + if ($oElementBuChar instanceof DOMElement) { $oParagraph->getBulletStyle()->setBulletType(Bullet::TYPE_BULLET); if ($oElementBuChar->hasAttribute('char')) { $oParagraph->getBulletStyle()->setBulletChar($oElementBuChar->getAttribute('char')); } } $oElementBuAutoNum = $document->getElement('a:buAutoNum', $oSubElement); - if ($oElementBuAutoNum instanceof \DOMElement) { + if ($oElementBuAutoNum instanceof DOMElement) { $oParagraph->getBulletStyle()->setBulletType(Bullet::TYPE_NUMERIC); if ($oElementBuAutoNum->hasAttribute('type')) { $oParagraph->getBulletStyle()->setBulletNumericStyle($oElementBuAutoNum->getAttribute('type')); } - if ($oElementBuAutoNum->hasAttribute('startAt') && $oElementBuAutoNum->getAttribute('startAt') != 1) { + if ($oElementBuAutoNum->hasAttribute('startAt') && 1 != $oElementBuAutoNum->getAttribute('startAt')) { $oParagraph->getBulletStyle()->setBulletNumericStartAt($oElementBuAutoNum->getAttribute('startAt')); } } $oElementBuClr = $document->getElement('a:buClr', $oSubElement); - if ($oElementBuClr instanceof \DOMElement) { + if ($oElementBuClr instanceof DOMElement) { $oColor = new Color(); /** * @todo Create protected for reading Color */ $oElementColor = $document->getElement('a:srgbClr', $oElementBuClr); - if ($oElementColor instanceof \DOMElement) { + if ($oElementColor instanceof DOMElement) { $oColor->setRGB($oElementColor->hasAttribute('val') ? $oElementColor->getAttribute('val') : null); } $oParagraph->getBulletStyle()->setBulletColor($oColor); @@ -1118,27 +1171,30 @@ class PowerPoint2007 implements ReaderInterface } $arraySubElements = $document->getElements('(a:r|a:br)', $oElement); foreach ($arraySubElements as $oSubElement) { - if ($oSubElement->tagName == 'a:br') { + if (!($oSubElement instanceof DOMElement)) { + continue; + } + if ('a:br' == $oSubElement->tagName) { $oParagraph->createBreak(); } - if ($oSubElement->tagName == 'a:r') { + if ('a:r' == $oSubElement->tagName) { $oElementrPr = $document->getElement('a:rPr', $oSubElement); if (is_object($oElementrPr)) { $oText = $oParagraph->createTextRun(); if ($oElementrPr->hasAttribute('b')) { $att = $oElementrPr->getAttribute('b'); - $oText->getFont()->setBold($att == 'true' || $att == '1' ? true : false); + $oText->getFont()->setBold('true' == $att || '1' == $att ? true : false); } if ($oElementrPr->hasAttribute('i')) { $att = $oElementrPr->getAttribute('i'); - $oText->getFont()->setItalic($att == 'true' || $att == '1' ? true : false); + $oText->getFont()->setItalic('true' == $att || '1' == $att ? true : false); } if ($oElementrPr->hasAttribute('strike')) { - $oText->getFont()->setStrikethrough($oElementrPr->getAttribute('strike') == 'noStrike' ? false : true); + $oText->getFont()->setStrikethrough('noStrike' == $oElementrPr->getAttribute('strike') ? false : true); } if ($oElementrPr->hasAttribute('sz')) { - $oText->getFont()->setSize((int)($oElementrPr->getAttribute('sz') / 100)); + $oText->getFont()->setSize((int) ($oElementrPr->getAttribute('sz') / 100)); } if ($oElementrPr->hasAttribute('u')) { $oText->getFont()->setUnderline($oElementrPr->getAttribute('u')); @@ -1153,13 +1209,31 @@ class PowerPoint2007 implements ReaderInterface // Hyperlink $oElementHlinkClick = $document->getElement('a:hlinkClick', $oElementrPr); if (is_object($oElementHlinkClick)) { - if ($oElementHlinkClick->hasAttribute('tooltip')) { - $oText->getHyperlink()->setTooltip($oElementHlinkClick->getAttribute('tooltip')); - } - if ($oElementHlinkClick->hasAttribute('r:id') && isset($this->arrayRels[$this->fileRels][$oElementHlinkClick->getAttribute('r:id')]['Target'])) { - $oText->getHyperlink()->setUrl($this->arrayRels[$this->fileRels][$oElementHlinkClick->getAttribute('r:id')]['Target']); - } + $oText->setHyperlink( + $this->loadHyperlink($document, $oElementHlinkClick, $oText->getHyperlink()) + ); } + // Font + $oElementFontFormat = null; + $oElementFontFormatLatin = $document->getElement('a:latin', $oElementrPr); + if (is_object($oElementFontFormatLatin)) { + $oText->getFont()->setFormat(Font::FORMAT_LATIN); + $oElementFontFormat = $oElementFontFormatLatin; + } + $oElementFontFormatEastAsian = $document->getElement('a:ea', $oElementrPr); + if (is_object($oElementFontFormatEastAsian)) { + $oText->getFont()->setFormat(Font::FORMAT_EAST_ASIAN); + $oElementFontFormat = $oElementFontFormatEastAsian; + } + $oElementFontFormatComplexScript = $document->getElement('a:cs', $oElementrPr); + if (is_object($oElementFontFormatComplexScript)) { + $oText->getFont()->setFormat(Font::FORMAT_COMPLEX_SCRIPT); + $oElementFontFormat = $oElementFontFormatComplexScript; + } + if (is_object($oElementFontFormat) && $oElementFontFormat->hasAttribute('typeface')) { + $oText->getFont()->setName($oElementFontFormat->getAttribute('typeface')); + } + //} else { // $oText = $oParagraph->createText(); @@ -1170,13 +1244,24 @@ class PowerPoint2007 implements ReaderInterface } } - /** - * @param XMLReader $xmlReader - * @param \DOMElement $oElement - * @param Border $oBorder - * @throws \Exception - */ - protected function loadStyleBorder(XMLReader $xmlReader, \DOMElement $oElement, Border $oBorder) + protected function loadHyperlink(XMLReader $xmlReader, DOMElement $element, Hyperlink $hyperlink): Hyperlink + { + if ($element->hasAttribute('tooltip')) { + $hyperlink->setTooltip($element->getAttribute('tooltip')); + } + if ($element->hasAttribute('r:id') && isset($this->arrayRels[$this->fileRels][$element->getAttribute('r:id')]['Target'])) { + $hyperlink->setUrl($this->arrayRels[$this->fileRels][$element->getAttribute('r:id')]['Target']); + } + if ($subElementExt = $xmlReader->getElement('a:extLst/a:ext', $element)) { + if ($subElementExt->hasAttribute('uri') && $subElementExt->getAttribute('uri') == '{A12FA001-AC4F-418D-AE19-62706E023703}') { + $hyperlink->setIsTextColorUsed(true); + } + } + + return $hyperlink; + } + + protected function loadStyleBorder(XMLReader $xmlReader, DOMElement $oElement, Border $oBorder): void { if ($oElement->hasAttribute('w')) { $oBorder->setLineWidth($oElement->getAttribute('w') / 12700); @@ -1186,116 +1271,111 @@ class PowerPoint2007 implements ReaderInterface } $oElementNoFill = $xmlReader->getElement('a:noFill', $oElement); - if ($oElementNoFill instanceof \DOMElement && $oBorder->getLineStyle() == Border::LINE_SINGLE) { + if ($oElementNoFill instanceof DOMElement && Border::LINE_SINGLE == $oBorder->getLineStyle()) { $oBorder->setLineStyle(Border::LINE_NONE); } $oElementColor = $xmlReader->getElement('a:solidFill/a:srgbClr', $oElement); - if ($oElementColor instanceof \DOMElement) { + if ($oElementColor instanceof DOMElement) { $oBorder->setColor($this->loadStyleColor($xmlReader, $oElementColor)); } $oElementDashStyle = $xmlReader->getElement('a:prstDash', $oElement); - if ($oElementDashStyle instanceof \DOMElement && $oElementDashStyle->hasAttribute('val')) { + if ($oElementDashStyle instanceof DOMElement && $oElementDashStyle->hasAttribute('val')) { $oBorder->setDashStyle($oElementDashStyle->getAttribute('val')); } } - /** - * @param XMLReader $xmlReader - * @param \DOMElement $oElement - * @return Color - */ - protected function loadStyleColor(XMLReader $xmlReader, \DOMElement $oElement) + protected function loadStyleColor(XMLReader $xmlReader, DOMElement $oElement): Color { $oColor = new Color(); $oColor->setRGB($oElement->getAttribute('val')); $oElementAlpha = $xmlReader->getElement('a:alpha', $oElement); - if ($oElementAlpha instanceof \DOMElement && $oElementAlpha->hasAttribute('val')) { + if ($oElementAlpha instanceof DOMElement && $oElementAlpha->hasAttribute('val')) { $alpha = strtoupper(dechex((($oElementAlpha->getAttribute('val') / 1000) / 100) * 255)); $oColor->setRGB($oElement->getAttribute('val'), $alpha); } + return $oColor; } - /** - * @param XMLReader $xmlReader - * @param \DOMElement $oElement - * @return null|Fill - * @throws \Exception - */ - protected function loadStyleFill(XMLReader $xmlReader, \DOMElement $oElement) + protected function loadStyleFill(XMLReader $xmlReader, DOMElement $oElement): ?Fill { // Gradient fill $oElementFill = $xmlReader->getElement('a:gradFill', $oElement); - if ($oElementFill instanceof \DOMElement) { + if ($oElementFill instanceof DOMElement) { $oFill = new Fill(); $oFill->setFillType(Fill::FILL_GRADIENT_LINEAR); $oElementColor = $xmlReader->getElement('a:gsLst/a:gs[@pos="0"]/a:srgbClr', $oElementFill); - if ($oElementColor instanceof \DOMElement && $oElementColor->hasAttribute('val')) { + if ($oElementColor instanceof DOMElement && $oElementColor->hasAttribute('val')) { $oFill->setStartColor($this->loadStyleColor($xmlReader, $oElementColor)); } $oElementColor = $xmlReader->getElement('a:gsLst/a:gs[@pos="100000"]/a:srgbClr', $oElementFill); - if ($oElementColor instanceof \DOMElement && $oElementColor->hasAttribute('val')) { + if ($oElementColor instanceof DOMElement && $oElementColor->hasAttribute('val')) { $oFill->setEndColor($this->loadStyleColor($xmlReader, $oElementColor)); } $oRotation = $xmlReader->getElement('a:lin', $oElementFill); - if ($oRotation instanceof \DOMElement && $oRotation->hasAttribute('ang')) { - $oFill->setRotation(CommonDrawing::angleToDegrees($oRotation->getAttribute('ang'))); + if ($oRotation instanceof DOMElement && $oRotation->hasAttribute('ang')) { + $oFill->setRotation(CommonDrawing::angleToDegrees((int) $oRotation->getAttribute('ang'))); } + return $oFill; } // Solid fill $oElementFill = $xmlReader->getElement('a:solidFill', $oElement); - if ($oElementFill instanceof \DOMElement) { + if ($oElementFill instanceof DOMElement) { $oFill = new Fill(); $oFill->setFillType(Fill::FILL_SOLID); $oElementColor = $xmlReader->getElement('a:srgbClr', $oElementFill); - if ($oElementColor instanceof \DOMElement) { + if ($oElementColor instanceof DOMElement) { $oFill->setStartColor($this->loadStyleColor($xmlReader, $oElementColor)); } + return $oFill; } + return null; } - /** - * @param string $fileRels - */ - protected function loadRels($fileRels) + protected function loadRels(string $fileRels): void { $sPart = $this->oZip->getFromName($fileRels); - if ($sPart !== false) { + if (false !== $sPart) { $xmlReader = new XMLReader(); + /* @phpstan-ignore-next-line */ if ($xmlReader->getDomFromString($sPart)) { foreach ($xmlReader->getElements('*') as $oNode) { - if (!($oNode instanceof \DOMElement)) { + if (!($oNode instanceof DOMElement)) { continue; } - $this->arrayRels[$fileRels][$oNode->getAttribute('Id')] = array( + $this->arrayRels[$fileRels][$oNode->getAttribute('Id')] = [ 'Target' => $oNode->getAttribute('Target'), 'Type' => $oNode->getAttribute('Type'), - ); + ]; } } } } /** - * @param $oSlide - * @param \DOMNodeList $oElements - * @param XMLReader $xmlReader - * @throws \Exception + * @param AbstractSlide|Note $oSlide + * @param DOMNodeList $oElements + * + * @throws FeatureNotImplementedException + * * @internal param $baseFile */ - protected function loadSlideShapes($oSlide, $oElements, $xmlReader) + protected function loadSlideShapes($oSlide, DOMNodeList $oElements, XMLReader $xmlReader): void { foreach ($oElements as $oNode) { + if (!($oNode instanceof DOMElement)) { + continue; + } switch ($oNode->tagName) { case 'p:graphicFrame': $this->loadShapeTable($xmlReader, $oNode, $oSlide); @@ -1307,7 +1387,7 @@ class PowerPoint2007 implements ReaderInterface $this->loadShapeRichText($xmlReader, $oNode, $oSlide); break; default: - //var_export($oNode->tagName); + //throw new FeatureNotImplementedException(); } } } diff --git a/PhpOffice/PhpPresentation/Reader/PowerPoint97.php b/PhpOffice/PhpPresentation/Reader/PowerPoint97.php old mode 100755 new mode 100644 index 385638c..b28b9fa --- a/PhpOffice/PhpPresentation/Reader/PowerPoint97.php +++ b/PhpOffice/PhpPresentation/Reader/PowerPoint97.php @@ -10,17 +10,24 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Reader; +use Exception; use PhpOffice\Common\Microsoft\OLERead; use PhpOffice\Common\Text; -use PhpOffice\PhpPresentation\PhpPresentation; use PhpOffice\PhpPresentation\AbstractShape; +use PhpOffice\PhpPresentation\Exception\FeatureNotImplementedException; +use PhpOffice\PhpPresentation\Exception\FileNotFoundException; +use PhpOffice\PhpPresentation\Exception\InvalidFileFormatException; +use PhpOffice\PhpPresentation\PhpPresentation; use PhpOffice\PhpPresentation\Shape; use PhpOffice\PhpPresentation\Shape\Drawing; use PhpOffice\PhpPresentation\Shape\Group; @@ -28,350 +35,356 @@ use PhpOffice\PhpPresentation\Shape\Hyperlink; use PhpOffice\PhpPresentation\Shape\Line; use PhpOffice\PhpPresentation\Shape\RichText; use PhpOffice\PhpPresentation\Style\Alignment; -use PhpOffice\PhpPresentation\Style\Color; use PhpOffice\PhpPresentation\Style\Bullet; +use PhpOffice\PhpPresentation\Style\Color; +use PhpOffice\PhpPresentation\Style\Font; /** - * Serialized format reader + * Serialized format reader. */ class PowerPoint97 implements ReaderInterface { - const OFFICEARTBLIPEMF = 0xF01A; - const OFFICEARTBLIPWMF = 0xF01B; - const OFFICEARTBLIPPICT = 0xF01C; - const OFFICEARTBLIPJPG = 0xF01D; - const OFFICEARTBLIPPNG = 0xF01E; - const OFFICEARTBLIPDIB = 0xF01F; - const OFFICEARTBLIPTIFF = 0xF029; - const OFFICEARTBLIPJPEG = 0xF02A; + public const OFFICEARTBLIPEMF = 0xF01A; + public const OFFICEARTBLIPWMF = 0xF01B; + public const OFFICEARTBLIPPICT = 0xF01C; + public const OFFICEARTBLIPJPG = 0xF01D; + public const OFFICEARTBLIPPNG = 0xF01E; + public const OFFICEARTBLIPDIB = 0xF01F; + public const OFFICEARTBLIPTIFF = 0xF029; + public const OFFICEARTBLIPJPEG = 0xF02A; /** - * @link http://msdn.microsoft.com/en-us/library/dd945336(v=office.12).aspx + * @see http://msdn.microsoft.com/en-us/library/dd945336(v=office.12).aspx */ - const RT_ANIMATIONINFO = 0x1014; - const RT_ANIMATIONINFOATOM = 0x0FF1; - const RT_BINARYTAGDATABLOB = 0x138B; - const RT_BLIPCOLLECTION9 = 0x07F8; - const RT_BLIPENTITY9ATOM = 0x07F9; - const RT_BOOKMARKCOLLECTION = 0x07E3; - const RT_BOOKMARKENTITYATOM = 0x0FD0; - const RT_BOOKMARKSEEDATOM = 0x07E9; - const RT_BROADCASTDOCINFO9 = 0x177E; - const RT_BROADCASTDOCINFO9ATOM = 0x177F; - const RT_BUILDATOM = 0x2B03; - const RT_BUILDLIST = 0x2B02; - const RT_CHARTBUILD = 0x2B04; - const RT_CHARTBUILDATOM = 0x2B05; - const RT_COLORSCHEMEATOM = 0x07F0; - const RT_COMMENT10 = 0x2EE0; - const RT_COMMENT10ATOM = 0x2EE1; - const RT_COMMENTINDEX10 = 0x2EE4; - const RT_COMMENTINDEX10ATOM = 0x2EE5; - const RT_CRYPTSESSION10CONTAINER = 0x2F14; - const RT_CURRENTUSERATOM = 0x0FF6; - const RT_CSTRING = 0x0FBA; - const RT_DATETIMEMETACHARATOM = 0x0FF7; - const RT_DEFAULTRULERATOM = 0x0FAB; - const RT_DOCROUTINGSLIPATOM = 0x0406; - const RT_DIAGRAMBUILD = 0x2B06; - const RT_DIAGRAMBUILDATOM = 0x2B07; - const RT_DIFF10 = 0x2EED; - const RT_DIFF10ATOM = 0x2EEE; - const RT_DIFFTREE10 = 0x2EEC; - const RT_DOCTOOLBARSTATES10ATOM = 0x36B1; - const RT_DOCUMENT = 0x03E8; - const RT_DOCUMENTATOM = 0x03E9; - const RT_DRAWING = 0x040C; - const RT_DRAWINGGROUP = 0x040B; - const RT_ENDDOCUMENTATOM = 0x03EA; - const RT_EXTERNALAVIMOVIE = 0x1006; - const RT_EXTERNALCDAUDIO = 0x100E; - const RT_EXTERNALCDAUDIOATOM = 0x1012; - const RT_EXTERNALHYPERLINK = 0x0FD7; - const RT_EXTERNALHYPERLINK9 = 0x0FE4; - const RT_EXTERNALHYPERLINKATOM = 0x0FD3; - const RT_EXTERNALHYPERLINKFLAGSATOM = 0x1018; - const RT_EXTERNALMCIMOVIE = 0x1007; - const RT_EXTERNALMEDIAATOM = 0x1004; - const RT_EXTERNALMIDIAUDIO = 0x100D; - const RT_EXTERNALOBJECTLIST = 0x0409; - const RT_EXTERNALOBJECTLISTATOM = 0x040A; - const RT_EXTERNALOBJECTREFATOM = 0x0BC1; - const RT_EXTERNALOLECONTROL = 0x0FEE; - const RT_EXTERNALOLECONTROLATOM = 0x0FFB; - const RT_EXTERNALOLEEMBED = 0x0FCC; - const RT_EXTERNALOLEEMBEDATOM = 0x0FCD; - const RT_EXTERNALOLELINK = 0x0FCE; - const RT_EXTERNALOLELINKATOM = 0x0FD1; - const RT_EXTERNALOLEOBJECTATOM = 0x0FC3; - const RT_EXTERNALOLEOBJECTSTG = 0x1011; - const RT_EXTERNALVIDEO = 0x1005; - const RT_EXTERNALWAVAUDIOEMBEDDED = 0x100F; - const RT_EXTERNALWAVAUDIOEMBEDDEDATOM = 0x1013; - const RT_EXTERNALWAVAUDIOLINK = 0x1010; - const RT_ENVELOPEDATA9ATOM = 0x1785; - const RT_ENVELOPEFLAGS9ATOM = 0x1784; - const RT_ENVIRONMENT = 0x03F2; - const RT_FONTCOLLECTION = 0x07D5; - const RT_FONTCOLLECTION10 = 0x07D6; - const RT_FONTEMBEDDATABLOB = 0x0FB8; - const RT_FONTEMBEDFLAGS10ATOM = 0x32C8; - const RT_FILTERPRIVACYFLAGS10ATOM = 0x36B0; - const RT_FONTENTITYATOM = 0x0FB7; - const RT_FOOTERMETACHARATOM = 0x0FFA; - const RT_GENERICDATEMETACHARATOM = 0x0FF8; - const RT_GRIDSPACING10ATOM = 0x040D; - const RT_GUIDEATOM = 0x03FB; - const RT_HANDOUT = 0x0FC9; - const RT_HASHCODEATOM = 0x2B00; - const RT_HEADERSFOOTERS = 0x0FD9; - const RT_HEADERSFOOTERSATOM = 0x0FDA; - const RT_HEADERMETACHARATOM = 0x0FF9; - const RT_HTMLDOCINFO9ATOM = 0x177B; - const RT_HTMLPUBLISHINFOATOM = 0x177C; - const RT_HTMLPUBLISHINFO9 = 0x177D; - const RT_INTERACTIVEINFO = 0x0FF2; - const RT_INTERACTIVEINFOATOM = 0x0FF3; - const RT_KINSOKU = 0x0FC8; - const RT_KINSOKUATOM = 0x0FD2; - const RT_LEVELINFOATOM = 0x2B0A; - const RT_LINKEDSHAPE10ATOM = 0x2EE6; - const RT_LINKEDSLIDE10ATOM = 0x2EE7; - const RT_LIST = 0x07D0; - const RT_MAINMASTER = 0x03F8; - const RT_MASTERTEXTPROPATOM = 0x0FA2; - const RT_METAFILE = 0x0FC1; - const RT_NAMEDSHOW = 0x0411; - const RT_NAMEDSHOWS = 0x0410; - const RT_NAMEDSHOWSLIDESATOM = 0x0412; - const RT_NORMALVIEWSETINFO9 = 0x0414; - const RT_NORMALVIEWSETINFO9ATOM = 0x0415; - const RT_NOTES= 0x03F0; - const RT_NOTESATOM = 0x03F1; - const RT_NOTESTEXTVIEWINFO9 = 0x0413; - const RT_OUTLINETEXTPROPS9 = 0x0FAE; - const RT_OUTLINETEXTPROPS10 = 0x0FB3; - const RT_OUTLINETEXTPROPS11 = 0x0FB5; - const RT_OUTLINETEXTPROPSHEADER9ATOM = 0x0FAF; - const RT_OUTLINETEXTREFATOM = 0x0F9E; - const RT_OUTLINEVIEWINFO = 0x0407; - const RT_PERSISTDIRECTORYATOM = 0x1772; - const RT_PARABUILD = 0x2B08; - const RT_PARABUILDATOM = 0x2B09; - const RT_PHOTOALBUMINFO10ATOM = 0x36B2; - const RT_PLACEHOLDERATOM = 0x0BC3; - const RT_PRESENTATIONADVISORFLAGS9ATOM = 0x177A; - const RT_PRINTOPTIONSATOM = 0x1770; - const RT_PROGBINARYTAG = 0x138A; - const RT_PROGSTRINGTAG = 0x1389; - const RT_PROGTAGS = 0x1388; - const RT_RECOLORINFOATOM = 0x0FE7; - const RT_RTFDATETIMEMETACHARATOM = 0x1015; - const RT_ROUNDTRIPANIMATIONATOM12ATOM = 0x2B0B; - const RT_ROUNDTRIPANIMATIONHASHATOM12ATOM = 0x2B0D; - const RT_ROUNDTRIPCOLORMAPPING12ATOM = 0x040F; - const RT_ROUNDTRIPCOMPOSITEMASTERID12ATOM = 0x041D; - const RT_ROUNDTRIPCONTENTMASTERID12ATOM = 0x0422; - const RT_ROUNDTRIPCONTENTMASTERINFO12ATOM = 0x041E; - const RT_ROUNDTRIPCUSTOMTABLESTYLES12ATOM = 0x0428; - const RT_ROUNDTRIPDOCFLAGS12ATOM = 0x0425; - const RT_ROUNDTRIPHEADERFOOTERDEFAULTS12ATOM = 0x0424; - const RT_ROUNDTRIPHFPLACEHOLDER12ATOM = 0x0420; - const RT_ROUNDTRIPNEWPLACEHOLDERID12ATOM = 0x0BDD; - const RT_ROUNDTRIPNOTESMASTERTEXTSTYLES12ATOM = 0x0427; - const RT_ROUNDTRIPOARTTEXTSTYLES12ATOM = 0x0423; - const RT_ROUNDTRIPORIGINALMAINMASTERID12ATOM = 0x041C; - const RT_ROUNDTRIPSHAPECHECKSUMFORCL12ATOM = 0x0426; - const RT_ROUNDTRIPSHAPEID12ATOM = 0x041F; - const RT_ROUNDTRIPSLIDESYNCINFO12 = 0x3714; - const RT_ROUNDTRIPSLIDESYNCINFOATOM12 = 0x3715; - const RT_ROUNDTRIPTHEME12ATOM = 0x040E; - const RT_SHAPEATOM = 0x0BDB; - const RT_SHAPEFLAGS10ATOM = 0x0BDC; - const RT_SLIDE = 0x03EE; - const RT_SLIDEATOM = 0x03EF; - const RT_SLIDEFLAGS10ATOM = 0x2EEA; - const RT_SLIDELISTENTRY10ATOM = 0x2EF0; - const RT_SLIDELISTTABLE10 = 0x2EF1; - const RT_SLIDELISTWITHTEXT = 0x0FF0; - const RT_SLIDELISTTABLESIZE10ATOM = 0x2EEF; - const RT_SLIDENUMBERMETACHARATOM = 0x0FD8; - const RT_SLIDEPERSISTATOM = 0x03F3; - const RT_SLIDESHOWDOCINFOATOM = 0x0401; - const RT_SLIDESHOWSLIDEINFOATOM = 0x03F9; - const RT_SLIDETIME10ATOM = 0x2EEB; - const RT_SLIDEVIEWINFO = 0x03FA; - const RT_SLIDEVIEWINFOATOM = 0x03FE; - const RT_SMARTTAGSTORE11CONTAINER = 0x36B3; - const RT_SOUND = 0x07E6; - const RT_SOUNDCOLLECTION = 0x07E4; - const RT_SOUNDCOLLECTIONATOM = 0x07E5; - const RT_SOUNDDATABLOB = 0x07E7; - const RT_SORTERVIEWINFO = 0x0408; - const RT_STYLETEXTPROPATOM = 0x0FA1; - const RT_STYLETEXTPROP10ATOM = 0x0FB1; - const RT_STYLETEXTPROP11ATOM = 0x0FB6; - const RT_STYLETEXTPROP9ATOM = 0x0FAC; - const RT_SUMMARY = 0x0402; - const RT_TEXTBOOKMARKATOM = 0x0FA7; - const RT_TEXTBYTESATOM = 0x0FA8; - const RT_TEXTCHARFORMATEXCEPTIONATOM = 0x0FA4; - const RT_TEXTCHARSATOM = 0x0FA0; - const RT_TEXTDEFAULTS10ATOM = 0x0FB4; - const RT_TEXTDEFAULTS9ATOM = 0x0FB0; - const RT_TEXTHEADERATOM = 0x0F9F; - const RT_TEXTINTERACTIVEINFOATOM = 0x0FDF; - const RT_TEXTMASTERSTYLEATOM = 0x0FA3; - const RT_TEXTMASTERSTYLE10ATOM = 0x0FB2; - const RT_TEXTMASTERSTYLE9ATOM = 0x0FAD; - const RT_TEXTPARAGRAPHFORMATEXCEPTIONATOM = 0x0FA5; - const RT_TEXTRULERATOM = 0x0FA6; - const RT_TEXTSPECIALINFOATOM = 0x0FAA; - const RT_TEXTSPECIALINFODEFAULTATOM = 0x0FA9; - const RT_TIMEANIMATEBEHAVIOR = 0xF134; - const RT_TIMEANIMATEBEHAVIORCONTAINER = 0xF12B; - const RT_TIMEANIMATIONVALUE = 0xF143; - const RT_TIMEANIMATIONVALUELIST = 0xF13F; - const RT_TIMEBEHAVIOR = 0xF133; - const RT_TIMEBEHAVIORCONTAINER = 0xF12A; - const RT_TIMECOLORBEHAVIOR = 0xF135; - const RT_TIMECOLORBEHAVIORCONTAINER = 0xF12C; - const RT_TIMECLIENTVISUALELEMENT = 0xF13C; - const RT_TIMECOMMANDBEHAVIOR = 0xF13B; - const RT_TIMECOMMANDBEHAVIORCONTAINER = 0xF132; - const RT_TIMECONDITION = 0xF128; - const RT_TIMECONDITIONCONTAINER = 0xF125; - const RT_TIMEEFFECTBEHAVIOR = 0xF136; - const RT_TIMEEFFECTBEHAVIORCONTAINER = 0xF12D; - const RT_TIMEEXTTIMENODECONTAINER = 0xF144; - const RT_TIMEITERATEDATA = 0xF140; - const RT_TIMEMODIFIER = 0xF129; - const RT_TIMEMOTIONBEHAVIOR = 0xF137; - const RT_TIMEMOTIONBEHAVIORCONTAINER = 0xF12E; - const RT_TIMENODE = 0xF127; - const RT_TIMEPROPERTYLIST = 0xF13D; - const RT_TIMEROTATIONBEHAVIOR = 0xF138; - const RT_TIMEROTATIONBEHAVIORCONTAINER = 0xF12F; - const RT_TIMESCALEBEHAVIOR = 0xF139; - const RT_TIMESCALEBEHAVIORCONTAINER = 0xF130; - const RT_TIMESEQUENCEDATA = 0xF141; - const RT_TIMESETBEHAVIOR = 0xF13A; - const RT_TIMESETBEHAVIORCONTAINER = 0xF131; - const RT_TIMESUBEFFECTCONTAINER = 0xF145; - const RT_TIMEVARIANT = 0xF142; - const RT_TIMEVARIANTLIST = 0xF13E; - const RT_USEREDITATOM = 0x0FF5; - const RT_VBAINFO = 0x03FF; - const RT_VBAINFOATOM = 0x0400; - const RT_VIEWINFOATOM = 0x03FD; - const RT_VISUALPAGEATOM = 0x2B01; - const RT_VISUALSHAPEATOM = 0x2AFB; + public const RT_ANIMATIONINFO = 0x1014; + public const RT_ANIMATIONINFOATOM = 0x0FF1; + public const RT_BINARYTAGDATABLOB = 0x138B; + public const RT_BLIPCOLLECTION9 = 0x07F8; + public const RT_BLIPENTITY9ATOM = 0x07F9; + public const RT_BOOKMARKCOLLECTION = 0x07E3; + public const RT_BOOKMARKENTITYATOM = 0x0FD0; + public const RT_BOOKMARKSEEDATOM = 0x07E9; + public const RT_BROADCASTDOCINFO9 = 0x177E; + public const RT_BROADCASTDOCINFO9ATOM = 0x177F; + public const RT_BUILDATOM = 0x2B03; + public const RT_BUILDLIST = 0x2B02; + public const RT_CHARTBUILD = 0x2B04; + public const RT_CHARTBUILDATOM = 0x2B05; + public const RT_COLORSCHEMEATOM = 0x07F0; + public const RT_COMMENT10 = 0x2EE0; + public const RT_COMMENT10ATOM = 0x2EE1; + public const RT_COMMENTINDEX10 = 0x2EE4; + public const RT_COMMENTINDEX10ATOM = 0x2EE5; + public const RT_CRYPTSESSION10CONTAINER = 0x2F14; + public const RT_CURRENTUSERATOM = 0x0FF6; + public const RT_CSTRING = 0x0FBA; + public const RT_DATETIMEMETACHARATOM = 0x0FF7; + public const RT_DEFAULTRULERATOM = 0x0FAB; + public const RT_DOCROUTINGSLIPATOM = 0x0406; + public const RT_DIAGRAMBUILD = 0x2B06; + public const RT_DIAGRAMBUILDATOM = 0x2B07; + public const RT_DIFF10 = 0x2EED; + public const RT_DIFF10ATOM = 0x2EEE; + public const RT_DIFFTREE10 = 0x2EEC; + public const RT_DOCTOOLBARSTATES10ATOM = 0x36B1; + public const RT_DOCUMENT = 0x03E8; + public const RT_DOCUMENTATOM = 0x03E9; + public const RT_DRAWING = 0x040C; + public const RT_DRAWINGGROUP = 0x040B; + public const RT_ENDDOCUMENTATOM = 0x03EA; + public const RT_EXTERNALAVIMOVIE = 0x1006; + public const RT_EXTERNALCDAUDIO = 0x100E; + public const RT_EXTERNALCDAUDIOATOM = 0x1012; + public const RT_EXTERNALHYPERLINK = 0x0FD7; + public const RT_EXTERNALHYPERLINK9 = 0x0FE4; + public const RT_EXTERNALHYPERLINKATOM = 0x0FD3; + public const RT_EXTERNALHYPERLINKFLAGSATOM = 0x1018; + public const RT_EXTERNALMCIMOVIE = 0x1007; + public const RT_EXTERNALMEDIAATOM = 0x1004; + public const RT_EXTERNALMIDIAUDIO = 0x100D; + public const RT_EXTERNALOBJECTLIST = 0x0409; + public const RT_EXTERNALOBJECTLISTATOM = 0x040A; + public const RT_EXTERNALOBJECTREFATOM = 0x0BC1; + public const RT_EXTERNALOLECONTROL = 0x0FEE; + public const RT_EXTERNALOLECONTROLATOM = 0x0FFB; + public const RT_EXTERNALOLEEMBED = 0x0FCC; + public const RT_EXTERNALOLEEMBEDATOM = 0x0FCD; + public const RT_EXTERNALOLELINK = 0x0FCE; + public const RT_EXTERNALOLELINKATOM = 0x0FD1; + public const RT_EXTERNALOLEOBJECTATOM = 0x0FC3; + public const RT_EXTERNALOLEOBJECTSTG = 0x1011; + public const RT_EXTERNALVIDEO = 0x1005; + public const RT_EXTERNALWAVAUDIOEMBEDDED = 0x100F; + public const RT_EXTERNALWAVAUDIOEMBEDDEDATOM = 0x1013; + public const RT_EXTERNALWAVAUDIOLINK = 0x1010; + public const RT_ENVELOPEDATA9ATOM = 0x1785; + public const RT_ENVELOPEFLAGS9ATOM = 0x1784; + public const RT_ENVIRONMENT = 0x03F2; + public const RT_FONTCOLLECTION = 0x07D5; + public const RT_FONTCOLLECTION10 = 0x07D6; + public const RT_FONTEMBEDDATABLOB = 0x0FB8; + public const RT_FONTEMBEDFLAGS10ATOM = 0x32C8; + public const RT_FILTERPRIVACYFLAGS10ATOM = 0x36B0; + public const RT_FONTENTITYATOM = 0x0FB7; + public const RT_FOOTERMETACHARATOM = 0x0FFA; + public const RT_GENERICDATEMETACHARATOM = 0x0FF8; + public const RT_GRIDSPACING10ATOM = 0x040D; + public const RT_GUIDEATOM = 0x03FB; + public const RT_HANDOUT = 0x0FC9; + public const RT_HASHCODEATOM = 0x2B00; + public const RT_HEADERSFOOTERS = 0x0FD9; + public const RT_HEADERSFOOTERSATOM = 0x0FDA; + public const RT_HEADERMETACHARATOM = 0x0FF9; + public const RT_HTMLDOCINFO9ATOM = 0x177B; + public const RT_HTMLPUBLISHINFOATOM = 0x177C; + public const RT_HTMLPUBLISHINFO9 = 0x177D; + public const RT_INTERACTIVEINFO = 0x0FF2; + public const RT_INTERACTIVEINFOATOM = 0x0FF3; + public const RT_KINSOKU = 0x0FC8; + public const RT_KINSOKUATOM = 0x0FD2; + public const RT_LEVELINFOATOM = 0x2B0A; + public const RT_LINKEDSHAPE10ATOM = 0x2EE6; + public const RT_LINKEDSLIDE10ATOM = 0x2EE7; + public const RT_LIST = 0x07D0; + public const RT_MAINMASTER = 0x03F8; + public const RT_MASTERTEXTPROPATOM = 0x0FA2; + public const RT_METAFILE = 0x0FC1; + public const RT_NAMEDSHOW = 0x0411; + public const RT_NAMEDSHOWS = 0x0410; + public const RT_NAMEDSHOWSLIDESATOM = 0x0412; + public const RT_NORMALVIEWSETINFO9 = 0x0414; + public const RT_NORMALVIEWSETINFO9ATOM = 0x0415; + public const RT_NOTES = 0x03F0; + public const RT_NOTESATOM = 0x03F1; + public const RT_NOTESTEXTVIEWINFO9 = 0x0413; + public const RT_OUTLINETEXTPROPS9 = 0x0FAE; + public const RT_OUTLINETEXTPROPS10 = 0x0FB3; + public const RT_OUTLINETEXTPROPS11 = 0x0FB5; + public const RT_OUTLINETEXTPROPSHEADER9ATOM = 0x0FAF; + public const RT_OUTLINETEXTREFATOM = 0x0F9E; + public const RT_OUTLINEVIEWINFO = 0x0407; + public const RT_PERSISTDIRECTORYATOM = 0x1772; + public const RT_PARABUILD = 0x2B08; + public const RT_PARABUILDATOM = 0x2B09; + public const RT_PHOTOALBUMINFO10ATOM = 0x36B2; + public const RT_PLACEHOLDERATOM = 0x0BC3; + public const RT_PRESENTATIONADVISORFLAGS9ATOM = 0x177A; + public const RT_PRINTOPTIONSATOM = 0x1770; + public const RT_PROGBINARYTAG = 0x138A; + public const RT_PROGSTRINGTAG = 0x1389; + public const RT_PROGTAGS = 0x1388; + public const RT_RECOLORINFOATOM = 0x0FE7; + public const RT_RTFDATETIMEMETACHARATOM = 0x1015; + public const RT_ROUNDTRIPANIMATIONATOM12ATOM = 0x2B0B; + public const RT_ROUNDTRIPANIMATIONHASHATOM12ATOM = 0x2B0D; + public const RT_ROUNDTRIPCOLORMAPPING12ATOM = 0x040F; + public const RT_ROUNDTRIPCOMPOSITEMASTERID12ATOM = 0x041D; + public const RT_ROUNDTRIPCONTENTMASTERID12ATOM = 0x0422; + public const RT_ROUNDTRIPCONTENTMASTERINFO12ATOM = 0x041E; + public const RT_ROUNDTRIPCUSTOMTABLESTYLES12ATOM = 0x0428; + public const RT_ROUNDTRIPDOCFLAGS12ATOM = 0x0425; + public const RT_ROUNDTRIPHEADERFOOTERDEFAULTS12ATOM = 0x0424; + public const RT_ROUNDTRIPHFPLACEHOLDER12ATOM = 0x0420; + public const RT_ROUNDTRIPNEWPLACEHOLDERID12ATOM = 0x0BDD; + public const RT_ROUNDTRIPNOTESMASTERTEXTSTYLES12ATOM = 0x0427; + public const RT_ROUNDTRIPOARTTEXTSTYLES12ATOM = 0x0423; + public const RT_ROUNDTRIPORIGINALMAINMASTERID12ATOM = 0x041C; + public const RT_ROUNDTRIPSHAPECHECKSUMFORCL12ATOM = 0x0426; + public const RT_ROUNDTRIPSHAPEID12ATOM = 0x041F; + public const RT_ROUNDTRIPSLIDESYNCINFO12 = 0x3714; + public const RT_ROUNDTRIPSLIDESYNCINFOATOM12 = 0x3715; + public const RT_ROUNDTRIPTHEME12ATOM = 0x040E; + public const RT_SHAPEATOM = 0x0BDB; + public const RT_SHAPEFLAGS10ATOM = 0x0BDC; + public const RT_SLIDE = 0x03EE; + public const RT_SLIDEATOM = 0x03EF; + public const RT_SLIDEFLAGS10ATOM = 0x2EEA; + public const RT_SLIDELISTENTRY10ATOM = 0x2EF0; + public const RT_SLIDELISTTABLE10 = 0x2EF1; + public const RT_SLIDELISTWITHTEXT = 0x0FF0; + public const RT_SLIDELISTTABLESIZE10ATOM = 0x2EEF; + public const RT_SLIDENUMBERMETACHARATOM = 0x0FD8; + public const RT_SLIDEPERSISTATOM = 0x03F3; + public const RT_SLIDESHOWDOCINFOATOM = 0x0401; + public const RT_SLIDESHOWSLIDEINFOATOM = 0x03F9; + public const RT_SLIDETIME10ATOM = 0x2EEB; + public const RT_SLIDEVIEWINFO = 0x03FA; + public const RT_SLIDEVIEWINFOATOM = 0x03FE; + public const RT_SMARTTAGSTORE11CONTAINER = 0x36B3; + public const RT_SOUND = 0x07E6; + public const RT_SOUNDCOLLECTION = 0x07E4; + public const RT_SOUNDCOLLECTIONATOM = 0x07E5; + public const RT_SOUNDDATABLOB = 0x07E7; + public const RT_SORTERVIEWINFO = 0x0408; + public const RT_STYLETEXTPROPATOM = 0x0FA1; + public const RT_STYLETEXTPROP10ATOM = 0x0FB1; + public const RT_STYLETEXTPROP11ATOM = 0x0FB6; + public const RT_STYLETEXTPROP9ATOM = 0x0FAC; + public const RT_SUMMARY = 0x0402; + public const RT_TEXTBOOKMARKATOM = 0x0FA7; + public const RT_TEXTBYTESATOM = 0x0FA8; + public const RT_TEXTCHARFORMATEXCEPTIONATOM = 0x0FA4; + public const RT_TEXTCHARSATOM = 0x0FA0; + public const RT_TEXTDEFAULTS10ATOM = 0x0FB4; + public const RT_TEXTDEFAULTS9ATOM = 0x0FB0; + public const RT_TEXTHEADERATOM = 0x0F9F; + public const RT_TEXTINTERACTIVEINFOATOM = 0x0FDF; + public const RT_TEXTMASTERSTYLEATOM = 0x0FA3; + public const RT_TEXTMASTERSTYLE10ATOM = 0x0FB2; + public const RT_TEXTMASTERSTYLE9ATOM = 0x0FAD; + public const RT_TEXTPARAGRAPHFORMATEXCEPTIONATOM = 0x0FA5; + public const RT_TEXTRULERATOM = 0x0FA6; + public const RT_TEXTSPECIALINFOATOM = 0x0FAA; + public const RT_TEXTSPECIALINFODEFAULTATOM = 0x0FA9; + public const RT_TIMEANIMATEBEHAVIOR = 0xF134; + public const RT_TIMEANIMATEBEHAVIORCONTAINER = 0xF12B; + public const RT_TIMEANIMATIONVALUE = 0xF143; + public const RT_TIMEANIMATIONVALUELIST = 0xF13F; + public const RT_TIMEBEHAVIOR = 0xF133; + public const RT_TIMEBEHAVIORCONTAINER = 0xF12A; + public const RT_TIMECOLORBEHAVIOR = 0xF135; + public const RT_TIMECOLORBEHAVIORCONTAINER = 0xF12C; + public const RT_TIMECLIENTVISUALELEMENT = 0xF13C; + public const RT_TIMECOMMANDBEHAVIOR = 0xF13B; + public const RT_TIMECOMMANDBEHAVIORCONTAINER = 0xF132; + public const RT_TIMECONDITION = 0xF128; + public const RT_TIMECONDITIONCONTAINER = 0xF125; + public const RT_TIMEEFFECTBEHAVIOR = 0xF136; + public const RT_TIMEEFFECTBEHAVIORCONTAINER = 0xF12D; + public const RT_TIMEEXTTIMENODECONTAINER = 0xF144; + public const RT_TIMEITERATEDATA = 0xF140; + public const RT_TIMEMODIFIER = 0xF129; + public const RT_TIMEMOTIONBEHAVIOR = 0xF137; + public const RT_TIMEMOTIONBEHAVIORCONTAINER = 0xF12E; + public const RT_TIMENODE = 0xF127; + public const RT_TIMEPROPERTYLIST = 0xF13D; + public const RT_TIMEROTATIONBEHAVIOR = 0xF138; + public const RT_TIMEROTATIONBEHAVIORCONTAINER = 0xF12F; + public const RT_TIMESCALEBEHAVIOR = 0xF139; + public const RT_TIMESCALEBEHAVIORCONTAINER = 0xF130; + public const RT_TIMESEQUENCEDATA = 0xF141; + public const RT_TIMESETBEHAVIOR = 0xF13A; + public const RT_TIMESETBEHAVIORCONTAINER = 0xF131; + public const RT_TIMESUBEFFECTCONTAINER = 0xF145; + public const RT_TIMEVARIANT = 0xF142; + public const RT_TIMEVARIANTLIST = 0xF13E; + public const RT_USEREDITATOM = 0x0FF5; + public const RT_VBAINFO = 0x03FF; + public const RT_VBAINFOATOM = 0x0400; + public const RT_VIEWINFOATOM = 0x03FD; + public const RT_VISUALPAGEATOM = 0x2B01; + public const RT_VISUALSHAPEATOM = 0x2AFB; /** - * @var http://msdn.microsoft.com/en-us/library/dd926394(v=office.12).aspx + * @see http://msdn.microsoft.com/en-us/library/dd926394(v=office.12).aspx */ - const SL_BIGOBJECT = 0x0000000F; - const SL_BLANK = 0x00000010; - const SL_COLUMNTWOROWS = 0x0000000A; - const SL_FOUROBJECTS = 0x0000000E; - const SL_MASTERTITLE = 0x00000002; - const SL_TITLEBODY = 0x00000001; - const SL_TITLEONLY = 0x00000007; - const SL_TITLESLIDE = 0x00000000; - const SL_TWOCOLUMNS = 0x00000008; - const SL_TWOCOLUMNSROW = 0x0000000D; - const SL_TWOROWS = 0x00000009; - const SL_TWOROWSCOLUMN = 0x0000000B; - const SL_VERTICALTITLEBODY = 0x00000011; - const SL_VERTICALTWOROWS = 0x00000012; + public const SL_BIGOBJECT = 0x0000000F; + public const SL_BLANK = 0x00000010; + public const SL_COLUMNTWOROWS = 0x0000000A; + public const SL_FOUROBJECTS = 0x0000000E; + public const SL_MASTERTITLE = 0x00000002; + public const SL_TITLEBODY = 0x00000001; + public const SL_TITLEONLY = 0x00000007; + public const SL_TITLESLIDE = 0x00000000; + public const SL_TWOCOLUMNS = 0x00000008; + public const SL_TWOCOLUMNSROW = 0x0000000D; + public const SL_TWOROWS = 0x00000009; + public const SL_TWOROWSCOLUMN = 0x0000000B; + public const SL_VERTICALTITLEBODY = 0x00000011; + public const SL_VERTICALTWOROWS = 0x00000012; /** - * Array with Fonts + * Array with Fonts. + * + * @var array */ - private $arrayFonts = array(); + private $arrayFonts = []; /** - * Array with Hyperlinks + * Array with Hyperlinks. + * + * @var array> */ - private $arrayHyperlinks = array(); + private $arrayHyperlinks = []; /** - * Array with Notes + * Array with Notes. + * + * @var array */ - private $arrayNotes = array(); + private $arrayNotes = []; /** - * Array with Pictures + * Array with Pictures. + * + * @var array */ - private $arrayPictures = array(); + private $arrayPictures = []; /** * Offset (in bytes) from the beginning of the PowerPoint Document Stream to the UserEditAtom record for the most recent user edit. + * * @var int */ private $offsetToCurrentEdit; /** * A structure that specifies a compressed table of sequential persist object identifiers and stream offsets to associated persist objects. - * @var int[] + * + * @var array */ private $rgPersistDirEntry; /** - * Offset (in bytes) from the beginning of the PowerPoint Document Stream to the PersistDirectoryAtom record for this user edit + * Offset (in bytes) from the beginning of the PowerPoint Document Stream to the PersistDirectoryAtom record for this user edit. + * * @var int */ private $offsetPersistDirectory; /** - * Output Object + * Output Object. + * * @var PhpPresentation */ private $oPhpPresentation; /** - * Group Object - * @var Group + * @var Group|null */ private $oCurrentGroup; /** - * @var boolean + * @var bool */ private $bFirstShapeGroup = false; /** - * Stream "Powerpoint Document" + * Stream "Powerpoint Document". + * * @var string */ private $streamPowerpointDocument; /** - * Stream "Current User" + * Stream "Current User". + * * @var string */ private $streamCurrentUser; /** - * Stream "Summary Information" - * @var string - */ - private $streamSummaryInformation; - /** - * Stream "Document Summary Information" - * @var string - */ - private $streamDocumentSummaryInformation; - /** - * Stream "Pictures" + * Stream "Pictures". + * * @var string */ private $streamPictures; /** - * @var integer + * @var int */ private $inMainType; /** - * @var integer + * @var int|null */ private $currentNote; /** - * Can the current \PhpOffice\PhpPresentation\Reader\ReaderInterface read the file? - * - * @param string $pFilename - * @throws \Exception - * @return boolean + * @var string|null */ - public function canRead($pFilename) + private $filename; + + /** + * Can the current \PhpOffice\PhpPresentation\Reader\ReaderInterface read the file? + */ + public function canRead(string $pFilename): bool { return $this->fileSupportsUnserializePhpPresentation($pFilename); } @@ -379,15 +392,13 @@ class PowerPoint97 implements ReaderInterface /** * Does a file support UnserializePhpPresentation ? * - * @param string $pFilename - * @throws \Exception - * @return boolean + * @throws FileNotFoundException */ - public function fileSupportsUnserializePhpPresentation($pFilename = '') + public function fileSupportsUnserializePhpPresentation(string $pFilename = ''): bool { // Check if file exists if (!file_exists($pFilename)) { - throw new \Exception("Could not open " . $pFilename . " for reading! File does not exist."); + throw new FileNotFoundException($pFilename); } try { @@ -395,43 +406,40 @@ class PowerPoint97 implements ReaderInterface $ole = new OLERead(); // get excel data $ole->read($pFilename); + return true; - } catch (\Exception $e) { + } catch (Exception $e) { return false; } } /** - * Loads PhpPresentation Serialized file + * Loads PhpPresentation Serialized file. * - * @param string $pFilename - * @return \PhpOffice\PhpPresentation\PhpPresentation - * @throws \Exception + * @throws InvalidFileFormatException */ - public function load($pFilename) + public function load(string $pFilename): PhpPresentation { // Unserialize... First make sure the file supports it! if (!$this->fileSupportsUnserializePhpPresentation($pFilename)) { - throw new \Exception("Invalid file format for PhpOffice\PhpPresentation\Reader\PowerPoint97: " . $pFilename . "."); + throw new InvalidFileFormatException($pFilename, PowerPoint97::class); } - return $this->loadFile($pFilename); + $this->filename = $pFilename; + + return $this->loadFile(); } /** * Load PhpPresentation Serialized file - * - * @param string $pFilename - * @return \PhpOffice\PhpPresentation\PhpPresentation - * @throws \Exception */ - private function loadFile($pFilename) + private function loadFile(): PhpPresentation { $this->oPhpPresentation = new PhpPresentation(); $this->oPhpPresentation->removeSlideByIndex(); // Read OLE Blocks - $this->loadOLE($pFilename); + $this->loadOLE(); // Read pictures in the Pictures Stream $this->loadPicturesStream(); // Read information in the Current User Stream @@ -444,14 +452,12 @@ class PowerPoint97 implements ReaderInterface /** * Read OLE Part - * @param string $pFilename - * @throws \Exception */ - private function loadOLE($pFilename) + private function loadOLE(): void { // OLE reader $oOLE = new OLERead(); - $oOLE->read($pFilename); + $oOLE->read($this->filename); // PowerPoint Document Stream $this->streamPowerpointDocument = $oOLE->getStream($oOLE->powerpointDocument); @@ -459,21 +465,18 @@ class PowerPoint97 implements ReaderInterface // Current User Stream $this->streamCurrentUser = $oOLE->getStream($oOLE->currentUser); - // Get summary information data - $this->streamSummaryInformation = $oOLE->getStream($oOLE->summaryInformation); - - // Get additional document summary information data - $this->streamDocumentSummaryInformation = $oOLE->getStream($oOLE->docSummaryInfos); - // Get pictures data $this->streamPictures = $oOLE->getStream($oOLE->pictures); } /** - * Stream Pictures - * @link http://msdn.microsoft.com/en-us/library/dd920746(v=office.12).aspx + * Stream Pictures. + * + * @throws FeatureNotImplementedException + * + * @see http://msdn.microsoft.com/en-us/library/dd920746(v=office.12).aspx */ - private function loadPicturesStream() + private function loadPicturesStream(): void { $stream = $this->streamPictures; @@ -482,11 +485,11 @@ class PowerPoint97 implements ReaderInterface $arrayRH = $this->loadRecordHeader($stream, $pos); $pos += 8; $readSuccess = false; - if ($arrayRH['recVer'] == 0x00 && ($arrayRH['recType'] == 0xF007 || ($arrayRH['recType'] >= 0xF018 && $arrayRH['recType'] <= 0xF117))) { + if (0x00 == $arrayRH['recVer'] && (0xF007 == $arrayRH['recType'] || ($arrayRH['recType'] >= 0xF018 && $arrayRH['recType'] <= 0xF117))) { //@link : http://msdn.microsoft.com/en-us/library/dd950560(v=office.12).aspx - if ($arrayRH['recType'] == 0xF007) { + if (0xF007 == $arrayRH['recType']) { // OfficeArtFBSE - throw new \Exception('Feature not implemented (l.'.__LINE__.')'); + throw new FeatureNotImplementedException(); } if ($arrayRH['recType'] >= 0xF018 && $arrayRH['recType'] <= 0xF117) { $arrayRecord = $this->readRecordOfficeArtBlip($stream, $pos - 8); @@ -497,39 +500,44 @@ class PowerPoint97 implements ReaderInterface } $readSuccess = true; } - } while ($readSuccess === true); + } while (true === $readSuccess); } /** - * Stream Current User - * @link http://msdn.microsoft.com/en-us/library/dd908567(v=office.12).aspx + * Stream Current User. + * + * @throws FeatureNotImplementedException + * @throws InvalidFileFormatException + * + * @see http://msdn.microsoft.com/en-us/library/dd908567(v=office.12).aspx */ - private function loadCurrentUserStream() + private function loadCurrentUserStream(): void { $pos = 0; /** - * CurrentUserAtom : http://msdn.microsoft.com/en-us/library/dd948895(v=office.12).aspx + * CurrentUserAtom : http://msdn.microsoft.com/en-us/library/dd948895(v=office.12).aspx. */ // RecordHeader : http://msdn.microsoft.com/en-us/library/dd926377(v=office.12).aspx $rHeader = $this->loadRecordHeader($this->streamCurrentUser, $pos); $pos += 8; - if ($rHeader['recVer'] != 0x0 || $rHeader['recInstance'] != 0x000 || $rHeader['recType'] != self::RT_CURRENTUSERATOM) { - throw new \Exception('File PowerPoint 97 in error (Location : CurrentUserAtom > RecordHeader).'); + if (0x0 != $rHeader['recVer'] || 0x000 != $rHeader['recInstance'] || self::RT_CURRENTUSERATOM != $rHeader['recType']) { + throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : CurrentUserAtom > RecordHeader'); } // Size $size = self::getInt4d($this->streamCurrentUser, $pos); $pos += 4; - if ($size != 0x00000014) { - throw new \Exception('File PowerPoint 97 in error (Location : CurrentUserAtom > Size).'); + if (0x00000014 != $size) { + throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : CurrentUserAtom > Size'); } // headerToken $headerToken = self::getInt4d($this->streamCurrentUser, $pos); $pos += 4; - if ($headerToken == 0xF3D1C4DF && $headerToken != 0xE391C05F) { - throw new \Exception('Feature not implemented (l.'.__LINE__.') : Encrypted file'); + if (0xF3D1C4DF == $headerToken && 0xE391C05F != $headerToken) { + // Encrypted file + throw new FeatureNotImplementedException(); } // offsetToCurrentEdit @@ -540,69 +548,70 @@ class PowerPoint97 implements ReaderInterface $lenUserName = self::getInt2d($this->streamCurrentUser, $pos); $pos += 2; if ($lenUserName > 255) { - throw new \Exception('File PowerPoint 97 in error (Location : CurrentUserAtom > lenUserName).'); + throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : CurrentUserAtom > lenUserName'); } // docFileVersion $docFileVersion = self::getInt2d($this->streamCurrentUser, $pos); $pos += 2; - if ($docFileVersion != 0x03F4) { - throw new \Exception('File PowerPoint 97 in error (Location : CurrentUserAtom > docFileVersion).'); + if (0x03F4 != $docFileVersion) { + throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : CurrentUserAtom > docFileVersion'); } // majorVersion $majorVersion = self::getInt1d($this->streamCurrentUser, $pos); - $pos += 1; - if ($majorVersion != 0x03) { - throw new \Exception('File PowerPoint 97 in error (Location : CurrentUserAtom > majorVersion).'); + ++$pos; + if (0x03 != $majorVersion) { + throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : CurrentUserAtom > majorVersion'); } // minorVersion $minorVersion = self::getInt1d($this->streamCurrentUser, $pos); - $pos += 1; - if ($minorVersion != 0x00) { - throw new \Exception('File PowerPoint 97 in error (Location : CurrentUserAtom > minorVersion).'); + ++$pos; + if (0x00 != $minorVersion) { + throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : CurrentUserAtom > minorVersion'); } // unused $pos += 2; // ansiUserName - $ansiUserName = ''; + // $ansiUserName = ''; do { $char = self::getInt1d($this->streamCurrentUser, $pos); if (($char >= 0x00 && $char <= 0x1F) || ($char >= 0x7F && $char <= 0x9F)) { $char = false; } else { - $ansiUserName .= chr($char); - $pos += 1; + // $ansiUserName .= chr($char); + ++$pos; } - } while ($char !== false); + } while (false !== $char); // relVersion $relVersion = self::getInt4d($this->streamCurrentUser, $pos); $pos += 4; - if ($relVersion != 0x00000008 && $relVersion != 0x00000009) { - throw new \Exception('File PowerPoint 97 in error (Location : CurrentUserAtom > relVersion).'); + if (0x00000008 != $relVersion && 0x00000009 != $relVersion) { + throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : CurrentUserAtom > relVersion'); } // unicodeUserName - $unicodeUserName = ''; - for ($inc = 0; $inc < $lenUserName; $inc++) { + // $unicodeUserName = ''; + for ($inc = 0; $inc < $lenUserName; ++$inc) { $char = self::getInt2d($this->streamCurrentUser, $pos); if (($char >= 0x00 && $char <= 0x1F) || ($char >= 0x7F && $char <= 0x9F)) { break; } - $unicodeUserName .= chr($char); + // $unicodeUserName .= chr($char); $pos += 2; } } /** - * Stream Powerpoint Document - * @link http://msdn.microsoft.com/en-us/library/dd921564(v=office.12).aspx + * Stream Powerpoint Document. + * + * @see http://msdn.microsoft.com/en-us/library/dd921564(v=office.12).aspx */ - private function loadPowerpointDocumentStream() + private function loadPowerpointDocumentStream(): void { $this->readRecordUserEditAtom($this->streamPowerpointDocument, $this->offsetToCurrentEdit); @@ -611,11 +620,11 @@ class PowerPoint97 implements ReaderInterface foreach ($this->rgPersistDirEntry as $offsetDir) { $pos = $offsetDir; - $rh = $this->loadRecordHeader($this->streamPowerpointDocument, $pos); + $rHeader = $this->loadRecordHeader($this->streamPowerpointDocument, $pos); $pos += 8; - $this->inMainType = $rh['recType']; + $this->inMainType = $rHeader['recType']; $this->currentNote = null; - switch ($rh['recType']) { + switch ($rHeader['recType']) { case self::RT_DOCUMENT: $this->readRecordDocumentContainer($this->streamPowerpointDocument, $pos); break; @@ -626,63 +635,50 @@ class PowerPoint97 implements ReaderInterface $this->readRecordSlideContainer($this->streamPowerpointDocument, $pos); break; default: - // throw new \Exception('Feature not implemented : l.'.__LINE__.'('.dechex($rh['recType']).')'); break; } } } /** - * Read a record header - * @param string $stream - * @param integer $pos - * @return array + * Read a record header. + * + * @return array */ - private function loadRecordHeader($stream, $pos) + private function loadRecordHeader(string $stream, int $pos): array { $rec = self::getInt2d($stream, $pos); $recType = self::getInt2d($stream, $pos + 2); $recLen = self::getInt4d($stream, $pos + 4); - return array( + + return [ 'recVer' => ($rec >> 0) & bindec('1111'), 'recInstance' => ($rec >> 4) & bindec('111111111111'), 'recType' => $recType, 'recLen' => $recLen, - ); + ]; } /** - * Read 8-bit unsigned integer - * - * @param string $data - * @param int $pos - * @return int + * Read 8-bit unsigned integer. */ - public static function getInt1d($data, $pos) + public static function getInt1d(string $data, int $pos): int { return ord($data[$pos]); } /** - * Read 16-bit unsigned integer - * - * @param string $data - * @param int $pos - * @return int + * Read 16-bit unsigned integer. */ - public static function getInt2d($data, $pos) + public static function getInt2d(string $data, int $pos): int { - return ord($data[$pos]) | (ord($data[$pos+1]) << 8); + return ord($data[$pos]) | (ord($data[$pos + 1]) << 8); } /** - * Read 32-bit signed integer - * - * @param string $data - * @param int $pos - * @return int + * Read 32-bit signed integer. */ - public static function getInt4d($data, $pos) + public static function getInt4d(string $data, int $pos): int { // FIX: represent numbers correctly on 64-bit system // http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334 @@ -694,30 +690,32 @@ class PowerPoint97 implements ReaderInterface // negative number $ord24 = -abs((256 - $or24) << 24); } - return ord($data[$pos]) | (ord($data[$pos+1]) << 8) | (ord($data[$pos+2]) << 16) | $ord24; + + return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $ord24; } /** * A container record that specifies the animation and sound information for a shape. - * @param string $stream - * @param integer $pos - * @return array - * @throws \Exception - * @link https://msdn.microsoft.com/en-us/library/dd772900(v=office.12).aspx + * + * @return array + * + * @throws FeatureNotImplementedException + * + * @see https://msdn.microsoft.com/en-us/library/dd772900(v=office.12).aspx */ - private function readRecordAnimationInfoContainer($stream, $pos) + private function readRecordAnimationInfoContainer(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0xF && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_ANIMATIONINFO) { + if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_ANIMATIONINFO == $data['recType']) { // Record Header $arrayReturn['length'] += 8; // animationAtom // animationSound - throw new \Exception('Feature not implemented (l.'.__LINE__.')'); + throw new FeatureNotImplementedException(); } return $arrayReturn; @@ -725,27 +723,28 @@ class PowerPoint97 implements ReaderInterface /** * A container record that specifies information about the document. - * @param string $stream - * @param integer $pos - * @throws \Exception - * @link http://msdn.microsoft.com/en-us/library/dd947357(v=office.12).aspx + * + * @throws FeatureNotImplementedException + * @throws InvalidFileFormatException + * + * @see http://msdn.microsoft.com/en-us/library/dd947357(v=office.12).aspx */ - private function readRecordDocumentContainer($stream, $pos) + private function readRecordDocumentContainer(string $stream, int $pos): void { $documentAtom = $this->loadRecordHeader($stream, $pos); $pos += 8; - if ($documentAtom['recVer'] != 0x1 || $documentAtom['recInstance'] != 0x000 || $documentAtom['recType'] != self::RT_DOCUMENTATOM) { - throw new \Exception('File PowerPoint 97 in error (Location : RTDocument > DocumentAtom).'); + if (0x1 != $documentAtom['recVer'] || 0x000 != $documentAtom['recInstance'] || self::RT_DOCUMENTATOM != $documentAtom['recType']) { + throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : RTDocument > DocumentAtom'); } $pos += $documentAtom['recLen']; $exObjList = $this->loadRecordHeader($stream, $pos); - if ($exObjList['recVer'] == 0xF && $exObjList['recInstance'] == 0x000 && $exObjList['recType'] == self::RT_EXTERNALOBJECTLIST) { + if (0xF == $exObjList['recVer'] && 0x000 == $exObjList['recInstance'] && self::RT_EXTERNALOBJECTLIST == $exObjList['recType']) { $pos += 8; // exObjListAtom > rh $exObjListAtom = $this->loadRecordHeader($stream, $pos); - if ($exObjListAtom['recVer'] != 0x0 || $exObjListAtom['recInstance'] != 0x000 || $exObjListAtom['recType'] != self::RT_EXTERNALOBJECTLISTATOM || $exObjListAtom['recLen'] != 0x00000004) { - throw new \Exception('File PowerPoint 97 in error (Location : RTDocument > DocumentAtom > exObjList > exObjListAtom).'); + if (0x0 != $exObjListAtom['recVer'] || 0x000 != $exObjListAtom['recInstance'] || self::RT_EXTERNALOBJECTLISTATOM != $exObjListAtom['recType'] || 0x00000004 != $exObjListAtom['recLen']) { + throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : RTDocument > DocumentAtom > exObjList > exObjListAtom'); } $pos += 8; // exObjListAtom > exObjIdSeed @@ -761,8 +760,8 @@ class PowerPoint97 implements ReaderInterface //@link : http://msdn.microsoft.com/en-us/library/dd944995(v=office.12).aspx // exHyperlinkAtom > rh $exHyperlinkAtom = $this->loadRecordHeader($stream, $pos); - if ($exHyperlinkAtom['recVer'] != 0x0 || $exHyperlinkAtom['recInstance'] != 0x000 || $exHyperlinkAtom['recType'] != self::RT_EXTERNALHYPERLINKATOM || $exObjListAtom['recLen'] != 0x00000004) { - throw new \Exception('File PowerPoint 97 in error (Location : RTDocument > DocumentAtom > exObjList > rgChildRec > RT_ExternalHyperlink).'); + if (0x0 != $exHyperlinkAtom['recVer'] || 0x000 != $exHyperlinkAtom['recInstance'] || self::RT_EXTERNALHYPERLINKATOM != $exHyperlinkAtom['recType'] || 0x00000004 != $exObjListAtom['recLen']) { + throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : RTDocument > DocumentAtom > exObjList > rgChildRec > RT_ExternalHyperlink'); } $pos += 8; $exObjList['recLen'] -= 8; @@ -771,14 +770,14 @@ class PowerPoint97 implements ReaderInterface $pos += 4; $exObjList['recLen'] -= 4; - $this->arrayHyperlinks[$exHyperlinkId] = array(); + $this->arrayHyperlinks[$exHyperlinkId] = []; // friendlyNameAtom - $friendlyNameAtom = $this->loadRecordHeader($stream, $pos); - if ($friendlyNameAtom['recVer'] == 0x0 && $friendlyNameAtom['recInstance'] == 0x000 && $friendlyNameAtom['recType'] == self::RT_CSTRING && $friendlyNameAtom['recLen'] % 2 == 0) { + $friendlyNameAtom = $this->loadRecordHeader($stream, $pos); + if (0x0 == $friendlyNameAtom['recVer'] && 0x000 == $friendlyNameAtom['recInstance'] && self::RT_CSTRING == $friendlyNameAtom['recType'] && $friendlyNameAtom['recLen'] % 2 == 0) { $pos += 8; $exObjList['recLen'] -= 8; $this->arrayHyperlinks[$exHyperlinkId]['text'] = ''; - for ($inc = 0; $inc < ($friendlyNameAtom['recLen'] / 2); $inc++) { + for ($inc = 0; $inc < ($friendlyNameAtom['recLen'] / 2); ++$inc) { $char = self::getInt2d($stream, $pos); $pos += 2; $exObjList['recLen'] -= 2; @@ -786,12 +785,12 @@ class PowerPoint97 implements ReaderInterface } } // targetAtom - $targetAtom = $this->loadRecordHeader($stream, $pos); - if ($targetAtom['recVer'] == 0x0 && $targetAtom['recInstance'] == 0x001 && $targetAtom['recType'] == self::RT_CSTRING && $targetAtom['recLen'] % 2 == 0) { + $targetAtom = $this->loadRecordHeader($stream, $pos); + if (0x0 == $targetAtom['recVer'] && 0x001 == $targetAtom['recInstance'] && self::RT_CSTRING == $targetAtom['recType'] && $targetAtom['recLen'] % 2 == 0) { $pos += 8; $exObjList['recLen'] -= 8; $this->arrayHyperlinks[$exHyperlinkId]['url'] = ''; - for ($inc = 0; $inc < ($targetAtom['recLen'] / 2); $inc++) { + for ($inc = 0; $inc < ($targetAtom['recLen'] / 2); ++$inc) { $char = self::getInt2d($stream, $pos); $pos += 2; $exObjList['recLen'] -= 2; @@ -799,12 +798,12 @@ class PowerPoint97 implements ReaderInterface } } // locationAtom - $locationAtom = $this->loadRecordHeader($stream, $pos); - if ($locationAtom['recVer'] == 0x0 && $locationAtom['recInstance'] == 0x003 && $locationAtom['recType'] == self::RT_CSTRING && $locationAtom['recLen'] % 2 == 0) { + $locationAtom = $this->loadRecordHeader($stream, $pos); + if (0x0 == $locationAtom['recVer'] && 0x003 == $locationAtom['recInstance'] && self::RT_CSTRING == $locationAtom['recType'] && $locationAtom['recLen'] % 2 == 0) { $pos += 8; $exObjList['recLen'] -= 8; $string = ''; - for ($inc = 0; $inc < ($locationAtom['recLen'] / 2); $inc++) { + for ($inc = 0; $inc < ($locationAtom['recLen'] / 2); ++$inc) { $char = self::getInt2d($stream, $pos); $pos += 2; $exObjList['recLen'] -= 2; @@ -813,35 +812,36 @@ class PowerPoint97 implements ReaderInterface } break; default: - throw new \Exception('Feature not implemented (l.'.__LINE__.' : '.dechex($childRec['recType'].')')); + // var_dump(dechex((int) $childRec['recType'])); + throw new FeatureNotImplementedException(); } } while ($exObjList['recLen'] > 0); } //@link : http://msdn.microsoft.com/en-us/library/dd907813(v=office.12).aspx $documentTextInfo = $this->loadRecordHeader($stream, $pos); - if ($documentTextInfo['recVer'] == 0xF && $documentTextInfo['recInstance'] == 0x000 && $documentTextInfo['recType'] == self::RT_ENVIRONMENT) { + if (0xF == $documentTextInfo['recVer'] && 0x000 == $documentTextInfo['recInstance'] && self::RT_ENVIRONMENT == $documentTextInfo['recType']) { $pos += 8; //@link : http://msdn.microsoft.com/en-us/library/dd952717(v=office.12).aspx $kinsoku = $this->loadRecordHeader($stream, $pos); - if ($kinsoku['recVer'] == 0xF && $kinsoku['recInstance'] == 0x002 && $kinsoku['recType'] == self::RT_KINSOKU) { + if (0xF == $kinsoku['recVer'] && 0x002 == $kinsoku['recInstance'] && self::RT_KINSOKU == $kinsoku['recType']) { $pos += 8; $pos += $kinsoku['recLen']; } //@link : http://msdn.microsoft.com/en-us/library/dd948152(v=office.12).aspx $fontCollection = $this->loadRecordHeader($stream, $pos); - if ($fontCollection['recVer'] == 0xF && $fontCollection['recInstance'] == 0x000 && $fontCollection['recType'] == self::RT_FONTCOLLECTION) { + if (0xF == $fontCollection['recVer'] && 0x000 == $fontCollection['recInstance'] && self::RT_FONTCOLLECTION == $fontCollection['recType']) { $pos += 8; do { $fontEntityAtom = $this->loadRecordHeader($stream, $pos); $pos += 8; $fontCollection['recLen'] -= 8; - if ($fontEntityAtom['recVer'] != 0x0 || $fontEntityAtom['recInstance'] > 128 || $fontEntityAtom['recType'] != self::RT_FONTENTITYATOM) { - throw new \Exception('File PowerPoint 97 in error (Location : RTDocument > RT_Environment > RT_FontCollection > RT_FontEntityAtom).'); + if (0x0 != $fontEntityAtom['recVer'] || $fontEntityAtom['recInstance'] > 128 || self::RT_FONTENTITYATOM != $fontEntityAtom['recType']) { + throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : RTDocument > RT_Environment > RT_FontCollection > RT_FontEntityAtom'); } $string = ''; - for ($inc = 0; $inc < 32; $inc++) { + for ($inc = 0; $inc < 32; ++$inc) { $char = self::getInt2d($stream, $pos); $pos += 2; $fontCollection['recLen'] -= 2; @@ -850,28 +850,28 @@ class PowerPoint97 implements ReaderInterface $this->arrayFonts[] = $string; // lfCharSet (1 byte) - $pos += 1; - $fontCollection['recLen'] -= 1; + ++$pos; + --$fontCollection['recLen']; // fEmbedSubsetted (1 bit) // unused (7 bits) - $pos += 1; - $fontCollection['recLen'] -= 1; + ++$pos; + --$fontCollection['recLen']; // rasterFontType (1 bit) // deviceFontType (1 bit) // truetypeFontType (1 bit) // fNoFontSubstitution (1 bit) // reserved (4 bits) - $pos += 1; - $fontCollection['recLen'] -= 1; + ++$pos; + --$fontCollection['recLen']; // lfPitchAndFamily (1 byte) - $pos += 1; - $fontCollection['recLen'] -= 1; + ++$pos; + --$fontCollection['recLen']; $fontEmbedData1 = $this->loadRecordHeader($stream, $pos); - if ($fontEmbedData1['recVer'] == 0x0 && $fontEmbedData1['recInstance'] >= 0x000 && $fontEmbedData1['recInstance'] <= 0x003 && $fontEmbedData1['recType'] == self::RT_FONTEMBEDDATABLOB) { + if (0x0 == $fontEmbedData1['recVer'] && $fontEmbedData1['recInstance'] >= 0x000 && $fontEmbedData1['recInstance'] <= 0x003 && self::RT_FONTEMBEDDATABLOB == $fontEmbedData1['recType']) { $pos += 8; $fontCollection['recLen'] -= 8; $pos += $fontEmbedData1['recLen']; @@ -879,7 +879,7 @@ class PowerPoint97 implements ReaderInterface } $fontEmbedData2 = $this->loadRecordHeader($stream, $pos); - if ($fontEmbedData2['recVer'] == 0x0 && $fontEmbedData2['recInstance'] >= 0x000 && $fontEmbedData2['recInstance'] <= 0x003 && $fontEmbedData2['recType'] == self::RT_FONTEMBEDDATABLOB) { + if (0x0 == $fontEmbedData2['recVer'] && $fontEmbedData2['recInstance'] >= 0x000 && $fontEmbedData2['recInstance'] <= 0x003 && self::RT_FONTEMBEDDATABLOB == $fontEmbedData2['recType']) { $pos += 8; $fontCollection['recLen'] -= 8; $pos += $fontEmbedData2['recLen']; @@ -887,7 +887,7 @@ class PowerPoint97 implements ReaderInterface } $fontEmbedData3 = $this->loadRecordHeader($stream, $pos); - if ($fontEmbedData3['recVer'] == 0x0 && $fontEmbedData3['recInstance'] >= 0x000 && $fontEmbedData3['recInstance'] <= 0x003 && $fontEmbedData3['recType'] == self::RT_FONTEMBEDDATABLOB) { + if (0x0 == $fontEmbedData3['recVer'] && $fontEmbedData3['recInstance'] >= 0x000 && $fontEmbedData3['recInstance'] <= 0x003 && self::RT_FONTEMBEDDATABLOB == $fontEmbedData3['recType']) { $pos += 8; $fontCollection['recLen'] -= 8; $pos += $fontEmbedData3['recLen']; @@ -895,7 +895,7 @@ class PowerPoint97 implements ReaderInterface } $fontEmbedData4 = $this->loadRecordHeader($stream, $pos); - if ($fontEmbedData4['recVer'] == 0x0 && $fontEmbedData4['recInstance'] >= 0x000 && $fontEmbedData4['recInstance'] <= 0x003 && $fontEmbedData4['recType'] == self::RT_FONTEMBEDDATABLOB) { + if (0x0 == $fontEmbedData4['recVer'] && $fontEmbedData4['recInstance'] >= 0x000 && $fontEmbedData4['recInstance'] <= 0x003 && self::RT_FONTEMBEDDATABLOB == $fontEmbedData4['recType']) { $pos += 8; $fontCollection['recLen'] -= 8; $pos += $fontEmbedData4['recLen']; @@ -905,81 +905,81 @@ class PowerPoint97 implements ReaderInterface } $textCFDefaultsAtom = $this->loadRecordHeader($stream, $pos); - if ($textCFDefaultsAtom['recVer'] == 0x0 && $textCFDefaultsAtom['recInstance'] == 0x000 && $textCFDefaultsAtom['recType'] == self::RT_TEXTCHARFORMATEXCEPTIONATOM) { + if (0x0 == $textCFDefaultsAtom['recVer'] && 0x000 == $textCFDefaultsAtom['recInstance'] && self::RT_TEXTCHARFORMATEXCEPTIONATOM == $textCFDefaultsAtom['recType']) { $pos += 8; $pos += $textCFDefaultsAtom['recLen']; } $textPFDefaultsAtom = $this->loadRecordHeader($stream, $pos); - if ($textPFDefaultsAtom['recVer'] == 0x0 && $textPFDefaultsAtom['recInstance'] == 0x000 && $textPFDefaultsAtom['recType'] == self::RT_TEXTPARAGRAPHFORMATEXCEPTIONATOM) { + if (0x0 == $textPFDefaultsAtom['recVer'] && 0x000 == $textPFDefaultsAtom['recInstance'] && self::RT_TEXTPARAGRAPHFORMATEXCEPTIONATOM == $textPFDefaultsAtom['recType']) { $pos += 8; $pos += $textPFDefaultsAtom['recLen']; } $defaultRulerAtom = $this->loadRecordHeader($stream, $pos); - if ($defaultRulerAtom['recVer'] == 0x0 && $defaultRulerAtom['recInstance'] == 0x000 && $defaultRulerAtom['recType'] == self::RT_DEFAULTRULERATOM) { + if (0x0 == $defaultRulerAtom['recVer'] && 0x000 == $defaultRulerAtom['recInstance'] && self::RT_DEFAULTRULERATOM == $defaultRulerAtom['recType']) { $pos += 8; $pos += $defaultRulerAtom['recLen']; } $textSIDefaultsAtom = $this->loadRecordHeader($stream, $pos); - if ($textSIDefaultsAtom['recVer'] == 0x0 && $textSIDefaultsAtom['recInstance'] == 0x000 && $textSIDefaultsAtom['recType'] == self::RT_TEXTSPECIALINFODEFAULTATOM) { + if (0x0 == $textSIDefaultsAtom['recVer'] && 0x000 == $textSIDefaultsAtom['recInstance'] && self::RT_TEXTSPECIALINFODEFAULTATOM == $textSIDefaultsAtom['recType']) { $pos += 8; $pos += $textSIDefaultsAtom['recLen']; } $textMasterStyleAtom = $this->loadRecordHeader($stream, $pos); - if ($textMasterStyleAtom['recVer'] == 0x0 && $textMasterStyleAtom['recType'] == self::RT_TEXTMASTERSTYLEATOM) { + if (0x0 == $textMasterStyleAtom['recVer'] && self::RT_TEXTMASTERSTYLEATOM == $textMasterStyleAtom['recType']) { $pos += 8; $pos += $textMasterStyleAtom['recLen']; } } $soundCollection = $this->loadRecordHeader($stream, $pos); - if ($soundCollection['recVer'] == 0xF && $soundCollection['recInstance'] == 0x005 && $soundCollection['recType'] == self::RT_SOUNDCOLLECTION) { + if (0xF == $soundCollection['recVer'] && 0x005 == $soundCollection['recInstance'] && self::RT_SOUNDCOLLECTION == $soundCollection['recType']) { $pos += 8; $pos += $soundCollection['recLen']; } $drawingGroup = $this->loadRecordHeader($stream, $pos); - if ($drawingGroup['recVer'] == 0xF && $drawingGroup['recInstance'] == 0x000 && $drawingGroup['recType'] == self::RT_DRAWINGGROUP) { + if (0xF == $drawingGroup['recVer'] && 0x000 == $drawingGroup['recInstance'] && self::RT_DRAWINGGROUP == $drawingGroup['recType']) { $drawing = $this->readRecordDrawingGroupContainer($stream, $pos); $pos += 8; $pos += $drawing['length']; } $masterList = $this->loadRecordHeader($stream, $pos); - if ($masterList['recVer'] == 0xF && $masterList['recInstance'] == 0x001 && $masterList['recType'] == self::RT_SLIDELISTWITHTEXT) { + if (0xF == $masterList['recVer'] && 0x001 == $masterList['recInstance'] && self::RT_SLIDELISTWITHTEXT == $masterList['recType']) { $pos += 8; $pos += $masterList['recLen']; } $docInfoList = $this->loadRecordHeader($stream, $pos); - if ($docInfoList['recVer'] == 0xF && $docInfoList['recInstance'] == 0x000 && $docInfoList['recType'] == self::RT_LIST) { + if (0xF == $docInfoList['recVer'] && 0x000 == $docInfoList['recInstance'] && self::RT_LIST == $docInfoList['recType']) { $pos += 8; $pos += $docInfoList['recLen']; } $slideHF = $this->loadRecordHeader($stream, $pos); - if ($slideHF['recVer'] == 0xF && $slideHF['recInstance'] == 0x003 && $slideHF['recType'] == self::RT_HEADERSFOOTERS) { + if (0xF == $slideHF['recVer'] && 0x003 == $slideHF['recInstance'] && self::RT_HEADERSFOOTERS == $slideHF['recType']) { $pos += 8; $pos += $slideHF['recLen']; } $notesHF = $this->loadRecordHeader($stream, $pos); - if ($notesHF['recVer'] == 0xF && $notesHF['recInstance'] == 0x004 && $notesHF['recType'] == self::RT_HEADERSFOOTERS) { + if (0xF == $notesHF['recVer'] && 0x004 == $notesHF['recInstance'] && self::RT_HEADERSFOOTERS == $notesHF['recType']) { $pos += 8; $pos += $notesHF['recLen']; } // SlideListWithTextContainer $slideList = $this->loadRecordHeader($stream, $pos); - if ($slideList['recVer'] == 0xF && $slideList['recInstance'] == 0x000 && $slideList['recType'] == self::RT_SLIDELISTWITHTEXT) { + if (0xF == $slideList['recVer'] && 0x000 == $slideList['recInstance'] && self::RT_SLIDELISTWITHTEXT == $slideList['recType']) { $pos += 8; do { // SlideListWithTextSubContainerOrAtom $rhSlideList = $this->loadRecordHeader($stream, $pos); - if ($rhSlideList['recVer'] == 0x0 && $rhSlideList['recInstance'] == 0x000 && $rhSlideList['recType'] == self::RT_SLIDEPERSISTATOM && $rhSlideList['recLen'] == 0x00000014) { + if (0x0 == $rhSlideList['recVer'] && 0x000 == $rhSlideList['recInstance'] && self::RT_SLIDEPERSISTATOM == $rhSlideList['recType'] && 0x00000014 == $rhSlideList['recLen']) { $pos += 8; $slideList['recLen'] -= 8; // persistIdRef @@ -993,7 +993,7 @@ class PowerPoint97 implements ReaderInterface $slideList['recLen'] -= 4; // slideId $slideId = self::getInt4d($stream, $pos); - if ($slideId == -2147483648) { + if (-2147483648 == $slideId) { $slideId = 0; } if ($slideId > 0) { @@ -1011,60 +1011,63 @@ class PowerPoint97 implements ReaderInterface /** * An atom record that specifies information about a slide. - * @param string $stream - * @param integer $pos - * @return array - * @throws \Exception - * @link https://msdn.microsoft.com/en-us/library/dd923801(v=office.12).aspx + * + * @return array + * + * @see https://msdn.microsoft.com/en-us/library/dd923801(v=office.12).aspx */ - private function readRecordDrawingContainer($stream, $pos) + private function readRecordDrawingContainer(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0xF && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_DRAWING) { + if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_DRAWING == $data['recType']) { // Record Header $arrayReturn['length'] += 8; $officeArtDg = $this->readRecordOfficeArtDgContainer($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $officeArtDg['length']; } + return $arrayReturn; } - private function readRecordDrawingGroupContainer($stream, $pos) + /** + * @return array + */ + private function readRecordDrawingGroupContainer(string $stream, int $pos): array { - - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0xF && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_DRAWINGGROUP) { + if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_DRAWINGGROUP == $data['recType']) { // Record Header $arrayReturn['length'] += 8; $arrayReturn['length'] += $data['recLen']; } + return $arrayReturn; } /** * An atom record that specifies a reference to an external object. - * @param string $stream - * @param integer $pos - * @return array - * @link https://msdn.microsoft.com/en-us/library/dd910388(v=office.12).aspx + * + * @return array + * + * @see https://msdn.microsoft.com/en-us/library/dd910388(v=office.12).aspx */ - private function readRecordExObjRefAtom($stream, $pos) + private function readRecordExObjRefAtom(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_EXTERNALOBJECTREFATOM && $data['recLen'] == 0x00000004) { + if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_EXTERNALOBJECTREFATOM == $data['recType'] && 0x00000004 == $data['recLen']) { // Record Header $arrayReturn['length'] += 8; // Datas @@ -1076,19 +1079,19 @@ class PowerPoint97 implements ReaderInterface /** * An atom record that specifies a type of action to be performed. - * @param string $stream - * @param integer $pos - * @return array - * @link https://msdn.microsoft.com/en-us/library/dd953300(v=office.12).aspx + * + * @return array + * + * @see https://msdn.microsoft.com/en-us/library/dd953300(v=office.12).aspx */ - private function readRecordInteractiveInfoAtom($stream, $pos) + private function readRecordInteractiveInfoAtom(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_INTERACTIVEINFOATOM && $data['recLen'] == 0x00000010) { + if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_INTERACTIVEINFOATOM == $data['recType'] && 0x00000010 == $data['recLen']) { // Record Header $arrayReturn['length'] += 8; // soundIdRef @@ -1097,19 +1100,19 @@ class PowerPoint97 implements ReaderInterface $arrayReturn['exHyperlinkIdRef'] = self::getInt4d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 4; // action - $arrayReturn['length'] += 1; + ++$arrayReturn['length']; // oleVerb - $arrayReturn['length'] += 1; + ++$arrayReturn['length']; // jump - $arrayReturn['length'] += 1; + ++$arrayReturn['length']; // fAnimated (1 bit) // fStopSound (1 bit) // fCustomShowReturn (1 bit) // fVisited (1 bit) // reserved (4 bits) - $arrayReturn['length'] += 1; + ++$arrayReturn['length']; // hyperlinkType - $arrayReturn['length'] += 1; + ++$arrayReturn['length']; // unused $arrayReturn['length'] += 3; } @@ -1119,19 +1122,19 @@ class PowerPoint97 implements ReaderInterface /** * An atom record that specifies the name of a macro, a file name, or a named show. - * @param string $stream - * @param integer $pos - * @return array - * @link https://msdn.microsoft.com/en-us/library/dd925121(v=office.12).aspx + * + * @return array + * + * @see https://msdn.microsoft.com/en-us/library/dd925121(v=office.12).aspx */ - private function readRecordMacroNameAtom($stream, $pos) + private function readRecordMacroNameAtom(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x002 && $data['recType'] == self::RT_CSTRING && $data['recLen'] % 2 == 0) { + if (0x0 == $data['recVer'] && 0x002 == $data['recInstance'] && self::RT_CSTRING == $data['recType'] && $data['recLen'] % 2 == 0) { // Record Header $arrayReturn['length'] += 8; // Datas @@ -1143,19 +1146,19 @@ class PowerPoint97 implements ReaderInterface /** * A container record that specifies what actions to perform when interacting with an object by means of a mouse click. - * @param string $stream - * @param integer $pos - * @return array - * @link https://msdn.microsoft.com/en-us/library/dd952348(v=office.12).aspx + * + * @return array + * + * @see https://msdn.microsoft.com/en-us/library/dd952348(v=office.12).aspx */ - private function readRecordMouseClickInteractiveInfoContainer($stream, $pos) + private function readRecordMouseClickInteractiveInfoContainer(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0xF && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_INTERACTIVEINFO) { + if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_INTERACTIVEINFO == $data['recType']) { // Record Header $arrayReturn['length'] += 8; // interactiveInfoAtom @@ -1174,25 +1177,26 @@ class PowerPoint97 implements ReaderInterface /** * A container record that specifies what actions to perform when interacting with an object by moving the mouse cursor over it. - * @param string $stream - * @param integer $pos - * @return array - * @throws \Exception - * @link https://msdn.microsoft.com/en-us/library/dd925811(v=office.12).aspx + * + * @return array + * + * @throws FeatureNotImplementedException + * + * @see https://msdn.microsoft.com/en-us/library/dd925811(v=office.12).aspx */ - private function readRecordMouseOverInteractiveInfoContainer($stream, $pos) + private function readRecordMouseOverInteractiveInfoContainer(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0xF && $data['recInstance'] == 0x001 && $data['recType'] == self::RT_INTERACTIVEINFO) { + if (0xF == $data['recVer'] && 0x001 == $data['recInstance'] && self::RT_INTERACTIVEINFO == $data['recType']) { // Record Header $arrayReturn['length'] += 8; // interactiveInfoAtom // macroNameAtom - throw new \Exception('Feature not implemented (l.'.__LINE__.')'); + throw new FeatureNotImplementedException(); } return $arrayReturn; @@ -1200,21 +1204,22 @@ class PowerPoint97 implements ReaderInterface /** * The OfficeArtBlip record specifies BLIP file data. - * @param string $stream - * @param integer $pos - * @return array - * @throws \Exception - * @link https://msdn.microsoft.com/en-us/library/dd910081(v=office.12).aspx + * + * @return array{'length': int, 'picture': null|string} + * + * @throws FeatureNotImplementedException + * + * @see https://msdn.microsoft.com/en-us/library/dd910081(v=office.12).aspx */ - private function readRecordOfficeArtBlip($stream, $pos) + private function readRecordOfficeArtBlip(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - 'picture' => null - ); + 'picture' => null, + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0x0 && ($data['recType'] >= 0xF018 && $data['recType'] <= 0xF117)) { + if (0x0 == $data['recVer'] && ($data['recType'] >= 0xF018 && $data['recType'] <= 0xF117)) { // Record Header $arrayReturn['length'] += 8; // Datas @@ -1224,20 +1229,21 @@ class PowerPoint97 implements ReaderInterface // rgbUid1 $arrayReturn['length'] += 16; $data['recLen'] -= 16; - if ($data['recInstance'] == 0x6E1) { + if (0x6E1 == $data['recInstance']) { // rgbUid2 $arrayReturn['length'] += 16; $data['recLen'] -= 16; } // tag - $arrayReturn['length'] += 1; - $data['recLen'] -= 1; + ++$arrayReturn['length']; + --$data['recLen']; // BLIPFileData $arrayReturn['picture'] = substr($this->streamPictures, $pos + $arrayReturn['length'], $data['recLen']); $arrayReturn['length'] += $data['recLen']; break; default: - throw new \Exception('Feature not implemented (l.'.__LINE__.' : '.dechex($data['recType'].')')); + // var_dump(dechex((int) $data['recType'])) + throw new FeatureNotImplementedException(); } } @@ -1246,19 +1252,19 @@ class PowerPoint97 implements ReaderInterface /** * The OfficeArtChildAnchor record specifies four signed integers that specify the anchor for the shape that contains this record. - * @param string $stream - * @param integer $pos - * @return array - * @link https://msdn.microsoft.com/en-us/library/dd922720(v=office.12).aspx + * + * @return array + * + * @see https://msdn.microsoft.com/en-us/library/dd922720(v=office.12).aspx */ - private function readRecordOfficeArtChildAnchor($stream, $pos) + private function readRecordOfficeArtChildAnchor(string $stream, int $pos) { - $arrayReturn = array( - 'length' => 0 - ); + $arrayReturn = [ + 'length' => 0, + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x000 && $data['recType'] == 0xF00F && $data['recLen'] == 0x00000010) { + if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF00F == $data['recType'] && 0x00000010 == $data['recLen']) { // Record Header $arrayReturn['length'] += 8; // Datas @@ -1277,20 +1283,21 @@ class PowerPoint97 implements ReaderInterface /** * An atom record that specifies the location of a shape. - * @param string $stream - * @param integer $pos - * @return array - * @throws \Exception - * @link https://msdn.microsoft.com/en-us/library/dd922797(v=office.12).aspx + * + * @return array + * + * @throws FeatureNotImplementedException + * + * @see https://msdn.microsoft.com/en-us/library/dd922797(v=office.12).aspx */ - private function readRecordOfficeArtClientAnchor($stream, $pos) + private function readRecordOfficeArtClientAnchor(string $stream, int $pos) { - $arrayReturn = array( - 'length' => 0 - ); + $arrayReturn = [ + 'length' => 0, + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x000 && $data['recType'] == 0xF010 && ($data['recLen'] == 0x00000008 || $data['recLen'] == 0x00000010)) { + if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF010 == $data['recType'] && (0x00000008 == $data['recLen'] || 0x00000010 == $data['recLen'])) { // Record Header $arrayReturn['length'] += 8; // Datas @@ -1307,7 +1314,8 @@ class PowerPoint97 implements ReaderInterface $pos += 8; break; case 0x00000010: - throw new \Exception('PowerPoint97 Reader : record OfficeArtClientAnchor (0x00000010)'); + // record OfficeArtClientAnchor (0x00000010) + throw new FeatureNotImplementedException(); } } @@ -1316,46 +1324,47 @@ class PowerPoint97 implements ReaderInterface /** * A container record that specifies text related data for a shape. - * @param string $stream - * @param integer $pos - * @return array - * @throws \Exception - * @link https://msdn.microsoft.com/en-us/library/dd910958(v=office.12).aspx + * + * @return array{'length': int, 'alignH': string|null, 'text': string, 'numParts': int, 'numTexts': int, 'hyperlink': array>, 'part': array{'length': int, 'strLenRT': int, 'partLength': int|float, 'bold': bool, 'italic': bool, 'underline': bool, 'fontName': string, 'fontSize': int, 'color': Color}} + * + * @throws FeatureNotImplementedException + * + * @see https://msdn.microsoft.com/en-us/library/dd910958(v=office.12).aspx */ - private function readRecordOfficeArtClientTextbox($stream, $pos) + private function readRecordOfficeArtClientTextbox(string $stream, int $pos) { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, 'text' => '', 'numParts' => 0, 'numTexts' => 0, - 'hyperlink' => array(), - ); + 'hyperlink' => [], + ]; $data = $this->loadRecordHeader($stream, $pos); // recVer 0xF // Doc : 0x0 https://msdn.microsoft.com/en-us/library/dd910958(v=office.12).aspx // Sample : 0xF https://msdn.microsoft.com/en-us/library/dd953497(v=office.12).aspx - if ($data['recVer'] == 0xF && $data['recInstance'] == 0x000 && $data['recType'] == 0xF00D) { + if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF00D == $data['recType']) { // Record Header $arrayReturn['length'] += 8; // Datas $strLen = 0; do { $rhChild = $this->loadRecordHeader($stream, $pos + $arrayReturn['length']); - /** + /* * @link : https://msdn.microsoft.com/en-us/library/dd947039(v=office.12).aspx */ // echo dechex($rhChild['recType']).'-'.$rhChild['recType'].EOL; switch ($rhChild['recType']) { case self::RT_INTERACTIVEINFO: //@link : http://msdn.microsoft.com/en-us/library/dd948623(v=office.12).aspx - if ($rhChild['recInstance'] == 0x0000) { + if (0x0000 == $rhChild['recInstance']) { $mouseClickInfo = $this->readRecordMouseClickInteractiveInfoContainer($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $mouseClickInfo['length']; $arrayReturn['hyperlink'][]['id'] = $mouseClickInfo['exHyperlinkIdRef']; } - if ($rhChild['recInstance'] == 0x0001) { + if (0x0001 == $rhChild['recInstance']) { $mouseOverInfo = $this->readRecordMouseOverInteractiveInfoContainer($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $mouseOverInfo['length']; } @@ -1367,8 +1376,8 @@ class PowerPoint97 implements ReaderInterface $strLenRT = $strLen + 1; do { $strucTextPFRun = $this->readStructureTextPFRun($stream, $pos + $arrayReturn['length'], $strLenRT); - $arrayReturn['numTexts']++; - $arrayReturn['text'.$arrayReturn['numTexts']] = $strucTextPFRun; + ++$arrayReturn['numTexts']; + $arrayReturn['text' . $arrayReturn['numTexts']] = $strucTextPFRun; if (isset($strucTextPFRun['alignH'])) { $arrayReturn['alignH'] = $strucTextPFRun['alignH']; } @@ -1379,8 +1388,8 @@ class PowerPoint97 implements ReaderInterface $strLenRT = $strLen + 1; do { $strucTextCFRun = $this->readStructureTextCFRun($stream, $pos + $arrayReturn['length'], $strLenRT); - $arrayReturn['numParts']++; - $arrayReturn['part'.$arrayReturn['numParts']] = $strucTextCFRun; + ++$arrayReturn['numParts']; + $arrayReturn['part' . $arrayReturn['numParts']] = $strucTextCFRun; $strLenRT = $strucTextCFRun['strLenRT']; $arrayReturn['length'] += $strucTextCFRun['length']; } while ($strLenRT > 0); @@ -1388,23 +1397,23 @@ class PowerPoint97 implements ReaderInterface case self::RT_TEXTBYTESATOM: $arrayReturn['length'] += 8; // @link : https://msdn.microsoft.com/en-us/library/dd947905(v=office.12).aspx - $strLen = (int)$rhChild['recLen']; - for ($inc = 0; $inc < $strLen; $inc++) { + $strLen = (int) $rhChild['recLen']; + for ($inc = 0; $inc < $strLen; ++$inc) { $char = self::getInt1d($stream, $pos + $arrayReturn['length']); - if ($char == 0x0B) { + if (0x0B == $char) { $char = 0x20; } $arrayReturn['text'] .= Text::chr($char); - $arrayReturn['length'] += 1; + ++$arrayReturn['length']; } break; case self::RT_TEXTCHARSATOM: $arrayReturn['length'] += 8; // @link : http://msdn.microsoft.com/en-us/library/dd772921(v=office.12).aspx - $strLen = (int)($rhChild['recLen']/2); - for ($inc = 0; $inc < $strLen; $inc++) { + $strLen = (int) ($rhChild['recLen'] / 2); + for ($inc = 0; $inc < $strLen; ++$inc) { $char = self::getInt2d($stream, $pos + $arrayReturn['length']); - if ($char == 0x0B) { + if (0x0B == $char) { $char = 0x20; } $arrayReturn['text'] .= Text::chr($char); @@ -1420,16 +1429,16 @@ class PowerPoint97 implements ReaderInterface case self::RT_TEXTINTERACTIVEINFOATOM: $arrayReturn['length'] += 8; //@link : http://msdn.microsoft.com/en-us/library/dd947973(v=office.12).aspx - if ($rhChild['recInstance'] == 0x0000) { + if (0x0000 == $rhChild['recInstance']) { //@todo : MouseClickTextInteractiveInfoAtom - $arrayReturn['hyperlink'][count($arrayReturn['hyperlink']) - 1]['start'] = self::getInt4d($stream, $pos + + $arrayReturn['length']); + $arrayReturn['hyperlink'][count($arrayReturn['hyperlink']) - 1]['start'] = self::getInt4d($stream, $pos + +$arrayReturn['length']); $arrayReturn['length'] += 4; - $arrayReturn['hyperlink'][count($arrayReturn['hyperlink']) - 1]['end'] = self::getInt4d($stream, $pos + + $arrayReturn['length']); + $arrayReturn['hyperlink'][count($arrayReturn['hyperlink']) - 1]['end'] = self::getInt4d($stream, $pos + +$arrayReturn['length']); $arrayReturn['length'] += 4; } - if ($rhChild['recInstance'] == 0x0001) { - throw new \Exception('Feature not implemented (l.'.__LINE__.')'); + if (0x0001 == $rhChild['recInstance']) { + throw new FeatureNotImplementedException(); } break; case self::RT_TEXTSPECIALINFOATOM: @@ -1455,30 +1464,31 @@ class PowerPoint97 implements ReaderInterface default: $arrayReturn['length'] += 8; $arrayReturn['length'] += $rhChild['recLen']; - // throw new \Exception('Feature not implemented (l.'.__LINE__.' : 0x'.dechex($rhChild['recType']).')'); } } while (($data['recLen'] - $arrayReturn['length']) > 0); } + return $arrayReturn; } /** * The OfficeArtSpContainer record specifies a shape container. - * @param string $stream - * @param integer $pos - * @return array - * @throws \Exception - * @link https://msdn.microsoft.com/en-us/library/dd943794(v=office.12).aspx + * + * @return array{'length': int, 'shape': null|AbstractShape} + * + * @throws InvalidFileFormatException + * + * @see https://msdn.microsoft.com/en-us/library/dd943794(v=office.12).aspx */ - private function readRecordOfficeArtSpContainer($stream, $pos) + private function readRecordOfficeArtSpContainer(string $stream, int $pos) { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, 'shape' => null, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0xF && $data['recInstance'] == 0x000 && $data['recType'] == 0xF004) { + if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF004 == $data['recType']) { // Record Header $arrayReturn['length'] += 8; // shapeGroup @@ -1487,12 +1497,12 @@ class PowerPoint97 implements ReaderInterface // shapeProp $shapeProp = $this->readRecordOfficeArtFSP($stream, $pos + $arrayReturn['length']); - if ($shapeProp['length'] == 0) { - throw new \Exception('PowerPoint97 Reader : record OfficeArtFSP'); + if (0 == $shapeProp['length']) { + throw new InvalidFileFormatException($this->filename, PowerPoint97::class); } $arrayReturn['length'] += $shapeProp['length']; - if ($shapeProp['fDeleted'] == 0x1 && $shapeProp['fChild'] == 0x0) { + if (0x1 == $shapeProp['fDeleted'] && 0x0 == $shapeProp['fChild']) { // deletedShape $deletedShape = $this->readRecordOfficeArtFPSPL($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $deletedShape['length']; @@ -1527,20 +1537,20 @@ class PowerPoint97 implements ReaderInterface $arrayReturn['length'] += $clientTextbox['length']; // shapeSecondaryOptions2 - if ($shpSecondaryOptions1['length'] == 0) { + if (0 == $shpSecondaryOptions1['length']) { $shpSecondaryOptions2 = $this->readRecordOfficeArtSecondaryFOPT($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $shpSecondaryOptions2['length']; } // shapeTertiaryOptions2 - if ($shpTertiaryOptions1['length'] == 0) { + if (0 == $shpTertiaryOptions1['length']) { $shpTertiaryOptions2 = $this->readRecordOfficeArtTertiaryFOPT($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $shpTertiaryOptions2['length']; } // Core : Shape // Informations about group are not defined - $arrayDimensions = array(); + $arrayDimensions = []; $bIsGroup = false; if (is_object($this->oCurrentGroup)) { if (!$this->bFirstShapeGroup) { @@ -1584,39 +1594,40 @@ class PowerPoint97 implements ReaderInterface $start = 0; $lastLevel = -1; $lastMarginLeft = 0; - for ($inc = 1; $inc <= $clientTextbox['numParts']; $inc++) { - if ($clientTextbox['numParts'] == $clientTextbox['numTexts'] && isset($clientTextbox['text'.$inc])) { - if (isset($clientTextbox['text'.$inc]['bulletChar'])) { + /* @phpstan-ignore-next-line */ + for ($inc = 1; $inc <= $clientTextbox['numParts']; ++$inc) { + if ($clientTextbox['numParts'] == $clientTextbox['numTexts'] && isset($clientTextbox['text' . $inc])) { + if (isset($clientTextbox['text' . $inc]['bulletChar'])) { $arrayReturn['shape']->getActiveParagraph()->getBulletStyle()->setBulletType(Bullet::TYPE_BULLET); - $arrayReturn['shape']->getActiveParagraph()->getBulletStyle()->setBulletChar($clientTextbox['text'.$inc]['bulletChar']); + $arrayReturn['shape']->getActiveParagraph()->getBulletStyle()->setBulletChar($clientTextbox['text' . $inc]['bulletChar']); } // Indent $indent = 0; - if (isset($clientTextbox['text'.$inc]['indent'])) { - $indent = $clientTextbox['text'.$inc]['indent']; + if (isset($clientTextbox['text' . $inc]['indent'])) { + $indent = $clientTextbox['text' . $inc]['indent']; } - if (isset($clientTextbox['text'.$inc]['leftMargin'])) { - if ($lastMarginLeft > $clientTextbox['text'.$inc]['leftMargin']) { - $lastLevel--; + if (isset($clientTextbox['text' . $inc]['leftMargin'])) { + if ($lastMarginLeft > $clientTextbox['text' . $inc]['leftMargin']) { + --$lastLevel; } - if ($lastMarginLeft < $clientTextbox['text'.$inc]['leftMargin']) { - $lastLevel++; + if ($lastMarginLeft < $clientTextbox['text' . $inc]['leftMargin']) { + ++$lastLevel; } $arrayReturn['shape']->getActiveParagraph()->getAlignment()->setLevel($lastLevel); - $lastMarginLeft = $clientTextbox['text'.$inc]['leftMargin']; + $lastMarginLeft = $clientTextbox['text' . $inc]['leftMargin']; - $arrayReturn['shape']->getActiveParagraph()->getAlignment()->setMarginLeft($clientTextbox['text'.$inc]['leftMargin']); - $arrayReturn['shape']->getActiveParagraph()->getAlignment()->setIndent($indent - $clientTextbox['text'.$inc]['leftMargin']); + $arrayReturn['shape']->getActiveParagraph()->getAlignment()->setMarginLeft($clientTextbox['text' . $inc]['leftMargin']); + $arrayReturn['shape']->getActiveParagraph()->getAlignment()->setIndent($indent - $clientTextbox['text' . $inc]['leftMargin']); } } // Texte - $sText = substr(isset($clientTextbox['text']) ? $clientTextbox['text'] : '', $start, $clientTextbox['part'.$inc]['partLength']); + $sText = substr(isset($clientTextbox['text']) ? $clientTextbox['text'] : '', $start, $clientTextbox['part' . $inc]['partLength']); $sHyperlinkURL = ''; if (empty($sText)) { // Is there a hyperlink ? - if (isset($clientTextbox['hyperlink']) && is_array($clientTextbox['hyperlink']) && !empty($clientTextbox['hyperlink'])) { + if (!empty($clientTextbox['hyperlink'])) { foreach ($clientTextbox['hyperlink'] as $itmHyperlink) { - if ($itmHyperlink['start'] == $start && ($itmHyperlink['end'] - $itmHyperlink['start']) == $clientTextbox['part'.$inc]['partLength']) { + if ($itmHyperlink['start'] == $start && ($itmHyperlink['end'] - $itmHyperlink['start']) == (float) $clientTextbox['part' . $inc]['partLength']) { $sText = $this->arrayHyperlinks[$itmHyperlink['id']]['text']; $sHyperlinkURL = $this->arrayHyperlinks[$itmHyperlink['id']]['url']; break; @@ -1626,36 +1637,38 @@ class PowerPoint97 implements ReaderInterface } // New paragraph $bCreateParagraph = false; - if (strpos($sText, "\r") !== false) { + if (false !== strpos($sText, "\r")) { $bCreateParagraph = true; $sText = str_replace("\r", '', $sText); } // TextRun $txtRun = $arrayReturn['shape']->createTextRun($sText); - if (isset($clientTextbox['part'.$inc]['bold'])) { - $txtRun->getFont()->setBold($clientTextbox['part'.$inc]['bold']); + if (isset($clientTextbox['part' . $inc]['bold'])) { + $txtRun->getFont()->setBold($clientTextbox['part' . $inc]['bold']); } - if (isset($clientTextbox['part'.$inc]['italic'])) { - $txtRun->getFont()->setItalic($clientTextbox['part'.$inc]['italic']); + if (isset($clientTextbox['part' . $inc]['italic'])) { + $txtRun->getFont()->setItalic($clientTextbox['part' . $inc]['italic']); } - if (isset($clientTextbox['part'.$inc]['underline'])) { - $txtRun->getFont()->setUnderline($clientTextbox['part'.$inc]['underline']); + if (isset($clientTextbox['part' . $inc]['underline'])) { + $txtRun->getFont()->setUnderline( + $clientTextbox['part' . $inc]['underline'] ? Font::UNDERLINE_SINGLE : Font::UNDERLINE_NONE + ); } - if (isset($clientTextbox['part'.$inc]['fontName'])) { - $txtRun->getFont()->setName($clientTextbox['part'.$inc]['fontName']); + if (isset($clientTextbox['part' . $inc]['fontName'])) { + $txtRun->getFont()->setName($clientTextbox['part' . $inc]['fontName']); } - if (isset($clientTextbox['part'.$inc]['fontSize'])) { - $txtRun->getFont()->setSize($clientTextbox['part'.$inc]['fontSize']); + if (isset($clientTextbox['part' . $inc]['fontSize'])) { + $txtRun->getFont()->setSize($clientTextbox['part' . $inc]['fontSize']); } - if (isset($clientTextbox['part'.$inc]['color'])) { - $txtRun->getFont()->setColor($clientTextbox['part'.$inc]['color']); + if (isset($clientTextbox['part' . $inc]['color'])) { + $txtRun->getFont()->setColor($clientTextbox['part' . $inc]['color']); } // Hyperlink if (!empty($sHyperlinkURL)) { $txtRun->setHyperlink(new Hyperlink($sHyperlinkURL)); } - $start += $clientTextbox['part'.$inc]['partLength']; + $start += $clientTextbox['part' . $inc]['partLength']; if ($bCreateParagraph) { $arrayReturn['shape']->createParagraph(); } @@ -1677,10 +1690,10 @@ class PowerPoint97 implements ReaderInterface $arrayReturn['shape']->setRotation($rotation); } // Shadow - if (isset($shpPrimaryOptions['shadowOffsetX']) && isset($shpPrimaryOptions['shadowOffsetY'])) { + if (isset($shpPrimaryOptions['shadowOffsetX'], $shpPrimaryOptions['shadowOffsetY'])) { $shadowOffsetX = $shpPrimaryOptions['shadowOffsetX']; $shadowOffsetY = $shpPrimaryOptions['shadowOffsetY']; - if ($shadowOffsetX != 0 && $shadowOffsetX != 0) { + if (0 != $shadowOffsetX && 0 != $shadowOffsetX) { $arrayReturn['shape']->getShadow()->setVisible(true); if ($shadowOffsetX > 0 && $shadowOffsetX == $shadowOffsetY) { $arrayReturn['shape']->getShadow()->setDistance($shadowOffsetX)->setDirection(45); @@ -1690,7 +1703,7 @@ class PowerPoint97 implements ReaderInterface // Specific Line if ($arrayReturn['shape'] instanceof Line) { if (isset($shpPrimaryOptions['lineColor'])) { - $arrayReturn['shape']->getBorder()->getColor()->setARGB('FF'.$shpPrimaryOptions['lineColor']); + $arrayReturn['shape']->getBorder()->getColor()->setARGB('FF' . $shpPrimaryOptions['lineColor']); } if (isset($shpPrimaryOptions['lineWidth'])) { $arrayReturn['shape']->setHeight($shpPrimaryOptions['lineWidth']); @@ -1726,27 +1739,31 @@ class PowerPoint97 implements ReaderInterface /** * The OfficeArtSpgrContainer record specifies a container for groups of shapes. + * * @param string $stream - * @param integer $pos - * @param boolean $bInGroup - * @return array - * @throws \Exception - * @link : https://msdn.microsoft.com/en-us/library/dd910416(v=office.12).aspx + * @param int $pos + * @param bool $bInGroup + * + * @return array + * + * @throws InvalidFileFormatException + * + * @see : https://msdn.microsoft.com/en-us/library/dd910416(v=office.12).aspx */ private function readRecordOfficeArtSpgrContainer($stream, $pos, $bInGroup = false) { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0xF && $data['recInstance'] == 0x000 && $data['recType'] == 0xF003) { + if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF003 == $data['recType']) { $arrayReturn['length'] += 8; do { $rhFileBlock = $this->loadRecordHeader($stream, $pos + $arrayReturn['length']); - if (!($rhFileBlock['recVer'] == 0xF && $rhFileBlock['recInstance'] == 0x0000 && ($rhFileBlock['recType'] == 0xF003 || $rhFileBlock['recType'] == 0xF004))) { - throw new \Exception('PowerPoint97 Reader : readRecordOfficeArtSpgrContainer.'); + if (!(0xF == $rhFileBlock['recVer'] && 0x0000 == $rhFileBlock['recInstance'] && (0xF003 == $rhFileBlock['recType'] || 0xF004 == $rhFileBlock['recType']))) { + throw new InvalidFileFormatException($this->filename, PowerPoint97::class); } switch ($rhFileBlock['recType']) { @@ -1776,7 +1793,7 @@ class PowerPoint97 implements ReaderInterface $arrayIdxSlide = array_flip($this->arrayNotes); if ($this->currentNote > 0 && isset($arrayIdxSlide[$this->currentNote])) { $oSlide = $this->oPhpPresentation->getSlide($arrayIdxSlide[$this->currentNote]); - if ($oSlide->getNote()->getShapeCollection()->count() == 0) { + if (0 == $oSlide->getNote()->getShapeCollection()->count()) { $oSlide->getNote()->addShape($fileBlock['shape']); } } @@ -1795,40 +1812,42 @@ class PowerPoint97 implements ReaderInterface } } while ($data['recLen'] > 0); } + return $arrayReturn; } /** * The OfficeArtTertiaryFOPT record specifies a table of OfficeArtRGFOPTE records,. - * @param string $stream - * @param integer $pos - * @return array - * @throws \Exception - * @link https://msdn.microsoft.com/en-us/library/dd950206(v=office.12).aspx + * + * @return array + * + * @throws FeatureNotImplementedException + * + * @see https://msdn.microsoft.com/en-us/library/dd950206(v=office.12).aspx */ - private function readRecordOfficeArtTertiaryFOPT($stream, $pos) + private function readRecordOfficeArtTertiaryFOPT(string $stream, int $pos) { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0x3 && $data['recType'] == 0xF122) { + if (0x3 == $data['recVer'] && 0xF122 == $data['recType']) { // Record Header $arrayReturn['length'] += 8; - $officeArtFOPTE = array(); - for ($inc = 0; $inc < $data['recInstance']; $inc++) { + $officeArtFOPTE = []; + for ($inc = 0; $inc < $data['recInstance']; ++$inc) { $opid = self::getInt2d($this->streamPowerpointDocument, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; $optOp = self::getInt4d($this->streamPowerpointDocument, $pos + $arrayReturn['length']); $arrayReturn['length'] += 4; - $officeArtFOPTE[] = array( + $officeArtFOPTE[] = [ 'opid' => ($opid >> 0) & bindec('11111111111111'), 'fBid' => ($opid >> 14) & bindec('1'), 'fComplex' => ($opid >> 15) & bindec('1'), 'op' => $optOp, - ); + ]; } //@link : http://code.metager.de/source/xref/kde/calligra/filters/libmso/OPID foreach ($officeArtFOPTE as $opt) { @@ -1840,14 +1859,14 @@ class PowerPoint97 implements ReaderInterface case 0x03A0: // Table Row Properties //@link : https://msdn.microsoft.com/en-us/library/dd923419(v=office.12).aspx - if ($opt['fComplex'] == 0x1) { + if (0x1 == $opt['fComplex']) { $arrayReturn['length'] += $opt['op']; } break; case 0x03A9: // GroupShape : metroBlob //@link : https://msdn.microsoft.com/en-us/library/dd943388(v=office.12).aspx - if ($opt['fComplex'] == 0x1) { + if (0x1 == $opt['fComplex']) { $arrayReturn['length'] += $opt['op']; } break; @@ -1856,33 +1875,34 @@ class PowerPoint97 implements ReaderInterface //@link : https://msdn.microsoft.com/en-us/library/dd951605(v=office.12).aspx break; default: - throw new \Exception('Feature not implemented (l.'.__LINE__.' : 0x'.dechex($opt['opid']).')'); + // var_dump('0x' . dechex($opt['opid'])); + throw new FeatureNotImplementedException(); } } } + return $arrayReturn; } /** * The OfficeArtDgContainer record specifies the container for all the file records for the objects in a drawing. - * @param string $stream - * @param integer $pos - * @return array - * @throws \Exception - * @link : https://msdn.microsoft.com/en-us/library/dd924455(v=office.12).aspx + * + * @return array + * + * @see : https://msdn.microsoft.com/en-us/library/dd924455(v=office.12).aspx */ - private function readRecordOfficeArtDgContainer($stream, $pos) + private function readRecordOfficeArtDgContainer(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0xF && $data['recInstance'] == 0x000 && $data['recType'] == 0xF002) { + if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF002 == $data['recType']) { // Record Header $arrayReturn['length'] += 8; // drawingData - $drawingData = $this->readRecordOfficeArtFDG($stream, $pos + $arrayReturn['length']); + $drawingData = $this->readRecordOfficeArtFDG($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += $drawingData['length']; // regroupItems //@todo @@ -1905,19 +1925,19 @@ class PowerPoint97 implements ReaderInterface /** * The OfficeArtFDG record specifies the number of shapes, the drawing identifier, and the shape identifier of the last shape in a drawing. - * @param string $stream - * @param integer $pos - * @return array - * @link : https://msdn.microsoft.com/en-us/library/dd946757(v=office.12).aspx + * + * @return array + * + * @see : https://msdn.microsoft.com/en-us/library/dd946757(v=office.12).aspx */ - private function readRecordOfficeArtFDG($stream, $pos) + private function readRecordOfficeArtFDG(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0x0 && $data['recInstance'] <= 0xFFE && $data['recType'] == 0xF008 && $data['recLen'] == 0x00000008) { + if (0x0 == $data['recVer'] && $data['recInstance'] <= 0xFFE && 0xF008 == $data['recType'] && 0x00000008 == $data['recLen']) { // Record Header $arrayReturn['length'] += 8; // Length @@ -1929,37 +1949,37 @@ class PowerPoint97 implements ReaderInterface /** * The OfficeArtFOPT record specifies a table of OfficeArtRGFOPTE records. - * @param string $stream - * @param integer $pos - * @return array - * @link https://msdn.microsoft.com/en-us/library/dd943404(v=office.12).aspx + * + * @return array + * + * @see https://msdn.microsoft.com/en-us/library/dd943404(v=office.12).aspx */ - private function readRecordOfficeArtFOPT($stream, $pos) + private function readRecordOfficeArtFOPT(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0x3 && $data['recType'] == 0xF00B) { + if (0x3 == $data['recVer'] && 0xF00B == $data['recType']) { // Record Header $arrayReturn['length'] += 8; //@link : http://msdn.microsoft.com/en-us/library/dd906086(v=office.12).aspx - $officeArtFOPTE = array(); - for ($inc = 0; $inc < $data['recInstance']; $inc++) { + $officeArtFOPTE = []; + for ($inc = 0; $inc < $data['recInstance']; ++$inc) { $opid = self::getInt2d($this->streamPowerpointDocument, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; $data['recLen'] -= 2; $optOp = self::getInt4d($this->streamPowerpointDocument, $pos + $arrayReturn['length']); $arrayReturn['length'] += 4; $data['recLen'] -= 4; - $officeArtFOPTE[] = array( + $officeArtFOPTE[] = [ 'opid' => ($opid >> 0) & bindec('11111111111111'), 'fBid' => ($opid >> 14) & bindec('1'), 'fComplex' => ($opid >> 15) & bindec('1'), 'op' => $optOp, - ); + ]; } //@link : http://code.metager.de/source/xref/kde/calligra/filters/libmso/OPID foreach ($officeArtFOPTE as $opt) { @@ -1981,22 +2001,22 @@ class PowerPoint97 implements ReaderInterface case 0x0081: // Text : dxTextLeft //@link : http://msdn.microsoft.com/en-us/library/dd953234(v=office.12).aspx - $arrayReturn['insetLeft'] = \PhpOffice\Common\Drawing::emuToPixels($opt['op']); + $arrayReturn['insetLeft'] = \PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']); break; case 0x0082: // Text : dyTextTop //@link : http://msdn.microsoft.com/en-us/library/dd925068(v=office.12).aspx - $arrayReturn['insetTop'] = \PhpOffice\Common\Drawing::emuToPixels($opt['op']); + $arrayReturn['insetTop'] = \PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']); break; case 0x0083: // Text : dxTextRight //@link : http://msdn.microsoft.com/en-us/library/dd906782(v=office.12).aspx - $arrayReturn['insetRight'] = \PhpOffice\Common\Drawing::emuToPixels($opt['op']); + $arrayReturn['insetRight'] = \PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']); break; case 0x0084: // Text : dyTextBottom //@link : http://msdn.microsoft.com/en-us/library/dd772858(v=office.12).aspx - $arrayReturn['insetBottom'] = \PhpOffice\Common\Drawing::emuToPixels($opt['op']); + $arrayReturn['insetBottom'] = \PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']); break; case 0x0085: // Text : WrapText @@ -2013,7 +2033,7 @@ class PowerPoint97 implements ReaderInterface case 0x0104: // Blip : pib //@link : http://msdn.microsoft.com/en-us/library/dd772837(v=office.12).aspx - if ($opt['fComplex'] == 0) { + if (0 == $opt['fComplex']) { $arrayReturn['pib'] = $opt['op']; $data['recLen'] -= $opt['op']; } else { @@ -2052,7 +2072,7 @@ class PowerPoint97 implements ReaderInterface case 0x145: // Geometry : pVertices //@link : http://msdn.microsoft.com/en-us/library/dd949814(v=office.12).aspx - if ($opt['fComplex'] == 1) { + if (1 == $opt['fComplex']) { $arrayReturn['length'] += $opt['op']; $data['recLen'] -= $opt['op']; } @@ -2060,7 +2080,7 @@ class PowerPoint97 implements ReaderInterface case 0x146: // Geometry : pSegmentInfo //@link : http://msdn.microsoft.com/en-us/library/dd905742(v=office.12).aspx - if ($opt['fComplex'] == 1) { + if (1 == $opt['fComplex']) { $arrayReturn['length'] += $opt['op']; $data['recLen'] -= $opt['op']; } @@ -2068,7 +2088,7 @@ class PowerPoint97 implements ReaderInterface case 0x155: // Geometry : pAdjustHandles //@link : http://msdn.microsoft.com/en-us/library/dd905890(v=office.12).aspx - if ($opt['fComplex'] == 1) { + if (1 == $opt['fComplex']) { $arrayReturn['length'] += $opt['op']; $data['recLen'] -= $opt['op']; } @@ -2076,7 +2096,7 @@ class PowerPoint97 implements ReaderInterface case 0x156: // Geometry : pGuides //@link : http://msdn.microsoft.com/en-us/library/dd910801(v=office.12).aspx - if ($opt['fComplex'] == 1) { + if (1 == $opt['fComplex']) { $arrayReturn['length'] += $opt['op']; $data['recLen'] -= $opt['op']; } @@ -2084,7 +2104,7 @@ class PowerPoint97 implements ReaderInterface case 0x157: // Geometry : pInscribe //@link : http://msdn.microsoft.com/en-us/library/dd904889(v=office.12).aspx - if ($opt['fComplex'] == 1) { + if (1 == $opt['fComplex']) { $arrayReturn['length'] += $opt['op']; $data['recLen'] -= $opt['op']; } @@ -2100,28 +2120,28 @@ class PowerPoint97 implements ReaderInterface case 0x0181: // Fill : fillColor //@link : http://msdn.microsoft.com/en-us/library/dd921332(v=office.12).aspx - $strColor = str_pad(dechex(($opt['op'] >> 0) & bindec('11111111')), 2, STR_PAD_LEFT, '0'); - $strColor .= str_pad(dechex(($opt['op'] >> 8) & bindec('11111111')), 2, STR_PAD_LEFT, '0'); - $strColor .= str_pad(dechex(($opt['op'] >> 16) & bindec('11111111')), 2, STR_PAD_LEFT, '0'); + $strColor = str_pad(dechex(($opt['op'] >> 0) & bindec('11111111')), 2, '0', STR_PAD_LEFT); + $strColor .= str_pad(dechex(($opt['op'] >> 8) & bindec('11111111')), 2, '0', STR_PAD_LEFT); + $strColor .= str_pad(dechex(($opt['op'] >> 16) & bindec('11111111')), 2, '0', STR_PAD_LEFT); // echo 'fillColor : '.$strColor.EOL; break; case 0x0183: // Fill : fillBackColor //@link : http://msdn.microsoft.com/en-us/library/dd950634(v=office.12).aspx - $strColor = str_pad(dechex(($opt['op'] >> 0) & bindec('11111111')), 2, STR_PAD_LEFT, '0'); - $strColor .= str_pad(dechex(($opt['op'] >> 8) & bindec('11111111')), 2, STR_PAD_LEFT, '0'); - $strColor .= str_pad(dechex(($opt['op'] >> 16) & bindec('11111111')), 2, STR_PAD_LEFT, '0'); + $strColor = str_pad(dechex(($opt['op'] >> 0) & bindec('11111111')), 2, '0', STR_PAD_LEFT); + $strColor .= str_pad(dechex(($opt['op'] >> 8) & bindec('11111111')), 2, '0', STR_PAD_LEFT); + $strColor .= str_pad(dechex(($opt['op'] >> 16) & bindec('11111111')), 2, '0', STR_PAD_LEFT); // echo 'fillBackColor : '.$strColor.EOL; break; case 0x0193: // Fill : fillRectRight //@link : http://msdn.microsoft.com/en-us/library/dd951294(v=office.12).aspx - // echo 'fillRectRight : '.\PhpOffice\Common\Drawing::emuToPixels($opt['op']).EOL; + // echo 'fillRectRight : '.\PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']).EOL; break; case 0x0194: // Fill : fillRectBottom //@link : http://msdn.microsoft.com/en-us/library/dd910194(v=office.12).aspx - // echo 'fillRectBottom : '.\PhpOffice\Common\Drawing::emuToPixels($opt['op']).EOL; + // echo 'fillRectBottom : '.\PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']).EOL; break; case 0x01BF: // Fill : Fill Style Boolean Properties @@ -2130,9 +2150,9 @@ class PowerPoint97 implements ReaderInterface case 0x01C0: // Line Style : lineColor //@link : http://msdn.microsoft.com/en-us/library/dd920397(v=office.12).aspx - $strColor = str_pad(dechex(($opt['op'] >> 0) & bindec('11111111')), 2, STR_PAD_LEFT, '0'); - $strColor .= str_pad(dechex(($opt['op'] >> 8) & bindec('11111111')), 2, STR_PAD_LEFT, '0'); - $strColor .= str_pad(dechex(($opt['op'] >> 16) & bindec('11111111')), 2, STR_PAD_LEFT, '0'); + $strColor = str_pad(dechex(($opt['op'] >> 0) & bindec('11111111')), 2, '0', STR_PAD_LEFT); + $strColor .= str_pad(dechex(($opt['op'] >> 8) & bindec('11111111')), 2, '0', STR_PAD_LEFT); + $strColor .= str_pad(dechex(($opt['op'] >> 16) & bindec('11111111')), 2, '0', STR_PAD_LEFT); $arrayReturn['lineColor'] = $strColor; break; case 0x01C1: @@ -2147,7 +2167,7 @@ class PowerPoint97 implements ReaderInterface case 0x01CB: // Line Style : lineWidth //@link : http://msdn.microsoft.com/en-us/library/dd926964(v=office.12).aspx - $arrayReturn['lineWidth'] = \PhpOffice\Common\Drawing::emuToPixels($opt['op']); + $arrayReturn['lineWidth'] = \PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']); break; case 0x01D6: // Line Style : lineJoinStyle @@ -2172,12 +2192,12 @@ class PowerPoint97 implements ReaderInterface case 0x0205: // Shadow Style : shadowOffsetX //@link : http://msdn.microsoft.com/en-us/library/dd945280(v=office.12).aspx - $arrayReturn['shadowOffsetX'] = \PhpOffice\Common\Drawing::emuToPixels($opt['op']); + $arrayReturn['shadowOffsetX'] = \PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']); break; case 0x0206: // Shadow Style : shadowOffsetY //@link : http://msdn.microsoft.com/en-us/library/dd907855(v=office.12).aspx - $arrayReturn['shadowOffsetY'] = \PhpOffice\Common\Drawing::emuToPixels($opt['op']); + $arrayReturn['shadowOffsetY'] = \PhpOffice\Common\Drawing::emuToPixels((int) $opt['op']); break; case 0x023F: // Shadow Style : Shadow Style Boolean Properties @@ -2194,7 +2214,7 @@ class PowerPoint97 implements ReaderInterface case 0x0380: // Group Shape Property Set : wzName //@link : http://msdn.microsoft.com/en-us/library/dd950681(v=office.12).aspx - if ($opt['fComplex'] == 1) { + if (1 == $opt['fComplex']) { $arrayReturn['length'] += $opt['op']; $data['recLen'] -= $opt['op']; } @@ -2204,7 +2224,6 @@ class PowerPoint97 implements ReaderInterface //@link : http://msdn.microsoft.com/en-us/library/dd949807(v=office.12).aspx break; default: - // throw new \Exception('Feature not implemented (l.'.__LINE__.' : 0x'.dechex($opt['opid']).')'); } } if ($data['recLen'] > 0) { @@ -2217,19 +2236,19 @@ class PowerPoint97 implements ReaderInterface /** * The OfficeArtFPSPL record specifies the former hierarchical position of the containing object that is either a shape or a group of shapes. - * @param string $stream - * @param integer $pos - * @return array - * @link https://msdn.microsoft.com/en-us/library/dd947479(v=office.12).aspx + * + * @return array + * + * @see https://msdn.microsoft.com/en-us/library/dd947479(v=office.12).aspx */ - private function readRecordOfficeArtFPSPL($stream, $pos) + private function readRecordOfficeArtFPSPL(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x000 && $data['recType'] == 0xF11D && $data['recLen'] == 0x00000004) { + if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF11D == $data['recType'] && 0x00000004 == $data['recLen']) { $arrayReturn['length'] += 8; $arrayReturn['length'] += $data['recLen']; } @@ -2239,19 +2258,19 @@ class PowerPoint97 implements ReaderInterface /** * The OfficeArtFSP record specifies an instance of a shape. - * @param string $stream - * @param integer $pos - * @return array - * @link https://msdn.microsoft.com/en-us/library/dd925898(v=office.12).aspx + * + * @return array + * + * @see https://msdn.microsoft.com/en-us/library/dd925898(v=office.12).aspx */ - private function readRecordOfficeArtFSP($stream, $pos) + private function readRecordOfficeArtFSP(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0x2 && $data['recType'] == 0xF00A && $data['recLen'] == 0x00000008) { + if (0x2 == $data['recVer'] && 0xF00A == $data['recType'] && 0x00000008 == $data['recLen']) { $arrayReturn['length'] += 8; // spid $arrayReturn['length'] += 4; @@ -2269,19 +2288,19 @@ class PowerPoint97 implements ReaderInterface /** * The OfficeArtFSPGR record specifies the coordinate system of the group shape that the anchors of the child shape are expressed in. - * @param string $stream - * @param integer $pos - * @return array - * @link https://msdn.microsoft.com/en-us/library/dd925381(v=office.12).aspx + * + * @return array + * + * @see https://msdn.microsoft.com/en-us/library/dd925381(v=office.12).aspx */ - private function readRecordOfficeArtFSPGR($stream, $pos) + private function readRecordOfficeArtFSPGR(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0x1 && $data['recInstance'] == 0x000 && $data['recType'] == 0xF009 && $data['recLen'] == 0x00000010) { + if (0x1 == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF009 == $data['recType'] && 0x00000010 == $data['recLen']) { $arrayReturn['length'] += 8; //$arrShapeGroup['xLeft'] = self::getInt4d($this->streamPowerpointDocument, $pos); $arrayReturn['length'] += 4; @@ -2298,43 +2317,45 @@ class PowerPoint97 implements ReaderInterface /** * The OfficeArtSecondaryFOPT record specifies a table of OfficeArtRGFOPTE records. - * @param string $stream - * @param integer $pos - * @return array - * @link https://msdn.microsoft.com/en-us/library/dd950259(v=office.12).aspx + * + * @return array + * + * @see https://msdn.microsoft.com/en-us/library/dd950259(v=office.12).aspx */ - private function readRecordOfficeArtSecondaryFOPT($stream, $pos) + private function readRecordOfficeArtSecondaryFOPT(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0x3 && $data['recType'] == 0xF121) { + if (0x3 == $data['recVer'] && 0xF121 == $data['recType']) { // Record Header $arrayReturn['length'] += 8; // Length $arrayReturn['length'] += $data['recLen']; } + return $arrayReturn; } /** * A container record that specifies information about a shape. - * @param string $stream - * @param integer $pos - * @return array - * @throws \Exception - * @link : https://msdn.microsoft.com/en-us/library/dd950927(v=office.12).aspx + * + * @return array + * + * @throws FeatureNotImplementedException + * + * @see : https://msdn.microsoft.com/en-us/library/dd950927(v=office.12).aspx */ - private function readRecordOfficeArtClientData($stream, $pos) + private function readRecordOfficeArtClientData(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0xF && $data['recInstance'] == 0x000 && $data['recType'] == 0xF011) { + if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && 0xF011 == $data['recType']) { $arrayReturn['length'] += 8; // shapeFlagsAtom (9 bytes) $dataShapeFlagsAtom = $this->readRecordShapeFlagsAtom($stream, $pos + $arrayReturn['length']); @@ -2369,13 +2390,13 @@ class PowerPoint97 implements ReaderInterface $arrayReturn['length'] += $dataRecolorInfo['length']; // rgShapeClientRoundtripData (variable) - $array = array( + $array = [ self::RT_PROGTAGS, self::RT_ROUNDTRIPNEWPLACEHOLDERID12ATOM, self::RT_ROUNDTRIPSHAPEID12ATOM, self::RT_ROUNDTRIPHFPLACEHOLDER12ATOM, self::RT_ROUNDTRIPSHAPECHECKSUMFORCL12ATOM, - ); + ]; do { $dataHeaderRG = $this->loadRecordHeader($stream, $pos + $arrayReturn['length']); if (in_array($dataHeaderRG['recType'], $array)) { @@ -2393,7 +2414,8 @@ class PowerPoint97 implements ReaderInterface $arrayReturn['length'] += $dataRG['length']; break; default: - throw new \Exception('Feature not implemented (l.'.__LINE__.' : 0x'.dechex($dataHeaderRG['recType']).')'); + // var_dump('0x' . dechex($dataHeaderRG['recType'])); + throw new FeatureNotImplementedException(); } } } while (in_array($dataHeaderRG['recType'], $array)); @@ -2404,17 +2426,17 @@ class PowerPoint97 implements ReaderInterface /** * An atom record that specifies a persist object directory. Each persist object identifier specified MUST be unique in that persist object directory. - * @link http://msdn.microsoft.com/en-us/library/dd952680(v=office.12).aspx - * @param string $stream - * @param integer $pos - * @throws \Exception + * + * @see http://msdn.microsoft.com/en-us/library/dd952680(v=office.12).aspx + * + * @throws InvalidFileFormatException */ - private function readRecordPersistDirectoryAtom($stream, $pos) + private function readRecordPersistDirectoryAtom(string $stream, int $pos): void { $rHeader = $this->loadRecordHeader($stream, $pos); $pos += 8; - if ($rHeader['recVer'] != 0x0 || $rHeader['recInstance'] != 0x000 || $rHeader['recType'] != self::RT_PERSISTDIRECTORYATOM) { - throw new \Exception('File PowerPoint 97 in error (Location : PersistDirectoryAtom > RecordHeader).'); + if (0x0 != $rHeader['recVer'] || 0x000 != $rHeader['recInstance'] || self::RT_PERSISTDIRECTORYATOM != $rHeader['recType']) { + throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : PersistDirectoryAtom > RecordHeader'); } // rgPersistDirEntry // @link : http://msdn.microsoft.com/en-us/library/dd947347(v=office.12).aspx @@ -2423,10 +2445,10 @@ class PowerPoint97 implements ReaderInterface $pos += 4; $rHeader['recLen'] -= 4; //$persistId = ($data >> 0) & bindec('11111111111111111111'); - $cPersist = ($data >> 20) & bindec('111111111111'); + $cPersist = ($data >> 20) & bindec('111111111111'); - $rgPersistOffset = array(); - for ($inc = 0; $inc < $cPersist; $inc++) { + $rgPersistOffset = []; + for ($inc = 0; $inc < $cPersist; ++$inc) { $rgPersistOffset[] = self::getInt4d($stream, $pos); $pos += 4; $rHeader['recLen'] -= 4; @@ -2437,19 +2459,19 @@ class PowerPoint97 implements ReaderInterface /** * A container record that specifies information about the headers (1) and footers within a slide. - * @param string $stream - * @param integer $pos - * @link https://msdn.microsoft.com/en-us/library/dd904856(v=office.12).aspx - * @return array + * + * @see https://msdn.microsoft.com/en-us/library/dd904856(v=office.12).aspx + * + * @return array */ - private function readRecordPerSlideHeadersFootersContainer($stream, $pos) + private function readRecordPerSlideHeadersFootersContainer(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0xF && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_HEADERSFOOTERS) { + if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_HEADERSFOOTERS == $data['recType']) { // Record Header $arrayReturn['length'] += 8; // Length @@ -2461,19 +2483,19 @@ class PowerPoint97 implements ReaderInterface /** * An atom record that specifies whether a shape is a placeholder shape. - * @param string $stream - * @param integer $pos - * @link https://msdn.microsoft.com/en-us/library/dd923930(v=office.12).aspx - * @return array + * + * @see https://msdn.microsoft.com/en-us/library/dd923930(v=office.12).aspx + * + * @return array */ - private function readRecordPlaceholderAtom($stream, $pos) + private function readRecordPlaceholderAtom(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_PLACEHOLDERATOM && $data['recLen'] == 0x00000008) { + if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_PLACEHOLDERATOM == $data['recType'] && 0x00000008 == $data['recLen']) { // Record Header $arrayReturn['length'] += 8; // Datas @@ -2485,19 +2507,19 @@ class PowerPoint97 implements ReaderInterface /** * An atom record that specifies a collection of re-color mappings for a metafile ([MS-WMF]). - * @param string $stream - * @param integer $pos - * @link https://msdn.microsoft.com/en-us/library/dd904899(v=office.12).aspx - * @return array + * + * @see https://msdn.microsoft.com/en-us/library/dd904899(v=office.12).aspx + * + * @return array */ - private function readRecordRecolorInfoAtom($stream, $pos) + private function readRecordRecolorInfoAtom(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_RECOLORINFOATOM) { + if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_RECOLORINFOATOM == $data['recType']) { // Record Header $arrayReturn['length'] += 8; // Datas @@ -2509,19 +2531,19 @@ class PowerPoint97 implements ReaderInterface /** * An atom record that specifies that a shape is a header or footerplaceholder shape. - * @param string $stream - * @param integer $pos - * @link https://msdn.microsoft.com/en-us/library/dd910800(v=office.12).aspx - * @return array + * + * @see https://msdn.microsoft.com/en-us/library/dd910800(v=office.12).aspx + * + * @return array */ - private function readRecordRoundTripHFPlaceholder12Atom($stream, $pos) + private function readRecordRoundTripHFPlaceholder12Atom(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_ROUNDTRIPHFPLACEHOLDER12ATOM && $data['recLen'] == 0x00000001) { + if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_ROUNDTRIPHFPLACEHOLDER12ATOM == $data['recType'] && 0x00000001 == $data['recLen']) { // Record Header $arrayReturn['length'] += 8; // Datas @@ -2533,19 +2555,19 @@ class PowerPoint97 implements ReaderInterface /** * An atom record that specifies a shape identifier. - * @param string $stream - * @param integer $pos - * @link https://msdn.microsoft.com/en-us/library/dd772926(v=office.12).aspx - * @return array + * + * @see https://msdn.microsoft.com/en-us/library/dd772926(v=office.12).aspx + * + * @return array */ - private function readRecordRoundTripShapeId12Atom($stream, $pos) + private function readRecordRoundTripShapeId12Atom(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_ROUNDTRIPSHAPEID12ATOM && $data['recLen'] == 0x00000004) { + if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_ROUNDTRIPSHAPEID12ATOM == $data['recType'] && 0x00000004 == $data['recLen']) { // Record Header $arrayReturn['length'] += 8; // Length @@ -2557,19 +2579,19 @@ class PowerPoint97 implements ReaderInterface /** * A container record that specifies information about a slide that synchronizes to a slide in a slide library. - * @param string $stream - * @param integer $pos - * @link https://msdn.microsoft.com/en-us/library/dd923801(v=office.12).aspx - * @return array + * + * @see https://msdn.microsoft.com/en-us/library/dd923801(v=office.12).aspx + * + * @return array */ - private function readRecordRoundTripSlideSyncInfo12Container($stream, $pos) + private function readRecordRoundTripSlideSyncInfo12Container(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0xF && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_ROUNDTRIPSLIDESYNCINFO12) { + if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_ROUNDTRIPSLIDESYNCINFO12 == $data['recType']) { // Record Header $arrayReturn['length'] += 8; // Length @@ -2581,19 +2603,19 @@ class PowerPoint97 implements ReaderInterface /** * An atom record that specifies shape-level Boolean flags. - * @param string $stream - * @param integer $pos - * @link https://msdn.microsoft.com/en-us/library/dd908949(v=office.12).aspx - * @return array + * + * @see https://msdn.microsoft.com/en-us/library/dd908949(v=office.12).aspx + * + * @return array */ - private function readRecordShapeFlags10Atom($stream, $pos) + private function readRecordShapeFlags10Atom(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_SHAPEFLAGS10ATOM && $data['recLen'] == 0x00000001) { + if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_SHAPEFLAGS10ATOM == $data['recType'] && 0x00000001 == $data['recLen']) { // Record Header $arrayReturn['length'] += 8; // Datas @@ -2605,19 +2627,19 @@ class PowerPoint97 implements ReaderInterface /** * An atom record that specifies shape-level Boolean flags. - * @param string $stream - * @param integer $pos - * @link https://msdn.microsoft.com/en-us/library/dd925824(v=office.12).aspx - * @return array + * + * @see https://msdn.microsoft.com/en-us/library/dd925824(v=office.12).aspx + * + * @return array */ - private function readRecordShapeFlagsAtom($stream, $pos) + private function readRecordShapeFlagsAtom(string $stream, int $pos): array { - $arrayReturn = array( - 'length' => 0, - ); + $arrayReturn = [ + 'length' => 0, + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_SHAPEATOM && $data['recLen'] == 0x00000001) { + if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_SHAPEATOM == $data['recType'] && 0x00000001 == $data['recLen']) { // Record Header $arrayReturn['length'] += 8; // Datas @@ -2629,19 +2651,19 @@ class PowerPoint97 implements ReaderInterface /** * A container record that specifies programmable tags with additional binary shape data. - * @param string $stream - * @param integer $pos - * @link https://msdn.microsoft.com/en-us/library/dd911033(v=office.12).aspx - * @return array + * + * @see https://msdn.microsoft.com/en-us/library/dd911033(v=office.12).aspx + * + * @return array */ - private function readRecordShapeProgBinaryTagContainer($stream, $pos) + private function readRecordShapeProgBinaryTagContainer(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0xF && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_PROGBINARYTAG) { + if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_PROGBINARYTAG == $data['recType']) { // Record Header $arrayReturn['length'] += 8; // Datas @@ -2653,20 +2675,21 @@ class PowerPoint97 implements ReaderInterface /** * A container record that specifies programmable tags with additional shape data. - * @param string $stream - * @param integer $pos - * @return array - * @throws \Exception - * @link https://msdn.microsoft.com/en-us/library/dd911266(v=office.12).aspx + * + * @return array + * + * @throws FeatureNotImplementedException + * + * @see https://msdn.microsoft.com/en-us/library/dd911266(v=office.12).aspx */ - private function readRecordShapeProgTagsContainer($stream, $pos) + private function readRecordShapeProgTagsContainer(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0xF && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_PROGTAGS) { + if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_PROGTAGS == $data['recType']) { // Record Header $arrayReturn['length'] += 8; @@ -2680,7 +2703,8 @@ class PowerPoint97 implements ReaderInterface break; //case self::RT_PROGSTRINGTAG: default: - throw new \Exception('Feature not implemented (l.'.__LINE__.')'); + // var_dump('0x' . dechex($dataHeaderRG['recType'])); + throw new FeatureNotImplementedException(); } } while ($length < $data['recLen']); // Datas @@ -2692,28 +2716,28 @@ class PowerPoint97 implements ReaderInterface /** * An atom record that specifies information about a slide. - * @param string $stream - * @param integer $pos - * @return array - * @link https://msdn.microsoft.com/en-us/library/dd923801(v=office.12).aspx + * + * @return array + * + * @see https://msdn.microsoft.com/en-us/library/dd923801(v=office.12).aspx */ - private function readRecordSlideAtom($stream, $pos) + private function readRecordSlideAtom(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0x2 && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_SLIDEATOM) { + if (0x2 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_SLIDEATOM == $data['recType']) { // Record Header $arrayReturn['length'] += 8; // slideAtom > geom $arrayReturn['length'] += 4; // slideAtom > rgPlaceholderTypes - $rgPlaceholderTypes = array(); - for ($inc = 0; $inc < 8; $inc++) { + $rgPlaceholderTypes = []; + for ($inc = 0; $inc < 8; ++$inc) { $rgPlaceholderTypes[] = self::getInt1d($this->streamPowerpointDocument, $pos); - $arrayReturn['length'] += 1; + ++$arrayReturn['length']; } // slideAtom > masterIdRef @@ -2731,12 +2755,12 @@ class PowerPoint97 implements ReaderInterface /** * A container record that specifies a presentation slide or title master slide. - * @param string $stream - * @param int $pos - * @throws \Exception - * @link http://msdn.microsoft.com/en-us/library/dd946323(v=office.12).aspx + * + * @throws InvalidFileFormatException + * + * @see http://msdn.microsoft.com/en-us/library/dd946323(v=office.12).aspx */ - private function readRecordSlideContainer($stream, $pos) + private function readRecordSlideContainer(string $stream, int $pos): void { // Core $this->oPhpPresentation->createSlide(); @@ -2744,8 +2768,8 @@ class PowerPoint97 implements ReaderInterface // *** slideAtom (32 bytes) $slideAtom = $this->readRecordSlideAtom($stream, $pos); - if ($slideAtom['length'] == 0) { - throw new \Exception('PowerPoint97 Reader : record SlideAtom'); + if (0 == $slideAtom['length']) { + throw new InvalidFileFormatException($this->filename, PowerPoint97::class); } $pos += $slideAtom['length']; @@ -2767,8 +2791,9 @@ class PowerPoint97 implements ReaderInterface // *** slideSchemeColorSchemeAtom (40 bytes) $slideSchemeColorAtom = $this->readRecordSlideSchemeColorSchemeAtom($stream, $pos); - if ($slideSchemeColorAtom['length'] == 0) { - throw new \Exception('PowerPoint97 Reader : record SlideSchemeColorSchemeAtom'); + if (0 == $slideSchemeColorAtom['length']) { + // Record SlideSchemeColorSchemeAtom + throw new InvalidFileFormatException($this->filename, PowerPoint97::class); } $pos += $slideSchemeColorAtom['length']; @@ -2785,25 +2810,25 @@ class PowerPoint97 implements ReaderInterface /** * An atom record that specifies the name of a slide. - * @param string $stream - * @param integer $pos - * @link https://msdn.microsoft.com/en-us/library/dd906297(v=office.12).aspx - * @return array + * + * @see https://msdn.microsoft.com/en-us/library/dd906297(v=office.12).aspx + * + * @return array{'length': int, 'slideName': string} */ - private function readRecordSlideNameAtom($stream, $pos) + private function readRecordSlideNameAtom(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, 'slideName' => '', - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x003 && $data['recType'] == self::RT_CSTRING && $data['recLen'] % 2 == 0) { + if (0x0 == $data['recVer'] && 0x003 == $data['recInstance'] && self::RT_CSTRING == $data['recType'] && $data['recLen'] % 2 == 0) { // Record Header $arrayReturn['length'] += 8; // Length $strLen = ($data['recLen'] / 2); - for ($inc = 0; $inc < $strLen; $inc++) { + for ($inc = 0; $inc < $strLen; ++$inc) { $char = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; $arrayReturn['slideName'] .= Text::chr($char); @@ -2815,19 +2840,19 @@ class PowerPoint97 implements ReaderInterface /** * An atom record that specifies a slide number metacharacter. - * @param string $stream - * @param integer $pos - * @link https://msdn.microsoft.com/en-us/library/dd945703(v=office.12).aspx - * @return array + * + * @see https://msdn.microsoft.com/en-us/library/dd945703(v=office.12).aspx + * + * @return array */ - private function readRecordSlideNumberMCAtom($stream, $pos) + private function readRecordSlideNumberMCAtom(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_SLIDENUMBERMETACHARATOM && $data['recLen'] == 0x00000004) { + if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_SLIDENUMBERMETACHARATOM == $data['recType'] && 0x00000004 == $data['recLen']) { // Record Header $arrayReturn['length'] += 8; // Datas @@ -2839,19 +2864,19 @@ class PowerPoint97 implements ReaderInterface /** * A container record that specifies programmable tags with additional slide data. - * @param string $stream - * @param integer $pos - * @link https://msdn.microsoft.com/en-us/library/dd951946(v=office.12).aspx - * @return array + * + * @see https://msdn.microsoft.com/en-us/library/dd951946(v=office.12).aspx + * + * @return array */ - private function readRecordSlideProgTagsContainer($stream, $pos) + private function readRecordSlideProgTagsContainer(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0xF && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_PROGTAGS) { + if (0xF == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_PROGTAGS == $data['recType']) { // Record Header $arrayReturn['length'] += 8; // Length @@ -2863,29 +2888,29 @@ class PowerPoint97 implements ReaderInterface /** * A container record that specifies the color scheme used by a slide. - * @param string $stream - * @param integer $pos - * @link https://msdn.microsoft.com/en-us/library/dd949420(v=office.12).aspx - * @return array + * + * @see https://msdn.microsoft.com/en-us/library/dd949420(v=office.12).aspx + * + * @return array */ - private function readRecordSlideSchemeColorSchemeAtom($stream, $pos) + private function readRecordSlideSchemeColorSchemeAtom(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x001 && $data['recType'] == self::RT_COLORSCHEMEATOM && $data['recLen'] == 0x00000020) { + if (0x0 == $data['recVer'] && 0x001 == $data['recInstance'] && self::RT_COLORSCHEMEATOM == $data['recType'] && 0x00000020 == $data['recLen']) { // Record Header $arrayReturn['length'] += 8; // Length - $rgSchemeColor = array(); - for ($inc = 0; $inc <= 7; $inc++) { - $rgSchemeColor[] = array( + $rgSchemeColor = []; + for ($inc = 0; $inc <= 7; ++$inc) { + $rgSchemeColor[] = [ 'red' => self::getInt1d($stream, $pos + $arrayReturn['length'] + $inc * 4), 'green' => self::getInt1d($stream, $pos + $arrayReturn['length'] + $inc * 4 + 1), 'blue' => self::getInt1d($stream, $pos + $arrayReturn['length'] + $inc * 4 + 2), - ); + ]; } $arrayReturn['length'] += (8 * 4); } @@ -2895,19 +2920,19 @@ class PowerPoint97 implements ReaderInterface /** * An atom record that specifies what transition effect to perform during a slide show, and how to advance to the next presentation slide. - * @param string $stream - * @param integer $pos - * @link https://msdn.microsoft.com/en-us/library/dd943408(v=office.12).aspx - * @return array + * + * @see https://msdn.microsoft.com/en-us/library/dd943408(v=office.12).aspx + * + * @return array */ - private function readRecordSlideShowSlideInfoAtom($stream, $pos) + private function readRecordSlideShowSlideInfoAtom(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] == 0x0 && $data['recInstance'] == 0x000 && $data['recType'] == self::RT_SLIDESHOWSLIDEINFOATOM && $data['recLen'] == 0x00000010) { + if (0x0 == $data['recVer'] && 0x000 == $data['recInstance'] && self::RT_SLIDESHOWSLIDEINFOATOM == $data['recType'] && 0x00000010 == $data['recLen']) { // Record Header $arrayReturn['length'] += 8; // Length; @@ -2918,18 +2943,18 @@ class PowerPoint97 implements ReaderInterface } /** - * UserEditAtom - * @link http://msdn.microsoft.com/en-us/library/dd945746(v=office.12).aspx - * @param string $stream - * @param integer $pos - * @throws \Exception + * UserEditAtom. + * + * @see http://msdn.microsoft.com/en-us/library/dd945746(v=office.12).aspx + * + * @throws InvalidFileFormatException */ - private function readRecordUserEditAtom($stream, $pos) + private function readRecordUserEditAtom(string $stream, int $pos): void { $rHeader = $this->loadRecordHeader($stream, $pos); $pos += 8; - if ($rHeader['recVer'] != 0x0 || $rHeader['recInstance'] != 0x000 || $rHeader['recType'] != self::RT_USEREDITATOM || ($rHeader['recLen'] != 0x0000001C && $rHeader['recLen'] != 0x00000020)) { - throw new \Exception('File PowerPoint 97 in error (Location : UserEditAtom > RecordHeader).'); + if (0x0 != $rHeader['recVer'] || 0x000 != $rHeader['recInstance'] || self::RT_USEREDITATOM != $rHeader['recType'] || (0x0000001C != $rHeader['recLen'] && 0x00000020 != $rHeader['recLen'])) { + throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : UserEditAtom > RecordHeader'); } // lastSlideIdRef @@ -2939,16 +2964,16 @@ class PowerPoint97 implements ReaderInterface // minorVersion $minorVersion = self::getInt1d($stream, $pos); - $pos += 1; - if ($minorVersion != 0x00) { - throw new \Exception('File PowerPoint 97 in error (Location : UserEditAtom > minorVersion).'); + ++$pos; + if (0x00 != $minorVersion) { + throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : UserEditAtom > minorVersion'); } // majorVersion $majorVersion = self::getInt1d($stream, $pos); - $pos += 1; - if ($majorVersion != 0x03) { - throw new \Exception('File PowerPoint 97 in error (Location : UserEditAtom > majorVersion).'); + ++$pos; + if (0x03 != $majorVersion) { + throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : UserEditAtom > majorVersion'); } // offsetLastEdit @@ -2958,10 +2983,10 @@ class PowerPoint97 implements ReaderInterface $pos += 4; // docPersistIdRef - $docPersistIdRef = self::getInt4d($stream, $pos); + $docPersistIdRef = self::getInt4d($stream, $pos); $pos += 4; - if ($docPersistIdRef != 0x00000001) { - throw new \Exception('File PowerPoint 97 in error (Location : UserEditAtom > docPersistIdRef).'); + if (0x00000001 != $docPersistIdRef) { + throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : UserEditAtom > docPersistIdRef'); } // persistIdSeed @@ -2974,19 +2999,19 @@ class PowerPoint97 implements ReaderInterface /** * A structure that specifies the character-level formatting of a run of text. - * @param string $stream - * @param int $pos - * @param int $strLenRT - * @return array - * @throws \Exception - * @link https://msdn.microsoft.com/en-us/library/dd945870(v=office.12).aspx + * + * @return array{'length': int, 'strLenRT': int, 'partLength': int, 'bold': bool, 'italic': bool, 'underline': bool, 'fontName': string, 'fontSize': int, 'color': Color} + * + * @throws FeatureNotImplementedException + * + * @see https://msdn.microsoft.com/en-us/library/dd945870(v=office.12).aspx */ - private function readStructureTextCFRun($stream, $pos, $strLenRT) + private function readStructureTextCFRun(string $stream, int $pos, int $strLenRT): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, 'strLenRT' => $strLenRT, - ); + ]; // rgTextCFRun $countRgTextCFRun = self::getInt4d($stream, $pos + $arrayReturn['length']); @@ -2997,7 +3022,7 @@ class PowerPoint97 implements ReaderInterface $masks = self::getInt4d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 4; - $masksData = array(); + $masksData = []; $masksData['bold'] = ($masks >> 0) & bindec('1'); $masksData['italic'] = ($masks >> 1) & bindec('1'); $masksData['underline'] = ($masks >> 2) & bindec('1'); @@ -3021,11 +3046,11 @@ class PowerPoint97 implements ReaderInterface $masksData['newEATypeface'] = ($masks >> 24) & bindec('1'); $masksData['csTypeface'] = ($masks >> 25) & bindec('1'); $masksData['pp11ext'] = ($masks >> 26) & bindec('1'); - if ($masksData['bold'] == 1 || $masksData['italic'] == 1 || $masksData['underline'] == 1 || $masksData['shadow'] == 1 || $masksData['fehint'] == 1 || $masksData['kumi'] == 1 || $masksData['emboss'] == 1 || $masksData['fHasStyle'] == 1) { + if (1 == $masksData['bold'] || 1 == $masksData['italic'] || 1 == $masksData['underline'] || 1 == $masksData['shadow'] || 1 == $masksData['fehint'] || 1 == $masksData['kumi'] || 1 == $masksData['emboss'] || 1 == $masksData['fHasStyle']) { $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; - $fontStyleFlags = array(); + $fontStyleFlags = []; $fontStyleFlags['bold'] = ($data >> 0) & bindec('1'); $fontStyleFlags['italic'] = ($data >> 1) & bindec('1'); $fontStyleFlags['underline'] = ($data >> 2) & bindec('1'); @@ -3039,51 +3064,51 @@ class PowerPoint97 implements ReaderInterface $fontStyleFlags['pp9rt'] = ($data >> 10) & bindec('1111'); $fontStyleFlags['unused4'] = ($data >> 14) & bindec('11'); - $arrayReturn['bold'] = ($fontStyleFlags['bold'] == 1) ? true : false; - $arrayReturn['italic'] = ($fontStyleFlags['italic'] == 1) ? true : false; - $arrayReturn['underline'] = ($fontStyleFlags['underline'] == 1) ? true : false; + $arrayReturn['bold'] = (1 == $fontStyleFlags['bold']) ? true : false; + $arrayReturn['italic'] = (1 == $fontStyleFlags['italic']) ? true : false; + $arrayReturn['underline'] = (1 == $fontStyleFlags['underline']) ? true : false; } - if ($masksData['typeface'] == 1) { + if (1 == $masksData['typeface']) { $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; $arrayReturn['fontName'] = isset($this->arrayFonts[$data]) ? $this->arrayFonts[$data] : ''; } - if ($masksData['oldEATypeface'] == 1) { + if (1 == $masksData['oldEATypeface']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } - if ($masksData['ansiTypeface'] == 1) { + if (1 == $masksData['ansiTypeface']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } - if ($masksData['symbolTypeface'] == 1) { + if (1 == $masksData['symbolTypeface']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } - if ($masksData['size'] == 1) { + if (1 == $masksData['size']) { $arrayReturn['fontSize'] = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } - if ($masksData['color'] == 1) { + if (1 == $masksData['color']) { $red = self::getInt1d($stream, $pos + $arrayReturn['length']); - $arrayReturn['length'] += 1; + ++$arrayReturn['length']; $green = self::getInt1d($stream, $pos + $arrayReturn['length']); - $arrayReturn['length'] += 1; + ++$arrayReturn['length']; $blue = self::getInt1d($stream, $pos + $arrayReturn['length']); - $arrayReturn['length'] += 1; + ++$arrayReturn['length']; $index = self::getInt1d($stream, $pos + $arrayReturn['length']); - $arrayReturn['length'] += 1; + ++$arrayReturn['length']; - if ($index == 0xFE) { - $strColor = str_pad(dechex($red), 2, STR_PAD_LEFT, '0'); - $strColor .= str_pad(dechex($green), 2, STR_PAD_LEFT, '0'); - $strColor .= str_pad(dechex($blue), 2, STR_PAD_LEFT, '0'); + if (0xFE == $index) { + $strColor = str_pad(dechex($red), 2, '0', STR_PAD_LEFT); + $strColor .= str_pad(dechex($green), 2, '0', STR_PAD_LEFT); + $strColor .= str_pad(dechex($blue), 2, '0', STR_PAD_LEFT); - $arrayReturn['color'] = new Color('FF'.$strColor); + $arrayReturn['color'] = new Color('FF' . $strColor); } } - if ($masksData['position'] == 1) { - throw new \Exception('Feature not implemented (l.'.__LINE__.')'); + if (1 == $masksData['position']) { + throw new FeatureNotImplementedException(); } return $arrayReturn; @@ -3091,19 +3116,19 @@ class PowerPoint97 implements ReaderInterface /** * A structure that specifies the paragraph-level formatting of a run of text. - * @param string $stream - * @param integer $pos - * @param integer $strLenRT - * @return array - * @throws \Exception - * @link https://msdn.microsoft.com/en-us/library/dd923535(v=office.12).aspx + * + * @return array{'length': int, 'strLenRT': int, 'alignH': string|null, 'bulletChar': string, 'leftMargin': int, 'indent': int} + * + * @throws FeatureNotImplementedException + * + * @see https://msdn.microsoft.com/en-us/library/dd923535(v=office.12).aspx */ - private function readStructureTextPFRun($stream, $pos, $strLenRT) + private function readStructureTextPFRun(string $stream, int $pos, int $strLenRT): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, 'strLenRT' => $strLenRT, - ); + ]; // rgTextPFRun $countRgTextPFRun = self::getInt4d($stream, $pos + $arrayReturn['length']); @@ -3116,7 +3141,7 @@ class PowerPoint97 implements ReaderInterface $masks = self::getInt4d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 4; - $masksData = array(); + $masksData = []; $masksData['hasBullet'] = ($masks >> 0) & bindec('1'); $masksData['bulletHasFont'] = ($masks >> 1) & bindec('1'); $masksData['bulletHasColor'] = ($masks >> 2) & bindec('1'); @@ -3144,8 +3169,8 @@ class PowerPoint97 implements ReaderInterface $masksData['bulletScheme'] = ($masks >> 24) & bindec('1'); $masksData['bulletHasScheme'] = ($masks >> 25) & bindec('1'); - $bulletFlags = array(); - if ($masksData['hasBullet'] == 1 || $masksData['bulletHasFont'] == 1 || $masksData['bulletHasColor'] == 1 || $masksData['bulletHasSize'] == 1) { + $bulletFlags = []; + if (1 == $masksData['hasBullet'] || 1 == $masksData['bulletHasFont'] || 1 == $masksData['bulletHasColor'] || 1 == $masksData['bulletHasSize']) { $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; @@ -3154,36 +3179,36 @@ class PowerPoint97 implements ReaderInterface $bulletFlags['fBulletHasColor'] = ($data >> 2) & bindec('1'); $bulletFlags['fBulletHasSize'] = ($data >> 3) & bindec('1'); } - if ($masksData['bulletChar'] == 1) { + if (1 == $masksData['bulletChar']) { $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; $arrayReturn['bulletChar'] = chr($data); } - if ($masksData['bulletFont'] == 1) { + if (1 == $masksData['bulletFont']) { // $data = self::getInt2d($stream, $pos); $arrayReturn['length'] += 2; } - if ($masksData['bulletSize'] == 1) { + if (1 == $masksData['bulletSize']) { // $data = self::getInt2d($stream, $pos); $arrayReturn['length'] += 2; } - if ($masksData['bulletColor'] == 1) { - $red = self::getInt1d($stream, $pos + $arrayReturn['length']); - $arrayReturn['length'] += 1; - $green = self::getInt1d($stream, $pos + $arrayReturn['length']); - $arrayReturn['length'] += 1; - $blue = self::getInt1d($stream, $pos + $arrayReturn['length']); - $arrayReturn['length'] += 1; + if (1 == $masksData['bulletColor']) { + // $red = self::getInt1d($stream, $pos + $arrayReturn['length']); + ++$arrayReturn['length']; + // $green = self::getInt1d($stream, $pos + $arrayReturn['length']); + ++$arrayReturn['length']; + // $blue = self::getInt1d($stream, $pos + $arrayReturn['length']); + ++$arrayReturn['length']; $index = self::getInt1d($stream, $pos + $arrayReturn['length']); - $arrayReturn['length'] += 1; + ++$arrayReturn['length']; - if ($index == 0xFE) { - $strColor = str_pad(dechex($red), 2, STR_PAD_LEFT, '0'); - $strColor .= str_pad(dechex($green), 2, STR_PAD_LEFT, '0'); - $strColor .= str_pad(dechex($blue), 2, STR_PAD_LEFT, '0'); + if (0xFE == $index) { + // $strColor = str_pad(dechex($red), 2, '0', STR_PAD_LEFT); + // $strColor .= str_pad(dechex($green), 2, '0', STR_PAD_LEFT); + // $strColor .= str_pad(dechex($blue), 2, '0', STR_PAD_LEFT); } } - if ($masksData['align'] == 1) { + if (1 == $masksData['align']) { $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; switch ($data) { @@ -3212,45 +3237,45 @@ class PowerPoint97 implements ReaderInterface break; } } - if ($masksData['lineSpacing'] == 1) { + if (1 == $masksData['lineSpacing']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } - if ($masksData['spaceBefore'] == 1) { + if (1 == $masksData['spaceBefore']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } - if ($masksData['spaceAfter'] == 1) { + if (1 == $masksData['spaceAfter']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } - if ($masksData['leftMargin'] == 1) { + if (1 == $masksData['leftMargin']) { $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; - $arrayReturn['leftMargin'] = (int)round($data/6); + $arrayReturn['leftMargin'] = (int) round($data / 6); } - if ($masksData['indent'] == 1) { + if (1 == $masksData['indent']) { $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; - $arrayReturn['indent'] = (int)round($data/6); + $arrayReturn['indent'] = (int) round($data / 6); } - if ($masksData['defaultTabSize'] == 1) { + if (1 == $masksData['defaultTabSize']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } - if ($masksData['tabStops'] == 1) { - throw new \Exception('Feature not implemented (l.'.__LINE__.')'); + if (1 == $masksData['tabStops']) { + throw new FeatureNotImplementedException(); } - if ($masksData['fontAlign'] == 1) { + if (1 == $masksData['fontAlign']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } - if ($masksData['charWrap'] == 1 || $masksData['wordWrap'] == 1 || $masksData['overflow'] == 1) { + if (1 == $masksData['charWrap'] || 1 == $masksData['wordWrap'] || 1 == $masksData['overflow']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } - if ($masksData['textDirection'] == 1) { - throw new \Exception('Feature not implemented (l.'.__LINE__.')'); + if (1 == $masksData['textDirection']) { + throw new FeatureNotImplementedException(); } return $arrayReturn; @@ -3258,26 +3283,26 @@ class PowerPoint97 implements ReaderInterface /** * A structure that specifies language and spelling information for a run of text. - * @param string $stream - * @param integer $pos - * @param string $strLenRT - * @return array - * @throws \Exception - * @link https://msdn.microsoft.com/en-us/library/dd909603(v=office.12).aspx + * + * @return array + * + * @throws FeatureNotImplementedException + * + * @see https://msdn.microsoft.com/en-us/library/dd909603(v=office.12).aspx */ - private function readStructureTextSIRun($stream, $pos, $strLenRT) + private function readStructureTextSIRun(string $stream, int $pos, int $strLenRT): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, 'strLenRT' => $strLenRT, - ); + ]; $arrayReturn['strLenRT'] -= self::getInt4d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 4; $data = self::getInt4d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 4; - $masksData = array(); + $masksData = []; $masksData['spell'] = ($data >> 0) & bindec('1'); $masksData['lang'] = ($data >> 1) & bindec('1'); $masksData['altLang'] = ($data >> 2) & bindec('1'); @@ -3289,30 +3314,30 @@ class PowerPoint97 implements ReaderInterface $masksData['reserved1'] = ($data >> 8) & bindec('1'); $masksData['smartTag'] = ($data >> 9) & bindec('1'); - if ($masksData['spell'] == 1) { + if (1 == $masksData['spell']) { $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; - $masksSpell = array(); + $masksSpell = []; $masksSpell['error'] = ($data >> 0) & bindec('1'); $masksSpell['clean'] = ($data >> 1) & bindec('1'); $masksSpell['grammar'] = ($data >> 2) & bindec('1'); } - if ($masksData['lang'] == 1) { + if (1 == $masksData['lang']) { // $data = self::getInt2d($stream, $pos); $arrayReturn['length'] += 2; } - if ($masksData['altLang'] == 1) { + if (1 == $masksData['altLang']) { // $data = self::getInt2d($stream, $pos); $arrayReturn['length'] += 2; } - if ($masksData['fBidi'] == 1) { - throw new \Exception('Feature not implemented (l.'.__LINE__.')'); + if (1 == $masksData['fBidi']) { + throw new FeatureNotImplementedException(); } - if ($masksData['fPp10ext'] == 1) { - throw new \Exception('Feature not implemented (l.'.__LINE__.')'); + if (1 == $masksData['fPp10ext']) { + throw new FeatureNotImplementedException(); } - if ($masksData['smartTag'] == 1) { - throw new \Exception('Feature not implemented (l.'.__LINE__.')'); + if (1 == $masksData['smartTag']) { + throw new FeatureNotImplementedException(); } return $arrayReturn; @@ -3320,22 +3345,23 @@ class PowerPoint97 implements ReaderInterface /** * A structure that specifies tabbing, margins, and indentation for text. - * @param string $stream - * @param integer $pos - * @return array - * @throws \Exception - * @link https://msdn.microsoft.com/en-us/library/dd922749(v=office.12).aspx + * + * @return array + * + * @throws FeatureNotImplementedException + * + * @see https://msdn.microsoft.com/en-us/library/dd922749(v=office.12).aspx */ - private function readStructureTextRuler($stream, $pos) + private function readStructureTextRuler(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = self::getInt4d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 4; - $masksData = array(); + $masksData = []; $masksData['fDefaultTabSize'] = ($data >> 0) & bindec('1'); $masksData['fCLevels'] = ($data >> 1) & bindec('1'); $masksData['fTabStops'] = ($data >> 2) & bindec('1'); @@ -3350,64 +3376,64 @@ class PowerPoint97 implements ReaderInterface $masksData['fIndent4'] = ($data >> 11) & bindec('1'); $masksData['fIndent5'] = ($data >> 12) & bindec('1'); - if ($masksData['fCLevels'] == 1) { - throw new \Exception('Feature not implemented (l.'.__LINE__.')'); + if (1 == $masksData['fCLevels']) { + throw new FeatureNotImplementedException(); } - if ($masksData['fDefaultTabSize'] == 1) { - throw new \Exception('Feature not implemented (l.'.__LINE__.')'); + if (1 == $masksData['fDefaultTabSize']) { + throw new FeatureNotImplementedException(); } - if ($masksData['fTabStops'] == 1) { + if (1 == $masksData['fTabStops']) { $count = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; - $arrayTabStops = array(); - for ($inc = 0; $inc < $count; $inc++) { + $arrayTabStops = []; + for ($inc = 0; $inc < $count; ++$inc) { $position = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; $type = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; - $arrayTabStops[] = array( + $arrayTabStops[] = [ 'position' => $position, 'type' => $type, - ); + ]; } } - if ($masksData['fLeftMargin1'] == 1) { + if (1 == $masksData['fLeftMargin1']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } - if ($masksData['fIndent1'] == 1) { + if (1 == $masksData['fIndent1']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } - if ($masksData['fLeftMargin2'] == 1) { + if (1 == $masksData['fLeftMargin2']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } - if ($masksData['fIndent2'] == 1) { + if (1 == $masksData['fIndent2']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } - if ($masksData['fLeftMargin3'] == 1) { + if (1 == $masksData['fLeftMargin3']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } - if ($masksData['fIndent3'] == 1) { + if (1 == $masksData['fIndent3']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } - if ($masksData['fLeftMargin4'] == 1) { + if (1 == $masksData['fLeftMargin4']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } - if ($masksData['fIndent4'] == 1) { + if (1 == $masksData['fIndent4']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } - if ($masksData['fLeftMargin5'] == 1) { + if (1 == $masksData['fLeftMargin5']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } - if ($masksData['fIndent5'] == 1) { + if (1 == $masksData['fIndent5']) { // $data = self::getInt2d($stream, $pos + $arrayReturn['length']); $arrayReturn['length'] += 2; } @@ -3415,12 +3441,7 @@ class PowerPoint97 implements ReaderInterface return $arrayReturn; } - /** - * @param $stream - * @param int $pos - * @throws \Exception - */ - private function readRecordNotesContainer($stream, $pos) + private function readRecordNotesContainer(string $stream, int $pos): void { // notesAtom $notesAtom = $this->readRecordNotesAtom($stream, $pos); @@ -3437,26 +3458,25 @@ class PowerPoint97 implements ReaderInterface } /** - * @param $stream - * @param int $pos - * @return array - * @throws \Exception + * @return array + * + * @throws InvalidFileFormatException */ - private function readRecordNotesAtom($stream, $pos) + private function readRecordNotesAtom(string $stream, int $pos): array { - $arrayReturn = array( + $arrayReturn = [ 'length' => 0, - ); + ]; $data = $this->loadRecordHeader($stream, $pos); - if ($data['recVer'] != 0x1 || $data['recInstance'] != 0x000 || $data['recType'] != self::RT_NOTESATOM || $data['recLen'] != 0x00000008) { - throw new \Exception('File PowerPoint 97 in error (Location : NotesAtom > RecordHeader)'); + if (0x1 != $data['recVer'] || 0x000 != $data['recInstance'] || self::RT_NOTESATOM != $data['recType'] || 0x00000008 != $data['recLen']) { + throw new InvalidFileFormatException($this->filename, PowerPoint97::class, 'Location : NotesAtom > RecordHeader)'); } // Record Header $arrayReturn['length'] += 8; // NotesAtom > slideIdRef $notesIdRef = self::getInt4d($stream, $pos + $arrayReturn['length']); - if ($notesIdRef == -2147483648) { + if (-2147483648 == $notesIdRef) { $notesIdRef = 0; } $this->currentNote = $notesIdRef; diff --git a/PhpOffice/PhpPresentation/Reader/ReaderInterface.php b/PhpOffice/PhpPresentation/Reader/ReaderInterface.php old mode 100755 new mode 100644 index c5149e9..d3e06d3 --- a/PhpOffice/PhpPresentation/Reader/ReaderInterface.php +++ b/PhpOffice/PhpPresentation/Reader/ReaderInterface.php @@ -10,32 +10,30 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Reader; +use PhpOffice\PhpPresentation\PhpPresentation; + /** - * Reader interface + * Reader interface. */ interface ReaderInterface { /** * Can the current \PhpOffice\PhpPresentation\Reader\ReaderInterface read the file? - * - * @param string $pFilename - * @return boolean */ - public function canRead($pFilename); + public function canRead(string $pFilename): bool; /** - * Loads PhpPresentation from file - * - * @param string $pFilename - * @return \PhpOffice\PhpPresentation\PhpPresentation - * @throws \Exception + * Loads PhpPresentation from file. */ - public function load($pFilename); + public function load(string $pFilename): PhpPresentation; } diff --git a/PhpOffice/PhpPresentation/Reader/Serialized.php b/PhpOffice/PhpPresentation/Reader/Serialized.php old mode 100755 new mode 100644 index 7349ecd..33de31b --- a/PhpOffice/PhpPresentation/Reader/Serialized.php +++ b/PhpOffice/PhpPresentation/Reader/Serialized.php @@ -10,29 +10,33 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Reader; use PhpOffice\Common\File; +use PhpOffice\PhpPresentation\Exception\FileNotFoundException; +use PhpOffice\PhpPresentation\Exception\InvalidFileFormatException; +use PhpOffice\PhpPresentation\PhpPresentation; use PhpOffice\PhpPresentation\Shape\Drawing\AbstractDrawingAdapter; +use PhpOffice\PhpPresentation\Shape\Drawing\File as DrawingFile; +use ZipArchive; /** - * Serialized format reader + * Serialized format reader. */ class Serialized implements ReaderInterface { /** * Can the current \PhpOffice\PhpPresentation\Reader\ReaderInterface read the file? - * - * @param string $pFilename - * @throws \Exception - * @return boolean */ - public function canRead($pFilename) + public function canRead(string $pFilename): bool { return $this->fileSupportsUnserializePhpPresentation($pFilename); } @@ -40,15 +44,13 @@ class Serialized implements ReaderInterface /** * Does a file support UnserializePhpPresentation ? * - * @param string $pFilename - * @throws \Exception - * @return boolean + * @throws FileNotFoundException */ - public function fileSupportsUnserializePhpPresentation($pFilename = '') + public function fileSupportsUnserializePhpPresentation(string $pFilename): bool { // Check if file exists if (!file_exists($pFilename)) { - throw new \Exception("Could not open " . $pFilename . " for reading! File does not exist."); + throw new FileNotFoundException($pFilename); } // File exists, does it contain PhpPresentation.xml? @@ -56,58 +58,63 @@ class Serialized implements ReaderInterface } /** - * Loads PhpPresentation Serialized file + * Loads PhpPresentation Serialized file. * - * @param string $pFilename - * @return \PhpOffice\PhpPresentation\PhpPresentation - * @throws \Exception + * @throws FileNotFoundException + * @throws InvalidFileFormatException */ - public function load($pFilename) + public function load(string $pFilename): PhpPresentation { // Check if file exists if (!file_exists($pFilename)) { - throw new \Exception("Could not open " . $pFilename . " for reading! File does not exist."); + throw new FileNotFoundException($pFilename); } // Unserialize... First make sure the file supports it! if (!$this->fileSupportsUnserializePhpPresentation($pFilename)) { - throw new \Exception("Invalid file format for PhpOffice\PhpPresentation\Reader\Serialized: " . $pFilename . "."); + throw new InvalidFileFormatException($pFilename, Serialized::class); } return $this->loadSerialized($pFilename); } /** - * Load PhpPresentation Serialized file + * Load PhpPresentation Serialized file. * - * @param string $pFilename - * @return \PhpOffice\PhpPresentation\PhpPresentation + * @throws InvalidFileFormatException */ - private function loadSerialized($pFilename) + private function loadSerialized(string $pFilename): PhpPresentation { - $oArchive = new \ZipArchive(); - if ($oArchive->open($pFilename) === true) { - $xmlContent = $oArchive->getFromName('PhpPresentation.xml'); + $oArchive = new ZipArchive(); + if (true !== $oArchive->open($pFilename)) { + throw new InvalidFileFormatException($pFilename, Serialized::class); + } - if (!empty($xmlContent)) { - $xmlData = simplexml_load_string($xmlContent); - $file = unserialize(base64_decode((string) $xmlData->data)); + $xmlContent = $oArchive->getFromName('PhpPresentation.xml'); + if (empty($xmlContent)) { + throw new InvalidFileFormatException($pFilename, Serialized::class, 'The file PhpPresentation.xml is malformed'); + } - // Update media links - for ($i = 0; $i < $file->getSlideCount(); ++$i) { - for ($j = 0; $j < $file->getSlide($i)->getShapeCollection()->count(); ++$j) { - if ($file->getSlide($i)->getShapeCollection()->offsetGet($j) instanceof AbstractDrawingAdapter) { - $imgTemp = $file->getSlide($i)->getShapeCollection()->offsetGet($j); - $imgTemp->setPath('zip://' . $pFilename . '#media/' . $imgTemp->getImageIndex() . '/' . pathinfo($imgTemp->getPath(), PATHINFO_BASENAME), false); - } + $xmlData = simplexml_load_string($xmlContent); + $file = unserialize(base64_decode((string) $xmlData->data)); + + // Update media links + for ($i = 0; $i < $file->getSlideCount(); ++$i) { + for ($j = 0; $j < $file->getSlide($i)->getShapeCollection()->count(); ++$j) { + if ($file->getSlide($i)->getShapeCollection()->offsetGet($j) instanceof AbstractDrawingAdapter) { + $imgTemp = $file->getSlide($i)->getShapeCollection()->offsetGet($j); + $imgPath = 'zip://' . $pFilename . '#media/' . $imgTemp->getImageIndex() . '/' . pathinfo($imgTemp->getPath(), PATHINFO_BASENAME); + if ($imgTemp instanceof DrawingFile) { + $imgTemp->setPath($imgPath, false); + } else { + $imgTemp->setPath($imgPath); } } - - $oArchive->close(); - return $file; } } - return null; + $oArchive->close(); + + return $file; } } diff --git a/PhpOffice/PhpPresentation/Shape/AbstractGraphic.php b/PhpOffice/PhpPresentation/Shape/AbstractGraphic.php old mode 100755 new mode 100644 index 982a08b..e7a4e73 --- a/PhpOffice/PhpPresentation/Shape/AbstractGraphic.php +++ b/PhpOffice/PhpPresentation/Shape/AbstractGraphic.php @@ -10,91 +10,94 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape; use PhpOffice\PhpPresentation\AbstractShape; use PhpOffice\PhpPresentation\ComparableInterface; /** - * Abstract drawing + * Abstract drawing. */ abstract class AbstractGraphic extends AbstractShape implements ComparableInterface { /** - * Image counter + * Image counter. * * @var int */ private static $imageCounter = 0; /** - * Image index + * Image index. * * @var int */ private $imageIndex = 0; /** - * Name + * Name. * * @var string */ protected $name; /** - * Description + * Description. * * @var string */ protected $description; /** - * Proportional resize + * Proportional resize. * - * @var boolean + * @var bool */ protected $resizeProportional; /** - * Slide relation ID (should not be used by user code!) + * Slide relation ID (should not be used by user code!). * * @var string */ public $relationId = null; /** - * Create a new \PhpOffice\PhpPresentation\Slide\AbstractDrawing + * Create a new \PhpOffice\PhpPresentation\Slide\AbstractDrawing. */ public function __construct() { // Initialise values - $this->name = ''; - $this->description = ''; + $this->name = ''; + $this->description = ''; $this->resizeProportional = true; // Set image index - self::$imageCounter++; + ++self::$imageCounter; $this->imageIndex = self::$imageCounter; // Initialize parent parent::__construct(); } - + public function __clone() { parent::__clone(); - - self::$imageCounter++; + + ++self::$imageCounter; $this->imageIndex = self::$imageCounter; } /** - * Get image index + * Get image index. * * @return int */ @@ -104,7 +107,7 @@ abstract class AbstractGraphic extends AbstractShape implements ComparableInterf } /** - * Get Name + * Get Name. * * @return string */ @@ -114,19 +117,21 @@ abstract class AbstractGraphic extends AbstractShape implements ComparableInterf } /** - * Set Name + * Set Name. + * + * @param string $pValue * - * @param string $pValue * @return $this */ public function setName($pValue = '') { $this->name = $pValue; + return $this; } /** - * Get Description + * Get Description. * * @return string */ @@ -136,9 +141,10 @@ abstract class AbstractGraphic extends AbstractShape implements ComparableInterf } /** - * Set Description + * Set Description. + * + * @param string $pValue * - * @param string $pValue * @return $this */ public function setDescription($pValue = '') @@ -149,16 +155,15 @@ abstract class AbstractGraphic extends AbstractShape implements ComparableInterf } /** - * Set Width + * Set Width. * - * @param int $pValue - * @return \PhpOffice\PhpPresentation\Shape\AbstractGraphic + * @return self */ - public function setWidth($pValue = 0) + public function setWidth(int $pValue = 0) { // Resize proportional? - if ($this->resizeProportional && $pValue != 0 && $this->width != 0) { - $ratio = $this->height / $this->width; + if ($this->resizeProportional && 0 != $pValue && 0 != $this->width) { + $ratio = $this->height / $this->width; $this->height = (int) round($ratio * $pValue); } @@ -169,16 +174,15 @@ abstract class AbstractGraphic extends AbstractShape implements ComparableInterf } /** - * Set Height + * Set Height. * - * @param int $pValue - * @return \PhpOffice\PhpPresentation\Shape\AbstractGraphic + * @return self */ - public function setHeight($pValue = 0) + public function setHeight(int $pValue = 0) { // Resize proportional? - if ($this->resizeProportional && $pValue != 0 && $this->height != 0) { - $ratio = $this->width / $this->height; + if ($this->resizeProportional && 0 != $pValue && 0 != $this->height) { + $ratio = $this->width / $this->height; $this->width = (int) round($ratio * $pValue); } @@ -189,22 +193,22 @@ abstract class AbstractGraphic extends AbstractShape implements ComparableInterf } /** - * Set width and height with proportional resize + * Set width and height with proportional resize. + * * @author Vincent@luo MSN:kele_100@hotmail.com - * @param int $width - * @param int $height - * @return \PhpOffice\PhpPresentation\Shape\AbstractGraphic + * + * @return self */ - public function setWidthAndHeight($width = 0, $height = 0) + public function setWidthAndHeight(int $width = 0, int $height = 0) { $xratio = $width / $this->width; $yratio = $height / $this->height; - if ($this->resizeProportional && !($width == 0 || $height == 0)) { + if ($this->resizeProportional && !(0 == $width || 0 == $height)) { if (($xratio * $this->height) < $height) { $this->height = (int) ceil($xratio * $this->height); - $this->width = $width; + $this->width = $width; } else { - $this->width = (int) ceil($yratio * $this->width); + $this->width = (int) ceil($yratio * $this->width); $this->height = $height; } } @@ -213,9 +217,9 @@ abstract class AbstractGraphic extends AbstractShape implements ComparableInterf } /** - * Get ResizeProportional + * Get ResizeProportional. * - * @return boolean + * @return bool */ public function isResizeProportional() { @@ -223,12 +227,11 @@ abstract class AbstractGraphic extends AbstractShape implements ComparableInterf } /** - * Set ResizeProportional + * Set ResizeProportional. * - * @param boolean $pValue - * @return \PhpOffice\PhpPresentation\Shape\AbstractGraphic + * @param bool $pValue */ - public function setResizeProportional($pValue = true) + public function setResizeProportional($pValue = true): self { $this->resizeProportional = $pValue; @@ -236,11 +239,11 @@ abstract class AbstractGraphic extends AbstractShape implements ComparableInterf } /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { return md5($this->name . $this->description . parent::getHashCode() . __CLASS__); } diff --git a/PhpOffice/PhpPresentation/Shape/AutoShape.php b/PhpOffice/PhpPresentation/Shape/AutoShape.php new file mode 100644 index 0000000..3d2c21c --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/AutoShape.php @@ -0,0 +1,300 @@ +outline = new Outline(); + } + + /** + * @return string + */ + public function getText(): string + { + return $this->text; + } + + /** + * @param string $text + * + * @return self + */ + public function setText(string $text): self + { + $this->text = $text; + + return $this; + } + + /** + * @return string + */ + public function getType(): string + { + return $this->type; + } + + /** + * @param string $type + * + * @return self + */ + public function setType(string $type): self + { + $this->type = $type; + + return $this; + } + + /** + * @return Outline + */ + public function getOutline(): Outline + { + return $this->outline; + } + + /** + * @param Outline $outline + * + * @return self + */ + public function setOutline(Outline $outline): self + { + $this->outline = $outline; + + return $this; + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Chart.php b/PhpOffice/PhpPresentation/Shape/Chart.php old mode 100755 new mode 100644 index a851801..3d56b73 --- a/PhpOffice/PhpPresentation/Shape/Chart.php +++ b/PhpOffice/PhpPresentation/Shape/Chart.php @@ -10,11 +10,14 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape; use PhpOffice\PhpPresentation\ComparableInterface; @@ -24,148 +27,187 @@ use PhpOffice\PhpPresentation\Shape\Chart\Title; use PhpOffice\PhpPresentation\Shape\Chart\View3D; /** - * Chart element + * Chart element. */ class Chart extends AbstractGraphic implements ComparableInterface { + public const BLANKAS_GAP = 'gap'; + public const BLANKAS_ZERO = 'zero'; + public const BLANKAS_SPAN = 'span'; + /** - * Title + * Title. * - * @var \PhpOffice\PhpPresentation\Shape\Chart\Title + * @var Title */ private $title; /** - * Legend + * Legend. * - * @var \PhpOffice\PhpPresentation\Shape\Chart\Legend + * @var Legend */ private $legend; /** - * Plot area + * Plot area. * - * @var \PhpOffice\PhpPresentation\Shape\Chart\PlotArea + * @var PlotArea */ private $plotArea; /** - * View 3D + * View 3D. * - * @var \PhpOffice\PhpPresentation\Shape\Chart\View3D + * @var View3D */ private $view3D; /** - * Include spreadsheet for editing data? Requires PHPExcel in the same folder as PhpPresentation + * Is the spreadsheet included for editing data ? * * @var bool */ private $includeSpreadsheet = false; /** - * Create a new Chart + * How to display blank (missing) values? Not set by default. + * + * @var string + */ + private $displayBlankAs = self::BLANKAS_ZERO; + + /** + * Create a new Chart. */ public function __construct() { // Initialize - $this->title = new Title(); - $this->legend = new Legend(); + $this->title = new Title(); + $this->legend = new Legend(); $this->plotArea = new PlotArea(); - $this->view3D = new View3D(); + $this->view3D = new View3D(); // Initialize parent parent::__construct(); } - + public function __clone() { parent::__clone(); - - $this->title = clone $this->title; - $this->legend = clone $this->legend; - $this->plotArea = clone $this->plotArea; - $this->view3D = clone $this->view3D; + + $this->title = clone $this->title; + $this->legend = clone $this->legend; + $this->plotArea = clone $this->plotArea; + $this->view3D = clone $this->view3D; } /** - * Get Title + * How missing/blank values are displayed on chart (dispBlanksAs property) * - * @return \PhpOffice\PhpPresentation\Shape\Chart\Title + * @return string */ - public function getTitle() + public function getDisplayBlankAs(): string + { + return $this->displayBlankAs; + } + + /** + * Get Title. + * + * @return Title + */ + public function getTitle(): Title { return $this->title; } /** - * Get Legend + * Get Legend. * - * @return \PhpOffice\PhpPresentation\Shape\Chart\Legend + * @return Legend */ - public function getLegend() + public function getLegend(): Legend { return $this->legend; } /** - * Get PlotArea + * Get PlotArea. * - * @return \PhpOffice\PhpPresentation\Shape\Chart\PlotArea + * @return PlotArea */ - public function getPlotArea() + public function getPlotArea(): PlotArea { return $this->plotArea; } /** - * Get View3D + * Get View3D. * - * @return \PhpOffice\PhpPresentation\Shape\Chart\View3D + * @return View3D */ - public function getView3D() + public function getView3D(): View3D { return $this->view3D; } /** - * Include spreadsheet for editing data? Requires PHPExcel in the same folder as PhpPresentation + * Is the spreadsheet included for editing data ? * - * @return boolean + * @return bool */ - public function hasIncludedSpreadsheet() + public function hasIncludedSpreadsheet(): bool { return $this->includeSpreadsheet; } /** - * Include spreadsheet for editing data? Requires PHPExcel in the same folder as PhpPresentation + * Define a way to display missing/blank values (dispBlanksAs property) * - * @param boolean $value - * @return \PhpOffice\PhpPresentation\Shape\Chart + * @param string $value + * + * @return self */ - public function setIncludeSpreadsheet($value = false) + public function setDisplayBlankAs(string $value): self { - $this->includeSpreadsheet = $value; + if (in_array($value, [self::BLANKAS_GAP, self::BLANKAS_SPAN, self::BLANKAS_ZERO])) { + $this->displayBlankAs = $value; + } + return $this; } /** - * Get indexed filename (using image index) + * Is the spreadsheet included for editing data ? + * + * @param bool $value + * + * @return self + */ + public function setIncludeSpreadsheet(bool $value = false): self + { + $this->includeSpreadsheet = $value; + + return $this; + } + + /** + * Get indexed filename (using image index). * * @return string */ - public function getIndexedFilename() + public function getIndexedFilename(): string { return 'chart' . $this->getImageIndex() . '.xml'; } /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { return md5(parent::getHashCode() . $this->title->getHashCode() . $this->legend->getHashCode() . $this->plotArea->getHashCode() . $this->view3D->getHashCode() . ($this->includeSpreadsheet ? 1 : 0) . __CLASS__); } diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Axis.php b/PhpOffice/PhpPresentation/Shape/Chart/Axis.php old mode 100755 new mode 100644 index e9433da..86e1878 --- a/PhpOffice/PhpPresentation/Shape/Chart/Axis.php +++ b/PhpOffice/PhpPresentation/Shape/Chart/Axis.php @@ -10,37 +10,50 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape\Chart; use PhpOffice\PhpPresentation\ComparableInterface; use PhpOffice\PhpPresentation\Style\Font; use PhpOffice\PhpPresentation\Style\Outline; -/** - * \PhpOffice\PhpPresentation\Shape\Chart\Axis - */ class Axis implements ComparableInterface { - const AXIS_X = 'x'; - const AXIS_Y = 'y'; + public const AXIS_X = 'x'; + public const AXIS_Y = 'y'; - const TICK_MARK_NONE = 'none'; - const TICK_MARK_CROSS = 'cross'; - const TICK_MARK_INSIDE = 'in'; - const TICK_MARK_OUTSIDE = 'out'; + public const TICK_MARK_NONE = 'none'; + public const TICK_MARK_CROSS = 'cross'; + public const TICK_MARK_INSIDE = 'in'; + public const TICK_MARK_OUTSIDE = 'out'; + + public const TICK_LABEL_POSITION_NEXT_TO = 'nextTo'; + public const TICK_LABEL_POSITION_HIGH = 'high'; + public const TICK_LABEL_POSITION_LOW = 'low'; + + public const CROSSES_AUTO = 'autoZero'; + public const CROSSES_MIN = 'min'; + public const CROSSES_MAX = 'max'; /** - * Title + * Title. * * @var string */ private $title = 'Axis Title'; + /** + * @var int + */ + private $titleRotation = 0; + /** * Format code * @@ -49,19 +62,19 @@ class Axis implements ComparableInterface private $formatCode = ''; /** - * Font + * Font. * - * @var \PhpOffice\PhpPresentation\Style\Font + * @var Font */ private $font; /** - * @var Gridlines + * @var Gridlines|null */ protected $majorGridlines; /** - * @var Gridlines + * @var Gridlines|null */ protected $minorGridlines; @@ -75,6 +88,16 @@ class Axis implements ComparableInterface */ protected $maxBounds; + /** + * @var string + */ + protected $crossesAt = self::CROSSES_AUTO; + + /** + * @var bool + */ + protected $isReversedOrder = false; + /** * @var string */ @@ -85,6 +108,11 @@ class Axis implements ComparableInterface */ protected $majorTickMark = self::TICK_MARK_NONE; + /** + * @var string + */ + protected $tickLabelPosition = self::TICK_LABEL_POSITION_NEXT_TO; + /** * @var float */ @@ -101,39 +129,40 @@ class Axis implements ComparableInterface protected $outline; /** - * @var boolean + * @var bool */ protected $isVisible = true; /** - * Create a new \PhpOffice\PhpPresentation\Shape\Chart\Axis instance + * Create a new \PhpOffice\PhpPresentation\Shape\Chart\Axis instance. * * @param string $title Title */ - public function __construct($title = 'Axis Title') + public function __construct(string $title = 'Axis Title') { $this->title = $title; $this->outline = new Outline(); - $this->font = new Font(); + $this->font = new Font(); } /** - * Get Title + * Get Title. * * @return string */ - public function getTitle() + public function getTitle(): string { return $this->title; } /** - * Set Title + * Set Title. * - * @param string $value - * @return \PhpOffice\PhpPresentation\Shape\Chart\Axis + * @param string $value + * + * @return self */ - public function setTitle($value = 'Axis Title') + public function setTitle(string $value = 'Axis Title'): self { $this->title = $value; @@ -141,45 +170,47 @@ class Axis implements ComparableInterface } /** - * Get font + * Get font. * - * @return \PhpOffice\PhpPresentation\Style\Font + * @return Font|null */ - public function getFont() + public function getFont(): ?Font { return $this->font; } /** - * Set font + * Set font. * - * @param \PhpOffice\PhpPresentation\Style\Font $pFont Font - * @throws \Exception - * @return \PhpOffice\PhpPresentation\Shape\Chart\Axis + * @param Font|null $font + * + * @return self */ - public function setFont(Font $pFont = null) + public function setFont(Font $font = null): self { - $this->font = $pFont; + $this->font = $font; + return $this; } /** - * Get Format Code + * Get Format Code. * * @return string */ - public function getFormatCode() + public function getFormatCode(): string { return $this->formatCode; } /** - * Set Format Code + * Set Format Code. * - * @param string $value - * @return \PhpOffice\PhpPresentation\Shape\Chart\Axis + * @param string $value + * + * @return self */ - public function setFormatCode($value = '') + public function setFormatCode(string $value = ''): self { $this->formatCode = $value; @@ -189,162 +220,230 @@ class Axis implements ComparableInterface /** * @return int|null */ - public function getMinBounds() + public function getMinBounds(): ?int { return $this->minBounds; } /** * @param int|null $minBounds - * @return Axis + * + * @return self */ - public function setMinBounds($minBounds = null) + public function setMinBounds(int $minBounds = null): self { - $this->minBounds = is_null($minBounds) ? null : (int)$minBounds; + $this->minBounds = is_null($minBounds) ? null : $minBounds; + return $this; } /** * @return int|null */ - public function getMaxBounds() + public function getMaxBounds(): ?int { return $this->maxBounds; } /** * @param int|null $maxBounds - * @return Axis + * + * @return self */ - public function setMaxBounds($maxBounds = null) + public function setMaxBounds(int $maxBounds = null): self { - $this->maxBounds = is_null($maxBounds) ? null : (int)$maxBounds; - return $this; - } + $this->maxBounds = is_null($maxBounds) ? null : $maxBounds; - /** - * @return Gridlines - */ - public function getMajorGridlines() - { - return $this->majorGridlines; - } - - /** - * @param Gridlines $majorGridlines - * @return Axis - */ - public function setMajorGridlines(Gridlines $majorGridlines) - { - $this->majorGridlines = $majorGridlines; - return $this; - } - - /** - * @return Gridlines - */ - public function getMinorGridlines() - { - return $this->minorGridlines; - } - - /** - * @param Gridlines $minorGridlines - * @return Axis - */ - public function setMinorGridlines(Gridlines $minorGridlines) - { - $this->minorGridlines = $minorGridlines; return $this; } /** * @return string */ - public function getMinorTickMark() + public function getCrossesAt(): string + { + return $this->crossesAt; + } + + /** + * @param string $value + * + * @return self + */ + public function setCrossesAt(string $value = self::CROSSES_AUTO): self + { + $this->crossesAt = $value; + + return $this; + } + + /** + * @return bool + */ + public function isReversedOrder(): bool + { + return $this->isReversedOrder; + } + + /** + * @param bool $value + * + * @return self + */ + public function setIsReversedOrder(bool $value = false): self + { + $this->isReversedOrder = $value; + + return $this; + } + + public function getMajorGridlines(): ?Gridlines + { + return $this->majorGridlines; + } + + public function setMajorGridlines(Gridlines $majorGridlines): self + { + $this->majorGridlines = $majorGridlines; + + return $this; + } + + public function getMinorGridlines(): ?Gridlines + { + return $this->minorGridlines; + } + + public function setMinorGridlines(Gridlines $minorGridlines): self + { + $this->minorGridlines = $minorGridlines; + + return $this; + } + + /** + * @return string + */ + public function getMinorTickMark(): string { return $this->minorTickMark; } /** - * @param string $pTickMark - * @return Axis + * @param string $tickMark + * + * @return self */ - public function setMinorTickMark($pTickMark = self::TICK_MARK_NONE) + public function setMinorTickMark(string $tickMark = self::TICK_MARK_NONE): self { - $this->minorTickMark = $pTickMark; + $this->minorTickMark = $tickMark; + return $this; } /** * @return string */ - public function getMajorTickMark() + public function getMajorTickMark(): string { return $this->majorTickMark; } /** - * @param string $pTickMark - * @return Axis + * @param string $tickMark + * + * @return self */ - public function setMajorTickMark($pTickMark = self::TICK_MARK_NONE) + public function setMajorTickMark(string $tickMark = self::TICK_MARK_NONE): self { - $this->majorTickMark = $pTickMark; + $this->majorTickMark = $tickMark; + return $this; } /** - * @return float + * @return float|null */ - public function getMinorUnit() + public function getMinorUnit(): ?float { return $this->minorUnit; } /** - * @param float $pUnit - * @return Axis + * @param float|null $unit + * + * @return self */ - public function setMinorUnit($pUnit = null) + public function setMinorUnit($unit = null): self { - $this->minorUnit = $pUnit; + $this->minorUnit = $unit; + return $this; } /** - * @return float + * @return float|null */ - public function getMajorUnit() + public function getMajorUnit(): ?float { return $this->majorUnit; } /** - * @param float $pUnit - * @return Axis + * @param float|null $unit + * + * @return self */ - public function setMajorUnit($pUnit = null) + public function setMajorUnit(float $unit = null): self { - $this->majorUnit = $pUnit; + $this->majorUnit = $unit; + return $this; } /** * @return Outline */ - public function getOutline() + public function getOutline(): Outline { return $this->outline; } /** * @param Outline $outline - * @return Axis + * + * @return self */ - public function setOutline(Outline $outline) + public function setOutline(Outline $outline): self { $this->outline = $outline; + + return $this; + } + + /** + * @return int + */ + public function getTitleRotation(): int + { + return $this->titleRotation; + } + + /** + * @param int $titleRotation + * + * @return self + */ + public function setTitleRotation(int $titleRotation): self + { + if ($titleRotation < 0) { + $titleRotation = 0; + } + if ($titleRotation > 360) { + $titleRotation = 360; + } + $this->titleRotation = $titleRotation; + return $this; } @@ -353,64 +452,95 @@ class Axis implements ComparableInterface * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { return md5($this->title . $this->formatCode . __CLASS__); } /** - * Hash index + * Hash index. * - * @var string + * @var int */ private $hashIndex; /** - * Get hash index + * Get hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @return string Hash index + * @return int|null Hash index */ - public function getHashIndex() + public function getHashIndex(): ?int { return $this->hashIndex; } /** - * Set hash index + * Set hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @param string $value Hash index - * @return $this + * @param int $value Hash index + * + * @return self */ - public function setHashIndex($value) + public function setHashIndex(int $value) { $this->hashIndex = $value; + return $this; } /** * Axis is hidden ? - * @return boolean + * + * @return bool */ - public function isVisible() + public function isVisible(): bool { return $this->isVisible; } /** - * Hide an axis + * Hide an axis. * - * @param boolean $value delete - * @return $this + * @param bool $value delete + * + * @return self */ - public function setIsVisible($value) + public function setIsVisible(bool $value): self { - $this->isVisible = (bool)$value; + $this->isVisible = $value; + + return $this; + } + + /** + * @return string + */ + public function getTickLabelPosition(): string + { + return $this->tickLabelPosition; + } + + /** + * @param string $value + * + * @return self + */ + public function setTickLabelPosition(string $value = self::TICK_LABEL_POSITION_NEXT_TO): self + { + if (in_array($value, [ + self::TICK_LABEL_POSITION_HIGH, + self::TICK_LABEL_POSITION_LOW, + self::TICK_LABEL_POSITION_NEXT_TO, + ])) { + $this->tickLabelPosition = $value; + } + return $this; } } diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Gridlines.php b/PhpOffice/PhpPresentation/Shape/Chart/Gridlines.php old mode 100755 new mode 100644 index 4040a5a..83f1248 --- a/PhpOffice/PhpPresentation/Shape/Chart/Gridlines.php +++ b/PhpOffice/PhpPresentation/Shape/Chart/Gridlines.php @@ -1,4 +1,22 @@ outline = $outline; + return $this; } } diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Legend.php b/PhpOffice/PhpPresentation/Shape/Chart/Legend.php old mode 100755 new mode 100644 index 989d932..6878f62 --- a/PhpOffice/PhpPresentation/Shape/Chart/Legend.php +++ b/PhpOffice/PhpPresentation/Shape/Chart/Legend.php @@ -10,11 +10,14 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape\Chart; use PhpOffice\PhpPresentation\ComparableInterface; @@ -24,102 +27,102 @@ use PhpOffice\PhpPresentation\Style\Fill; use PhpOffice\PhpPresentation\Style\Font; /** - * \PhpOffice\PhpPresentation\Shape\Chart\Legend + * \PhpOffice\PhpPresentation\Shape\Chart\Legend. */ class Legend implements ComparableInterface { /** Legend positions */ - const POSITION_BOTTOM = 'b'; - const POSITION_LEFT = 'l'; - const POSITION_RIGHT = 'r'; - const POSITION_TOP = 't'; - const POSITION_TOPRIGHT = 'tr'; + public const POSITION_BOTTOM = 'b'; + public const POSITION_LEFT = 'l'; + public const POSITION_RIGHT = 'r'; + public const POSITION_TOP = 't'; + public const POSITION_TOPRIGHT = 'tr'; /** - * Visible + * Visible. * - * @var boolean + * @var bool */ private $visible = true; /** - * Position + * Position. * * @var string */ private $position = self::POSITION_RIGHT; /** - * OffsetX (as a fraction of the chart) + * OffsetX (as a fraction of the chart). * * @var float */ private $offsetX = 0; /** - * OffsetY (as a fraction of the chart) + * OffsetY (as a fraction of the chart). * * @var float */ private $offsetY = 0; /** - * Width (as a fraction of the chart) + * Width (as a fraction of the chart). * * @var float */ private $width = 0; /** - * Height (as a fraction of the chart) + * Height (as a fraction of the chart). * * @var float */ private $height = 0; /** - * Font + * Font. * - * @var \PhpOffice\PhpPresentation\Style\Font + * @var Font|null */ private $font; /** - * Border + * Border. * * @var \PhpOffice\PhpPresentation\Style\Border */ private $border; /** - * Fill + * Fill. * * @var \PhpOffice\PhpPresentation\Style\Fill */ private $fill; /** - * Alignment + * Alignment. * * @var \PhpOffice\PhpPresentation\Style\Alignment */ private $alignment; /** - * Create a new \PhpOffice\PhpPresentation\Shape\Chart\Legend instance + * Create a new \PhpOffice\PhpPresentation\Shape\Chart\Legend instance. */ public function __construct() { - $this->font = new Font(); - $this->border = new Border(); - $this->fill = new Fill(); + $this->font = new Font(); + $this->border = new Border(); + $this->fill = new Fill(); $this->alignment = new Alignment(); } /** - * Get Visible + * Get Visible. * - * @return boolean + * @return bool */ public function isVisible() { @@ -127,19 +130,21 @@ class Legend implements ComparableInterface } /** - * Set Visible + * Set Visible. + * + * @param bool $value * - * @param boolean $value * @return \PhpOffice\PhpPresentation\Shape\Chart\Legend */ public function setVisible($value = true) { $this->visible = $value; + return $this; } /** - * Get Position + * Get Position. * * @return string */ @@ -149,130 +154,113 @@ class Legend implements ComparableInterface } /** - * Set Position + * Set Position. + * + * @param string $value * - * @param string $value * @return \PhpOffice\PhpPresentation\Shape\Chart\Legend */ public function setPosition($value = self::POSITION_RIGHT) { $this->position = $value; + return $this; } /** - * Get OffsetX (as a fraction of the chart) - * - * @return float + * Get OffsetX (as a fraction of the chart). */ - public function getOffsetX() + public function getOffsetX(): float { return $this->offsetX; } /** - * Set OffsetX (as a fraction of the chart) - * - * @param float|int $value - * @return \PhpOffice\PhpPresentation\Shape\Chart\Legend + * Set OffsetX (as a fraction of the chart). */ - public function setOffsetX($value = 0) + public function setOffsetX(float $pValue = 0): self { - $this->offsetX = (double)$value; + $this->offsetX = $pValue; + return $this; } /** - * Get OffsetY (as a fraction of the chart) - * - * @return float + * Get OffsetY (as a fraction of the chart). */ - public function getOffsetY() + public function getOffsetY(): float { return $this->offsetY; } /** - * Set OffsetY (as a fraction of the chart) - * - * @param float|int $value - * @return \PhpOffice\PhpPresentation\Shape\Chart\Legend + * Set OffsetY (as a fraction of the chart). */ - public function setOffsetY($value = 0) + public function setOffsetY(float $pValue = 0): self { - $this->offsetY = (double)$value; + $this->offsetY = $pValue; + return $this; } /** - * Get Width (as a fraction of the chart) - * - * @return float + * Get Width (as a fraction of the chart). */ - public function getWidth() + public function getWidth(): float { return $this->width; } /** - * Set Width (as a fraction of the chart) - * - * @param float|int $value - * @return \PhpOffice\PhpPresentation\Shape\Chart\Legend + * Set Width (as a fraction of the chart). */ - public function setWidth($value = 0) + public function setWidth(float $pValue = 0): self { - $this->width = (double)$value; + $this->width = $pValue; + return $this; } /** - * Get Height (as a fraction of the chart) - * - * @return float + * Get Height (as a fraction of the chart). */ - public function getHeight() + public function getHeight(): float { return $this->height; } /** - * Set Height (as a fraction of the chart) - * - * @param float|int $value - * @return \PhpOffice\PhpPresentation\Shape\Chart\Legend + * Set Height (as a fraction of the chart). */ - public function setHeight($value = 0) + public function setHeight(float $value = 0): self { - $this->height = (double)$value; + $this->height = $value; + return $this; } /** - * Get font - * - * @return \PhpOffice\PhpPresentation\Style\Font + * Get font. */ - public function getFont() + public function getFont(): ?Font { return $this->font; } /** - * Set font + * Set font. * - * @param \PhpOffice\PhpPresentation\Style\Font $pFont Font - * @throws \Exception - * @return \PhpOffice\PhpPresentation\Shape\Chart\Legend + * @param Font|null $pFont Font */ - public function setFont(Font $pFont = null) + public function setFont(Font $pFont = null): self { $this->font = $pFont; + return $this; } /** - * Get Border + * Get Border. * * @return \PhpOffice\PhpPresentation\Style\Border */ @@ -282,19 +270,19 @@ class Legend implements ComparableInterface } /** - * Set Border + * Set Border. * - * @param \PhpOffice\PhpPresentation\Style\Border $border * @return \PhpOffice\PhpPresentation\Shape\Chart\Legend */ public function setBorder(Border $border) { $this->border = $border; + return $this; } /** - * Get Fill + * Get Fill. * * @return \PhpOffice\PhpPresentation\Style\Fill */ @@ -304,19 +292,19 @@ class Legend implements ComparableInterface } /** - * Set Fill + * Set Fill. * - * @param \PhpOffice\PhpPresentation\Style\Fill $fill * @return \PhpOffice\PhpPresentation\Shape\Chart\Legend */ public function setFill(Fill $fill) { $this->fill = $fill; + return $this; } /** - * Get alignment + * Get alignment. * * @return \PhpOffice\PhpPresentation\Style\Alignment */ @@ -326,59 +314,61 @@ class Legend implements ComparableInterface } /** - * Set alignment + * Set alignment. * - * @param \PhpOffice\PhpPresentation\Style\Alignment $alignment * @return \PhpOffice\PhpPresentation\Shape\Chart\Legend */ public function setAlignment(Alignment $alignment) { $this->alignment = $alignment; + return $this; } /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { return md5($this->position . $this->offsetX . $this->offsetY . $this->width . $this->height . $this->font->getHashCode() . $this->border->getHashCode() . $this->fill->getHashCode() . $this->alignment->getHashCode() . ($this->visible ? 't' : 'f') . __CLASS__); } /** - * Hash index + * Hash index. * - * @var string + * @var int */ private $hashIndex; /** - * Get hash index + * Get hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @return string Hash index + * @return int|null Hash index */ - public function getHashIndex() + public function getHashIndex(): ?int { return $this->hashIndex; } /** - * Set hash index + * Set hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @param string $value Hash index + * @param int $value Hash index + * * @return Legend */ - public function setHashIndex($value) + public function setHashIndex(int $value) { $this->hashIndex = $value; + return $this; } } diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Marker.php b/PhpOffice/PhpPresentation/Shape/Chart/Marker.php old mode 100755 new mode 100644 index c1ca912..008f787 --- a/PhpOffice/PhpPresentation/Shape/Chart/Marker.php +++ b/PhpOffice/PhpPresentation/Shape/Chart/Marker.php @@ -10,30 +10,36 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape\Chart; -/** - * \PhpOffice\PhpPresentation\Shape\Chart\Axis - */ +use PhpOffice\PhpPresentation\Style\Border; +use PhpOffice\PhpPresentation\Style\Fill; + class Marker { - const SYMBOL_CIRCLE = 'circle'; - const SYMBOL_DASH = 'dash'; - const SYMBOL_DIAMOND = 'diamond'; - const SYMBOL_DOT = 'dot'; - const SYMBOL_NONE = 'none'; - const SYMBOL_PLUS = 'plus'; - const SYMBOL_SQUARE = 'square'; - const SYMBOL_STAR = 'star'; - const SYMBOL_TRIANGLE = 'triangle'; - const SYMBOL_X = 'x'; + public const SYMBOL_CIRCLE = 'circle'; + public const SYMBOL_DASH = 'dash'; + public const SYMBOL_DIAMOND = 'diamond'; + public const SYMBOL_DOT = 'dot'; + public const SYMBOL_NONE = 'none'; + public const SYMBOL_PLUS = 'plus'; + public const SYMBOL_SQUARE = 'square'; + public const SYMBOL_STAR = 'star'; + public const SYMBOL_TRIANGLE = 'triangle'; + public const SYMBOL_X = 'x'; - public static $arraySymbol = array( + /** + * @var array + */ + public static $arraySymbol = [ self::SYMBOL_CIRCLE, self::SYMBOL_DASH, self::SYMBOL_DIAMOND, @@ -43,8 +49,8 @@ class Marker self::SYMBOL_SQUARE, self::SYMBOL_STAR, self::SYMBOL_TRIANGLE, - self::SYMBOL_X - ); + self::SYMBOL_X, + ]; /** * @var string @@ -57,38 +63,82 @@ class Marker protected $size = 5; /** - * @return string + * @var Fill */ - public function getSymbol() + protected $fill; + + /** + * @var Border + */ + protected $border; + + public function __construct() + { + $this->fill = new Fill(); + $this->border = new Border(); + } + + public function getSymbol(): string { return $this->symbol; } - /** - * @param string $symbol - * @return $this - */ - public function setSymbol($symbol = self::SYMBOL_NONE) + public function setSymbol(string $symbol = self::SYMBOL_NONE): self { $this->symbol = $symbol; + return $this; } - /** - * @return int - */ - public function getSize() + public function getSize(): int { return $this->size; } - /** - * @param int $size - * @return $this - */ - public function setSize($size = 5) + public function setSize(int $size = 5): self { $this->size = $size; + + return $this; + } + + /** + * @return Fill + */ + public function getFill(): Fill + { + return $this->fill; + } + + /** + * @param Fill $fill + * + * @return self + */ + public function setFill(Fill $fill): self + { + $this->fill = $fill; + + return $this; + } + + /** + * @return Border + */ + public function getBorder(): Border + { + return $this->border; + } + + /** + * @param Border $border + * + * @return self + */ + public function setBorder(Border $border): self + { + $this->border = $border; + return $this; } } diff --git a/PhpOffice/PhpPresentation/Shape/Chart/PlotArea.php b/PhpOffice/PhpPresentation/Shape/Chart/PlotArea.php old mode 100755 new mode 100644 index a46a3b2..2eb9717 --- a/PhpOffice/PhpPresentation/Shape/Chart/PlotArea.php +++ b/PhpOffice/PhpPresentation/Shape/Chart/PlotArea.php @@ -10,108 +10,99 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape\Chart; use PhpOffice\PhpPresentation\ComparableInterface; +use PhpOffice\PhpPresentation\Exception\UndefinedChartTypeException; use PhpOffice\PhpPresentation\Shape\Chart\Type\AbstractType; /** - * \PhpOffice\PhpPresentation\Shape\Chart\PlotArea + * \PhpOffice\PhpPresentation\Shape\Chart\PlotArea. */ class PlotArea implements ComparableInterface { /** - * Type + * Type. * - * @var \PhpOffice\PhpPresentation\Shape\Chart\Type\AbstractType + * @var AbstractType|null */ private $type; /** - * Axis X + * Axis X. * - * @var \PhpOffice\PhpPresentation\Shape\Chart\Axis + * @var Axis */ private $axisX; /** - * Axis Y + * Axis Y. * - * @var \PhpOffice\PhpPresentation\Shape\Chart\Axis + * @var Axis */ private $axisY; /** - * OffsetX (as a fraction of the chart) + * OffsetX (as a fraction of the chart). * * @var float */ private $offsetX = 0; /** - * OffsetY (as a fraction of the chart) + * OffsetY (as a fraction of the chart). * * @var float */ private $offsetY = 0; /** - * Width (as a fraction of the chart) + * Width (as a fraction of the chart). * * @var float */ private $width = 0; /** - * Height (as a fraction of the chart) + * Height (as a fraction of the chart). * * @var float */ private $height = 0; - /** - * Create a new \PhpOffice\PhpPresentation\Shape\Chart\PlotArea instance - */ public function __construct() { - $this->type = null; $this->axisX = new Axis(); $this->axisY = new Axis(); } - + public function __clone() { - $this->axisX = clone $this->axisX; - $this->axisY = clone $this->axisY; + $this->axisX = clone $this->axisX; + $this->axisY = clone $this->axisY; } /** - * Get type - * - * @return AbstractType - * @throws \Exception + * @throws UndefinedChartTypeException */ - public function getType() + public function getType(): AbstractType { if (is_null($this->type)) { - throw new \Exception('Chart type has not been set.'); + throw new UndefinedChartTypeException(); } return $this->type; } - /** - * Set type - * - * @param \PhpOffice\PhpPresentation\Shape\Chart\Type\AbstractType $value - * @return \PhpOffice\PhpPresentation\Shape\Chart\PlotArea - */ - public function setType(Type\AbstractType $value) + public function setType(AbstractType $value): self { $this->type = $value; @@ -119,159 +110,141 @@ class PlotArea implements ComparableInterface } /** - * Get Axis X - * - * @return \PhpOffice\PhpPresentation\Shape\Chart\Axis + * Get Axis X. */ - public function getAxisX() + public function getAxisX(): Axis { return $this->axisX; } /** - * Get Axis Y - * - * @return \PhpOffice\PhpPresentation\Shape\Chart\Axis + * Get Axis Y. */ - public function getAxisY() + public function getAxisY(): Axis { return $this->axisY; } /** - * Get OffsetX (as a fraction of the chart) - * - * @return float + * Get OffsetX (as a fraction of the chart). */ - public function getOffsetX() + public function getOffsetX(): float { return $this->offsetX; } /** - * Set OffsetX (as a fraction of the chart) - * - * @param float|int $value - * @return \PhpOffice\PhpPresentation\Shape\Chart\PlotArea + * Set OffsetX (as a fraction of the chart). */ - public function setOffsetX($value = 0) + public function setOffsetX(float $pValue = 0): self { - $this->offsetX = (double)$value; + $this->offsetX = $pValue; return $this; } /** - * Get OffsetY (as a fraction of the chart) - * - * @return float + * Get OffsetY (as a fraction of the chart). */ - public function getOffsetY() + public function getOffsetY(): float { return $this->offsetY; } /** - * Set OffsetY (as a fraction of the chart) + * Set OffsetY (as a fraction of the chart). * - * @param float|int $value * @return \PhpOffice\PhpPresentation\Shape\Chart\PlotArea */ - public function setOffsetY($value = 0) + public function setOffsetY(float $pValue = 0): self { - $this->offsetY = (double)$value; + $this->offsetY = $pValue; return $this; } /** - * Get Width (as a fraction of the chart) - * - * @return float + * Get Width (as a fraction of the chart). */ - public function getWidth() + public function getWidth(): float { return $this->width; } /** - * Set Width (as a fraction of the chart) - * - * @param float|int $value - * @return \PhpOffice\PhpPresentation\Shape\Chart\PlotArea + * Set Width (as a fraction of the chart). */ - public function setWidth($value = 0) + public function setWidth(int $pValue = 0): self { - $this->width = (double)$value; + $this->width = $pValue; return $this; } /** - * Get Height (as a fraction of the chart) - * - * @return float + * Get Height (as a fraction of the chart). */ - public function getHeight() + public function getHeight(): float { return $this->height; } /** - * Set Height (as a fraction of the chart) + * Set Height (as a fraction of the chart). * - * @param float|int $value * @return \PhpOffice\PhpPresentation\Shape\Chart\PlotArea */ - public function setHeight($value = 0) + public function setHeight(float $value = 0): self { - $this->height = (double)$value; + $this->height = $value; return $this; } /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { return md5((is_null($this->type) ? 'null' : $this->type->getHashCode()) . $this->axisX->getHashCode() . $this->axisY->getHashCode() . $this->offsetX . $this->offsetY . $this->width . $this->height . __CLASS__); } /** - * Hash index + * Hash index. * - * @var string + * @var int */ private $hashIndex; /** - * Get hash index + * Get hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @return string Hash index + * @return int|null Hash index */ - public function getHashIndex() + public function getHashIndex(): ?int { return $this->hashIndex; } /** - * Set hash index + * Set hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @param string $value Hash index + * @param int $value Hash index + * * @return PlotArea */ - public function setHashIndex($value) + public function setHashIndex(int $value) { $this->hashIndex = $value; + return $this; } } diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Series.php b/PhpOffice/PhpPresentation/Shape/Chart/Series.php old mode 100755 new mode 100644 index 73cd03d..ea1bf49 --- a/PhpOffice/PhpPresentation/Shape/Chart/Series.php +++ b/PhpOffice/PhpPresentation/Shape/Chart/Series.php @@ -10,11 +10,14 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape\Chart; use PhpOffice\PhpPresentation\ComparableInterface; @@ -22,54 +25,49 @@ use PhpOffice\PhpPresentation\Style\Fill; use PhpOffice\PhpPresentation\Style\Font; use PhpOffice\PhpPresentation\Style\Outline; -/** - * \PhpOffice\PhpPresentation\Shape\Chart\Series - */ class Series implements ComparableInterface { /* Label positions */ - const LABEL_BESTFIT = 'bestFit'; - const LABEL_BOTTOM = 'b'; - const LABEL_CENTER = 'ctr'; - const LABEL_INSIDEBASE = 'inBase'; - const LABEL_INSIDEEND = 'inEnd'; - const LABEL_LEFT = 'i'; - const LABEL_OUTSIDEEND = 'outEnd'; - const LABEL_RIGHT = 'r'; - const LABEL_TOP = 't'; + public const LABEL_BESTFIT = 'bestFit'; + public const LABEL_BOTTOM = 'b'; + public const LABEL_CENTER = 'ctr'; + public const LABEL_INSIDEBASE = 'inBase'; + public const LABEL_INSIDEEND = 'inEnd'; + public const LABEL_LEFT = 'i'; + public const LABEL_OUTSIDEEND = 'outEnd'; + public const LABEL_RIGHT = 'r'; + public const LABEL_TOP = 't'; /** - * DataPointFills (key/value) - * @var array + * DataPointFills (key/value). + * + * @var array */ - protected $dataPointFills = array(); + protected $dataPointFills = []; /** - * Data Label Number Format + * Data Label Number Format. + * * @var string */ protected $DlblNumFormat = ''; /** - * Separator - * @var string + * @var string|null */ - protected $separator = null; + protected $separator; /** - * Fill - * @var \PhpOffice\PhpPresentation\Style\Fill + * @var Fill|null */ protected $fill; /** - * Font - * @var \PhpOffice\PhpPresentation\Style\Font + * @var Font|null */ protected $font; /** - * Label position * @var string */ protected $labelPosition = 'ctr'; @@ -80,98 +78,101 @@ class Series implements ComparableInterface protected $marker; /** - * @var Outline + * @var Outline|null */ protected $outline; /** - * Show Category Name - * @var boolean + * Show Category Name. + * + * @var bool */ private $showCategoryName = false; /** - * Show Leader Lines - * @var boolean + * Show Leader Lines. + * + * @var bool */ private $showLeaderLines = true; /** - * Show Legend Key - * @var boolean + * Show Legend Key. + * + * @var bool */ private $showLegendKey = false; /** - * ShowPercentage - * @var boolean + * ShowPercentage. + * + * @var bool */ private $showPercentage = false; /** - * ShowSeriesName - * @var boolean + * ShowSeriesName. + * + * @var bool */ private $showSeriesName = false; /** - * ShowValue - * @var boolean + * ShowValue. + * + * @var bool */ private $showValue = true; /** - * Title + * Title. + * * @var string */ private $title = 'Series Title'; /** - * Values (key/value) - * @var array + * Values (key/value). + * + * @var array */ - private $values = array(); + private $values = []; /** - * Hash index - * @var string + * Hash index. + * + * @var int */ private $hashIndex; /** - * Create a new \PhpOffice\PhpPresentation\Shape\Chart\Series instance - * - * @param string $title Title - * @param array $values Values + * @param string $title + * @param array $values */ - public function __construct($title = 'Series Title', $values = array()) + public function __construct(string $title = 'Series Title', array $values = []) { $this->fill = new Fill(); $this->font = new Font(); $this->font->setName('Calibri'); $this->font->setSize(9); - $this->title = $title; - $this->values = $values; $this->marker = new Marker(); + + $this->title = $title; + $this->values = $values; } /** - * Get Title - * - * @return string + * Get Title. */ - public function getTitle() + public function getTitle(): string { return $this->title; } /** - * Set Title - * - * @param string $value - * @return \PhpOffice\PhpPresentation\Shape\Chart\Series + * Set Title. */ - public function setTitle($value = 'Series Title') + public function setTitle(string $value = 'Series Title'): self { $this->title = $value; @@ -179,66 +180,50 @@ class Series implements ComparableInterface } /** - * Get Data Label NumFormat - * - * @return string + * Get Data Label NumFormat. */ - public function getDlblNumFormat() + public function getDlblNumFormat(): string { return $this->DlblNumFormat; } /** - * Has Data Label NumFormat - * - * @return string + * Has Data Label NumFormat. */ - public function hasDlblNumFormat() + public function hasDlblNumFormat(): bool { return !empty($this->DlblNumFormat); } /** - * Set Data Label NumFormat - * - * @param string $value - * @return \PhpOffice\PhpPresentation\Shape\Chart\Series + * Set Data Label NumFormat. */ - public function setDlblNumFormat($value = '') + public function setDlblNumFormat(string $value = ''): self { $this->DlblNumFormat = $value; + return $this; } /** - * Get Fill - * - * @return \PhpOffice\PhpPresentation\Style\Fill + * @return Fill */ - public function getFill() + public function getFill(): ?Fill { return $this->fill; } - /** - * Set Fill - * - * @param \PhpOffice\PhpPresentation\Style\Fill $fill - * @return Series - */ - public function setFill(Fill $fill = null) + public function setFill(Fill $fill = null): self { $this->fill = $fill; + return $this; } /** - * Get DataPointFill - * - * @param int $dataPointIndex Data point index. - * @return \PhpOffice\PhpPresentation\Style\Fill + * @param int $dataPointIndex data point index */ - public function getDataPointFill($dataPointIndex) + public function getDataPointFill(int $dataPointIndex): Fill { if (!isset($this->dataPointFills[$dataPointIndex])) { $this->dataPointFills[$dataPointIndex] = new Fill(); @@ -248,46 +233,44 @@ class Series implements ComparableInterface } /** - * Get DataPointFills - * * @return Fill[] */ - public function getDataPointFills() + public function getDataPointFills(): array { return $this->dataPointFills; } /** - * Get Values + * Get Values. * - * @return array + * @return array */ - public function getValues() + public function getValues(): array { return $this->values; } /** - * Set Values + * Set Values. * - * @param array $value - * @return \PhpOffice\PhpPresentation\Shape\Chart\Series + * @param array $values */ - public function setValues($value = array()) + public function setValues(array $values = []): self { - $this->values = $value; + $this->values = $values; return $this; } /** - * Add Value + * Add Value. * - * @param mixed $key - * @param mixed $value - * @return \PhpOffice\PhpPresentation\Shape\Chart\Series + * @param string $key + * @param string|null $value + * + * @return self */ - public function addValue($key, $value) + public function addValue(string $key, ?string $value): self { $this->values[$key] = $value; @@ -295,22 +278,17 @@ class Series implements ComparableInterface } /** - * Get ShowSeriesName - * - * @return boolean + * Get ShowSeriesName. */ - public function hasShowSeriesName() + public function hasShowSeriesName(): bool { return $this->showSeriesName; } /** - * Set ShowSeriesName - * - * @param boolean $value - * @return \PhpOffice\PhpPresentation\Shape\Chart\Series + * Set ShowSeriesName. */ - public function setShowSeriesName($value) + public function setShowSeriesName(bool $value): self { $this->showSeriesName = $value; @@ -318,22 +296,17 @@ class Series implements ComparableInterface } /** - * Get ShowCategoryName - * - * @return boolean + * Get ShowCategoryName. */ - public function hasShowCategoryName() + public function hasShowCategoryName(): bool { return $this->showCategoryName; } /** - * Set ShowCategoryName - * - * @param boolean $value - * @return \PhpOffice\PhpPresentation\Shape\Chart\Series + * Set ShowCategoryName. */ - public function setShowCategoryName($value) + public function setShowCategoryName(bool $value): self { $this->showCategoryName = $value; @@ -341,45 +314,35 @@ class Series implements ComparableInterface } /** - * Get ShowValue - * - * @return boolean + * Get ShowValue. */ - public function hasShowLegendKey() + public function hasShowLegendKey(): bool { return $this->showLegendKey; } /** - * Set ShowValue - * - * @param boolean $value - * @return \PhpOffice\PhpPresentation\Shape\Chart\Series + * Set ShowValue. */ - public function setShowLegendKey($value) + public function setShowLegendKey(bool $value): self { - $this->showLegendKey = (bool)$value; + $this->showLegendKey = $value; return $this; } /** - * Get ShowValue - * - * @return boolean + * Get ShowValue. */ - public function hasShowValue() + public function hasShowValue(): bool { return $this->showValue; } /** - * Set ShowValue - * - * @param boolean $value - * @return \PhpOffice\PhpPresentation\Shape\Chart\Series + * Set ShowValue. */ - public function setShowValue($value) + public function setShowValue(bool $value): self { $this->showValue = $value; @@ -387,73 +350,54 @@ class Series implements ComparableInterface } /** - * Get ShowPercentage - * - * @return boolean + * Get ShowPercentage. */ - public function hasShowPercentage() + public function hasShowPercentage(): bool { return $this->showPercentage; } /** - * Set ShowPercentage - * - * @param boolean $value - * @return \PhpOffice\PhpPresentation\Shape\Chart\Series + * Set ShowPercentage. */ - public function setShowPercentage($value) + public function setShowPercentage(bool $value): self { $this->showPercentage = $value; return $this; } - /** - * Get ShowLeaderLines - * - * @return boolean - */ - public function hasShowSeparator() + public function hasShowSeparator(): bool { return !is_null($this->separator); } - /** - * Set Separator - * @param string $pValue - * @return \PhpOffice\PhpPresentation\Shape\Chart\Series - */ - public function setSeparator($pValue) + public function setSeparator(?string $pValue): self { $this->separator = $pValue; + return $this; } - /** - * Get Separator - * @return string - */ - public function getSeparator() + public function getSeparator(): ?string { return $this->separator; } /** - * Get ShowLeaderLines - * - * @return boolean + * Get ShowLeaderLines. */ - public function hasShowLeaderLines() + public function hasShowLeaderLines(): bool { return $this->showLeaderLines; } /** - * Set ShowLeaderLines + * Set ShowLeaderLines. * - * @param boolean $value - * @return \PhpOffice\PhpPresentation\Shape\Chart\Series + * @param bool $value + * + * @return self */ public function setShowLeaderLines($value) { @@ -463,23 +407,21 @@ class Series implements ComparableInterface } /** - * Get font + * Get font. * - * @return \PhpOffice\PhpPresentation\Style\Font + * @return Font */ - public function getFont() + public function getFont(): ?Font { return $this->font; } /** - * Set font + * Set font. * - * @param \PhpOffice\PhpPresentation\Style\Font $pFont Font - * @throws \Exception - * @return \PhpOffice\PhpPresentation\Shape\Chart\Series + * @param Font|null $pFont Font */ - public function setFont(Font $pFont = null) + public function setFont(Font $pFont = null): self { $this->font = $pFont; @@ -487,105 +429,87 @@ class Series implements ComparableInterface } /** - * Get label position - * - * @return string + * Get label position. */ - public function getLabelPosition() + public function getLabelPosition(): string { return $this->labelPosition; } /** - * Set label position - * - * @param string $value - * @return \PhpOffice\PhpPresentation\Shape\Chart\Series + * Set label position. */ - public function setLabelPosition($value) + public function setLabelPosition(string $value): self { $this->labelPosition = $value; return $this; } - /** - * @return Marker - */ - public function getMarker() + public function getMarker(): Marker { return $this->marker; } - /** - * @param Marker $marker - * @return \PhpOffice\PhpPresentation\Shape\Chart\Series - */ - public function setMarker(Marker $marker) + public function setMarker(Marker $marker): self { $this->marker = $marker; + return $this; } - /** - * @return Outline - */ - public function getOutline() + public function getOutline(): ?Outline { return $this->outline; } - /** - * @param Outline $outline - * @return \PhpOffice\PhpPresentation\Shape\Chart\Series - */ - public function setOutline(Outline $outline) + public function setOutline(?Outline $outline): self { $this->outline = $outline; + return $this; } /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { return md5((is_null($this->fill) ? 'null' : $this->fill->getHashCode()) . (is_null($this->font) ? 'null' : $this->font->getHashCode()) . var_export($this->values, true) . var_export($this, true) . __CLASS__); } /** - * Get hash index + * Get hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @return string Hash index + * @return int|null Hash index */ - public function getHashIndex() + public function getHashIndex(): ?int { return $this->hashIndex; } /** - * Set hash index + * Set hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @param string $value Hash index - * @return \PhpOffice\PhpPresentation\Shape\Chart\Series + * @param int $value Hash index */ - public function setHashIndex($value) + public function setHashIndex(int $value): self { $this->hashIndex = $value; + return $this; } - /** - * @link http://php.net/manual/en/language.oop5.cloning.php + * @see http://php.net/manual/en/language.oop5.cloning.php */ public function __clone() { diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Title.php b/PhpOffice/PhpPresentation/Shape/Chart/Title.php old mode 100755 new mode 100644 index 5643a6d..a031657 --- a/PhpOffice/PhpPresentation/Shape/Chart/Title.php +++ b/PhpOffice/PhpPresentation/Shape/Chart/Title.php @@ -10,11 +10,14 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape\Chart; use PhpOffice\PhpPresentation\ComparableInterface; @@ -22,88 +25,88 @@ use PhpOffice\PhpPresentation\Style\Alignment; use PhpOffice\PhpPresentation\Style\Font; /** - * \PhpOffice\PhpPresentation\Shape\Chart\Title + * \PhpOffice\PhpPresentation\Shape\Chart\Title. */ class Title implements ComparableInterface { /** - * Visible + * Visible. * - * @var boolean + * @var bool */ private $visible = true; /** - * Text + * Text. * * @var string */ private $text = 'Chart Title'; /** - * OffsetX (as a fraction of the chart) + * OffsetX (as a fraction of the chart). * * @var float */ private $offsetX = 0.01; /** - * OffsetY (as a fraction of the chart) + * OffsetY (as a fraction of the chart). * * @var float */ private $offsetY = 0.01; /** - * Width (as a fraction of the chart) + * Width (as a fraction of the chart). * * @var float */ private $width = 0; /** - * Height (as a fraction of the chart) + * Height (as a fraction of the chart). * * @var float */ private $height = 0; /** - * Alignment + * Alignment. * * @var \PhpOffice\PhpPresentation\Style\Alignment */ private $alignment; /** - * Font + * Font. * * @var \PhpOffice\PhpPresentation\Style\Font */ private $font; /** - * Hash index + * Hash index. * - * @var string + * @var int */ private $hashIndex; /** - * Create a new \PhpOffice\PhpPresentation\Shape\Chart\Title instance + * Create a new \PhpOffice\PhpPresentation\Shape\Chart\Title instance. */ public function __construct() { $this->alignment = new Alignment(); - $this->font = new Font(); + $this->font = new Font(); $this->font->setName('Calibri'); $this->font->setSize(18); } /** - * Get Visible + * Get Visible. * - * @return boolean + * @return bool */ public function isVisible() { @@ -111,9 +114,10 @@ class Title implements ComparableInterface } /** - * Set Visible + * Set Visible. + * + * @param bool $value * - * @param boolean $value * @return \PhpOffice\PhpPresentation\Shape\Chart\Title */ public function setVisible($value = true) @@ -124,7 +128,7 @@ class Title implements ComparableInterface } /** - * Get Text + * Get Text. * * @return string */ @@ -134,9 +138,10 @@ class Title implements ComparableInterface } /** - * Set Text + * Set Text. + * + * @param string $value * - * @param string $value * @return \PhpOffice\PhpPresentation\Shape\Chart\Title */ public function setText($value = null) @@ -147,22 +152,17 @@ class Title implements ComparableInterface } /** - * Get OffsetX (as a fraction of the chart) - * - * @return float + * Get OffsetX (as a fraction of the chart). */ - public function getOffsetX() + public function getOffsetX(): float { return $this->offsetX; } /** - * Set OffsetX (as a fraction of the chart) - * - * @param float $value - * @return \PhpOffice\PhpPresentation\Shape\Chart\Title + * Set OffsetX (as a fraction of the chart). */ - public function setOffsetX($value = 0.01) + public function setOffsetX(float $value = 0.01): self { $this->offsetX = $value; @@ -170,92 +170,73 @@ class Title implements ComparableInterface } /** - * Get OffsetY (as a fraction of the chart) - * - * @return float + * Get OffsetY (as a fraction of the chart). */ - public function getOffsetY() + public function getOffsetY(): float { return $this->offsetY; } /** - * Set OffsetY (as a fraction of the chart) - * - * @param float $value - * @return \PhpOffice\PhpPresentation\Shape\Chart\Title + * Set OffsetY (as a fraction of the chart). */ - public function setOffsetY($value = 0.01) + public function setOffsetY(float $pValue = 0.01): self { - $this->offsetY = $value; + $this->offsetY = $pValue; return $this; } /** - * Get Width (as a fraction of the chart) - * - * @return float + * Get Width (as a fraction of the chart). */ - public function getWidth() + public function getWidth(): float { return $this->width; } /** - * Set Width (as a fraction of the chart) - * - * @param float|int $value - * @return \PhpOffice\PhpPresentation\Shape\Chart\Title + * Set Width (as a fraction of the chart). */ - public function setWidth($value = 0) + public function setWidth(float $pValue = 0): self { - $this->width = (double)$value; + $this->width = $pValue; return $this; } /** - * Get Height (as a fraction of the chart) - * - * @return float + * Get Height (as a fraction of the chart). */ - public function getHeight() + public function getHeight(): float { return $this->height; } /** - * Set Height (as a fraction of the chart) - * - * @param float|int $value - * @return \PhpOffice\PhpPresentation\Shape\Chart\Title + * Set Height (as a fraction of the chart). */ - public function setHeight($value = 0) + public function setHeight(float $value = 0): self { - $this->height = (double)$value; + $this->height = $value; return $this; } /** - * Get font - * - * @return \PhpOffice\PhpPresentation\Style\Font + * Get font. */ - public function getFont() + public function getFont(): ?Font { return $this->font; } /** - * Set font + * Set font. * - * @param \PhpOffice\PhpPresentation\Style\Font $pFont Font - * @throws \Exception - * @return \PhpOffice\PhpPresentation\Shape\Chart\Title + * @param Font|null $pFont Font */ - public function setFont(Font $pFont = null) + public function setFont(Font $pFont = null): self { $this->font = $pFont; @@ -263,7 +244,7 @@ class Title implements ComparableInterface } /** - * Get alignment + * Get alignment. * * @return \PhpOffice\PhpPresentation\Style\Alignment */ @@ -273,9 +254,8 @@ class Title implements ComparableInterface } /** - * Set alignment + * Set alignment. * - * @param \PhpOffice\PhpPresentation\Style\Alignment $alignment * @return \PhpOffice\PhpPresentation\Shape\Chart\Title */ public function setAlignment(Alignment $alignment) @@ -286,40 +266,42 @@ class Title implements ComparableInterface } /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { return md5($this->text . $this->offsetX . $this->offsetY . $this->width . $this->height . $this->font->getHashCode() . $this->alignment->getHashCode() . ($this->visible ? 't' : 'f') . __CLASS__); } /** - * Get hash index + * Get hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @return string Hash index + * @return int|null Hash index */ - public function getHashIndex() + public function getHashIndex(): ?int { return $this->hashIndex; } /** - * Set hash index + * Set hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @param string $value Hash index + * @param int $value Hash index + * * @return Title */ - public function setHashIndex($value) + public function setHashIndex(int $value) { $this->hashIndex = $value; + return $this; } } diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractType.php b/PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractType.php old mode 100755 new mode 100644 index 57c74db..1121231 --- a/PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractType.php +++ b/PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractType.php @@ -10,162 +10,141 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape\Chart\Type; use PhpOffice\PhpPresentation\ComparableInterface; use PhpOffice\PhpPresentation\Shape\Chart\Series; /** - * \PhpOffice\PhpPresentation\Shape\Chart\Type + * \PhpOffice\PhpPresentation\Shape\Chart\Type. */ abstract class AbstractType implements ComparableInterface { /** * Has Axis X? * - * @var boolean + * @var bool */ protected $hasAxisX = true; /** * Has Axis Y? * - * @var boolean + * @var bool */ protected $hasAxisY = true; /** - * Hash index + * Hash index. * - * @var string + * @var int */ private $hashIndex; - + /** - * Data - * - * @var array + * @var array */ - private $data = array(); + private $series = []; /** * Has Axis X? - * - * @return boolean */ - public function hasAxisX() + public function hasAxisX(): bool { return $this->hasAxisX; } /** * Has Axis Y? - * - * @return boolean */ - public function hasAxisY() + public function hasAxisY(): bool { return $this->hasAxisY; } /** - * Get hash index + * Get hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @return string Hash index + * @return int|null Hash index */ - public function getHashIndex() + public function getHashIndex(): ?int { return $this->hashIndex; } /** - * Set hash index + * Set hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @param string $value Hash index + * @param int $value Hash index + * * @return AbstractType */ - public function setHashIndex($value) + public function setHashIndex(int $value) { $this->hashIndex = $value; + return $this; } /** - * Add Series + * Add Series. * - * @param \PhpOffice\PhpPresentation\Shape\Chart\Series $value * @return $this */ public function addSeries(Series $value) { - $this->data[] = $value; + $this->series[] = $value; + return $this; } /** - * Get Series + * Get Series. * - * @return \PhpOffice\PhpPresentation\Shape\Chart\Series[] + * @return array */ - public function getSeries() + public function getSeries(): array { - return $this->data; + return $this->series; } /** - * Set Series + * Set Series. + * + * @param array $series * - * @param array $value Array of \PhpOffice\PhpPresentation\Shape\Chart\Series * @return $this */ - public function setSeries($value = array()) + public function setSeries(array $series = []) { - $this->data = $value; + $this->series = $series; + return $this; } /** - * Get Data - * - * @deprecated getSeries - */ - public function getData() - { - return $this->getSeries(); - } - - /** - * Set Data - * - * @deprecated setSeries - * @param array $value - * @return AbstractType - */ - public function setData($value = array()) - { - return $this->setSeries($value); - } - - /** - * @link http://php.net/manual/en/language.oop5.cloning.php + * @see http://php.net/manual/en/language.oop5.cloning.php */ public function __clone() { - $arrayClone = array(); - foreach ($this->data as $itemSeries) { + $arrayClone = []; + foreach ($this->series as $itemSeries) { $arrayClone[] = clone $itemSeries; } - $this->data = $arrayClone; + $this->series = $arrayClone; } } diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractTypeBar.php b/PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractTypeBar.php old mode 100755 new mode 100644 index f0a8c31..2347176 --- a/PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractTypeBar.php +++ b/PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractTypeBar.php @@ -10,66 +10,75 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape\Chart\Type; /** - * \PhpOffice\PhpPresentation\Shape\Chart\Type\Bar + * \PhpOffice\PhpPresentation\Shape\Chart\Type\Bar. */ class AbstractTypeBar extends AbstractType { /** Orientation of bars */ - const DIRECTION_VERTICAL = 'col'; - const DIRECTION_HORIZONTAL = 'bar'; + public const DIRECTION_VERTICAL = 'col'; + public const DIRECTION_HORIZONTAL = 'bar'; /** Grouping of bars */ - const GROUPING_CLUSTERED = 'clustered'; //Chart series are drawn next to each other along the category axis. - const GROUPING_STACKED = 'stacked'; //Chart series are drawn next to each other on the value axis. - const GROUPING_PERCENTSTACKED = 'percentStacked'; //Chart series are drawn next to each other along the value axis and scaled to total 100% - + public const GROUPING_CLUSTERED = 'clustered'; //Chart series are drawn next to each other along the category axis. + public const GROUPING_STACKED = 'stacked'; //Chart series are drawn next to each other on the value axis. + public const GROUPING_PERCENTSTACKED = 'percentStacked'; //Chart series are drawn next to each other along the value axis and scaled to total 100% /** - * Orientation of bars + * Orientation of bars. * * @var string */ protected $barDirection = self::DIRECTION_VERTICAL; - /** - * Grouping of bars + * Grouping of bars. * * @var string */ protected $barGrouping = self::GROUPING_CLUSTERED; - /** - * Space between bar or columns clusters + * Space between bar or columns clusters. * * @var int */ protected $gapWidthPercent = 150; + /** + * Overlap within bar or columns clusters. Value between 100 and -100 percent. + * For stacked bar charts, the default overlap will be 100, for grouped bar charts 0. + * + * @var int + */ + protected $overlapWidthPercent = 0; /** - * Set bar orientation + * Set bar orientation. + * + * @param string $value * - * @param string $value * @return \PhpOffice\PhpPresentation\Shape\Chart\Type\AbstractTypeBar */ public function setBarDirection($value = self::DIRECTION_VERTICAL) { $this->barDirection = $value; + return $this; } /** - * Get orientation + * Get orientation. * * @return string */ @@ -79,19 +88,26 @@ class AbstractTypeBar extends AbstractType } /** - * Set bar grouping (stack or expanded style bar) + * Set bar grouping (stack or expanded style bar). + * + * @param string $value * - * @param string $value * @return \PhpOffice\PhpPresentation\Shape\Chart\Type\AbstractTypeBar */ public function setBarGrouping($value = self::GROUPING_CLUSTERED) { $this->barGrouping = $value; + $this->overlapWidthPercent = 0; + + if ($value === self::GROUPING_STACKED || $value === self::GROUPING_PERCENTSTACKED) { + $this->overlapWidthPercent = 100; + } + return $this; } /** - * Get grouping (stack or expanded style bar) + * Get grouping (stack or expanded style bar). * * @return string */ @@ -110,6 +126,7 @@ class AbstractTypeBar extends AbstractType /** * @param int $gapWidthPercent + * * @return $this */ public function setGapWidthPercent($gapWidthPercent) @@ -121,20 +138,48 @@ class AbstractTypeBar extends AbstractType $gapWidthPercent = 500; } $this->gapWidthPercent = $gapWidthPercent; + return $this; } - + /** - * Get hash code + * @return int + */ + public function getOverlapWidthPercent(): int + { + return $this->overlapWidthPercent; + } + + /** + * @param int $value overlap width percentage + * + * @return self + */ + public function setOverlapWidthPercent(int $value): self + { + if ($value < -100) { + $value = -100; + } + if ($value > 100) { + $value = 100; + } + $this->overlapWidthPercent = $value; + + return $this; + } + + /** + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { $hash = ''; foreach ($this->getSeries() as $series) { $hash .= $series->getHashCode(); } + return $hash; } } diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractTypeLine.php b/PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractTypeLine.php new file mode 100644 index 0000000..3040f0b --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractTypeLine.php @@ -0,0 +1,65 @@ +isSmooth; + } + + /** + * Set Line Smoothness + * + * @param bool $value + * + * @return AbstractTypeLine + */ + public function setIsSmooth(bool $value = true): AbstractTypeLine + { + $this->isSmooth = $value; + + return $this; + } + + /** + * Get hash code. + * + * @return string Hash code + */ + public function getHashCode(): string + { + return md5($this->isSmooth() ? '1' : '0'); + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractTypePie.php b/PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractTypePie.php old mode 100755 new mode 100644 index f055fe3..b538d9a --- a/PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractTypePie.php +++ b/PhpOffice/PhpPresentation/Shape/Chart/Type/AbstractTypePie.php @@ -10,67 +10,67 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape\Chart\Type; /** - * \PhpOffice\PhpPresentation\Shape\Chart\Type\Bar + * \PhpOffice\PhpPresentation\Shape\Chart\Type\Bar. */ class AbstractTypePie extends AbstractType { /** - * Create a new self instance + * Create a new self instance. */ public function __construct() { $this->hasAxisX = false; $this->hasAxisY = false; } - + /** - * Explosion of the Pie + * Explosion of the Pie. * - * @var integer + * @var int */ protected $explosion = 0; - + /** - * Set explosion - * - * @param integer $value - * @return \PhpOffice\PhpPresentation\Shape\Chart\Type\AbstractTypePie + * Set explosion. */ - public function setExplosion($value = 0) + public function setExplosion(int $value = 0): self { $this->explosion = $value; + return $this; } - + /** - * Get orientation - * - * @return string + * Get orientation. */ - public function getExplosion() + public function getExplosion(): int { return $this->explosion; } - + /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { $hash = ''; foreach ($this->getSeries() as $series) { $hash .= $series->getHashCode(); } + return $hash; } } diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Type/Area.php b/PhpOffice/PhpPresentation/Shape/Chart/Type/Area.php old mode 100755 new mode 100644 index fdfc5aa..49fc9bd --- a/PhpOffice/PhpPresentation/Shape/Chart/Type/Area.php +++ b/PhpOffice/PhpPresentation/Shape/Chart/Type/Area.php @@ -10,31 +10,35 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape\Chart\Type; use PhpOffice\PhpPresentation\ComparableInterface; /** - * \PhpOffice\PhpPresentation\Shape\Chart\Type\Area + * \PhpOffice\PhpPresentation\Shape\Chart\Type\Area. */ class Area extends AbstractType implements ComparableInterface { /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { $hash = ''; foreach ($this->getSeries() as $series) { $hash .= $series->getHashCode(); } + return md5($hash . __CLASS__); } } diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Type/Bar.php b/PhpOffice/PhpPresentation/Shape/Chart/Type/Bar.php old mode 100755 new mode 100644 index da44e04..9ea5b3d --- a/PhpOffice/PhpPresentation/Shape/Chart/Type/Bar.php +++ b/PhpOffice/PhpPresentation/Shape/Chart/Type/Bar.php @@ -10,26 +10,29 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape\Chart\Type; use PhpOffice\PhpPresentation\ComparableInterface; /** - * \PhpOffice\PhpPresentation\Shape\Chart\Type\Bar + * \PhpOffice\PhpPresentation\Shape\Chart\Type\Bar. */ class Bar extends AbstractTypeBar implements ComparableInterface { /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { return md5(parent::getHashCode() . __CLASS__); } diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Type/Bar3D.php b/PhpOffice/PhpPresentation/Shape/Chart/Type/Bar3D.php old mode 100755 new mode 100644 index 540a530..1011ad0 --- a/PhpOffice/PhpPresentation/Shape/Chart/Type/Bar3D.php +++ b/PhpOffice/PhpPresentation/Shape/Chart/Type/Bar3D.php @@ -10,26 +10,29 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape\Chart\Type; use PhpOffice\PhpPresentation\ComparableInterface; /** - * \PhpOffice\PhpPresentation\Shape\Chart\Type\Bar3D + * \PhpOffice\PhpPresentation\Shape\Chart\Type\Bar3D. */ class Bar3D extends AbstractTypeBar implements ComparableInterface { /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { return md5(parent::getHashCode() . __CLASS__); } diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Type/Doughnut.php b/PhpOffice/PhpPresentation/Shape/Chart/Type/Doughnut.php old mode 100755 new mode 100644 index 5f30a51..292c823 --- a/PhpOffice/PhpPresentation/Shape/Chart/Type/Doughnut.php +++ b/PhpOffice/PhpPresentation/Shape/Chart/Type/Doughnut.php @@ -10,22 +10,26 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape\Chart\Type; use PhpOffice\PhpPresentation\ComparableInterface; /** - * self + * self. */ class Doughnut extends AbstractTypePie implements ComparableInterface { /** - * Hole Size + * Hole Size. + * * @var int */ protected $holeSize = 50; @@ -40,8 +44,10 @@ class Doughnut extends AbstractTypePie implements ComparableInterface /** * @param int $holeSize + * * @return Doughnut - * @link https://msdn.microsoft.com/en-us/library/documentformat.openxml.drawing.charts.holesize(v=office.14).aspx + * + * @see https://msdn.microsoft.com/en-us/library/documentformat.openxml.drawing.charts.holesize(v=office.14).aspx */ public function setHoleSize($holeSize = 50) { @@ -52,15 +58,16 @@ class Doughnut extends AbstractTypePie implements ComparableInterface $holeSize = 90; } $this->holeSize = $holeSize; + return $this; } /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { return md5(parent::getHashCode() . __CLASS__); } diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Type/Line.php b/PhpOffice/PhpPresentation/Shape/Chart/Type/Line.php old mode 100755 new mode 100644 index 9be7a78..cefca9d --- a/PhpOffice/PhpPresentation/Shape/Chart/Type/Line.php +++ b/PhpOffice/PhpPresentation/Shape/Chart/Type/Line.php @@ -10,31 +10,32 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape\Chart\Type; use PhpOffice\PhpPresentation\ComparableInterface; -/** - * \PhpOffice\PhpPresentation\Shape\Chart\Type\Line - */ -class Line extends AbstractType implements ComparableInterface +class Line extends AbstractTypeLine implements ComparableInterface { /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { $hash = ''; foreach ($this->getSeries() as $series) { $hash .= $series->getHashCode(); } - return md5($hash . __CLASS__); + + return md5(parent::getHashCode() . $hash . __CLASS__); } } diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Type/Pie.php b/PhpOffice/PhpPresentation/Shape/Chart/Type/Pie.php old mode 100755 new mode 100644 index a1c0d57..53cb6b1 --- a/PhpOffice/PhpPresentation/Shape/Chart/Type/Pie.php +++ b/PhpOffice/PhpPresentation/Shape/Chart/Type/Pie.php @@ -10,26 +10,29 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape\Chart\Type; use PhpOffice\PhpPresentation\ComparableInterface; /** - * self + * self. */ class Pie extends AbstractTypePie implements ComparableInterface { /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { return md5(parent::getHashCode() . __CLASS__); } diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Type/Pie3D.php b/PhpOffice/PhpPresentation/Shape/Chart/Type/Pie3D.php old mode 100755 new mode 100644 index b7bc6bb..05689c6 --- a/PhpOffice/PhpPresentation/Shape/Chart/Type/Pie3D.php +++ b/PhpOffice/PhpPresentation/Shape/Chart/Type/Pie3D.php @@ -10,26 +10,29 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape\Chart\Type; use PhpOffice\PhpPresentation\ComparableInterface; /** - * self + * self. */ class Pie3D extends AbstractTypePie implements ComparableInterface { /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { return md5(parent::getHashCode() . __CLASS__); } diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Type/Radar.php b/PhpOffice/PhpPresentation/Shape/Chart/Type/Radar.php new file mode 100644 index 0000000..9f45b5d --- /dev/null +++ b/PhpOffice/PhpPresentation/Shape/Chart/Type/Radar.php @@ -0,0 +1,41 @@ +getSeries() as $series) { + $hash .= $series->getHashCode(); + } + + return md5($hash . __CLASS__); + } +} diff --git a/PhpOffice/PhpPresentation/Shape/Chart/Type/Scatter.php b/PhpOffice/PhpPresentation/Shape/Chart/Type/Scatter.php old mode 100755 new mode 100644 index ba1fa3b..58f2576 --- a/PhpOffice/PhpPresentation/Shape/Chart/Type/Scatter.php +++ b/PhpOffice/PhpPresentation/Shape/Chart/Type/Scatter.php @@ -10,31 +10,32 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape\Chart\Type; use PhpOffice\PhpPresentation\ComparableInterface; -/** - * \PhpOffice\PhpPresentation\Shape\Chart\Type\Scatter - */ -class Scatter extends AbstractType implements ComparableInterface +class Scatter extends AbstractTypeLine implements ComparableInterface { /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { $hash = ''; foreach ($this->getSeries() as $series) { $hash .= $series->getHashCode(); } - return md5($hash . __CLASS__); + + return md5(parent::getHashCode() . $hash . __CLASS__); } } diff --git a/PhpOffice/PhpPresentation/Shape/Chart/View3D.php b/PhpOffice/PhpPresentation/Shape/Chart/View3D.php old mode 100755 new mode 100644 index 80ffea6..21bbaee --- a/PhpOffice/PhpPresentation/Shape/Chart/View3D.php +++ b/PhpOffice/PhpPresentation/Shape/Chart/View3D.php @@ -10,78 +10,81 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape\Chart; use PhpOffice\PhpPresentation\ComparableInterface; /** - * \PhpOffice\PhpPresentation\Shape\Chart\View3D + * \PhpOffice\PhpPresentation\Shape\Chart\View3D. */ class View3D implements ComparableInterface { /** - * Rotation X + * Rotation X. * * @var int */ protected $rotationX = 0; /** - * Rotation Y + * Rotation Y. * * @var int */ protected $rotationY = 0; /** - * Right Angle Axes + * Right Angle Axes. * - * @var boolean + * @var bool */ private $rightAngleAxes = true; /** - * Perspective + * Perspective. * * @var int */ private $perspective = 30; /** - * Height Percent + * Height Percent. * - * @var int + * @var int|null */ private $heightPercent = 100; /** - * Depth Percent + * Depth Percent. * * @var int */ private $depthPercent = 100; /** - * Hash index + * Hash index. * - * @var string + * @var int */ private $hashIndex; /** - * Create a new \PhpOffice\PhpPresentation\Shape\Chart\View3D instance + * Create a new \PhpOffice\PhpPresentation\Shape\Chart\View3D instance. */ public function __construct() { } /** - * Get Rotation X + * Get Rotation X. * * @return int */ @@ -91,9 +94,10 @@ class View3D implements ComparableInterface } /** - * Set Rotation X (-90 to 90) + * Set Rotation X (-90 to 90). + * + * @param int $pValue * - * @param int $pValue * @return \PhpOffice\PhpPresentation\Shape\Chart\View3D */ public function setRotationX($pValue = 0) @@ -104,7 +108,7 @@ class View3D implements ComparableInterface } /** - * Get Rotation Y + * Get Rotation Y. * * @return int */ @@ -114,9 +118,10 @@ class View3D implements ComparableInterface } /** - * Set Rotation Y (-90 to 90) + * Set Rotation Y (-90 to 90). + * + * @param int $pValue * - * @param int $pValue * @return \PhpOffice\PhpPresentation\Shape\Chart\View3D */ public function setRotationY($pValue = 0) @@ -127,9 +132,9 @@ class View3D implements ComparableInterface } /** - * Get RightAngleAxes + * Get RightAngleAxes. * - * @return boolean + * @return bool */ public function hasRightAngleAxes() { @@ -137,9 +142,10 @@ class View3D implements ComparableInterface } /** - * Set RightAngleAxes + * Set RightAngleAxes. + * + * @param bool $value * - * @param boolean $value * @return \PhpOffice\PhpPresentation\Shape\Chart\View3D */ public function setRightAngleAxes($value = true) @@ -150,7 +156,7 @@ class View3D implements ComparableInterface } /** - * Get Perspective + * Get Perspective. * * @return int */ @@ -160,9 +166,10 @@ class View3D implements ComparableInterface } /** - * Set Perspective (0 to 100) + * Set Perspective (0 to 100). + * + * @param int $value * - * @param int $value * @return \PhpOffice\PhpPresentation\Shape\Chart\View3D */ public function setPerspective($value = 30) @@ -173,7 +180,7 @@ class View3D implements ComparableInterface } /** - * Get HeightPercent + * Get HeightPercent. * * @return int */ @@ -183,12 +190,9 @@ class View3D implements ComparableInterface } /** - * Set HeightPercent (5 to 500) - * - * @param int $value - * @return $this + * Set HeightPercent (5 to 500). */ - public function setHeightPercent($value = 100) + public function setHeightPercent(?int $value = 100): self { $this->heightPercent = $value; @@ -196,19 +200,18 @@ class View3D implements ComparableInterface } /** - * Get DepthPercent - * - * @return int + * Get DepthPercent. */ - public function getDepthPercent() + public function getDepthPercent(): ?int { return $this->depthPercent; } /** - * Set DepthPercent (20 to 2000) + * Set DepthPercent (20 to 2000). + * + * @param int $value * - * @param int $value * @return $this */ public function setDepthPercent($value = 100) @@ -219,40 +222,42 @@ class View3D implements ComparableInterface } /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { return md5($this->rotationX . $this->rotationY . ($this->rightAngleAxes ? 't' : 'f') . $this->perspective . $this->heightPercent . $this->depthPercent . __CLASS__); } /** - * Get hash index + * Get hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @return string Hash index + * @return int|null Hash index */ - public function getHashIndex() + public function getHashIndex(): ?int { return $this->hashIndex; } /** - * Set hash index + * Set hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @param string $value Hash index + * @param int $value Hash index + * * @return View3D */ - public function setHashIndex($value) + public function setHashIndex(int $value) { $this->hashIndex = $value; + return $this; } } diff --git a/PhpOffice/PhpPresentation/Shape/Comment.php b/PhpOffice/PhpPresentation/Shape/Comment.php old mode 100755 new mode 100644 index 9dfe73f..d909e05 --- a/PhpOffice/PhpPresentation/Shape/Comment.php +++ b/PhpOffice/PhpPresentation/Shape/Comment.php @@ -10,11 +10,14 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape; use PhpOffice\PhpPresentation\AbstractShape; @@ -22,12 +25,12 @@ use PhpOffice\PhpPresentation\ComparableInterface; use PhpOffice\PhpPresentation\Shape\Comment\Author; /** - * Comment shape + * Comment shape. */ class Comment extends AbstractShape implements ComparableInterface { /** - * @var Author + * @var Author|null */ protected $author; @@ -47,21 +50,15 @@ class Comment extends AbstractShape implements ComparableInterface $this->setDate(time()); } - /** - * @return Author - */ - public function getAuthor() + public function getAuthor(): ?Author { return $this->author; } - /** - * @param Author $author - * @return Comment - */ - public function setAuthor(Author $author) + public function setAuthor(Author $author): self { $this->author = $author; + return $this; } @@ -75,11 +72,13 @@ class Comment extends AbstractShape implements ComparableInterface /** * @param int $dtComment timestamp of the comment + * * @return Comment */ public function setDate($dtComment) { - $this->dtComment = (int)$dtComment; + $this->dtComment = (int) $dtComment; + return $this; } @@ -93,18 +92,20 @@ class Comment extends AbstractShape implements ComparableInterface /** * @param string $text + * * @return Comment */ public function setText($text = '') { $this->text = $text; + return $this; } /** - * Comment has not height + * Comment has not height. * - * @return null + * @return int|null */ public function getHeight() { @@ -112,20 +113,19 @@ class Comment extends AbstractShape implements ComparableInterface } /** - * Set Height + * Set Height. * - * @param int $pValue * @return $this */ - public function setHeight($pValue = 0) + public function setHeight(int $pValue = 0) { return $this; } /** - * Comment has not width + * Comment has not width. * - * @return null + * @return int|null */ public function getWidth() { @@ -133,12 +133,11 @@ class Comment extends AbstractShape implements ComparableInterface } /** - * Set Width + * Set Width. * - * @param int $pValue - * @return $this + * @return self */ - public function setWidth($pValue = 0) + public function setWidth(int $pValue = 0) { return $this; } diff --git a/PhpOffice/PhpPresentation/Shape/Comment/Author.php b/PhpOffice/PhpPresentation/Shape/Comment/Author.php old mode 100755 new mode 100644 index 3c0ba2a..59784f3 --- a/PhpOffice/PhpPresentation/Shape/Comment/Author.php +++ b/PhpOffice/PhpPresentation/Shape/Comment/Author.php @@ -1,4 +1,22 @@ idxAuthor = (int) $idxAuthor; + return $this; } @@ -47,11 +67,13 @@ class Author /** * @param mixed $initials + * * @return Author */ public function setInitials($initials) { $this->initials = $initials; + return $this; } @@ -65,20 +87,22 @@ class Author /** * @param string $name + * * @return Author */ public function setName($name) { $this->name = $name; + return $this; } /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { return md5($this->getInitials() . $this->getName() . __CLASS__); } diff --git a/PhpOffice/PhpPresentation/Shape/Drawing/AbstractDrawingAdapter.php b/PhpOffice/PhpPresentation/Shape/Drawing/AbstractDrawingAdapter.php old mode 100755 new mode 100644 index 1301e25..f361ba7 --- a/PhpOffice/PhpPresentation/Shape/Drawing/AbstractDrawingAdapter.php +++ b/PhpOffice/PhpPresentation/Shape/Drawing/AbstractDrawingAdapter.php @@ -1,4 +1,22 @@ */ - protected $arrayMimeExtension = array( + protected $arrayMimeExtension = [ 'image/jpeg' => 'jpg', 'image/png' => 'png', 'image/gif' => 'gif', - ); + 'image/svg+xml' => 'svg', + ]; + + /** + * @var string + */ + protected $path; /** * Base64 constructor. @@ -32,65 +58,58 @@ class Base64 extends AbstractDrawingAdapter { parent::__construct(); $this->uniqueName = md5(rand(0, 9999) . time() . rand(0, 9999)); + $this->data = ''; } - /** - * @return mixed - */ - public function getData() + public function getData(): string { return $this->data; } - /** - * @param mixed $data - * @return Base64 - */ - public function setData($data) + public function setData(string $data): self { $this->data = $data; + return $this; } - /** - * @return string - */ - public function getContents() + public function getContents(): string { list(, $imageContents) = explode(';', $this->getData()); list(, $imageContents) = explode(',', $imageContents); + return base64_decode($imageContents); } /** - * @return string - * @throws \Exception + * @throws UnauthorizedMimetypeException */ - public function getExtension() + public function getExtension(): string { - list($data, ) = explode(';', $this->getData()); + list($data) = explode(';', $this->getData()); list(, $mime) = explode(':', $data); if (!array_key_exists($mime, $this->arrayMimeExtension)) { - throw new \Exception('Type Mime not found : "'.$mime.'"'); + throw new UnauthorizedMimetypeException($mime, $this->arrayMimeExtension); } + return $this->arrayMimeExtension[$mime]; } - /** - * @return string - * @throws \Exception - */ - public function getIndexedFilename() + public function getIndexedFilename(): string { return $this->uniqueName . $this->getImageIndex() . '.' . $this->getExtension(); } - /** - * @return string - */ - public function getMimeType() + public function getMimeType(): string { + list($data) = explode(';', $this->getData()); + list(, $mime) = explode(':', $data); + + if (!empty($mime)) { + return $mime; + } + $sImage = $this->getContents(); if (!function_exists('getimagesizefromstring')) { $uri = 'data://application/octet-stream;base64,' . base64_encode($sImage); @@ -98,6 +117,22 @@ class Base64 extends AbstractDrawingAdapter } else { $image = getimagesizefromstring($sImage); } + return image_type_to_mime_type($image[2]); } + + /** + * Get Path. + */ + public function getPath(): string + { + return $this->path; + } + + public function setPath(string $path): self + { + $this->path = $path; + + return $this; + } } diff --git a/PhpOffice/PhpPresentation/Shape/Drawing/File.php b/PhpOffice/PhpPresentation/Shape/Drawing/File.php old mode 100755 new mode 100644 index e920988..754985a --- a/PhpOffice/PhpPresentation/Shape/Drawing/File.php +++ b/PhpOffice/PhpPresentation/Shape/Drawing/File.php @@ -1,45 +1,64 @@ path; } /** - * Set Path + * Set Path. * - * @param string $pValue File path - * @param boolean $pVerifyFile Verify file - * @throws \Exception - * @return \PhpOffice\PhpPresentation\Shape\Drawing\File + * @param string $pValue File path + * @param bool $pVerifyFile Verify file + * + * @throws FileNotFoundException + * + * @return self */ - public function setPath($pValue = '', $pVerifyFile = true) + public function setPath(string $pValue = '', bool $pVerifyFile = true): self { if ($pVerifyFile) { if (!file_exists($pValue)) { - throw new \Exception("File $pValue not found!"); + throw new FileNotFoundException($pValue); } } $this->path = $pValue; if ($pVerifyFile) { - if ($this->width == 0 && $this->height == 0) { + if (0 == $this->width && 0 == $this->height) { list($this->width, $this->height) = getimagesize($this->getPath()); } } @@ -47,45 +66,40 @@ class File extends AbstractDrawingAdapter return $this; } - /** - * @return string - */ - public function getContents() + public function getContents(): string { return CommonFile::fileGetContents($this->getPath()); } - - /** - * @return string - */ - public function getExtension() + public function getExtension(): string { return pathinfo($this->getPath(), PATHINFO_EXTENSION); } /** - * @throws \Exception - * @return string + * @throws FileNotFoundException */ - public function getMimeType() + public function getMimeType(): string { if (!CommonFile::fileExists($this->getPath())) { - throw new \Exception('File '.$this->getPath().' does not exist'); + throw new FileNotFoundException($this->getPath()); } $image = getimagesizefromstring(CommonFile::fileGetContents($this->getPath())); - return image_type_to_mime_type($image[2]); + + if (is_array($image)) { + return image_type_to_mime_type($image[2]); + } + + return mime_content_type($this->getPath()); } - /** - * @return string - */ - public function getIndexedFilename() + public function getIndexedFilename(): string { $output = str_replace('.' . $this->getExtension(), '', pathinfo($this->getPath(), PATHINFO_FILENAME)); $output .= $this->getImageIndex(); - $output .= '.'.$this->getExtension(); + $output .= '.' . $this->getExtension(); $output = str_replace(' ', '_', $output); + return $output; } } diff --git a/PhpOffice/PhpPresentation/Shape/Drawing/Gd.php b/PhpOffice/PhpPresentation/Shape/Drawing/Gd.php old mode 100755 new mode 100644 index d4509d9..59bcf8d --- a/PhpOffice/PhpPresentation/Shape/Drawing/Gd.php +++ b/PhpOffice/PhpPresentation/Shape/Drawing/Gd.php @@ -1,51 +1,69 @@ imageResource)) { // Get width/height - $this->width = imagesx($this->imageResource); + $this->width = imagesx($this->imageResource); $this->height = imagesy($this->imageResource); } @@ -83,7 +102,7 @@ class Gd extends AbstractDrawingAdapter } /** - * Get rendering function + * Get rendering function. * * @return string */ @@ -93,71 +112,86 @@ class Gd extends AbstractDrawingAdapter } /** - * Set rendering function + * Set rendering function. + * + * @param string $value * - * @param string $value * @return $this */ public function setRenderingFunction($value = self::RENDERING_DEFAULT) { $this->renderingFunction = $value; + return $this; } /** - * Get mime type - * - * @return string + * Get mime type. */ - public function getMimeType() + public function getMimeType(): string { return $this->mimeType; } /** - * Set mime type + * Set mime type. + * + * @param string $value * - * @param string $value * @return $this */ public function setMimeType($value = self::MIMETYPE_DEFAULT) { $this->mimeType = $value; + return $this; } - /** - * @return string - */ - public function getContents() + public function getContents(): string { ob_start(); - if ($this->getMimeType() === self::MIMETYPE_DEFAULT) { + if (self::MIMETYPE_DEFAULT === $this->getMimeType()) { imagealphablending($this->getImageResource(), false); imagesavealpha($this->getImageResource(), true); } call_user_func($this->getRenderingFunction(), $this->getImageResource()); $imageContents = ob_get_contents(); ob_end_clean(); + return $imageContents; } - /** - * @return string - */ - public function getExtension() + public function getExtension(): string { $extension = strtolower($this->getMimeType()); $extension = explode('/', $extension); $extension = $extension[1]; + return $extension; } - /** - * @return string - */ - public function getIndexedFilename() + public function getIndexedFilename(): string { return $this->uniqueName . $this->getImageIndex() . '.' . $this->getExtension(); } + + /** + * @var string + */ + protected $path; + + /** + * Get Path. + */ + public function getPath(): string + { + return $this->path; + } + + public function setPath(string $path): self + { + $this->path = $path; + + return $this; + } } diff --git a/PhpOffice/PhpPresentation/Shape/Drawing/ZipFile.php b/PhpOffice/PhpPresentation/Shape/Drawing/ZipFile.php old mode 100755 new mode 100644 index 769f1d9..2a921e9 --- a/PhpOffice/PhpPresentation/Shape/Drawing/ZipFile.php +++ b/PhpOffice/PhpPresentation/Shape/Drawing/ZipFile.php @@ -1,8 +1,27 @@ path; } /** - * Set Path + * Set Path. + * + * @param string $pValue File path * - * @param string $pValue File path * @return \PhpOffice\PhpPresentation\Shape\Drawing\ZipFile */ - public function setPath($pValue = '') + public function setPath(string $pValue = ''): self { $this->path = $pValue; + return $this; } /** - * @return string - * @throws \Exception + * @throws FileNotFoundException */ - public function getContents() + public function getContents(): string { if (!CommonFile::fileExists($this->getZipFileOut())) { - throw new \Exception('File '.$this->getZipFileOut().' does not exist'); + throw new FileNotFoundException($this->getZipFileOut()); } $imageZip = new \ZipArchive(); @@ -48,26 +66,22 @@ class ZipFile extends AbstractDrawingAdapter $imageContents = $imageZip->getFromName($this->getZipFileIn()); $imageZip->close(); unset($imageZip); + return $imageContents; } - - /** - * @return string - */ - public function getExtension() + public function getExtension(): string { return pathinfo($this->getZipFileIn(), PATHINFO_EXTENSION); } /** - * @return string - * @throws \Exception + * @throws FileNotFoundException */ - public function getMimeType() + public function getMimeType(): string { if (!CommonFile::fileExists($this->getZipFileOut())) { - throw new \Exception('File '.$this->getZipFileOut().' does not exist'); + throw new FileNotFoundException($this->getZipFileOut()); } $oArchive = new \ZipArchive(); $oArchive->open($this->getZipFileOut()); @@ -77,33 +91,34 @@ class ZipFile extends AbstractDrawingAdapter } else { $image = getimagesizefromstring($oArchive->getFromName($this->getZipFileIn())); } + return image_type_to_mime_type($image[2]); } - /** - * @return string - */ - public function getIndexedFilename() + public function getIndexedFilename(): string { $output = pathinfo($this->getZipFileIn(), PATHINFO_FILENAME); $output = str_replace('.' . $this->getExtension(), '', $output); $output .= $this->getImageIndex(); - $output .= '.'.$this->getExtension(); + $output .= '.' . $this->getExtension(); $output = str_replace(' ', '_', $output); + return $output; } - protected function getZipFileOut() + protected function getZipFileOut(): string { $path = str_replace('zip://', '', $this->getPath()); $path = explode('#', $path); + return empty($path[0]) ? '' : $path[0]; } - protected function getZipFileIn() + protected function getZipFileIn(): string { $path = str_replace('zip://', '', $this->getPath()); $path = explode('#', $path); + return empty($path[1]) ? '' : $path[1]; } } diff --git a/PhpOffice/PhpPresentation/Shape/Group.php b/PhpOffice/PhpPresentation/Shape/Group.php old mode 100755 new mode 100644 index 5a4faf9..dcb29ac --- a/PhpOffice/PhpPresentation/Shape/Group.php +++ b/PhpOffice/PhpPresentation/Shape/Group.php @@ -10,73 +10,68 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape; +use ArrayObject; use PhpOffice\PhpPresentation\AbstractShape; use PhpOffice\PhpPresentation\GeometryCalculator; -use PHPOffice\PhpPresentation\ShapeContainerInterface; -use PhpOffice\PhpPresentation\Shape\Drawing; -use PhpOffice\PhpPresentation\Shape\RichText; -use PhpOffice\PhpPresentation\Shape\Table; +use PhpOffice\PhpPresentation\ShapeContainerInterface; class Group extends AbstractShape implements ShapeContainerInterface { /** - * Collection of shapes - * - * @var \ArrayObject|\PhpOffice\PhpPresentation\AbstractShape[] - */ - private $shapeCollection = null; + * Collection of shapes. + * + * @var array|ArrayObject + */ + private $shapeCollection; /** - * Extent X - * - * @var int - */ + * Extent X. + * + * @var int + */ protected $extentX; /** - * Extent Y - * - * @var int - */ + * Extent Y. + * + * @var int + */ protected $extentY; public function __construct() { parent::__construct(); - // For logic purposes. - $this->offsetX = null; - $this->offsetY = null; - // Shape collection - $this->shapeCollection = new \ArrayObject(); + $this->shapeCollection = new ArrayObject(); } /** - * Get collection of shapes - * - * @return \ArrayObject|AbstractShape[] - */ + * Get collection of shapes. + * + * @return array|ArrayObject + */ public function getShapeCollection() { return $this->shapeCollection; } /** - * Add shape to slide + * Add shape to slide. * - * @param \PhpOffice\PhpPresentation\AbstractShape $shape - * @return \PhpOffice\PhpPresentation\AbstractShape - * @throws \Exception + * @return AbstractShape */ - public function addShape(AbstractShape $shape) + public function addShape(AbstractShape $shape): AbstractShape { $shape->setContainer($this); @@ -84,13 +79,11 @@ class Group extends AbstractShape implements ShapeContainerInterface } /** - * Get X Offset - * - * @return int - */ - public function getOffsetX() + * Get X Offset. + */ + public function getOffsetX(): int { - if ($this->offsetX === null) { + if (empty($this->offsetX)) { $offsets = GeometryCalculator::calculateOffsets($this); $this->offsetX = $offsets[GeometryCalculator::X]; $this->offsetY = $offsets[GeometryCalculator::Y]; @@ -100,24 +93,21 @@ class Group extends AbstractShape implements ShapeContainerInterface } /** - * Ignores setting the X Offset, preserving the default behavior. - * - * @param int $pValue - * @return $this - */ - public function setOffsetX($pValue = 0) + * Ignores setting the X Offset, preserving the default behavior. + * + * @return $this + */ + public function setOffsetX(int $pValue = 0) { return $this; } /** - * Get Y Offset - * - * @return int - */ - public function getOffsetY() + * Get Y Offset. + */ + public function getOffsetY(): int { - if ($this->offsetY === null) { + if (empty($this->offsetY)) { $offsets = GeometryCalculator::calculateOffsets($this); $this->offsetX = $offsets[GeometryCalculator::X]; $this->offsetY = $offsets[GeometryCalculator::Y]; @@ -127,24 +117,21 @@ class Group extends AbstractShape implements ShapeContainerInterface } /** - * Ignores setting the Y Offset, preserving the default behavior. - * - * @param int $pValue - * @return $this - */ - public function setOffsetY($pValue = 0) + * Ignores setting the Y Offset, preserving the default behavior. + * + * @return $this + */ + public function setOffsetY(int $pValue = 0) { return $this; } /** - * Get X Extent - * - * @return int - */ - public function getExtentX() + * Get X Extent. + */ + public function getExtentX(): int { - if ($this->extentX === null) { + if (null === $this->extentX) { $extents = GeometryCalculator::calculateExtents($this); $this->extentX = $extents[GeometryCalculator::X] - $this->getOffsetX(); $this->extentY = $extents[GeometryCalculator::Y] - $this->getOffsetY(); @@ -154,13 +141,11 @@ class Group extends AbstractShape implements ShapeContainerInterface } /** - * Get Y Extent - * - * @return int - */ - public function getExtentY() + * Get Y Extent. + */ + public function getExtentY(): int { - if ($this->extentY === null) { + if (null === $this->extentY) { $extents = GeometryCalculator::calculateExtents($this); $this->extentX = $extents[GeometryCalculator::X] - $this->getOffsetX(); $this->extentY = $extents[GeometryCalculator::Y] - $this->getOffsetY(); @@ -170,34 +155,31 @@ class Group extends AbstractShape implements ShapeContainerInterface } /** - * Ignores setting the width, preserving the default behavior. - * - * @param int $pValue - * @return $this - */ - public function setWidth($pValue = 0) - { - return $this; - } - - /** - * Ignores setting the height, preserving the default behavior. - * - * @param int $pValue - * @return $this - */ - public function setHeight($pValue = 0) - { - return $this; - } - - /** - * Create rich text shape + * Ignores setting the width, preserving the default behavior. * - * @return \PhpOffice\PhpPresentation\Shape\RichText - * @throws \Exception + * @return self */ - public function createRichTextShape() + public function setWidth(int $pValue = 0) + { + return $this; + } + + /** + * Ignores setting the height, preserving the default behavior. + * + * @return $this + */ + public function setHeight(int $pValue = 0) + { + return $this; + } + + /** + * Create rich text shape. + * + * @return RichText + */ + public function createRichTextShape(): RichText { $shape = new RichText(); $this->addShape($shape); @@ -206,16 +188,16 @@ class Group extends AbstractShape implements ShapeContainerInterface } /** - * Create line shape + * Create line shape. * - * @param int $fromX Starting point x offset - * @param int $fromY Starting point y offset - * @param int $toX Ending point x offset - * @param int $toY Ending point y offset - * @return \PhpOffice\PhpPresentation\Shape\Line - * @throws \Exception + * @param int $fromX Starting point x offset + * @param int $fromY Starting point y offset + * @param int $toX Ending point x offset + * @param int $toY Ending point y offset + * + * @return Line */ - public function createLineShape($fromX, $fromY, $toX, $toY) + public function createLineShape(int $fromX, int $fromY, int $toX, int $toY): Line { $shape = new Line($fromX, $fromY, $toX, $toY); $this->addShape($shape); @@ -224,12 +206,11 @@ class Group extends AbstractShape implements ShapeContainerInterface } /** - * Create chart shape + * Create chart shape. * - * @return \PhpOffice\PhpPresentation\Shape\Chart - * @throws \Exception + * @return Chart */ - public function createChartShape() + public function createChartShape(): Chart { $shape = new Chart(); $this->addShape($shape); @@ -238,12 +219,11 @@ class Group extends AbstractShape implements ShapeContainerInterface } /** - * Create drawing shape + * Create drawing shape. * - * @return \PhpOffice\PhpPresentation\Shape\Drawing\File - * @throws \Exception + * @return Drawing\File */ - public function createDrawingShape() + public function createDrawingShape(): Drawing\File { $shape = new Drawing\File(); $this->addShape($shape); @@ -252,13 +232,13 @@ class Group extends AbstractShape implements ShapeContainerInterface } /** - * Create table shape + * Create table shape. * - * @param int $columns Number of columns - * @return \PhpOffice\PhpPresentation\Shape\Table - * @throws \Exception + * @param int $columns Number of columns + * + * @return Table */ - public function createTableShape($columns = 1) + public function createTableShape(int $columns = 1): Table { $shape = new Table($columns); $this->addShape($shape); diff --git a/PhpOffice/PhpPresentation/Shape/Hyperlink.php b/PhpOffice/PhpPresentation/Shape/Hyperlink.php old mode 100755 new mode 100644 index 0a95507..06e0be9 --- a/PhpOffice/PhpPresentation/Shape/Hyperlink.php +++ b/PhpOffice/PhpPresentation/Shape/Hyperlink.php @@ -10,84 +10,93 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape; /** - * Hyperlink element + * Hyperlink element. */ class Hyperlink { /** - * URL to link the shape to + * URL to link the shape to. * * @var string */ private $url; /** - * Tooltip to display on the hyperlink + * Tooltip to display on the hyperlink. * * @var string */ private $tooltip; /** - * Slide number to link to + * Slide number to link to. * * @var int */ private $slideNumber = null; /** - * Slide relation ID (should not be used by user code!) + * Slide relation ID (should not be used by user code!). * * @var string */ public $relationId = null; /** - * Hash index + * Hash index. * - * @var string + * @var int */ private $hashIndex; /** - * Create a new \PhpOffice\PhpPresentation\Shape\Hyperlink + * If true, uses the text color, instead of theme color * - * @param string $pUrl Url to link the shape to - * @param string $pTooltip Tooltip to display on the hyperlink - * @throws \Exception + * @var bool */ - public function __construct($pUrl = '', $pTooltip = '') + private $isTextColorUsed = false; + + /** + * Create a new \PhpOffice\PhpPresentation\Shape\Hyperlink. + * + * @param string $pUrl Url to link the shape to + * @param string $pTooltip Tooltip to display on the hyperlink + */ + public function __construct(string $pUrl = '', string $pTooltip = '') { - // Initialise member variables $this->setUrl($pUrl); $this->setTooltip($pTooltip); } /** - * Get URL + * Get URL. * * @return string */ - public function getUrl() + public function getUrl(): string { return $this->url; } /** - * Set URL + * Set URL. * - * @param string $value - * @return \PhpOffice\PhpPresentation\Shape\Hyperlink + * @param string $value + * + * @return self */ - public function setUrl($value = '') + public function setUrl(string $value = ''): self { $this->url = $value; @@ -95,22 +104,23 @@ class Hyperlink } /** - * Get tooltip + * Get tooltip. * * @return string */ - public function getTooltip() + public function getTooltip(): string { return $this->tooltip; } /** - * Set tooltip + * Set tooltip. * - * @param string $value - * @return \PhpOffice\PhpPresentation\Shape\Hyperlink + * @param string $value + * + * @return self */ - public function setTooltip($value = '') + public function setTooltip(string $value = ''): self { $this->tooltip = $value; @@ -118,72 +128,105 @@ class Hyperlink } /** - * Get slide number + * Get slide number. * * @return int */ - public function getSlideNumber() + public function getSlideNumber(): int { return $this->slideNumber; } /** - * Set slide number + * Set slide number. * - * @param int $value - * @return \PhpOffice\PhpPresentation\Shape\Hyperlink + * @param int $value + * + * @return self */ - public function setSlideNumber($value = 1) + public function setSlideNumber(int $value = 1): self { - $this->url = 'ppaction://hlinksldjump'; + $this->url = 'ppaction://hlinksldjump'; $this->slideNumber = $value; return $this; } /** - * Is this hyperlink internal? (to another slide) + * Is this hyperlink internal? (to another slide). * - * @return boolean + * @return bool */ - public function isInternal() + public function isInternal(): bool { - return strpos($this->url, 'ppaction://') !== false; + return false !== strpos($this->url, 'ppaction://'); } /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { return md5($this->url . $this->tooltip . __CLASS__); } /** - * Get hash index + * Get hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @return string Hash index + * @return int|null Hash index */ - public function getHashIndex() + public function getHashIndex(): ?int { return $this->hashIndex; } /** - * Set hash index + * Set hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @param string $value Hash index + * @param int $value Hash index + * + * @return $this */ - public function setHashIndex($value) + public function setHashIndex(int $value) { $this->hashIndex = $value; + + return $this; + } + + /** + * Get whether or not to use text color for a hyperlink, instead of theme color. + * + * @see https://docs.microsoft.com/en-us/openspecs/office_standards/ms-odrawxml/014fbc20-3705-4812-b8cd-93f5af05b504 + * + * @return bool whether or not to use text color for a hyperlink, instead of theme color + */ + public function isTextColorUsed(): bool + { + return $this->isTextColorUsed; + } + + /** + * Set whether or not to use text color for a hyperlink, instead of theme color. + * + * @see https://docs.microsoft.com/en-us/openspecs/office_standards/ms-odrawxml/014fbc20-3705-4812-b8cd-93f5af05b504 + * + * @param bool $isTextColorUsed + * + * @return self + */ + public function setIsTextColorUsed(bool $isTextColorUsed): self + { + $this->isTextColorUsed = $isTextColorUsed; + + return $this; } } diff --git a/PhpOffice/PhpPresentation/Shape/Line.php b/PhpOffice/PhpPresentation/Shape/Line.php old mode 100755 new mode 100644 index e9a7a07..be7574d --- a/PhpOffice/PhpPresentation/Shape/Line.php +++ b/PhpOffice/PhpPresentation/Shape/Line.php @@ -10,11 +10,14 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape; use PhpOffice\PhpPresentation\AbstractShape; @@ -22,12 +25,12 @@ use PhpOffice\PhpPresentation\ComparableInterface; use PhpOffice\PhpPresentation\Style\Border; /** - * Line shape + * Line shape. */ class Line extends AbstractShape implements ComparableInterface { /** - * Create a new \PhpOffice\PhpPresentation\Shape\Line instance + * Create a new \PhpOffice\PhpPresentation\Shape\Line instance. * * @param int $fromX * @param int $fromY @@ -46,11 +49,11 @@ class Line extends AbstractShape implements ComparableInterface } /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { return md5($this->getBorder()->getLineStyle() . parent::getHashCode() . __CLASS__); } diff --git a/PhpOffice/PhpPresentation/Shape/Media.php b/PhpOffice/PhpPresentation/Shape/Media.php old mode 100755 new mode 100644 index 64a98b7..1207195 --- a/PhpOffice/PhpPresentation/Shape/Media.php +++ b/PhpOffice/PhpPresentation/Shape/Media.php @@ -10,26 +10,25 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape; use PhpOffice\PhpPresentation\ComparableInterface; use PhpOffice\PhpPresentation\Shape\Drawing\File; /** - * Media element + * Media element. */ class Media extends File implements ComparableInterface { - - /** - * @return string - */ - public function getMimeType() + public function getMimeType(): string { switch (strtolower($this->getExtension())) { case 'mp4': @@ -44,6 +43,7 @@ class Media extends File implements ComparableInterface default: $mimetype = 'application/octet-stream'; } + return $mimetype; } } diff --git a/PhpOffice/PhpPresentation/Shape/Placeholder.php b/PhpOffice/PhpPresentation/Shape/Placeholder.php old mode 100755 new mode 100644 index a334104..45f6ac8 --- a/PhpOffice/PhpPresentation/Shape/Placeholder.php +++ b/PhpOffice/PhpPresentation/Shape/Placeholder.php @@ -10,86 +10,75 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape; class Placeholder { /** Placeholder Type constants */ - const PH_TYPE_BODY = 'body'; - const PH_TYPE_CHART = 'chart'; - const PH_TYPE_SUBTITLE = 'subTitle'; - const PH_TYPE_TITLE = 'title'; - const PH_TYPE_FOOTER = 'ftr'; - const PH_TYPE_DATETIME = 'dt'; - const PH_TYPE_SLIDENUM = 'sldNum'; + public const PH_TYPE_BODY = 'body'; + public const PH_TYPE_CHART = 'chart'; + public const PH_TYPE_SUBTITLE = 'subTitle'; + public const PH_TYPE_TITLE = 'title'; + public const PH_TYPE_FOOTER = 'ftr'; + public const PH_TYPE_DATETIME = 'dt'; + public const PH_TYPE_SLIDENUM = 'sldNum'; + /** - * hasCustomPrompt * Indicates whether the placeholder should have a customer prompt. * * @var bool */ protected $hasCustomPrompt; + /** - * idx * Specifies the index of the placeholder. This is used when applying templates or changing layouts to * match a placeholder on one template or master to another. * - * @var int + * @var int|null */ protected $idx; + /** - * type - * Specifies what content type the placeholder is to contains + * Specifies what content type the placeholder is to contains. + * + * @var string */ protected $type; - /** - * Placeholder constructor. - * @param $type - */ - public function __construct($type) + public function __construct(string $type) { $this->type = $type; } - /** - * @return mixed - */ - public function getType() + public function getType(): string { return $this->type; } - /** - * @param mixed $type - * @return Placeholder - */ - public function setType($type) + public function setType(string $type): self { $this->type = $type; + return $this; } - /** - * @return int - */ - public function getIdx() + public function getIdx(): ?int { return $this->idx; } - /** - * @param int $idx - * @return Placeholder - */ - public function setIdx($idx) + public function setIdx(int $idx): self { $this->idx = $idx; + return $this; } } diff --git a/PhpOffice/PhpPresentation/Shape/RichText.php b/PhpOffice/PhpPresentation/Shape/RichText.php old mode 100755 new mode 100644 index d7cc341..b908277 --- a/PhpOffice/PhpPresentation/Shape/RichText.php +++ b/PhpOffice/PhpPresentation/Shape/RichText.php @@ -10,74 +10,78 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape; use PhpOffice\PhpPresentation\AbstractShape; use PhpOffice\PhpPresentation\ComparableInterface; +use PhpOffice\PhpPresentation\Exception\OutOfBoundsException; use PhpOffice\PhpPresentation\Shape\RichText\Paragraph; use PhpOffice\PhpPresentation\Shape\RichText\TextElementInterface; /** - * \PhpOffice\PhpPresentation\Shape\RichText + * \PhpOffice\PhpPresentation\Shape\RichText. */ class RichText extends AbstractShape implements ComparableInterface { /** Wrapping */ - const WRAP_NONE = 'none'; - const WRAP_SQUARE = 'square'; + public const WRAP_NONE = 'none'; + public const WRAP_SQUARE = 'square'; /** Autofit */ - const AUTOFIT_DEFAULT = 'spAutoFit'; - const AUTOFIT_SHAPE = 'spAutoFit'; - const AUTOFIT_NOAUTOFIT = 'noAutofit'; - const AUTOFIT_NORMAL = 'normAutofit'; + public const AUTOFIT_DEFAULT = 'spAutoFit'; + public const AUTOFIT_SHAPE = 'spAutoFit'; + public const AUTOFIT_NOAUTOFIT = 'noAutofit'; + public const AUTOFIT_NORMAL = 'normAutofit'; /** Overflow */ - const OVERFLOW_CLIP = 'clip'; - const OVERFLOW_OVERFLOW = 'overflow'; + public const OVERFLOW_CLIP = 'clip'; + public const OVERFLOW_OVERFLOW = 'overflow'; /** - * Rich text paragraphs + * Rich text paragraphs. * - * @var \PhpOffice\PhpPresentation\Shape\RichText\Paragraph[] + * @var array */ private $richTextParagraphs; /** - * Active paragraph + * Active paragraph. * * @var int */ private $activeParagraph = 0; /** - * Text wrapping + * Text wrapping. * * @var string */ private $wrap = self::WRAP_SQUARE; /** - * Autofit + * Autofit. * * @var string */ private $autoFit = self::AUTOFIT_DEFAULT; /** - * Horizontal overflow + * Horizontal overflow. * * @var string */ private $horizontalOverflow = self::OVERFLOW_OVERFLOW; /** - * Vertical overflow + * Vertical overflow. * * @var string */ @@ -86,122 +90,125 @@ class RichText extends AbstractShape implements ComparableInterface /** * Text upright? * - * @var boolean + * @var bool */ private $upright = false; /** * Vertical text? * - * @var boolean + * @var bool */ private $vertical = false; /** - * Number of columns (1 - 16) + * Number of columns (1 - 16). * * @var int */ private $columns = 1; /** - * Bottom inset (in pixels) + * The spacing between columns + * + * @var int + */ + private $columnSpacing = 0; + + /** + * Bottom inset (in pixels). * * @var float */ private $bottomInset = 4.8; /** - * Left inset (in pixels) + * Left inset (in pixels). * * @var float */ private $leftInset = 9.6; /** - * Right inset (in pixels) + * Right inset (in pixels). * * @var float */ private $rightInset = 9.6; /** - * Top inset (in pixels) + * Top inset (in pixels). * * @var float */ private $topInset = 4.8; /** - * Horizontal Auto Shrink - * @var boolean + * Horizontal Auto Shrink. + * + * @var bool|null */ private $autoShrinkHorizontal; /** - * Vertical Auto Shrink - * @var boolean + * Vertical Auto Shrink. + * + * @var bool|null */ private $autoShrinkVertical; - + /** - * The percentage of the original font size to which the text is scaled - * @var float + * The percentage of the original font size to which the text is scaled. + * + * @var float|null */ private $fontScale; - + /** - * The percentage of the reduction of the line spacing - * @var float + * The percentage of the reduction of the line spacing. + * + * @var float|null */ private $lnSpcReduction; /** - * Create a new \PhpOffice\PhpPresentation\Shape\RichText instance + * Create a new \PhpOffice\PhpPresentation\Shape\RichText instance. */ public function __construct() { // Initialise variables - $this->richTextParagraphs = array( - new Paragraph() - ); - $this->activeParagraph = 0; + $this->richTextParagraphs = [ + new Paragraph(), + ]; // Initialize parent parent::__construct(); } /** - * Get active paragraph index + * Get active paragraph index. * * @return int */ - public function getActiveParagraphIndex() + public function getActiveParagraphIndex(): int { return $this->activeParagraph; } - /** - * Get active paragraph - * - * @return \PhpOffice\PhpPresentation\Shape\RichText\Paragraph - */ - public function getActiveParagraph() + public function getActiveParagraph(): Paragraph { return $this->richTextParagraphs[$this->activeParagraph]; } /** - * Set active paragraph + * Set active paragraph. * - * @param int $index - * @throws \Exception - * @return \PhpOffice\PhpPresentation\Shape\RichText\Paragraph + * @throws OutOfBoundsException */ - public function setActiveParagraph($index = 0) + public function setActiveParagraph(int $index = 0): Paragraph { if ($index >= count($this->richTextParagraphs)) { - throw new \Exception("Invalid paragraph count."); + throw new OutOfBoundsException(0, count($this->richTextParagraphs), $index); } $this->activeParagraph = $index; @@ -210,38 +217,33 @@ class RichText extends AbstractShape implements ComparableInterface } /** - * Get paragraph + * Get paragraph. * - * @param int $index - * @throws \Exception - * @return \PhpOffice\PhpPresentation\Shape\RichText\Paragraph + * @throws OutOfBoundsException */ - public function getParagraph($index = 0) + public function getParagraph(int $index = 0): Paragraph { if ($index >= count($this->richTextParagraphs)) { - throw new \Exception("Invalid paragraph count."); + throw new OutOfBoundsException(0, count($this->richTextParagraphs), $index); } return $this->richTextParagraphs[$index]; } /** - * Create paragraph - * - * @return \PhpOffice\PhpPresentation\Shape\RichText\Paragraph - * @throws \Exception + * Create paragraph. */ - public function createParagraph() + public function createParagraph(): Paragraph { $numParagraphs = count($this->richTextParagraphs); if ($numParagraphs > 0) { - $alignment = clone $this->getActiveParagraph()->getAlignment(); - $font = clone $this->getActiveParagraph()->getFont(); + $alignment = clone $this->getActiveParagraph()->getAlignment(); + $font = clone $this->getActiveParagraph()->getFont(); $bulletStyle = clone $this->getActiveParagraph()->getBulletStyle(); } $this->richTextParagraphs[] = new Paragraph(); - $this->activeParagraph = count($this->richTextParagraphs) - 1; + $this->activeParagraph = count($this->richTextParagraphs) - 1; if (isset($alignment)) { $this->getActiveParagraph()->setAlignment($alignment); @@ -252,17 +254,18 @@ class RichText extends AbstractShape implements ComparableInterface if (isset($bulletStyle)) { $this->getActiveParagraph()->setBulletStyle($bulletStyle); } + return $this->getActiveParagraph(); } /** - * Add text + * Add text. * - * @param \PhpOffice\PhpPresentation\Shape\RichText\TextElementInterface $pText Rich text element - * @throws \Exception - * @return \PhpOffice\PhpPresentation\Shape\RichText + * @param TextElementInterface|null $pText Rich text element + * + * @return self */ - public function addText(TextElementInterface $pText = null) + public function addText(TextElementInterface $pText = null): self { $this->richTextParagraphs[$this->activeParagraph]->addText($pText); @@ -270,51 +273,50 @@ class RichText extends AbstractShape implements ComparableInterface } /** - * Create text (can not be formatted !) + * Create text (can not be formatted !). * - * @param string $pText Text - * @return \PhpOffice\PhpPresentation\Shape\RichText\TextElement - * @throws \Exception + * @param string $pText Text + * + * @return RichText\TextElement */ - public function createText($pText = '') + public function createText(string $pText = ''): RichText\TextElement { return $this->richTextParagraphs[$this->activeParagraph]->createText($pText); } /** - * Create break + * Create break. * - * @return \PhpOffice\PhpPresentation\Shape\RichText\BreakElement - * @throws \Exception + * @return RichText\BreakElement */ - public function createBreak() + public function createBreak(): RichText\BreakElement { return $this->richTextParagraphs[$this->activeParagraph]->createBreak(); } /** - * Create text run (can be formatted) + * Create text run (can be formatted). * - * @param string $pText Text - * @return \PhpOffice\PhpPresentation\Shape\RichText\Run - * @throws \Exception + * @param string $pText Text + * + * @return RichText\Run */ - public function createTextRun($pText = '') + public function createTextRun(string $pText = ''): RichText\Run { return $this->richTextParagraphs[$this->activeParagraph]->createTextRun($pText); } /** - * Get plain text + * Get plain text. * * @return string */ - public function getPlainText() + public function getPlainText(): string { // Return value $returnValue = ''; - // Loop trough all \PhpOffice\PhpPresentation\Shape\RichText\Paragraph + // Loop trough all Paragraph foreach ($this->richTextParagraphs as $p) { $returnValue .= $p->getPlainText(); } @@ -324,7 +326,7 @@ class RichText extends AbstractShape implements ComparableInterface } /** - * Convert to string + * Convert to string. * * @return string */ @@ -334,50 +336,44 @@ class RichText extends AbstractShape implements ComparableInterface } /** - * Get paragraphs - * - * @return \PhpOffice\PhpPresentation\Shape\RichText\Paragraph[] + * @return array */ - public function getParagraphs() + public function getParagraphs(): array { return $this->richTextParagraphs; } /** - * Set paragraphs + * Set paragraphs. * - * @param \PhpOffice\PhpPresentation\Shape\RichText\Paragraph[] $paragraphs Array of paragraphs - * @throws \Exception - * @return \PhpOffice\PhpPresentation\Shape\RichText + * @param array $paragraphs Array of paragraphs */ - public function setParagraphs($paragraphs = null) + public function setParagraphs(array $paragraphs = []): self { - if (!is_array($paragraphs)) { - throw new \Exception("Invalid \PhpOffice\PhpPresentation\Shape\RichText\Paragraph[] array passed."); - } - $this->richTextParagraphs = $paragraphs; - $this->activeParagraph = count($this->richTextParagraphs) - 1; + $this->activeParagraph = count($this->richTextParagraphs) - 1; + return $this; } /** - * Get text wrapping + * Get text wrapping. * * @return string */ - public function getWrap() + public function getWrap(): string { return $this->wrap; } /** - * Set text wrapping + * Set text wrapping. * - * @param $value string - * @return \PhpOffice\PhpPresentation\Shape\RichText + * @param string $value + * + * @return self */ - public function setWrap($value = self::WRAP_SQUARE) + public function setWrap(string $value = self::WRAP_SQUARE): self { $this->wrap = $value; @@ -385,51 +381,48 @@ class RichText extends AbstractShape implements ComparableInterface } /** - * Get autofit + * Get autofit. * * @return string */ - public function getAutoFit() + public function getAutoFit(): string { return $this->autoFit; } /** - * Get pourcentage of fontScale - * - * @return float + * Get pourcentage of fontScale. */ - public function getFontScale() + public function getFontScale(): ?float { return $this->fontScale; } /** - * Get pourcentage of the line space reduction - * - * @return float + * Get pourcentage of the line space reduction. */ - public function getLineSpaceReduction() + public function getLineSpaceReduction(): ?float { return $this->lnSpcReduction; } /** - * Set autofit + * Set autofit. * - * @param $value string - * @param $fontScale float - * @param $lnSpcReduction float - * @return \PhpOffice\PhpPresentation\Shape\RichText + * @param string $value + * @param float|null $fontScale + * @param float|null $lnSpcReduction + * + * @return self */ - public function setAutoFit($value = self::AUTOFIT_DEFAULT, $fontScale = null, $lnSpcReduction = null) + public function setAutoFit(string $value = self::AUTOFIT_DEFAULT, float $fontScale = null, float $lnSpcReduction = null): self { $this->autoFit = $value; - + if (!is_null($fontScale)) { $this->fontScale = $fontScale; } - + if (!is_null($lnSpcReduction)) { $this->lnSpcReduction = $lnSpcReduction; } @@ -438,22 +431,23 @@ class RichText extends AbstractShape implements ComparableInterface } /** - * Get horizontal overflow + * Get horizontal overflow. * * @return string */ - public function getHorizontalOverflow() + public function getHorizontalOverflow(): string { return $this->horizontalOverflow; } /** - * Set horizontal overflow + * Set horizontal overflow. * - * @param $value string - * @return \PhpOffice\PhpPresentation\Shape\RichText + * @param string $value + * + * @return self */ - public function setHorizontalOverflow($value = self::OVERFLOW_OVERFLOW) + public function setHorizontalOverflow(string $value = self::OVERFLOW_OVERFLOW): self { $this->horizontalOverflow = $value; @@ -461,22 +455,23 @@ class RichText extends AbstractShape implements ComparableInterface } /** - * Get vertical overflow + * Get vertical overflow. * * @return string */ - public function getVerticalOverflow() + public function getVerticalOverflow(): string { return $this->verticalOverflow; } /** - * Set vertical overflow + * Set vertical overflow. * - * @param $value string - * @return \PhpOffice\PhpPresentation\Shape\RichText + * @param string $value + * + * @return self */ - public function setVerticalOverflow($value = self::OVERFLOW_OVERFLOW) + public function setVerticalOverflow(string $value = self::OVERFLOW_OVERFLOW): self { $this->verticalOverflow = $value; @@ -484,22 +479,23 @@ class RichText extends AbstractShape implements ComparableInterface } /** - * Get upright + * Get upright. * - * @return boolean + * @return bool */ - public function isUpright() + public function isUpright(): bool { return $this->upright; } /** - * Set vertical + * Set vertical. * - * @param $value boolean - * @return \PhpOffice\PhpPresentation\Shape\RichText + * @param bool $value + * + * @return self */ - public function setUpright($value = false) + public function setUpright(bool $value = false): self { $this->upright = $value; @@ -507,22 +503,23 @@ class RichText extends AbstractShape implements ComparableInterface } /** - * Get vertical + * Get vertical. * - * @return boolean + * @return bool */ - public function isVertical() + public function isVertical(): bool { return $this->vertical; } /** - * Set vertical + * Set vertical. * - * @param $value boolean - * @return \PhpOffice\PhpPresentation\Shape\RichText + * @param bool $value + * + * @return self */ - public function setVertical($value = false) + public function setVertical(bool $value = false): self { $this->vertical = $value; @@ -530,26 +527,28 @@ class RichText extends AbstractShape implements ComparableInterface } /** - * Get columns + * Get columns. * * @return int */ - public function getColumns() + public function getColumns(): int { return $this->columns; } /** - * Set columns + * Set columns. * - * @param $value int - * @throws \Exception - * @return \PhpOffice\PhpPresentation\Shape\RichText + * @param int $value + * + * @return self + * + * @throws OutOfBoundsException */ - public function setColumns($value = 1) + public function setColumns(int $value = 1): self { if ($value > 16 || $value < 1) { - throw new \Exception('Number of columns should be 1-16'); + throw new OutOfBoundsException(1, 16, $value); } $this->columns = $value; @@ -558,22 +557,23 @@ class RichText extends AbstractShape implements ComparableInterface } /** - * Get bottom inset + * Get bottom inset. * * @return float */ - public function getInsetBottom() + public function getInsetBottom(): float { return $this->bottomInset; } /** - * Set bottom inset + * Set bottom inset. * - * @param $value float - * @return \PhpOffice\PhpPresentation\Shape\RichText + * @param float $value + * + * @return self */ - public function setInsetBottom($value = 4.8) + public function setInsetBottom(float $value = 4.8): self { $this->bottomInset = $value; @@ -581,22 +581,23 @@ class RichText extends AbstractShape implements ComparableInterface } /** - * Get left inset + * Get left inset. * * @return float */ - public function getInsetLeft() + public function getInsetLeft(): float { return $this->leftInset; } /** - * Set left inset + * Set left inset. * - * @param $value float - * @return \PhpOffice\PhpPresentation\Shape\RichText + * @param float $value + * + * @return self */ - public function setInsetLeft($value = 9.6) + public function setInsetLeft(float $value = 9.6): self { $this->leftInset = $value; @@ -604,22 +605,23 @@ class RichText extends AbstractShape implements ComparableInterface } /** - * Get right inset + * Get right inset. * * @return float */ - public function getInsetRight() + public function getInsetRight(): float { return $this->rightInset; } /** - * Set left inset + * Set left inset. * - * @param $value float - * @return \PhpOffice\PhpPresentation\Shape\RichText + * @param float $value + * + * @return self */ - public function setInsetRight($value = 9.6) + public function setInsetRight(float $value = 9.6): self { $this->rightInset = $value; @@ -627,84 +629,115 @@ class RichText extends AbstractShape implements ComparableInterface } /** - * Get top inset + * Get top inset. * * @return float */ - public function getInsetTop() + public function getInsetTop(): float { return $this->topInset; } /** - * Set top inset + * Set top inset. * - * @param $value float - * @return \PhpOffice\PhpPresentation\Shape\RichText + * @param float $value + * + * @return self */ - public function setInsetTop($value = 4.8) + public function setInsetTop(float $value = 4.8): self { $this->topInset = $value; return $this; } - /** - * Set horizontal auto shrink - * @param bool $value - * @return RichText - */ - public function setAutoShrinkHorizontal($value = null) + public function setAutoShrinkHorizontal(bool $value = null): self { - if (is_bool($value)) { - $this->autoShrinkHorizontal = $value; - } + $this->autoShrinkHorizontal = $value; + return $this; } - - /** - * Get horizontal auto shrink - * @return bool - */ - public function hasAutoShrinkHorizontal() + + public function hasAutoShrinkHorizontal(): ?bool { return $this->autoShrinkHorizontal; } /** - * Set vertical auto shrink - * @param bool $value + * Set vertical auto shrink. + * * @return RichText */ - public function setAutoShrinkVertical($value = null) + public function setAutoShrinkVertical(bool $value = null): self { - if (is_bool($value)) { - $this->autoShrinkVertical = $value; - } + $this->autoShrinkVertical = $value; + return $this; } - + /** - * Set vertical auto shrink - * @return bool + * Set vertical auto shrink. */ - public function hasAutoShrinkVertical() + public function hasAutoShrinkVertical(): ?bool { return $this->autoShrinkVertical; } - + /** - * Get hash code + * Get spacing between columns + * + * @return int + */ + public function getColumnSpacing(): int + { + return $this->columnSpacing; + } + + /** + * Set spacing between columns + * + * @param int $value + * + * @return self + */ + public function setColumnSpacing(int $value = 0): self + { + if ($value >= 0) { + $this->columnSpacing = $value; + } + + return $this; + } + + /** + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { $hashElements = ''; foreach ($this->richTextParagraphs as $element) { $hashElements .= $element->getHashCode(); } - return md5($hashElements . $this->wrap . $this->autoFit . $this->horizontalOverflow . $this->verticalOverflow . ($this->upright ? '1' : '0') . ($this->vertical ? '1' : '0') . $this->columns . $this->bottomInset . $this->leftInset . $this->rightInset . $this->topInset . parent::getHashCode() . __CLASS__); + return md5( + $hashElements + . $this->wrap + . $this->autoFit + . $this->horizontalOverflow + . $this->verticalOverflow + . ($this->upright ? '1' : '0') + . ($this->vertical ? '1' : '0') + . $this->columns + . $this->columnSpacing + . $this->bottomInset + . $this->leftInset + . $this->rightInset + . $this->topInset + . parent::getHashCode() + . __CLASS__ + ); } } diff --git a/PhpOffice/PhpPresentation/Shape/RichText/BreakElement.php b/PhpOffice/PhpPresentation/Shape/RichText/BreakElement.php old mode 100755 new mode 100644 index dbd5bb7..5df5f06 --- a/PhpOffice/PhpPresentation/Shape/RichText/BreakElement.php +++ b/PhpOffice/PhpPresentation/Shape/RichText/BreakElement.php @@ -10,27 +10,32 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape\RichText; +use PhpOffice\PhpPresentation\Style\Font; + /** - * Rich text break + * Rich text break. */ class BreakElement implements TextElementInterface { /** - * Create a new \PhpOffice\PhpPresentation\Shape\RichText\Break instance + * Create a new \PhpOffice\PhpPresentation\Shape\RichText\Break instance. */ public function __construct() { } /** - * Get text + * Get text. * * @return string Text */ @@ -40,53 +45,47 @@ class BreakElement implements TextElementInterface } /** - * Set text + * Set text. * - * @param $pText string Text - * @return \PhpOffice\PhpPresentation\Shape\RichText\TextElementInterface + * @param string $pText Text value */ - public function setText($pText = '') + public function setText($pText = ''): self { return $this; } /** - * Get font - * - * @return \PhpOffice\PhpPresentation\Style\Font + * Get font. */ - public function getFont() + public function getFont(): ?Font { return null; } /** - * Set language + * Set language. * - * @param $lang - * @return \PhpOffice\PhpPresentation\Shape\RichText\TextElementInterface + * @param string $lang */ - public function setLanguage($lang) + public function setLanguage($lang): self { return $this; } /** - * Get language - * - * @return string Language + * Get language. */ - public function getLanguage() + public function getLanguage(): ?string { return null; } /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { return md5(__CLASS__); } diff --git a/PhpOffice/PhpPresentation/Shape/RichText/Paragraph.php b/PhpOffice/PhpPresentation/Shape/RichText/Paragraph.php old mode 100755 new mode 100644 index ab64b70..5fa2692 --- a/PhpOffice/PhpPresentation/Shape/RichText/Paragraph.php +++ b/PhpOffice/PhpPresentation/Shape/RichText/Paragraph.php @@ -10,11 +10,14 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape\RichText; use PhpOffice\PhpPresentation\ComparableInterface; @@ -23,79 +26,90 @@ use PhpOffice\PhpPresentation\Style\Bullet; use PhpOffice\PhpPresentation\Style\Font; /** - * \PhpOffice\PhpPresentation\Shape\RichText\Paragraph + * \PhpOffice\PhpPresentation\Shape\RichText\Paragraph. */ class Paragraph implements ComparableInterface { - /** - * Rich text elements - * - * @var \PhpOffice\PhpPresentation\Shape\RichText\TextElementInterface[] - */ - private $richTextElements; + public const LINE_SPACING_MODE_PERCENT = 'percent'; + public const LINE_SPACING_MODE_POINT = 'point'; /** - * Alignment + * Rich text elements. * - * @var \PhpOffice\PhpPresentation\Style\Alignment + * @var array + */ + private $richTextElements = []; + + /** + * Alignment. + * + * @var Alignment */ private $alignment; /** - * Font + * Font. * - * @var \PhpOffice\PhpPresentation\Style\Font + * @var Font|null */ private $font; /** - * Bullet style + * Bullet style. * - * @var \PhpOffice\PhpPresentation\Style\Bullet + * @var Bullet */ private $bulletStyle; /** - * @var integer + * @var int */ private $lineSpacing = 100; /** - * Hash index - * * @var string */ + private $lineSpacingMode = self::LINE_SPACING_MODE_PERCENT; + + /** + * @var int + */ + private $spacingBefore = 0; + + /** + * @var int + */ + private $spacingAfter = 0; + + /** + * Hash index. + * + * @var int + */ private $hashIndex; /** - * Create a new \PhpOffice\PhpPresentation\Shape\RichText\Paragraph instance + * Create a new \PhpOffice\PhpPresentation\Shape\RichText\Paragraph instance. */ public function __construct() { - // Initialise variables - $this->richTextElements = array(); $this->alignment = new Alignment(); $this->font = new Font(); $this->bulletStyle = new Bullet(); } /** - * Get alignment - * - * @return \PhpOffice\PhpPresentation\Style\Alignment + * Get alignment. */ - public function getAlignment() + public function getAlignment(): Alignment { return $this->alignment; } /** - * Set alignment - * - * @param \PhpOffice\PhpPresentation\Style\Alignment $alignment - * @return \PhpOffice\PhpPresentation\Shape\RichText\Paragraph + * Set alignment. */ - public function setAlignment(Alignment $alignment) + public function setAlignment(Alignment $alignment): self { $this->alignment = $alignment; @@ -103,23 +117,19 @@ class Paragraph implements ComparableInterface } /** - * Get font - * - * @return \PhpOffice\PhpPresentation\Style\Font + * Get font. */ - public function getFont() + public function getFont(): ?Font { return $this->font; } /** - * Set font + * Set font. * - * @param \PhpOffice\PhpPresentation\Style\Font $pFont Font - * @throws \Exception - * @return \PhpOffice\PhpPresentation\Shape\RichText\Paragraph + * @param Font|null $pFont Font */ - public function setFont(Font $pFont = null) + public function setFont(Font $pFont = null): self { $this->font = $pFont; @@ -127,23 +137,17 @@ class Paragraph implements ComparableInterface } /** - * Get bullet style - * - * @return \PhpOffice\PhpPresentation\Style\Bullet + * Get bullet style. */ - public function getBulletStyle() + public function getBulletStyle(): ?Bullet { return $this->bulletStyle; } /** * Set bullet style - * - * @param \PhpOffice\PhpPresentation\Style\Bullet $style - * @throws \Exception - * @return \PhpOffice\PhpPresentation\Shape\RichText\Paragraph */ - public function setBulletStyle(Bullet $style = null) + public function setBulletStyle(Bullet $style = null): self { $this->bulletStyle = $style; @@ -151,13 +155,11 @@ class Paragraph implements ComparableInterface } /** - * Create text (can not be formatted !) + * Create text (can not be formatted !). * - * @param string $pText Text - * @return \PhpOffice\PhpPresentation\Shape\RichText\TextElement - * @throws \Exception + * @param string $pText Text */ - public function createText($pText = '') + public function createText(string $pText = ''): TextElement { $objText = new TextElement($pText); $this->addText($objText); @@ -166,13 +168,11 @@ class Paragraph implements ComparableInterface } /** - * Add text + * Add text. * - * @param \PhpOffice\PhpPresentation\Shape\RichText\TextElementInterface $pText Rich text element - * @throws \Exception - * @return \PhpOffice\PhpPresentation\Shape\RichText\Paragraph + * @param TextElementInterface|null $pText Rich text element */ - public function addText(TextElementInterface $pText = null) + public function addText(TextElementInterface $pText = null): self { $this->richTextElements[] = $pText; @@ -180,12 +180,9 @@ class Paragraph implements ComparableInterface } /** - * Create break - * - * @return \PhpOffice\PhpPresentation\Shape\RichText\BreakElement - * @throws \Exception + * Create break. */ - public function createBreak() + public function createBreak(): BreakElement { $objText = new BreakElement(); $this->addText($objText); @@ -194,13 +191,11 @@ class Paragraph implements ComparableInterface } /** - * Create text run (can be formatted) + * Create text run (can be formatted). * - * @param string $pText Text - * @return \PhpOffice\PhpPresentation\Shape\RichText\Run - * @throws \Exception + * @param string $pText Text */ - public function createTextRun($pText = '') + public function createTextRun(string $pText = ''): Run { $objText = new Run($pText); $objText->setFont(clone $this->font); @@ -210,7 +205,7 @@ class Paragraph implements ComparableInterface } /** - * Convert to string + * Convert to string. * * @return string */ @@ -220,16 +215,14 @@ class Paragraph implements ComparableInterface } /** - * Get plain text - * - * @return string + * Get plain text. */ - public function getPlainText() + public function getPlainText(): string { // Return value $returnValue = ''; - // Loop trough all \PhpOffice\PhpPresentation\Shape\RichText\TextElementInterface + // Loop trough all TextElementInterface foreach ($this->richTextElements as $text) { if ($text instanceof TextElementInterface) { $returnValue .= $text->getText(); @@ -241,37 +234,33 @@ class Paragraph implements ComparableInterface } /** - * Get Rich Text elements + * Get Rich Text elements. * - * @return \PhpOffice\PhpPresentation\Shape\RichText\TextElementInterface[] + * @return array */ - public function getRichTextElements() + public function getRichTextElements(): array { return $this->richTextElements; } /** - * Set Rich Text elements + * Set Rich Text elements. * - * @param \PhpOffice\PhpPresentation\Shape\RichText\TextElementInterface[] $pElements Array of elements - * @throws \Exception - * @return \PhpOffice\PhpPresentation\Shape\RichText\Paragraph + * @param array $pElements Array of elements */ - public function setRichTextElements($pElements = null) + public function setRichTextElements(array $pElements = []): self { - if (!is_array($pElements)) { - throw new \Exception("Invalid \PhpOffice\PhpPresentation\Shape\RichText\TextElementInterface[] array passed."); - } $this->richTextElements = $pElements; + return $this; } /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { $hashElements = ''; foreach ($this->richTextElements as $element) { @@ -282,46 +271,127 @@ class Paragraph implements ComparableInterface } /** - * Get hash index + * Get hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @return string Hash index + * @return int|null Hash index */ - public function getHashIndex() + public function getHashIndex(): ?int { return $this->hashIndex; } /** - * Set hash index + * Set hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @param string $value Hash index + * @param int $value Hash index + * + * @return $this */ - public function setHashIndex($value) + public function setHashIndex(int $value) { $this->hashIndex = $value; + + return $this; } /** * @return int */ - public function getLineSpacing() + public function getLineSpacing(): int { return $this->lineSpacing; } /** + * Value in points + * * @param int $lineSpacing - * @return Paragraph + * + * @return self */ - public function setLineSpacing($lineSpacing) + public function setLineSpacing($lineSpacing): self { $this->lineSpacing = $lineSpacing; + + return $this; + } + + /** + * @return string + */ + public function getLineSpacingMode(): string + { + return $this->lineSpacingMode; + } + + /** + * @param string $lineSpacingMode + * + * @return self + */ + public function setLineSpacingMode(string $lineSpacingMode): self + { + if (in_array($lineSpacingMode, [ + self::LINE_SPACING_MODE_PERCENT, + self::LINE_SPACING_MODE_POINT, + ])) { + $this->lineSpacingMode = $lineSpacingMode; + } + + return $this; + } + + /** + * Value in points + * + * @return int + */ + public function getSpacingBefore(): int + { + return $this->spacingBefore; + } + + /** + * Value in points + * + * @param int $spacingBefore + * + * @return self + */ + public function setSpacingBefore(int $spacingBefore): self + { + $this->spacingBefore = $spacingBefore; + + return $this; + } + + /** + * Value in points + * + * @return int + */ + public function getSpacingAfter(): int + { + return $this->spacingAfter; + } + + /** + * Value in points + * + * @param int $spacingAfter + * + * @return self + */ + public function setSpacingAfter(int $spacingAfter): self + { + $this->spacingAfter = $spacingAfter; + return $this; } } diff --git a/PhpOffice/PhpPresentation/Shape/RichText/Run.php b/PhpOffice/PhpPresentation/Shape/RichText/Run.php old mode 100755 new mode 100644 index 97455c0..ba4f532 --- a/PhpOffice/PhpPresentation/Shape/RichText/Run.php +++ b/PhpOffice/PhpPresentation/Shape/RichText/Run.php @@ -10,29 +10,32 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape\RichText; use PhpOffice\PhpPresentation\Style\Font; /** - * Rich text run + * Rich text run. */ class Run extends TextElement implements TextElementInterface { /** - * Font + * Font. * * @var \PhpOffice\PhpPresentation\Style\Font */ private $font; /** - * Create a new \PhpOffice\PhpPresentation\Shape\RichText\Run instance + * Create a new \PhpOffice\PhpPresentation\Shape\RichText\Run instance. * * @param string $pText Text */ @@ -44,20 +47,18 @@ class Run extends TextElement implements TextElementInterface } /** - * Get font - * - * @return \PhpOffice\PhpPresentation\Style\Font + * Get font. */ - public function getFont() + public function getFont(): Font { return $this->font; } /** - * Set font + * Set font. + * + * @param Font|null $pFont Font * - * @param \PhpOffice\PhpPresentation\Style\Font $pFont Font - * @throws \Exception * @return \PhpOffice\PhpPresentation\Shape\RichText\TextElementInterface */ public function setFont(Font $pFont = null) @@ -68,11 +69,11 @@ class Run extends TextElement implements TextElementInterface } /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { return md5($this->getText() . $this->font->getHashCode() . __CLASS__); } diff --git a/PhpOffice/PhpPresentation/Shape/RichText/TextElement.php b/PhpOffice/PhpPresentation/Shape/RichText/TextElement.php old mode 100755 new mode 100644 index e19b9b8..3c74d02 --- a/PhpOffice/PhpPresentation/Shape/RichText/TextElement.php +++ b/PhpOffice/PhpPresentation/Shape/RichText/TextElement.php @@ -10,22 +10,26 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape\RichText; use PhpOffice\PhpPresentation\Shape\Hyperlink; +use PhpOffice\PhpPresentation\Style\Font; /** - * Rich text text element + * Rich text text element. */ class TextElement implements TextElementInterface { /** - * Text + * Text. * * @var string */ @@ -37,14 +41,14 @@ class TextElement implements TextElementInterface protected $language; /** - * Hyperlink + * Hyperlink. * - * @var \PhpOffice\PhpPresentation\Shape\Hyperlink + * @var Hyperlink|null */ protected $hyperlink; /** - * Create a new \PhpOffice\PhpPresentation\Shape\RichText\TextElement instance + * Create a new \PhpOffice\PhpPresentation\Shape\RichText\TextElement instance. * * @param string $pText Text */ @@ -55,7 +59,7 @@ class TextElement implements TextElementInterface } /** - * Get text + * Get text. * * @return string Text */ @@ -65,9 +69,10 @@ class TextElement implements TextElementInterface } /** - * Set text + * Set text. + * + * @param string $pText Text value * - * @param $pText string Text * @return \PhpOffice\PhpPresentation\Shape\RichText\TextElementInterface */ public function setText($pText = '') @@ -78,32 +83,19 @@ class TextElement implements TextElementInterface } /** - * Get font - * - * @return \PhpOffice\PhpPresentation\Style\Font + * Get font. */ - public function getFont() + public function getFont(): ?Font { return null; } - /** - * Has Hyperlink? - * - * @return boolean - */ - public function hasHyperlink() + public function hasHyperlink(): bool { return !is_null($this->hyperlink); } - /** - * Get Hyperlink - * - * @return \PhpOffice\PhpPresentation\Shape\Hyperlink - * @throws \Exception - */ - public function getHyperlink() + public function getHyperlink(): Hyperlink { if (is_null($this->hyperlink)) { $this->hyperlink = new Hyperlink(); @@ -113,10 +105,8 @@ class TextElement implements TextElementInterface } /** - * Set Hyperlink + * Set Hyperlink. * - * @param \PhpOffice\PhpPresentation\Shape\Hyperlink $pHyperlink - * @throws \Exception * @return \PhpOffice\PhpPresentation\Shape\RichText\TextElement */ public function setHyperlink(Hyperlink $pHyperlink = null) @@ -127,7 +117,8 @@ class TextElement implements TextElementInterface } /** - * Get language + * Get language. + * * @return string */ public function getLanguage() @@ -136,22 +127,25 @@ class TextElement implements TextElementInterface } /** - * Set language + * Set language. + * * @param string $language + * * @return TextElement */ public function setLanguage($language) { $this->language = $language; + return $this; } /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { return md5($this->text . (is_null($this->hyperlink) ? '' : $this->hyperlink->getHashCode()) . __CLASS__); } diff --git a/PhpOffice/PhpPresentation/Shape/RichText/TextElementInterface.php b/PhpOffice/PhpPresentation/Shape/RichText/TextElementInterface.php old mode 100755 new mode 100644 index 9830244..4c70443 --- a/PhpOffice/PhpPresentation/Shape/RichText/TextElementInterface.php +++ b/PhpOffice/PhpPresentation/Shape/RichText/TextElementInterface.php @@ -10,35 +10,39 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape\RichText; /** - * Rich text element interface + * Rich text element interface. */ interface TextElementInterface { /** - * Get text + * Get text. * * @return string Text */ public function getText(); /** - * Set text + * Set text. + * + * @param string $pText Text value * - * @param $pText string Text * @return \PhpOffice\PhpPresentation\Shape\RichText\TextElementInterface */ public function setText($pText = ''); /** - * Get font + * Get font. * * @return \PhpOffice\PhpPresentation\Style\Font */ @@ -51,14 +55,15 @@ interface TextElementInterface /** * @param string $lang + * * @return \PhpOffice\PhpPresentation\Shape\RichText\TextElementInterface */ public function setLanguage($lang); /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode(); + public function getHashCode(): string; } diff --git a/PhpOffice/PhpPresentation/Shape/Table.php b/PhpOffice/PhpPresentation/Shape/Table.php old mode 100755 new mode 100644 index 21da321..d6313be --- a/PhpOffice/PhpPresentation/Shape/Table.php +++ b/PhpOffice/PhpPresentation/Shape/Table.php @@ -10,44 +10,46 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape; use PhpOffice\PhpPresentation\ComparableInterface; +use PhpOffice\PhpPresentation\Exception\OutOfBoundsException; use PhpOffice\PhpPresentation\Shape\Table\Row; /** - * Table shape + * Table shape. */ class Table extends AbstractGraphic implements ComparableInterface { /** - * Rows + * Rows. * - * @var \PhpOffice\PhpPresentation\Shape\Table\Row[] + * @var array */ - private $rows; + private $rows = []; /** - * Number of columns + * Number of columns. * * @var int */ private $columnCount = 1; /** - * Create a new \PhpOffice\PhpPresentation\Shape\Table instance + * Create a new \PhpOffice\PhpPresentation\Shape\Table instance. * * @param int $columns Number of columns */ public function __construct($columns = 1) { - // Initialise variables - $this->rows = array(); $this->columnCount = $columns; // Initialize parent @@ -58,43 +60,53 @@ class Table extends AbstractGraphic implements ComparableInterface } /** - * Get row + * Get row. * - * @param int $row Row number - * @param boolean $exceptionAsNull Return a null value instead of an exception? - * @throws \Exception - * @return \PhpOffice\PhpPresentation\Shape\Table\Row + * @param int $row Row number + * + * @throws OutOfBoundsException */ - public function getRow($row = 0, $exceptionAsNull = false) + public function getRow(int $row = 0): Row { if (!isset($this->rows[$row])) { - if ($exceptionAsNull) { - return null; - } - throw new \Exception('Row number out of bounds.'); + throw new OutOfBoundsException( + 0, + (count($this->rows) - 1) < 0 ? 0 : count($this->rows) - 1, + $row + ); } return $this->rows[$row]; } /** - * Get rows + * @param int $row * - * @return \PhpOffice\PhpPresentation\Shape\Table\Row[] + * @return bool */ - public function getRows() + public function hasRow(int $row): bool + { + return isset($this->rows[$row]); + } + + /** + * Get rows. + * + * @return Row[] + */ + public function getRows(): array { return $this->rows; } /** - * Create row + * Create row. * - * @return \PhpOffice\PhpPresentation\Shape\Table\Row + * @return Row */ - public function createRow() + public function createRow(): Row { - $row = new Row($this->columnCount); + $row = new Row($this->columnCount); $this->rows[] = $row; return $row; @@ -103,27 +115,29 @@ class Table extends AbstractGraphic implements ComparableInterface /** * @return int */ - public function getNumColumns() + public function getNumColumns(): int { return $this->columnCount; } /** * @param int $numColumn - * @return Table + * + * @return self */ - public function setNumColumns($numColumn) + public function setNumColumns(int $numColumn): self { $this->columnCount = $numColumn; + return $this; } /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { $hashElements = ''; foreach ($this->rows as $row) { diff --git a/PhpOffice/PhpPresentation/Shape/Table/Cell.php b/PhpOffice/PhpPresentation/Shape/Table/Cell.php old mode 100755 new mode 100644 index 0a184d2..c8e8a94 --- a/PhpOffice/PhpPresentation/Shape/Table/Cell.php +++ b/PhpOffice/PhpPresentation/Shape/Table/Cell.php @@ -10,90 +10,94 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape\Table; use PhpOffice\PhpPresentation\ComparableInterface; +use PhpOffice\PhpPresentation\Exception\OutOfBoundsException; use PhpOffice\PhpPresentation\Shape\RichText\Paragraph; use PhpOffice\PhpPresentation\Shape\RichText\TextElementInterface; use PhpOffice\PhpPresentation\Style\Borders; use PhpOffice\PhpPresentation\Style\Fill; /** - * Table cell + * Table cell. */ class Cell implements ComparableInterface { /** - * Rich text paragraphs + * Rich text paragraphs. * - * @var \PhpOffice\PhpPresentation\Shape\RichText\Paragraph[] + * @var array */ private $richTextParagraphs; /** - * Active paragraph + * Active paragraph. * * @var int */ private $activeParagraph = 0; /** - * Fill + * Fill. * * @var \PhpOffice\PhpPresentation\Style\Fill */ private $fill; /** - * Borders + * Borders. * * @var \PhpOffice\PhpPresentation\Style\Borders */ private $borders; /** - * Width (in pixels) + * Width (in pixels). * * @var int */ private $width = 0; /** - * Colspan + * Colspan. * * @var int */ private $colSpan = 0; /** - * Rowspan + * Rowspan. * * @var int */ private $rowSpan = 0; /** - * Hash index + * Hash index. * - * @var string + * @var int */ private $hashIndex; /** - * Create a new \PhpOffice\PhpPresentation\Shape\RichText instance + * Create a new \PhpOffice\PhpPresentation\Shape\RichText instance. */ public function __construct() { // Initialise variables - $this->richTextParagraphs = array( - new Paragraph() - ); - $this->activeParagraph = 0; + $this->richTextParagraphs = [ + new Paragraph(), + ]; + $this->activeParagraph = 0; // Set fill $this->fill = new Fill(); @@ -103,7 +107,7 @@ class Cell implements ComparableInterface } /** - * Get active paragraph index + * Get active paragraph index. * * @return int */ @@ -113,26 +117,24 @@ class Cell implements ComparableInterface } /** - * Get active paragraph - * - * @return \PhpOffice\PhpPresentation\Shape\RichText\Paragraph + * Get active paragraph. */ - public function getActiveParagraph() + public function getActiveParagraph(): Paragraph { return $this->richTextParagraphs[$this->activeParagraph]; } /** - * Set active paragraph + * Set active paragraph. * - * @param int $index - * @throws \Exception - * @return \PhpOffice\PhpPresentation\Shape\RichText\Paragraph + * @param int $index + * + * @throws OutOfBoundsException */ - public function setActiveParagraph($index = 0) + public function setActiveParagraph($index = 0): Paragraph { if ($index >= count($this->richTextParagraphs)) { - throw new \Exception("Invalid paragraph count."); + throw new OutOfBoundsException(0, count($this->richTextParagraphs), $index); } $this->activeParagraph = $index; @@ -141,28 +143,25 @@ class Cell implements ComparableInterface } /** - * Get paragraph + * Get paragraph. * - * @param int $index - * @throws \Exception - * @return \PhpOffice\PhpPresentation\Shape\RichText\Paragraph + * @param int $index + * + * @throws OutOfBoundsException */ - public function getParagraph($index = 0) + public function getParagraph(int $index = 0): Paragraph { if ($index >= count($this->richTextParagraphs)) { - throw new \Exception("Invalid paragraph count."); + throw new OutOfBoundsException(0, count($this->richTextParagraphs), $index); } return $this->richTextParagraphs[$index]; } /** - * Create paragraph - * - * @return \PhpOffice\PhpPresentation\Shape\RichText\Paragraph - * @throws \Exception + * Create paragraph. */ - public function createParagraph() + public function createParagraph(): Paragraph { $this->richTextParagraphs[] = new Paragraph(); $totalRichTextParagraphs = count($this->richTextParagraphs); @@ -177,14 +176,15 @@ class Cell implements ComparableInterface $this->getActiveParagraph()->setFont($font); $this->getActiveParagraph()->setBulletStyle($bulletStyle); } + return $this->getActiveParagraph(); } /** - * Add text + * Add text. + * + * @param TextElementInterface $pText Rich text element * - * @param \PhpOffice\PhpPresentation\Shape\RichText\TextElementInterface $pText Rich text element - * @throws \Exception * @return \PhpOffice\PhpPresentation\Shape\Table\Cell */ public function addText(TextElementInterface $pText = null) @@ -195,11 +195,11 @@ class Cell implements ComparableInterface } /** - * Create text (can not be formatted !) + * Create text (can not be formatted !). + * + * @param string $pText Text * - * @param string $pText Text * @return \PhpOffice\PhpPresentation\Shape\RichText\TextElement - * @throws \Exception */ public function createText($pText = '') { @@ -207,10 +207,9 @@ class Cell implements ComparableInterface } /** - * Create break + * Create break. * * @return \PhpOffice\PhpPresentation\Shape\RichText\BreakElement - * @throws \Exception */ public function createBreak() { @@ -218,19 +217,19 @@ class Cell implements ComparableInterface } /** - * Create text run (can be formatted) + * Create text run (can be formatted). + * + * @param string $pText Text * - * @param string $pText Text * @return \PhpOffice\PhpPresentation\Shape\RichText\Run - * @throws \Exception */ - public function createTextRun($pText = '') + public function createTextRun(string $pText = '') { return $this->richTextParagraphs[$this->activeParagraph]->createTextRun($pText); } /** - * Get plain text + * Get plain text. * * @return string */ @@ -239,7 +238,7 @@ class Cell implements ComparableInterface // Return value $returnValue = ''; - // Loop trough all \PhpOffice\PhpPresentation\Shape\RichText\Paragraph + // Loop trough all Paragraph foreach ($this->richTextParagraphs as $p) { $returnValue .= $p->getPlainText(); } @@ -249,7 +248,7 @@ class Cell implements ComparableInterface } /** - * Convert to string + * Convert to string. * * @return string */ @@ -259,9 +258,9 @@ class Cell implements ComparableInterface } /** - * Get paragraphs + * Get paragraphs. * - * @return \PhpOffice\PhpPresentation\Shape\RichText\Paragraph[] + * @return array */ public function getParagraphs() { @@ -269,24 +268,22 @@ class Cell implements ComparableInterface } /** - * Set paragraphs + * Set paragraphs. + * + * @param array $paragraphs Array of paragraphs * - * @param \PhpOffice\PhpPresentation\Shape\RichText\Paragraph[] $paragraphs Array of paragraphs - * @throws \Exception * @return \PhpOffice\PhpPresentation\Shape\Table\Cell */ - public function setParagraphs($paragraphs = null) + public function setParagraphs(array $paragraphs = []): self { - if (!is_array($paragraphs)) { - throw new \Exception("Invalid \PhpOffice\PhpPresentation\Shape\RichText\Paragraph[] array passed."); - } $this->richTextParagraphs = $paragraphs; - $this->activeParagraph = count($this->richTextParagraphs) - 1; + $this->activeParagraph = count($this->richTextParagraphs) - 1; + return $this; } /** - * Get fill + * Get fill. * * @return \PhpOffice\PhpPresentation\Style\Fill */ @@ -296,9 +293,8 @@ class Cell implements ComparableInterface } /** - * Set fill + * Set fill. * - * @param \PhpOffice\PhpPresentation\Style\Fill $fill * @return \PhpOffice\PhpPresentation\Shape\Table\Cell */ public function setFill(Fill $fill) @@ -309,7 +305,7 @@ class Cell implements ComparableInterface } /** - * Get borders + * Get borders. * * @return \PhpOffice\PhpPresentation\Style\Borders */ @@ -319,9 +315,8 @@ class Cell implements ComparableInterface } /** - * Set borders + * Set borders. * - * @param \PhpOffice\PhpPresentation\Style\Borders $borders * @return \PhpOffice\PhpPresentation\Shape\Table\Cell */ public function setBorders(Borders $borders) @@ -332,7 +327,7 @@ class Cell implements ComparableInterface } /** - * Get width + * Get width. * * @return int */ @@ -342,58 +337,35 @@ class Cell implements ComparableInterface } /** - * Set width + * Set width. * - * @param int $value - * @return \PhpOffice\PhpPresentation\Shape\Table\Cell + * @return self */ - public function setWidth($value = 0) + public function setWidth(int $pValue = 0) { - $this->width = $value; + $this->width = $pValue; return $this; } - /** - * Get colSpan - * - * @return int - */ - public function getColSpan() + public function getColSpan(): int { return $this->colSpan; } - /** - * Set colSpan - * - * @param int $value - * @return \PhpOffice\PhpPresentation\Shape\Table\Cell - */ - public function setColSpan($value = 0) + public function setColSpan(int $value = 0): self { $this->colSpan = $value; return $this; } - /** - * Get rowSpan - * - * @return int - */ - public function getRowSpan() + public function getRowSpan(): int { return $this->rowSpan; } - /** - * Set rowSpan - * - * @param int $value - * @return \PhpOffice\PhpPresentation\Shape\Table\Cell - */ - public function setRowSpan($value = 0) + public function setRowSpan(int $value = 0): self { $this->rowSpan = $value; @@ -401,11 +373,11 @@ class Cell implements ComparableInterface } /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { $hashElements = ''; foreach ($this->richTextParagraphs as $element) { @@ -416,28 +388,32 @@ class Cell implements ComparableInterface } /** - * Get hash index + * Get hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @return string Hash index + * @return int|null Hash index */ - public function getHashIndex() + public function getHashIndex(): ?int { return $this->hashIndex; } /** - * Set hash index + * Set hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @param string $value Hash index + * @param int $value Hash index + * + * @return $this */ - public function setHashIndex($value) + public function setHashIndex(int $value) { $this->hashIndex = $value; + + return $this; } } diff --git a/PhpOffice/PhpPresentation/Shape/Table/Row.php b/PhpOffice/PhpPresentation/Shape/Table/Row.php old mode 100755 new mode 100644 index 18d4475..039bbf1 --- a/PhpOffice/PhpPresentation/Shape/Table/Row.php +++ b/PhpOffice/PhpPresentation/Shape/Table/Row.php @@ -10,136 +10,154 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Shape\Table; use PhpOffice\PhpPresentation\ComparableInterface; +use PhpOffice\PhpPresentation\Exception\OutOfBoundsException; use PhpOffice\PhpPresentation\Style\Fill; /** - * Table row + * Table row. */ class Row implements ComparableInterface { /** - * Cells + * Cells. * - * @var \PhpOffice\PhpPresentation\Shape\Table\Cell[] + * @var Cell[] */ - private $cells; + private $cells = []; /** - * Fill + * Fill. * - * @var \PhpOffice\PhpPresentation\Style\Fill + * @var Fill */ private $fill; /** - * Height (in pixels) + * Height (in pixels). * * @var int */ private $height = 38; /** - * Active cell index + * Active cell index. * * @var int */ private $activeCellIndex = -1; /** - * Hash index + * Hash index. * - * @var string + * @var int */ private $hashIndex; /** - * Create a new \PhpOffice\PhpPresentation\Shape\Table\Row instance - * * @param int $columns Number of columns */ - public function __construct($columns = 1) + public function __construct(int $columns = 1) { - // Initialise variables - $this->cells = array(); - for ($i = 0; $i < $columns; $i++) { + // Fill + $this->fill = new Fill(); + // Cells + for ($inc = 0; $inc < $columns; ++$inc) { $this->cells[] = new Cell(); } - - // Set fill - $this->fill = new Fill(); } /** - * Get cell + * Get cell. * - * @param int $cell Cell number - * @param boolean $exceptionAsNull Return a null value instead of an exception? - * @throws \Exception - * @return \PhpOffice\PhpPresentation\Shape\Table\Cell + * @param int $cell Cell number + * + * @throws OutOfBoundsException */ - public function getCell($cell = 0, $exceptionAsNull = false) + public function getCell(int $cell = 0): Cell { if (!isset($this->cells[$cell])) { - if ($exceptionAsNull) { - return null; - } - throw new \Exception('Cell number out of bounds.'); + throw new OutOfBoundsException( + 0, + (count($this->cells) - 1) < 0 ? count($this->cells) - 1 : 0, + $cell + ); } return $this->cells[$cell]; } /** - * Get cells + * Get cell. * - * @return \PhpOffice\PhpPresentation\Shape\Table\Cell[] + * @param int $cell Cell number + * + * @return bool */ - public function getCells() + public function hasCell(int $cell): bool + { + return isset($this->cells[$cell]); + } + + /** + * Get cells. + * + * @return array + */ + public function getCells(): array { return $this->cells; } /** - * Next cell (moves one cell to the right) + * Next cell (moves one cell to the right). * - * @return \PhpOffice\PhpPresentation\Shape\Table\Cell - * @throws \Exception + * @return Cell + * + * @throws OutOfBoundsException */ - public function nextCell() + public function nextCell(): Cell { - $this->activeCellIndex++; + ++$this->activeCellIndex; if (isset($this->cells[$this->activeCellIndex])) { $this->cells[$this->activeCellIndex]->setFill(clone $this->getFill()); + return $this->cells[$this->activeCellIndex]; } - throw new \Exception("Cell count out of bounds."); + + throw new OutOfBoundsException( + 0, + (count($this->cells) - 1) < 0 ? count($this->cells) - 1 : 0, + $this->activeCellIndex + ); } /** - * Get fill + * Get fill. * - * @return \PhpOffice\PhpPresentation\Style\Fill + * @return Fill */ - public function getFill() + public function getFill(): Fill { return $this->fill; } /** - * Set fill + * Set fill. * - * @param \PhpOffice\PhpPresentation\Style\Fill $fill - * @return \PhpOffice\PhpPresentation\Shape\Table\Row + * @return self */ - public function setFill(Fill $fill) + public function setFill(Fill $fill): self { $this->fill = $fill; @@ -147,22 +165,23 @@ class Row implements ComparableInterface } /** - * Get height + * Get height. * * @return int */ - public function getHeight() + public function getHeight(): int { return $this->height; } /** - * Set height + * Set height. * - * @param int $value - * @return \PhpOffice\PhpPresentation\Shape\Table\Row + * @param int $value + * + * @return self */ - public function setHeight($value = 0) + public function setHeight(int $value = 0): self { $this->height = $value; @@ -170,11 +189,11 @@ class Row implements ComparableInterface } /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { $hashElements = ''; foreach ($this->cells as $cell) { @@ -185,28 +204,32 @@ class Row implements ComparableInterface } /** - * Get hash index + * Get hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @return string Hash index + * @return int|null Hash index */ - public function getHashIndex() + public function getHashIndex(): ?int { return $this->hashIndex; } /** - * Set hash index + * Set hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @param string $value Hash index + * @param int $value Hash index + * + * @return $this */ - public function setHashIndex($value) + public function setHashIndex(int $value) { $this->hashIndex = $value; + + return $this; } } diff --git a/PhpOffice/PhpPresentation/ShapeContainerInterface.php b/PhpOffice/PhpPresentation/ShapeContainerInterface.php old mode 100755 new mode 100644 index 4575c74..48e24f1 --- a/PhpOffice/PhpPresentation/ShapeContainerInterface.php +++ b/PhpOffice/PhpPresentation/ShapeContainerInterface.php @@ -10,58 +10,56 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation; +use ArrayObject; + /** - * PhpOffice\PhpPresentation\ShapeContainerInterface + * PhpOffice\PhpPresentation\ShapeContainerInterface. */ interface ShapeContainerInterface { /** - * Get collection of shapes - * - * @return \ArrayObject|\PhpOffice\PhpPresentation\AbstractShape[] - */ + * Get collection of shapes. + * + * @return array|ArrayObject + */ public function getShapeCollection(); /** - * Add shape to slide - * - * @param \PhpOffice\PhpPresentation\AbstractShape $shape - * @return \PhpOffice\PhpPresentation\AbstractShape - */ + * Add shape to slide. + * + * @return AbstractShape + */ public function addShape(AbstractShape $shape); /** - * Get X Offset - * - * @return int - */ - public function getOffsetX(); + * Get X Offset. + */ + public function getOffsetX(): int; /** - * Get Y Offset - * - * @return int - */ - public function getOffsetY(); + * Get Y Offset. + */ + public function getOffsetY(): int; /** - * Get X Extent - * - * @return int - */ - public function getExtentX(); + * Get X Extent. + */ + public function getExtentX(): int; /** - * Get Y Extent - * - * @return int - */ - public function getExtentY(); + * Get Y Extent. + */ + public function getExtentY(): int; + + public function getHashCode(): string; } diff --git a/PhpOffice/PhpPresentation/Slide.php b/PhpOffice/PhpPresentation/Slide.php old mode 100755 new mode 100644 index 473d7b7..61c0d57 --- a/PhpOffice/PhpPresentation/Slide.php +++ b/PhpOffice/PhpPresentation/Slide.php @@ -10,66 +10,65 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation; -use PhpOffice\PhpPresentation\Shape\Chart; -use PhpOffice\PhpPresentation\Shape\RichText; -use PhpOffice\PhpPresentation\Shape\Table; use PhpOffice\PhpPresentation\Slide\AbstractSlide; use PhpOffice\PhpPresentation\Slide\Note; use PhpOffice\PhpPresentation\Slide\SlideLayout; /** - * Slide class + * Slide class. */ class Slide extends AbstractSlide implements ComparableInterface, ShapeContainerInterface { /** - * The slide is shown in presentation + * The slide is shown in presentation. + * * @var bool */ protected $isVisible = true; /** - * Slide layout + * Slide layout. * - * @var SlideLayout + * @var SlideLayout|null */ private $slideLayout; /** - * Slide master id + * Slide master id. * - * @var integer + * @var int */ private $slideMasterId = 1; /** - * - * @var \PhpOffice\PhpPresentation\Slide\Note + * @var Note */ private $slideNote; /** - * * @var \PhpOffice\PhpPresentation\Slide\Animation[] */ - protected $animations = array(); + protected $animations = []; /** - * Name of the title + * Name of the title. * - * @var string + * @var string|null */ protected $name; /** - * Create a new slide + * Create a new slide. * * @param PhpPresentation $pParent */ @@ -89,32 +88,30 @@ class Slide extends AbstractSlide implements ComparableInterface, ShapeContainer $oSlideLayout = reset($arraySlideLayouts); $this->setSlideLayout($oSlideLayout); } + // Set note + $this->setNote(new Note()); } /** - * Get slide layout - * - * @return SlideLayout + * Get slide layout. */ - public function getSlideLayout() + public function getSlideLayout(): ?SlideLayout { return $this->slideLayout; } /** - * Set slide layout - * - * @param SlideLayout $layout - * @return \PhpOffice\PhpPresentation\Slide + * Set slide layout. */ - public function setSlideLayout(SlideLayout $layout) + public function setSlideLayout(SlideLayout $layout): self { $this->slideLayout = $layout; + return $this; } /** - * Get slide master id + * Get slide master id. * * @return int */ @@ -124,9 +121,10 @@ class Slide extends AbstractSlide implements ComparableInterface, ShapeContainer } /** - * Set slide master id + * Set slide master id. + * + * @param int $masterId * - * @param int $masterId * @return \PhpOffice\PhpPresentation\Slide */ public function setSlideMasterId($masterId = 1) @@ -137,7 +135,7 @@ class Slide extends AbstractSlide implements ComparableInterface, ShapeContainer } /** - * Copy slide (!= clone!) + * Copy slide (!= clone!). * * @return \PhpOffice\PhpPresentation\Slide */ @@ -148,24 +146,12 @@ class Slide extends AbstractSlide implements ComparableInterface, ShapeContainer return $copied; } - /** - * - * @return \PhpOffice\PhpPresentation\Slide\Note - */ - public function getNote() + public function getNote(): Note { - if (is_null($this->slideNote)) { - $this->setNote(); - } return $this->slideNote; } - /** - * - * @param \PhpOffice\PhpPresentation\Slide\Note $note - * @return \PhpOffice\PhpPresentation\Slide - */ - public function setNote(Note $note = null) + public function setNote(Note $note = null): self { $this->slideNote = (is_null($note) ? new Note() : $note); $this->slideNote->setParent($this); @@ -174,27 +160,27 @@ class Slide extends AbstractSlide implements ComparableInterface, ShapeContainer } /** - * Get the name of the slide + * Get the name of the slide. + * * @return string */ - public function getName() + public function getName(): ?string { return $this->name; } /** - * Set the name of the slide - * @param string $name - * @return $this + * Set the name of the slide. */ - public function setName($name = null) + public function setName(?string $name = null): self { $this->name = $name; + return $this; } /** - * @return boolean + * @return bool */ public function isVisible() { @@ -202,29 +188,33 @@ class Slide extends AbstractSlide implements ComparableInterface, ShapeContainer } /** - * @param boolean $value + * @param bool $value + * * @return Slide */ public function setIsVisible($value = true) { - $this->isVisible = (bool)$value; + $this->isVisible = (bool) $value; + return $this; } /** - * Add an animation to the slide + * Add an animation to the slide. + * + * @param \PhpOffice\PhpPresentation\Slide\Animation $animation * - * @param \PhpOffice\PhpPresentation\Slide\Animation * @return Slide */ public function addAnimation($animation) { $this->animations[] = $animation; + return $this; } /** - * Get collection of animations + * Get collection of animations. * * @return \PhpOffice\PhpPresentation\Slide\Animation[] */ @@ -234,13 +224,16 @@ class Slide extends AbstractSlide implements ComparableInterface, ShapeContainer } /** - * Set collection of animations + * Set collection of animations. + * * @param \PhpOffice\PhpPresentation\Slide\Animation[] $array + * * @return Slide */ - public function setAnimations(array $array = array()) + public function setAnimations(array $array = []) { $this->animations = $array; + return $this; } } diff --git a/PhpOffice/PhpPresentation/Slide/AbstractBackground.php b/PhpOffice/PhpPresentation/Slide/AbstractBackground.php old mode 100755 new mode 100644 index 72edbdf..42aa4d5 --- a/PhpOffice/PhpPresentation/Slide/AbstractBackground.php +++ b/PhpOffice/PhpPresentation/Slide/AbstractBackground.php @@ -1,8 +1,25 @@ |ArrayObject */ - protected $shapeCollection = null; + protected $shapeCollection = []; /** - * Extent Y + * Extent Y. * * @var int */ protected $extentY; /** - * Extent X + * Extent X. * * @var int */ protected $extentX; /** - * Offset X + * Offset X. * * @var int */ protected $offsetX; /** - * Offset Y + * Offset Y. * * @var int */ protected $offsetY; /** - * Slide identifier + * Slide identifier. * * @var string */ protected $identifier; /** - * Hash index + * Hash index. * - * @var string + * @var int */ protected $hashIndex; /** - * Parent presentation + * Parent presentation. * - * @var PhpPresentation + * @var PhpPresentation|null */ protected $parent; /** - * Background of the slide + * Background of the slide. * * @var AbstractBackground */ protected $background; /** - * Get collection of shapes + * Get collection of shapes. * - * @return \ArrayObject|\PhpOffice\PhpPresentation\AbstractShape[] + * @return array|ArrayObject */ public function getShapeCollection() { @@ -107,284 +110,252 @@ abstract class AbstractSlide implements ComparableInterface, ShapeContainerInter } /** - * Get collection of shapes + * Get collection of shapes. + * + * @param array|ArrayObject $shapeCollection * - * @param array $shapeCollection * @return AbstractSlide */ - public function setShapeCollection($shapeCollection = array()) + public function setShapeCollection($shapeCollection = []) { $this->shapeCollection = $shapeCollection; + return $this; } /** - * Add shape to slide + * Add shape to slide. * - * @param \PhpOffice\PhpPresentation\AbstractShape $shape - * @return \PhpOffice\PhpPresentation\AbstractShape - * @throws \Exception + * @return AbstractShape */ - public function addShape(AbstractShape $shape) + public function addShape(AbstractShape $shape): AbstractShape { $shape->setContainer($this); + return $shape; } /** - * Get X Offset - * - * @return int + * Get X Offset. */ - public function getOffsetX() + public function getOffsetX(): int { - if ($this->offsetX === null) { + if (null === $this->offsetX) { $offsets = GeometryCalculator::calculateOffsets($this); $this->offsetX = $offsets[GeometryCalculator::X]; $this->offsetY = $offsets[GeometryCalculator::Y]; } + return $this->offsetX; } /** - * Get Y Offset - * - * @return int + * Get Y Offset. */ - public function getOffsetY() + public function getOffsetY(): int { - if ($this->offsetY === null) { + if (null === $this->offsetY) { $offsets = GeometryCalculator::calculateOffsets($this); $this->offsetX = $offsets[GeometryCalculator::X]; $this->offsetY = $offsets[GeometryCalculator::Y]; } + return $this->offsetY; } /** - * Get X Extent - * - * @return int + * Get X Extent. */ - public function getExtentX() + public function getExtentX(): int { - if ($this->extentX === null) { + if (null === $this->extentX) { $extents = GeometryCalculator::calculateExtents($this); $this->extentX = $extents[GeometryCalculator::X]; $this->extentY = $extents[GeometryCalculator::Y]; } + return $this->extentX; } /** - * Get Y Extent - * - * @return int + * Get Y Extent. */ - public function getExtentY() + public function getExtentY(): int { - if ($this->extentY === null) { + if (null === $this->extentY) { $extents = GeometryCalculator::calculateExtents($this); $this->extentX = $extents[GeometryCalculator::X]; $this->extentY = $extents[GeometryCalculator::Y]; } + return $this->extentY; } /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { return md5($this->identifier . __CLASS__); } /** - * Get hash index + * Get hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @return string Hash index + * @return int|null Hash index */ - public function getHashIndex() + public function getHashIndex(): ?int { return $this->hashIndex; } /** - * Set hash index + * Set hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @param string $value Hash index + * @param int $value Hash index + * + * @return $this */ - public function setHashIndex($value) + public function setHashIndex(int $value) { $this->hashIndex = $value; + + return $this; } /** - * Create rich text shape - * - * @return \PhpOffice\PhpPresentation\Shape\RichText - * @throws \Exception + * Create rich text shape. */ - public function createRichTextShape() + public function createRichTextShape(): RichText { $shape = new RichText(); $this->addShape($shape); + return $shape; } /** - * Create line shape + * Create line shape. * - * @param int $fromX Starting point x offset - * @param int $fromY Starting point y offset - * @param int $toX Ending point x offset - * @param int $toY Ending point y offset - * @return \PhpOffice\PhpPresentation\Shape\Line - * @throws \Exception + * @param int $fromX Starting point x offset + * @param int $fromY Starting point y offset + * @param int $toX Ending point x offset + * @param int $toY Ending point y offset */ - public function createLineShape($fromX, $fromY, $toX, $toY) + public function createLineShape(int $fromX, int $fromY, int $toX, int $toY): Line { $shape = new Line($fromX, $fromY, $toX, $toY); $this->addShape($shape); + return $shape; } /** - * Create chart shape - * - * @return \PhpOffice\PhpPresentation\Shape\Chart - * @throws \Exception + * Create chart shape. */ - public function createChartShape() + public function createChartShape(): Chart { $shape = new Chart(); $this->addShape($shape); + return $shape; } /** - * Create drawing shape - * - * @return \PhpOffice\PhpPresentation\Shape\Drawing\File - * @throws \Exception + * Create drawing shape. */ - public function createDrawingShape() + public function createDrawingShape(): File { $shape = new File(); $this->addShape($shape); + return $shape; } /** - * Create table shape + * Create table shape. * - * @param int $columns Number of columns - * @return \PhpOffice\PhpPresentation\Shape\Table - * @throws \Exception + * @param int $columns Number of columns */ - public function createTableShape($columns = 1) + public function createTableShape(int $columns = 1): Table { $shape = new Table($columns); $this->addShape($shape); + return $shape; } /** - * Creates a group within this slide - * - * @return \PhpOffice\PhpPresentation\Shape\Group - * @throws \Exception + * Creates a group within this slide. */ - public function createGroup() + public function createGroup(): Group { $shape = new Group(); $this->addShape($shape); + return $shape; } /** - * Get parent - * - * @return PhpPresentation + * Get parent. */ - public function getParent() + public function getParent(): ?PhpPresentation { return $this->parent; } /** - * Re-bind parent - * - * @param \PhpOffice\PhpPresentation\PhpPresentation $parent - * @return \PhpOffice\PhpPresentation\Slide\AbstractSlide - * @throws \Exception + * Re-bind parent. */ - public function rebindParent(PhpPresentation $parent) + public function rebindParent(PhpPresentation $parent): AbstractSlide { $this->parent->removeSlideByIndex($this->parent->getIndex($this)); $this->parent = $parent; + return $this; } - /** - * @return AbstractBackground - */ - public function getBackground() + public function getBackground(): ?AbstractBackground { return $this->background; } - /** - * @param AbstractBackground $background - * @return \PhpOffice\PhpPresentation\Slide\AbstractSlide - */ - public function setBackground(AbstractBackground $background = null) + public function setBackground(AbstractBackground $background = null): AbstractSlide { $this->background = $background; + return $this; } - /** - * - * @return \PhpOffice\PhpPresentation\Slide\Transition - */ - public function getTransition() + public function getTransition(): ?Transition { return $this->slideTransition; } - /** - * - * @param \PhpOffice\PhpPresentation\Slide\Transition $transition - * @return \PhpOffice\PhpPresentation\Slide\AbstractSlide - */ - public function setTransition(Transition $transition = null) + public function setTransition(Transition $transition = null): self { $this->slideTransition = $transition; + return $this; } - /** - * @return string - */ - public function getRelsIndex() + public function getRelsIndex(): string { return $this->relsIndex; } - /** - * @param string $indexName - */ - public function setRelsIndex($indexName) + public function setRelsIndex(string $indexName): self { $this->relsIndex = $indexName; + + return $this; } } diff --git a/PhpOffice/PhpPresentation/Slide/Animation.php b/PhpOffice/PhpPresentation/Slide/Animation.php old mode 100755 new mode 100644 index 54f77da..2e6d1dc --- a/PhpOffice/PhpPresentation/Slide/Animation.php +++ b/PhpOffice/PhpPresentation/Slide/Animation.php @@ -1,4 +1,23 @@ */ - protected $shapeCollection = array(); + protected $shapeCollection = []; /** - * @param AbstractShape $shape * @return Animation */ public function addShape(AbstractShape $shape) { $this->shapeCollection[] = $shape; + return $this; } /** - * @return array + * @return array */ - public function getShapeCollection() + public function getShapeCollection(): array { return $this->shapeCollection; } /** - * @param array $array + * @param array $array + * * @return Animation */ - public function setShapeCollection(array $array = array()) + public function setShapeCollection(array $array = []) { $this->shapeCollection = $array; + return $this; } } diff --git a/PhpOffice/PhpPresentation/Slide/Background/Color.php b/PhpOffice/PhpPresentation/Slide/Background/Color.php old mode 100755 new mode 100644 index fb975f6..24507d8 --- a/PhpOffice/PhpPresentation/Slide/Background/Color.php +++ b/PhpOffice/PhpPresentation/Slide/Background/Color.php @@ -1,4 +1,22 @@ color = $color; + return $this; } - /** - * @return StyleColor - */ - public function getColor() + public function getColor(): ?StyleColor { return $this->color; } diff --git a/PhpOffice/PhpPresentation/Slide/Background/Image.php b/PhpOffice/PhpPresentation/Slide/Background/Image.php old mode 100755 new mode 100644 index ca6eb54..39c998e --- a/PhpOffice/PhpPresentation/Slide/Background/Image.php +++ b/PhpOffice/PhpPresentation/Slide/Background/Image.php @@ -1,7 +1,26 @@ path; } /** - * Set Path + * Set Path. * - * @param string $pValue File path - * @param boolean $pVerifyFile Verify file - * @throws \Exception - * @return \PhpOffice\PhpPresentation\Slide\Background\Image + * @param string $pValue File path + * @param bool $pVerifyFile Verify file + * + * @throws FileNotFoundException + * + * @return self */ - public function setPath($pValue = '', $pVerifyFile = true) + public function setPath(string $pValue = '', bool $pVerifyFile = true) { if ($pVerifyFile) { if (!file_exists($pValue)) { - throw new \Exception("File not found : $pValue"); + throw new FileNotFoundException($pValue); } - if ($this->width == 0 && $this->height == 0) { + if (0 == $this->width && 0 == $this->height) { // Get width/height list($this->width, $this->height) = getimagesize($pValue); } } $this->path = $pValue; + return $this; } /** - * Get Filename + * Get Filename. * * @return string */ - public function getFilename() + public function getFilename(): string { - return basename($this->path); + return $this->path ? basename($this->path) : ''; } /** - * Get Extension + * Get Extension. * * @return string */ - public function getExtension() + public function getExtension(): string { - $exploded = explode('.', basename($this->path)); + $exploded = explode('.', $this->getFilename()); return $exploded[count($exploded) - 1]; } /** - * Get indexed filename (using image index) + * Get indexed filename (using image index). + * + * @param string $numSlide * - * @param integer $numSlide * @return string */ public function getIndexedFilename($numSlide) diff --git a/PhpOffice/PhpPresentation/Slide/Background/SchemeColor.php b/PhpOffice/PhpPresentation/Slide/Background/SchemeColor.php old mode 100755 new mode 100644 index 51bba87..3cb785d --- a/PhpOffice/PhpPresentation/Slide/Background/SchemeColor.php +++ b/PhpOffice/PhpPresentation/Slide/Background/SchemeColor.php @@ -1,4 +1,22 @@ schemeColor = $color; + return $this; } - /** - * @return StyleSchemeColor - */ - public function getSchemeColor() + public function getSchemeColor(): ?StyleSchemeColor { return $this->schemeColor; } diff --git a/PhpOffice/PhpPresentation/Slide/Iterator.php b/PhpOffice/PhpPresentation/Slide/Iterator.php old mode 100755 new mode 100644 index 66d159d..d761be9 --- a/PhpOffice/PhpPresentation/Slide/Iterator.php +++ b/PhpOffice/PhpPresentation/Slide/Iterator.php @@ -10,49 +10,46 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Slide; +use IteratorIterator; use PhpOffice\PhpPresentation\PhpPresentation; -/** - * \PhpOffice\PhpPresentation\Slide\Iterator - * - * Used to iterate slides in PhpPresentation - */ -class Iterator extends \IteratorIterator +// @phpstan-ignore-next-line +class Iterator extends IteratorIterator { /** - * Presentation to iterate + * Presentation to iterate. * - * @var \PhpOffice\PhpPresentation\PhpPresentation + * @var PhpPresentation */ private $subject; /** - * Current iterator position + * Current iterator position. * * @var int */ private $position = 0; /** - * Create a new slide iterator - * - * @param PhpPresentation $subject + * Create a new slide iterator. */ - public function __construct(PhpPresentation $subject = null) + public function __construct(PhpPresentation $subject) { - // Set subject $this->subject = $subject; } /** - * Destructor + * Destructor. */ public function __destruct() { @@ -60,18 +57,18 @@ class Iterator extends \IteratorIterator } /** - * Rewind iterator + * Rewind iterator. */ + #[\ReturnTypeWillChange] public function rewind() { $this->position = 0; } /** - * Current \PhpOffice\PhpPresentation\Slide + * Current \PhpOffice\PhpPresentation\Slide. * * @return \PhpOffice\PhpPresentation\Slide - * @throws \Exception */ public function current() { @@ -79,7 +76,7 @@ class Iterator extends \IteratorIterator } /** - * Current key + * Current key. * * @return int */ @@ -89,8 +86,9 @@ class Iterator extends \IteratorIterator } /** - * Next value + * Next value. */ + #[\ReturnTypeWillChange] public function next() { ++$this->position; @@ -99,8 +97,9 @@ class Iterator extends \IteratorIterator /** * More \PhpOffice\PhpPresentation\Slide instances available? * - * @return boolean + * @return bool */ + #[\ReturnTypeWillChange] public function valid() { return $this->position < $this->subject->getSlideCount(); diff --git a/PhpOffice/PhpPresentation/Slide/Layout.php b/PhpOffice/PhpPresentation/Slide/Layout.php old mode 100755 new mode 100644 index b4fc027..1c1ec93 --- a/PhpOffice/PhpPresentation/Slide/Layout.php +++ b/PhpOffice/PhpPresentation/Slide/Layout.php @@ -10,28 +10,31 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Slide; /** - * \PhpOffice\PhpPresentation\Slide\Layout + * \PhpOffice\PhpPresentation\Slide\Layout. */ class Layout { /** Layout constants */ - const TITLE_SLIDE = 'Title Slide'; - const TITLE_AND_CONTENT = 'Title and Content'; - const SECTION_HEADER = 'Section Header'; - const TWO_CONTENT = 'Two Content'; - const COMPARISON = 'Comparison'; - const TITLE_ONLY = 'Title Only'; - const BLANK = 'Blank'; - const CONTENT_WITH_CAPTION = 'Content with Caption'; - const PICTURE_WITH_CAPTION = 'Picture with Caption'; - const TITLE_AND_VERTICAL_TEXT = 'Title and Vertical Text'; - const VERTICAL_TITLE_AND_TEXT = 'Vertical Title and Text'; + public const TITLE_SLIDE = 'Title Slide'; + public const TITLE_AND_CONTENT = 'Title and Content'; + public const SECTION_HEADER = 'Section Header'; + public const TWO_CONTENT = 'Two Content'; + public const COMPARISON = 'Comparison'; + public const TITLE_ONLY = 'Title Only'; + public const BLANK = 'Blank'; + public const CONTENT_WITH_CAPTION = 'Content with Caption'; + public const PICTURE_WITH_CAPTION = 'Picture with Caption'; + public const TITLE_AND_VERTICAL_TEXT = 'Title and Vertical Text'; + public const VERTICAL_TITLE_AND_TEXT = 'Vertical Title and Text'; } diff --git a/PhpOffice/PhpPresentation/Slide/Note.php b/PhpOffice/PhpPresentation/Slide/Note.php old mode 100755 new mode 100644 index f4af120..d1423a0 --- a/PhpOffice/PhpPresentation/Slide/Note.php +++ b/PhpOffice/PhpPresentation/Slide/Note.php @@ -10,83 +10,84 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Slide; +use ArrayObject; use PhpOffice\PhpPresentation\AbstractShape; use PhpOffice\PhpPresentation\ComparableInterface; use PhpOffice\PhpPresentation\GeometryCalculator; +use PhpOffice\PhpPresentation\Shape\RichText; use PhpOffice\PhpPresentation\ShapeContainerInterface; use PhpOffice\PhpPresentation\Slide; -use PhpOffice\PhpPresentation\Shape\RichText; -/** - * Note class - */ class Note implements ComparableInterface, ShapeContainerInterface { /** - * Parent slide + * Parent slide. * * @var Slide */ private $parent; /** - * Collection of shapes + * Collection of shapes. * - * @var \ArrayObject|\PhpOffice\PhpPresentation\AbstractShape[] + * @var array|ArrayObject */ - private $shapeCollection = null; + private $shapeCollection; /** - * Note identifier + * Note identifier. * * @var string */ private $identifier; /** - * Hash index + * Hash index. * - * @var string + * @var int */ private $hashIndex; /** - * Offset X + * Offset X. * * @var int */ protected $offsetX; /** - * Offset Y + * Offset Y. * * @var int */ protected $offsetY; /** - * Extent X + * Extent X. * * @var int */ protected $extentX; /** - * Extent Y + * Extent Y. * * @var int */ protected $extentY; /** - * Create a new note + * Create a new note. * * @param Slide $pParent */ @@ -96,16 +97,16 @@ class Note implements ComparableInterface, ShapeContainerInterface $this->parent = $pParent; // Shape collection - $this->shapeCollection = new \ArrayObject(); + $this->shapeCollection = new ArrayObject(); // Set identifier $this->identifier = md5(rand(0, 9999) . time()); } /** - * Get collection of shapes + * Get collection of shapes. * - * @return \ArrayObject|\PhpOffice\PhpPresentation\AbstractShape[] + * @return array|ArrayObject */ public function getShapeCollection() { @@ -113,13 +114,11 @@ class Note implements ComparableInterface, ShapeContainerInterface } /** - * Add shape to slide + * Add shape to slide. * - * @param \PhpOffice\PhpPresentation\AbstractShape $shape - * @return \PhpOffice\PhpPresentation\AbstractShape - * @throws \Exception + * @return AbstractShape */ - public function addShape(AbstractShape $shape) + public function addShape(AbstractShape $shape): AbstractShape { $shape->setContainer($this); @@ -127,12 +126,9 @@ class Note implements ComparableInterface, ShapeContainerInterface } /** - * Create rich text shape - * - * @return \PhpOffice\PhpPresentation\Shape\RichText - * @throws \Exception + * Create rich text shape. */ - public function createRichTextShape() + public function createRichTextShape(): RichText { $shape = new RichText(); $this->addShape($shape); @@ -141,7 +137,7 @@ class Note implements ComparableInterface, ShapeContainerInterface } /** - * Get parent + * Get parent. * * @return Slide */ @@ -151,111 +147,110 @@ class Note implements ComparableInterface, ShapeContainerInterface } /** - * Set parent + * Set parent. * - * @param Slide $parent * @return Note */ public function setParent(Slide $parent) { $this->parent = $parent; + return $this; } - /** - * Get X Offset - * - * @return int + * Get X Offset. */ - public function getOffsetX() + public function getOffsetX(): int { - if ($this->offsetX === null) { + if (null === $this->offsetX) { $offsets = GeometryCalculator::calculateOffsets($this); $this->offsetX = $offsets[GeometryCalculator::X]; $this->offsetY = $offsets[GeometryCalculator::Y]; } + return $this->offsetX; } - + /** - * Get Y Offset - * - * @return int + * Get Y Offset. */ - public function getOffsetY() + public function getOffsetY(): int { - if ($this->offsetY === null) { + if (null === $this->offsetY) { $offsets = GeometryCalculator::calculateOffsets($this); $this->offsetX = $offsets[GeometryCalculator::X]; $this->offsetY = $offsets[GeometryCalculator::Y]; } + return $this->offsetY; } - + /** - * Get X Extent - * - * @return int + * Get X Extent. */ - public function getExtentX() + public function getExtentX(): int { - if ($this->extentX === null) { + if (null === $this->extentX) { $extents = GeometryCalculator::calculateExtents($this); $this->extentX = $extents[GeometryCalculator::X]; $this->extentY = $extents[GeometryCalculator::Y]; } + return $this->extentX; } - + /** - * Get Y Extent - * - * @return int + * Get Y Extent. */ - public function getExtentY() + public function getExtentY(): int { - if ($this->extentY === null) { + if (null === $this->extentY) { $extents = GeometryCalculator::calculateExtents($this); $this->extentX = $extents[GeometryCalculator::X]; $this->extentY = $extents[GeometryCalculator::Y]; } + return $this->extentY; } /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { return md5($this->identifier . __CLASS__); } /** - * Get hash index + * Get hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @return string Hash index + * @return int|null Hash index */ - public function getHashIndex() + public function getHashIndex(): ?int { return $this->hashIndex; } /** - * Set hash index + * Set hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @param string $value Hash index + * @param int $value Hash index + * + * @return $this */ - public function setHashIndex($value) + public function setHashIndex(int $value) { $this->hashIndex = $value; + + return $this; } } diff --git a/PhpOffice/PhpPresentation/Slide/SlideLayout.php b/PhpOffice/PhpPresentation/Slide/SlideLayout.php old mode 100755 new mode 100644 index 72f42b8..9c30db2 --- a/PhpOffice/PhpPresentation/Slide/SlideLayout.php +++ b/PhpOffice/PhpPresentation/Slide/SlideLayout.php @@ -10,10 +10,14 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ + +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Slide; use PhpOffice\PhpPresentation\ComparableInterface; @@ -22,42 +26,43 @@ use PhpOffice\PhpPresentation\Style\ColorMap; class SlideLayout extends AbstractSlide implements ComparableInterface, ShapeContainerInterface { + /** + * @var SlideMaster + */ protected $slideMaster; /** - * Slide relation ID (should not be used by user code!) + * Slide relation ID (should not be used by user code!). * * @var string */ public $relationId; /** - * Slide layout NR (should not be used by user code!) + * Slide layout NR (should not be used by user code!). * * @var int */ public $layoutNr; /** - * Slide layout ID (should not be used by user code!) + * Slide layout ID (should not be used by user code!). * * @var int */ public $layoutId; /** - * Slide layout ID (should not be used by user code!) + * Slide layout ID (should not be used by user code!). * - * @var int + * @var string|null */ protected $layoutName; /** - * Mapping of colors to the theme + * Mapping of colors to the theme. * * @var \PhpOffice\PhpPresentation\Style\ColorMap */ public $colorMap; /** - * Create a new slideLayout - * - * @param SlideMaster $pSlideMaster + * Create a new slideLayout. */ public function __construct(SlideMaster $pSlideMaster) { @@ -71,28 +76,19 @@ class SlideLayout extends AbstractSlide implements ComparableInterface, ShapeCon $this->colorMap = new ColorMap(); } - /** - * @return int - */ - public function getLayoutName() + public function getLayoutName(): ?string { return $this->layoutName; } - /** - * @param int $layoutName - * @return SlideLayout - */ - public function setLayoutName($layoutName) + public function setLayoutName(string $layoutName): self { $this->layoutName = $layoutName; + return $this; } - /** - * @return SlideMaster - */ - public function getSlideMaster() + public function getSlideMaster(): SlideMaster { return $this->slideMaster; } diff --git a/PhpOffice/PhpPresentation/Slide/SlideMaster.php b/PhpOffice/PhpPresentation/Slide/SlideMaster.php old mode 100755 new mode 100644 index f06f740..3966f00 --- a/PhpOffice/PhpPresentation/Slide/SlideMaster.php +++ b/PhpOffice/PhpPresentation/Slide/SlideMaster.php @@ -10,10 +10,14 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ + +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Slide; use PhpOffice\PhpPresentation\ComparableInterface; @@ -25,35 +29,32 @@ use PhpOffice\PhpPresentation\Style\ColorMap; use PhpOffice\PhpPresentation\Style\SchemeColor; use PhpOffice\PhpPresentation\Style\TextStyle; -/** - * Class SlideMaster - */ class SlideMaster extends AbstractSlide implements ComparableInterface, ShapeContainerInterface { /** - * Collection of Slide objects + * Collection of Slide objects. * - * @var \PhpOffice\PhpPresentation\Slide\SlideLayout[] + * @var array */ - protected $slideLayouts = array(); + protected $slideLayouts = []; /** - * Mapping of colors to the theme + * Mapping of colors to the theme. * - * @var \PhpOffice\PhpPresentation\Style\ColorMap + * @var ColorMap */ public $colorMap; /** - * @var \PhpOffice\PhpPresentation\Style\TextStyle + * @var TextStyle */ protected $textStyles; /** - * @var \PhpOffice\PhpPresentation\Style\SchemeColor[] + * @var array */ - protected $arraySchemeColor = array(); + protected $arraySchemeColor = []; /** - * @var array + * @var array */ - protected $defaultSchemeColor = array( + protected $defaultSchemeColor = [ 'dk1' => '000000', 'lt1' => 'FFFFFF', 'dk2' => '1F497D', @@ -66,13 +67,10 @@ class SlideMaster extends AbstractSlide implements ComparableInterface, ShapeCon 'accent6' => 'F79646', 'hlink' => '0000FF', 'folHlink' => '800080', - ); + ]; /** - * Create a new slideMaster - * - * @param PhpPresentation $pParent - * @throws \Exception + * Create a new slideMaster. */ public function __construct(PhpPresentation $pParent = null) { @@ -99,35 +97,36 @@ class SlideMaster extends AbstractSlide implements ComparableInterface, ShapeCon } /** - * Create a slideLayout and add it to this presentation + * Create a slideLayout and add it to this presentation. * - * @return \PhpOffice\PhpPresentation\Slide\SlideLayout - * @throws \Exception + * @return SlideLayout */ - public function createSlideLayout() + public function createSlideLayout(): SlideLayout { $newSlideLayout = new SlideLayout($this); $this->addSlideLayout($newSlideLayout); + return $newSlideLayout; } /** - * Add slideLayout + * Add slideLayout. * - * @param \PhpOffice\PhpPresentation\Slide\SlideLayout $slideLayout - * @throws \Exception - * @return \PhpOffice\PhpPresentation\Slide\SlideLayout + * @param SlideLayout|null $slideLayout + * + * @return SlideLayout */ - public function addSlideLayout(SlideLayout $slideLayout = null) + public function addSlideLayout(SlideLayout $slideLayout = null): SlideLayout { $this->slideLayouts[] = $slideLayout; + return $slideLayout; } /** - * @return SlideLayout[] + * @return array */ - public function getAllSlideLayouts() + public function getAllSlideLayouts(): array { return $this->slideLayouts; } @@ -135,35 +134,35 @@ class SlideMaster extends AbstractSlide implements ComparableInterface, ShapeCon /** * @return TextStyle */ - public function getTextStyles() + public function getTextStyles(): TextStyle { return $this->textStyles; } /** - * @param TextStyle $textStyle - * @return $this + * @return self */ - public function setTextStyles(TextStyle $textStyle) + public function setTextStyles(TextStyle $textStyle): self { $this->textStyles = $textStyle; + return $this; } /** - * @param SchemeColor $schemeColor - * @return $this + * @return self */ - public function addSchemeColor(SchemeColor $schemeColor) + public function addSchemeColor(SchemeColor $schemeColor): self { $this->arraySchemeColor[$schemeColor->getValue()] = $schemeColor; + return $this; } /** - * @return \PhpOffice\PhpPresentation\Style\SchemeColor[] + * @return array */ - public function getAllSchemeColors() + public function getAllSchemeColors(): array { return $this->arraySchemeColor; } diff --git a/PhpOffice/PhpPresentation/Slide/Transition.php b/PhpOffice/PhpPresentation/Slide/Transition.php old mode 100755 new mode 100644 index b2e6a29..9836f7c --- a/PhpOffice/PhpPresentation/Slide/Transition.php +++ b/PhpOffice/PhpPresentation/Slide/Transition.php @@ -10,72 +10,72 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Slide; -use PhpOffice\PhpPresentation\Slide; -use PhpOffice\PhpPresentation\Shape\RichText; - /** - * Transition class + * Transition class. */ class Transition { - const SPEED_FAST = 'fast'; - const SPEED_MEDIUM = 'med'; - const SPEED_SLOW = 'slow'; + public const SPEED_FAST = 'fast'; + public const SPEED_MEDIUM = 'med'; + public const SPEED_SLOW = 'slow'; - const TRANSITION_BLINDS_HORIZONTAL = 'blinds_horz'; - const TRANSITION_BLINDS_VERTICAL = 'blinds_vert'; - const TRANSITION_CHECKER_HORIZONTAL = 'checker_horz'; - const TRANSITION_CHECKER_VERTICAL = 'checker_vert'; - const TRANSITION_CIRCLE = 'circle'; - const TRANSITION_COMB_HORIZONTAL = 'comb_horz'; - const TRANSITION_COMB_VERTICAL = 'comb_vert'; - const TRANSITION_COVER_DOWN = 'cover_d'; - const TRANSITION_COVER_LEFT = 'cover_l'; - const TRANSITION_COVER_LEFT_DOWN = 'cover_ld'; - const TRANSITION_COVER_LEFT_UP = 'cover_lu'; - const TRANSITION_COVER_RIGHT = 'cover_r'; - const TRANSITION_COVER_RIGHT_DOWN = 'cover_rd'; - const TRANSITION_COVER_RIGHT_UP = 'cover_ru'; - const TRANSITION_COVER_UP = 'cover_u'; - const TRANSITION_CUT = 'cut'; - const TRANSITION_DIAMOND = 'diamond'; - const TRANSITION_DISSOLVE = 'dissolve'; - const TRANSITION_FADE = 'fade'; - const TRANSITION_NEWSFLASH = 'newsflash'; - const TRANSITION_PLUS = 'plus'; - const TRANSITION_PULL_DOWN = 'pull_d'; - const TRANSITION_PULL_LEFT = 'pull_l'; - const TRANSITION_PULL_RIGHT = 'pull_r'; - const TRANSITION_PULL_UP = 'pull_u'; - const TRANSITION_PUSH_DOWN = 'push_d'; - const TRANSITION_PUSH_LEFT = 'push_l'; - const TRANSITION_PUSH_RIGHT = 'push_r'; - const TRANSITION_PUSH_UP = 'push_u'; - const TRANSITION_RANDOM = 'random'; - const TRANSITION_RANDOMBAR_HORIZONTAL = 'randomBar_horz'; - const TRANSITION_RANDOMBAR_VERTICAL = 'randomBar_vert'; - const TRANSITION_SPLIT_IN_HORIZONTAL = 'split_in_horz'; - const TRANSITION_SPLIT_OUT_HORIZONTAL = 'split_out_horz'; - const TRANSITION_SPLIT_IN_VERTICAL = 'split_in_vert'; - const TRANSITION_SPLIT_OUT_VERTICAL = 'split_out_vert'; - const TRANSITION_STRIPS_LEFT_DOWN = 'strips_ld'; - const TRANSITION_STRIPS_LEFT_UP = 'strips_lu'; - const TRANSITION_STRIPS_RIGHT_DOWN = 'strips_rd'; - const TRANSITION_STRIPS_RIGHT_UP = 'strips_ru'; - const TRANSITION_WEDGE = 'wedge'; - const TRANSITION_WIPE_DOWN = 'wipe_d'; - const TRANSITION_WIPE_LEFT = 'wipe_l'; - const TRANSITION_WIPE_RIGHT = 'wipe_r'; - const TRANSITION_WIPE_UP = 'wipe_u'; - const TRANSITION_ZOOM_IN = 'zoom_in'; - const TRANSITION_ZOOM_OUT = 'zoom_out'; + public const TRANSITION_BLINDS_HORIZONTAL = 'blinds_horz'; + public const TRANSITION_BLINDS_VERTICAL = 'blinds_vert'; + public const TRANSITION_CHECKER_HORIZONTAL = 'checker_horz'; + public const TRANSITION_CHECKER_VERTICAL = 'checker_vert'; + public const TRANSITION_CIRCLE = 'circle'; + public const TRANSITION_COMB_HORIZONTAL = 'comb_horz'; + public const TRANSITION_COMB_VERTICAL = 'comb_vert'; + public const TRANSITION_COVER_DOWN = 'cover_d'; + public const TRANSITION_COVER_LEFT = 'cover_l'; + public const TRANSITION_COVER_LEFT_DOWN = 'cover_ld'; + public const TRANSITION_COVER_LEFT_UP = 'cover_lu'; + public const TRANSITION_COVER_RIGHT = 'cover_r'; + public const TRANSITION_COVER_RIGHT_DOWN = 'cover_rd'; + public const TRANSITION_COVER_RIGHT_UP = 'cover_ru'; + public const TRANSITION_COVER_UP = 'cover_u'; + public const TRANSITION_CUT = 'cut'; + public const TRANSITION_DIAMOND = 'diamond'; + public const TRANSITION_DISSOLVE = 'dissolve'; + public const TRANSITION_FADE = 'fade'; + public const TRANSITION_NEWSFLASH = 'newsflash'; + public const TRANSITION_PLUS = 'plus'; + public const TRANSITION_PULL_DOWN = 'pull_d'; + public const TRANSITION_PULL_LEFT = 'pull_l'; + public const TRANSITION_PULL_RIGHT = 'pull_r'; + public const TRANSITION_PULL_UP = 'pull_u'; + public const TRANSITION_PUSH_DOWN = 'push_d'; + public const TRANSITION_PUSH_LEFT = 'push_l'; + public const TRANSITION_PUSH_RIGHT = 'push_r'; + public const TRANSITION_PUSH_UP = 'push_u'; + public const TRANSITION_RANDOM = 'random'; + public const TRANSITION_RANDOMBAR_HORIZONTAL = 'randomBar_horz'; + public const TRANSITION_RANDOMBAR_VERTICAL = 'randomBar_vert'; + public const TRANSITION_SPLIT_IN_HORIZONTAL = 'split_in_horz'; + public const TRANSITION_SPLIT_OUT_HORIZONTAL = 'split_out_horz'; + public const TRANSITION_SPLIT_IN_VERTICAL = 'split_in_vert'; + public const TRANSITION_SPLIT_OUT_VERTICAL = 'split_out_vert'; + public const TRANSITION_STRIPS_LEFT_DOWN = 'strips_ld'; + public const TRANSITION_STRIPS_LEFT_UP = 'strips_lu'; + public const TRANSITION_STRIPS_RIGHT_DOWN = 'strips_rd'; + public const TRANSITION_STRIPS_RIGHT_UP = 'strips_ru'; + public const TRANSITION_WEDGE = 'wedge'; + public const TRANSITION_WIPE_DOWN = 'wipe_d'; + public const TRANSITION_WIPE_LEFT = 'wipe_l'; + public const TRANSITION_WIPE_RIGHT = 'wipe_r'; + public const TRANSITION_WIPE_UP = 'wipe_u'; + public const TRANSITION_ZOOM_IN = 'zoom_in'; + public const TRANSITION_ZOOM_OUT = 'zoom_out'; /** * @var bool @@ -86,25 +86,21 @@ class Transition */ protected $hasTimeTrigger = false; /** - * @var int + * @var int|null */ protected $advanceTimeTrigger = null; /** - * @var null|self::SPEED_SLOW|self::SPEED_MEDIUM|self::SPEED_FAST + * @var string|null */ protected $speed = null; /** - * @var null|self::TRANSITION_* + * @var string|null */ protected $transitionType = null; - /** - * @var array - */ - protected $transitionOptions = array(); - public function setSpeed($speed = self::SPEED_MEDIUM) + public function setSpeed(?string $speed = self::SPEED_MEDIUM): self { - if (in_array($speed, array(self::SPEED_FAST, self::SPEED_MEDIUM, self::SPEED_SLOW))) { + if (in_array($speed, [self::SPEED_FAST, self::SPEED_MEDIUM, self::SPEED_SLOW])) { $this->speed = $speed; } else { $this->speed = null; @@ -113,53 +109,49 @@ class Transition return $this; } - public function getSpeed() + public function getSpeed(): ?string { return $this->speed; } - public function setManualTrigger($value = false) + public function setManualTrigger(bool $value = false): self { - if (is_bool($value)) { - $this->hasManualTrigger = $value; - } + $this->hasManualTrigger = $value; + return $this; } - public function hasManualTrigger() + public function hasManualTrigger(): bool { return $this->hasManualTrigger; } - public function setTimeTrigger($value = false, $advanceTime = 1000) + public function setTimeTrigger(bool $value = false, int $advanceTime = 1000): self { - if (is_bool($value)) { - $this->hasTimeTrigger = $value; - } - $this->advanceTimeTrigger = null; - if ($this->hasTimeTrigger === true) { - $this->advanceTimeTrigger = (int) $advanceTime; - } + $this->hasTimeTrigger = $value; + $this->advanceTimeTrigger = true === $value ? $advanceTime : null; + return $this; } - public function hasTimeTrigger() + public function hasTimeTrigger(): bool { return $this->hasTimeTrigger; } - public function getAdvanceTimeTrigger() + public function getAdvanceTimeTrigger(): ?int { return $this->advanceTimeTrigger; } - public function setTransitionType($type = null) + public function setTransitionType(string $type = null): self { $this->transitionType = $type; + return $this; } - public function getTransitionType() + public function getTransitionType(): ?string { return $this->transitionType; } diff --git a/PhpOffice/PhpPresentation/Style/Alignment.php b/PhpOffice/PhpPresentation/Style/Alignment.php old mode 100755 new mode 100644 index 89cde6a..fae963e --- a/PhpOffice/PhpPresentation/Style/Alignment.php +++ b/PhpOffice/PhpPresentation/Style/Alignment.php @@ -10,136 +10,142 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Style; use PhpOffice\PhpPresentation\ComparableInterface; +use PhpOffice\PhpPresentation\Exception\OutOfBoundsException; -/** - * \PhpOffice\PhpPresentation\Style\Alignment - */ class Alignment implements ComparableInterface { /* Horizontal alignment */ - const HORIZONTAL_GENERAL = 'l'; - const HORIZONTAL_LEFT = 'l'; - const HORIZONTAL_RIGHT = 'r'; - const HORIZONTAL_CENTER = 'ctr'; - const HORIZONTAL_JUSTIFY = 'just'; - const HORIZONTAL_DISTRIBUTED = 'dist'; + public const HORIZONTAL_GENERAL = 'l'; + public const HORIZONTAL_LEFT = 'l'; + public const HORIZONTAL_RIGHT = 'r'; + public const HORIZONTAL_CENTER = 'ctr'; + public const HORIZONTAL_JUSTIFY = 'just'; + public const HORIZONTAL_DISTRIBUTED = 'dist'; /* Vertical alignment */ - const VERTICAL_BASE = 'base'; - const VERTICAL_AUTO = 'auto'; - const VERTICAL_BOTTOM = 'b'; - const VERTICAL_TOP = 't'; - const VERTICAL_CENTER = 'ctr'; + public const VERTICAL_BASE = 'base'; + public const VERTICAL_AUTO = 'auto'; + public const VERTICAL_BOTTOM = 'b'; + public const VERTICAL_TOP = 't'; + public const VERTICAL_CENTER = 'ctr'; /* Text direction */ - const TEXT_DIRECTION_HORIZONTAL = 'horz'; - const TEXT_DIRECTION_VERTICAL_90 = 'vert'; - const TEXT_DIRECTION_VERTICAL_270 = 'vert270'; - const TEXT_DIRECTION_STACKED = 'wordArtVert'; + public const TEXT_DIRECTION_HORIZONTAL = 'horz'; + public const TEXT_DIRECTION_VERTICAL_90 = 'vert'; + public const TEXT_DIRECTION_VERTICAL_270 = 'vert270'; + public const TEXT_DIRECTION_STACKED = 'wordArtVert'; - private $supportedStyles = array( + /** + * @var array + */ + private $supportedStyles = [ self::HORIZONTAL_GENERAL, self::HORIZONTAL_LEFT, self::HORIZONTAL_RIGHT, - ); + ]; /** - * Horizontal + * Horizontal. + * * @var string */ - private $horizontal; + private $horizontal = self::HORIZONTAL_LEFT; /** - * Vertical + * Vertical. + * * @var string */ - private $vertical; + private $vertical = self::VERTICAL_BASE; /** - * Text Direction + * Text Direction. + * * @var string */ private $textDirection = self::TEXT_DIRECTION_HORIZONTAL; /** - * Level + * Level. + * * @var int */ private $level = 0; /** - * Indent - only possible with horizontal alignment left and right - * @var int + * Indent - only possible with horizontal alignment left and right. + * + * @var float */ private $indent = 0; /** - * Margin left - only possible with horizontal alignment left and right - * @var int + * Margin left - only possible with horizontal alignment left and right. + * + * @var float */ private $marginLeft = 0; /** - * Margin right - only possible with horizontal alignment left and right - * @var int + * Margin right - only possible with horizontal alignment left and right. + * + * @var float */ private $marginRight = 0; /** - * Margin top - * @var int + * Margin top. + * + * @var float */ private $marginTop = 0; /** - * Margin bottom - * @var int + * Margin bottom. + * + * @var float */ private $marginBottom = 0; /** - * Hash index - * @var string + * RTL Direction Support + * + * @var bool + */ + private $isRTL = false; + + /** + * Hash index. + * + * @var int */ private $hashIndex; /** - * Create a new \PhpOffice\PhpPresentation\Style\Alignment + * Get Horizontal. */ - public function __construct() - { - // Initialise values - $this->horizontal = self::HORIZONTAL_LEFT; - $this->vertical = self::VERTICAL_BASE; - } - - /** - * Get Horizontal - * - * @return string - */ - public function getHorizontal() + public function getHorizontal(): string { return $this->horizontal; } /** - * Set Horizontal - * - * @param string $pValue - * @return \PhpOffice\PhpPresentation\Style\Alignment + * Set Horizontal. */ - public function setHorizontal($pValue = self::HORIZONTAL_LEFT) + public function setHorizontal(string $pValue = self::HORIZONTAL_LEFT): self { - if ($pValue == '') { + if ('' == $pValue) { $pValue = self::HORIZONTAL_LEFT; } $this->horizontal = $pValue; @@ -148,24 +154,19 @@ class Alignment implements ComparableInterface } /** - * Get Vertical - * - * @return string + * Get Vertical. */ - public function getVertical() + public function getVertical(): string { return $this->vertical; } /** - * Set Vertical - * - * @param string $pValue - * @return \PhpOffice\PhpPresentation\Style\Alignment + * Set Vertical. */ - public function setVertical($pValue = self::VERTICAL_BASE) + public function setVertical(string $pValue = self::VERTICAL_BASE): self { - if ($pValue == '') { + if ('' == $pValue) { $pValue = self::VERTICAL_BASE; } $this->vertical = $pValue; @@ -174,26 +175,24 @@ class Alignment implements ComparableInterface } /** - * Get Level - * - * @return int + * Get Level. */ - public function getLevel() + public function getLevel(): int { return $this->level; } /** - * Set Level + * Set Level. * - * @param int $pValue Ranging 0 - 8 - * @throws \Exception - * @return \PhpOffice\PhpPresentation\Style\Alignment + * @param int $pValue Ranging 0 - 8 + * + * @throws OutOfBoundsException */ - public function setLevel($pValue = 0) + public function setLevel(int $pValue = 0): self { if ($pValue < 0) { - throw new \Exception("Invalid value should be more than 0."); + throw new OutOfBoundsException(0, null, $pValue); } $this->level = $pValue; @@ -201,22 +200,17 @@ class Alignment implements ComparableInterface } /** - * Get indent - * - * @return int + * Get indent. */ - public function getIndent() + public function getIndent(): float { return $this->indent; } /** - * Set indent - * - * @param int $pValue - * @return \PhpOffice\PhpPresentation\Style\Alignment + * Set indent. */ - public function setIndent($pValue = 0) + public function setIndent(float $pValue = 0): self { if ($pValue > 0 && !in_array($this->getHorizontal(), $this->supportedStyles)) { $pValue = 0; // indent not supported @@ -228,22 +222,17 @@ class Alignment implements ComparableInterface } /** - * Get margin left - * - * @return int + * Get margin left. */ - public function getMarginLeft() + public function getMarginLeft(): float { return $this->marginLeft; } /** - * Set margin left - * - * @param int $pValue - * @return \PhpOffice\PhpPresentation\Style\Alignment + * Set margin left. */ - public function setMarginLeft($pValue = 0) + public function setMarginLeft(float $pValue = 0): self { if ($pValue > 0 && !in_array($this->getHorizontal(), $this->supportedStyles)) { $pValue = 0; // margin left not supported @@ -255,22 +244,17 @@ class Alignment implements ComparableInterface } /** - * Get margin right - * - * @return int + * Get margin right. */ - public function getMarginRight() + public function getMarginRight(): float { return $this->marginRight; } /** - * Set margin ight - * - * @param int $pValue - * @return \PhpOffice\PhpPresentation\Style\Alignment + * Set margin ight. */ - public function setMarginRight($pValue = 0) + public function setMarginRight(float $pValue = 0): self { if ($pValue > 0 && !in_array($this->getHorizontal(), $this->supportedStyles)) { $pValue = 0; // margin right not supported @@ -282,22 +266,17 @@ class Alignment implements ComparableInterface } /** - * Get margin top - * - * @return int + * Get margin top. */ - public function getMarginTop() + public function getMarginTop(): float { return $this->marginTop; } /** - * Set margin top - * - * @param int $pValue - * @return \PhpOffice\PhpPresentation\Style\Alignment + * Set margin top. */ - public function setMarginTop($pValue = 0) + public function setMarginTop(float $pValue = 0): self { $this->marginTop = $pValue; @@ -305,55 +284,64 @@ class Alignment implements ComparableInterface } /** - * Get margin bottom - * - * @return int + * Get margin bottom. */ - public function getMarginBottom() + public function getMarginBottom(): float { return $this->marginBottom; } /** - * Set margin bottom - * - * @param int $pValue - * @return \PhpOffice\PhpPresentation\Style\Alignment + * Set margin bottom. */ - public function setMarginBottom($pValue = 0) + public function setMarginBottom(float $pValue = 0): self { $this->marginBottom = $pValue; return $this; } - /** - * @return string - */ - public function getTextDirection() + public function getTextDirection(): string { return $this->textDirection; } - /** - * @param string $pValue - * @return Alignment - */ - public function setTextDirection($pValue = self::TEXT_DIRECTION_HORIZONTAL) + public function setTextDirection(string $pValue = self::TEXT_DIRECTION_HORIZONTAL): self { if (empty($pValue)) { $pValue = self::TEXT_DIRECTION_HORIZONTAL; } $this->textDirection = $pValue; + return $this; } /** - * Get hash code + * @return bool + */ + public function isRTL(): bool + { + return $this->isRTL; + } + + /** + * @param bool $value + * + * @return self + */ + public function setIsRTL(bool $value = false): self + { + $this->isRTL = $value; + + return $this; + } + + /** + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { return md5( $this->horizontal @@ -362,33 +350,38 @@ class Alignment implements ComparableInterface . $this->indent . $this->marginLeft . $this->marginRight + . ($this->isRTL ? '1' : '0') . __CLASS__ ); } /** - * Get hash index + * Get hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @return string Hash index + * @return int|null Hash index */ - public function getHashIndex() + public function getHashIndex(): ?int { return $this->hashIndex; } /** - * Set hash index + * Set hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @param string $value Hash index + * @param int $value Hash index + * + * @return $this */ - public function setHashIndex($value) + public function setHashIndex(int $value) { $this->hashIndex = $value; + + return $this; } } diff --git a/PhpOffice/PhpPresentation/Style/Border.php b/PhpOffice/PhpPresentation/Style/Border.php old mode 100755 new mode 100644 index f8dddf8..9842432 --- a/PhpOffice/PhpPresentation/Style/Border.php +++ b/PhpOffice/PhpPresentation/Style/Border.php @@ -10,130 +10,125 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Style; use PhpOffice\PhpPresentation\ComparableInterface; -/** - * \PhpOffice\PhpPresentation\Style\Border - */ class Border implements ComparableInterface { /* Line style */ - const LINE_NONE = 'none'; - const LINE_SINGLE = 'sng'; - const LINE_DOUBLE = 'dbl'; - const LINE_THICKTHIN = 'thickThin'; - const LINE_THINTHICK = 'thinThick'; - const LINE_TRI = 'tri'; + public const LINE_NONE = 'none'; + public const LINE_SINGLE = 'sng'; + public const LINE_DOUBLE = 'dbl'; + public const LINE_THICKTHIN = 'thickThin'; + public const LINE_THINTHICK = 'thinThick'; + public const LINE_TRI = 'tri'; /* Dash style */ - const DASH_DASH = 'dash'; - const DASH_DASHDOT = 'dashDot'; - const DASH_DOT = 'dot'; - const DASH_LARGEDASH = 'lgDash'; - const DASH_LARGEDASHDOT = 'lgDashDot'; - const DASH_LARGEDASHDOTDOT = 'lgDashDotDot'; - const DASH_SOLID = 'solid'; - const DASH_SYSDASH = 'sysDash'; - const DASH_SYSDASHDOT = 'sysDashDot'; - const DASH_SYSDASHDOTDOT = 'sysDashDotDot'; - const DASH_SYSDOT = 'sysDot'; + public const DASH_DASH = 'dash'; + public const DASH_DASHDOT = 'dashDot'; + public const DASH_DOT = 'dot'; + public const DASH_LARGEDASH = 'lgDash'; + public const DASH_LARGEDASHDOT = 'lgDashDot'; + public const DASH_LARGEDASHDOTDOT = 'lgDashDotDot'; + public const DASH_SOLID = 'solid'; + public const DASH_SYSDASH = 'sysDash'; + public const DASH_SYSDASHDOT = 'sysDashDot'; + public const DASH_SYSDASHDOTDOT = 'sysDashDotDot'; + public const DASH_SYSDOT = 'sysDot'; /** - * Line width + * Line width. * * @var int */ private $lineWidth = 1; /** - * Line style + * Line style. * * @var string */ - private $lineStyle; + private $lineStyle = self::LINE_SINGLE; /** - * Dash style + * Dash style. * * @var string */ - private $dashStyle; + private $dashStyle = self::DASH_SOLID; /** - * Border color + * Border color. * - * @var \PhpOffice\PhpPresentation\Style\Color + * @var Color */ private $color; /** - * Hash index + * Hash index. * - * @var string + * @var int */ private $hashIndex; - /** - * Create a new \PhpOffice\PhpPresentation\Style\Border - */ public function __construct() { - // Initialise values - $this->lineWidth = 1; - $this->lineStyle = self::LINE_SINGLE; - $this->dashStyle = self::DASH_SOLID; - $this->color = new Color(Color::COLOR_BLACK); + $this->color = new Color(Color::COLOR_BLACK); } /** - * Get line width (in points) + * Get line width (in points). * * @return int */ - public function getLineWidth() + public function getLineWidth(): int { return $this->lineWidth; } /** - * Set line width (in points) + * Set line width (in points). * - * @param int $pValue - * @return \PhpOffice\PhpPresentation\Style\Border + * @param int $pValue + * + * @return self */ - public function setLineWidth($pValue = 1) + public function setLineWidth($pValue = 1): self { - $this->lineWidth = $pValue; + $this->lineWidth = (int) $pValue; return $this; } /** - * Get line style + * Get line style. * * @return string */ - public function getLineStyle() + public function getLineStyle(): string { return $this->lineStyle; } /** - * Set line style + * Set line style. * - * @param string $pValue - * @return \PhpOffice\PhpPresentation\Style\Border + * @param string $pValue + * + * @return self */ - public function setLineStyle($pValue = self::LINE_SINGLE) + public function setLineStyle(string $pValue = self::LINE_SINGLE): self { - if ($pValue == '') { + if ('' == $pValue) { $pValue = self::LINE_SINGLE; } $this->lineStyle = $pValue; @@ -142,24 +137,25 @@ class Border implements ComparableInterface } /** - * Get dash style + * Get dash style. * * @return string */ - public function getDashStyle() + public function getDashStyle(): string { return $this->dashStyle; } /** - * Set dash style + * Set dash style. * - * @param string $pValue - * @return \PhpOffice\PhpPresentation\Style\Border + * @param string $pValue + * + * @return self */ - public function setDashStyle($pValue = self::DASH_SOLID) + public function setDashStyle(string $pValue = self::DASH_SOLID): self { - if ($pValue == '') { + if ('' == $pValue) { $pValue = self::DASH_SOLID; } $this->dashStyle = $pValue; @@ -168,23 +164,23 @@ class Border implements ComparableInterface } /** - * Get Border Color + * Get Border Color. * - * @return \PhpOffice\PhpPresentation\Style\Color + * @return Color */ - public function getColor() + public function getColor(): ?Color { return $this->color; } /** - * Set Border Color + * Set Border Color. * - * @param \PhpOffice\PhpPresentation\Style\Color $color - * @throws \Exception - * @return \PhpOffice\PhpPresentation\Style\Border + * @param Color|null $color + * + * @return self */ - public function setColor(Color $color = null) + public function setColor(Color $color = null): self { $this->color = $color; @@ -192,11 +188,11 @@ class Border implements ComparableInterface } /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { return md5( $this->lineStyle @@ -208,28 +204,32 @@ class Border implements ComparableInterface } /** - * Get hash index + * Get hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @return string Hash index + * @return int|null Hash index */ - public function getHashIndex() + public function getHashIndex(): ?int { return $this->hashIndex; } /** - * Set hash index + * Set hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @param string $value Hash index + * @param int $value Hash index + * + * @return $this */ - public function setHashIndex($value) + public function setHashIndex(int $value) { $this->hashIndex = $value; + + return $this; } } diff --git a/PhpOffice/PhpPresentation/Style/Borders.php b/PhpOffice/PhpPresentation/Style/Borders.php old mode 100755 new mode 100644 index 3d01568..01fa011 --- a/PhpOffice/PhpPresentation/Style/Borders.php +++ b/PhpOffice/PhpPresentation/Style/Borders.php @@ -10,87 +10,90 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Style; use PhpOffice\PhpPresentation\ComparableInterface; /** - * \PhpOffice\PhpPresentation\Style\Borders + * \PhpOffice\PhpPresentation\Style\Borders. */ class Borders implements ComparableInterface { /** - * Left + * Left. * * @var \PhpOffice\PhpPresentation\Style\Border */ private $left; /** - * Right + * Right. * * @var \PhpOffice\PhpPresentation\Style\Border */ private $right; /** - * Top + * Top. * * @var \PhpOffice\PhpPresentation\Style\Border */ private $top; /** - * Bottom + * Bottom. * * @var \PhpOffice\PhpPresentation\Style\Border */ private $bottom; /** - * Diagonal up + * Diagonal up. * * @var \PhpOffice\PhpPresentation\Style\Border */ private $diagonalUp; /** - * Diagonal down + * Diagonal down. * * @var \PhpOffice\PhpPresentation\Style\Border */ private $diagonalDown; /** - * Hash index + * Hash index. * - * @var string + * @var int */ private $hashIndex; /** - * Create a new \PhpOffice\PhpPresentation\Style\Borders + * Create a new \PhpOffice\PhpPresentation\Style\Borders. */ public function __construct() { // Initialise values - $this->left = new Border(); - $this->right = new Border(); - $this->top = new Border(); - $this->bottom = new Border(); - $this->diagonalUp = new Border(); + $this->left = new Border(); + $this->right = new Border(); + $this->top = new Border(); + $this->bottom = new Border(); + $this->diagonalUp = new Border(); $this->diagonalUp->setLineStyle(Border::LINE_NONE); - $this->diagonalDown = new Border(); + $this->diagonalDown = new Border(); $this->diagonalDown->setLineStyle(Border::LINE_NONE); } /** - * Get Left + * Get Left. * * @return \PhpOffice\PhpPresentation\Style\Border */ @@ -100,7 +103,7 @@ class Borders implements ComparableInterface } /** - * Get Right + * Get Right. * * @return \PhpOffice\PhpPresentation\Style\Border */ @@ -110,7 +113,7 @@ class Borders implements ComparableInterface } /** - * Get Top + * Get Top. * * @return \PhpOffice\PhpPresentation\Style\Border */ @@ -120,7 +123,7 @@ class Borders implements ComparableInterface } /** - * Get Bottom + * Get Bottom. * * @return \PhpOffice\PhpPresentation\Style\Border */ @@ -130,7 +133,7 @@ class Borders implements ComparableInterface } /** - * Get Diagonal Up + * Get Diagonal Up. * * @return \PhpOffice\PhpPresentation\Style\Border */ @@ -140,7 +143,7 @@ class Borders implements ComparableInterface } /** - * Get Diagonal Down + * Get Diagonal Down. * * @return \PhpOffice\PhpPresentation\Style\Border */ @@ -150,11 +153,11 @@ class Borders implements ComparableInterface } /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { return md5( $this->getLeft()->getHashCode() @@ -168,28 +171,32 @@ class Borders implements ComparableInterface } /** - * Get hash index + * Get hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @return string Hash index + * @return int|null Hash index */ - public function getHashIndex() + public function getHashIndex(): ?int { return $this->hashIndex; } /** - * Set hash index + * Set hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @param string $value Hash index + * @param int $value Hash index + * + * @return $this */ - public function setHashIndex($value) + public function setHashIndex(int $value) { $this->hashIndex = $value; + + return $this; } } diff --git a/PhpOffice/PhpPresentation/Style/Bullet.php b/PhpOffice/PhpPresentation/Style/Bullet.php old mode 100755 new mode 100644 index de078a1..dd3729d --- a/PhpOffice/PhpPresentation/Style/Bullet.php +++ b/PhpOffice/PhpPresentation/Style/Bullet.php @@ -10,134 +10,133 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Style; use PhpOffice\PhpPresentation\ComparableInterface; /** - * \PhpOffice\PhpPresentation\Style\Bullet + * \PhpOffice\PhpPresentation\Style\Bullet. */ class Bullet implements ComparableInterface { /* Bullet types */ - const TYPE_NONE = 'none'; - const TYPE_BULLET = 'bullet'; - const TYPE_NUMERIC = 'numeric'; + public const TYPE_NONE = 'none'; + public const TYPE_BULLET = 'bullet'; + public const TYPE_NUMERIC = 'numeric'; /* Numeric bullet styles */ - const NUMERIC_DEFAULT = 'arabicPeriod'; - const NUMERIC_ALPHALCPARENBOTH = 'alphaLcParenBoth'; - const NUMERIC_ALPHAUCPARENBOTH = 'alphaUcParenBoth'; - const NUMERIC_ALPHALCPARENR = 'alphaLcParenR'; - const NUMERIC_ALPHAUCPARENR = 'alphaUcParenR'; - const NUMERIC_ALPHALCPERIOD = 'alphaLcPeriod'; - const NUMERIC_ALPHAUCPERIOD = 'alphaUcPeriod'; - const NUMERIC_ARABICPARENBOTH = 'arabicParenBoth'; - const NUMERIC_ARABICPARENR = 'arabicParenR'; - const NUMERIC_ARABICPERIOD = 'arabicPeriod'; - const NUMERIC_ARABICPLAIN = 'arabicPlain'; - const NUMERIC_ROMANLCPARENBOTH = 'romanLcParenBoth'; - const NUMERIC_ROMANUCPARENBOTH = 'romanUcParenBoth'; - const NUMERIC_ROMANLCPARENR = 'romanLcParenR'; - const NUMERIC_ROMANUCPARENR = 'romanUcParenR'; - const NUMERIC_ROMANLCPERIOD = 'romanLcPeriod'; - const NUMERIC_ROMANUCPERIOD = 'romanUcPeriod'; - const NUMERIC_CIRCLENUMDBPLAIN = 'circleNumDbPlain'; - const NUMERIC_CIRCLENUMWDBLACKPLAIN = 'circleNumWdBlackPlain'; - const NUMERIC_CIRCLENUMWDWHITEPLAIN = 'circleNumWdWhitePlain'; - const NUMERIC_ARABICDBPERIOD = 'arabicDbPeriod'; - const NUMERIC_ARABICDBPLAIN = 'arabicDbPlain'; - const NUMERIC_EA1CHSPERIOD = 'ea1ChsPeriod'; - const NUMERIC_EA1CHSPLAIN = 'ea1ChsPlain'; - const NUMERIC_EA1CHTPERIOD = 'ea1ChtPeriod'; - const NUMERIC_EA1CHTPLAIN = 'ea1ChtPlain'; - const NUMERIC_EA1JPNCHSDBPERIOD = 'ea1JpnChsDbPeriod'; - const NUMERIC_EA1JPNKORPLAIN = 'ea1JpnKorPlain'; - const NUMERIC_EA1JPNKORPERIOD = 'ea1JpnKorPeriod'; - const NUMERIC_ARABIC1MINUS = 'arabic1Minus'; - const NUMERIC_ARABIC2MINUS = 'arabic2Minus'; - const NUMERIC_HEBREW2MINUS = 'hebrew2Minus'; - const NUMERIC_THAIALPHAPERIOD = 'thaiAlphaPeriod'; - const NUMERIC_THAIALPHAPARENR = 'thaiAlphaParenR'; - const NUMERIC_THAIALPHAPARENBOTH = 'thaiAlphaParenBoth'; - const NUMERIC_THAINUMPERIOD = 'thaiNumPeriod'; - const NUMERIC_THAINUMPARENR = 'thaiNumParenR'; - const NUMERIC_THAINUMPARENBOTH = 'thaiNumParenBoth'; - const NUMERIC_HINDIALPHAPERIOD = 'hindiAlphaPeriod'; - const NUMERIC_HINDINUMPERIOD = 'hindiNumPeriod'; - const NUMERIC_HINDINUMPARENR = 'hindiNumParenR'; - const NUMERIC_HINDIALPHA1PERIOD = 'hindiAlpha1Period'; + public const NUMERIC_DEFAULT = 'arabicPeriod'; + public const NUMERIC_ALPHALCPARENBOTH = 'alphaLcParenBoth'; + public const NUMERIC_ALPHAUCPARENBOTH = 'alphaUcParenBoth'; + public const NUMERIC_ALPHALCPARENR = 'alphaLcParenR'; + public const NUMERIC_ALPHAUCPARENR = 'alphaUcParenR'; + public const NUMERIC_ALPHALCPERIOD = 'alphaLcPeriod'; + public const NUMERIC_ALPHAUCPERIOD = 'alphaUcPeriod'; + public const NUMERIC_ARABICPARENBOTH = 'arabicParenBoth'; + public const NUMERIC_ARABICPARENR = 'arabicParenR'; + public const NUMERIC_ARABICPERIOD = 'arabicPeriod'; + public const NUMERIC_ARABICPLAIN = 'arabicPlain'; + public const NUMERIC_ROMANLCPARENBOTH = 'romanLcParenBoth'; + public const NUMERIC_ROMANUCPARENBOTH = 'romanUcParenBoth'; + public const NUMERIC_ROMANLCPARENR = 'romanLcParenR'; + public const NUMERIC_ROMANUCPARENR = 'romanUcParenR'; + public const NUMERIC_ROMANLCPERIOD = 'romanLcPeriod'; + public const NUMERIC_ROMANUCPERIOD = 'romanUcPeriod'; + public const NUMERIC_CIRCLENUMDBPLAIN = 'circleNumDbPlain'; + public const NUMERIC_CIRCLENUMWDBLACKPLAIN = 'circleNumWdBlackPlain'; + public const NUMERIC_CIRCLENUMWDWHITEPLAIN = 'circleNumWdWhitePlain'; + public const NUMERIC_ARABICDBPERIOD = 'arabicDbPeriod'; + public const NUMERIC_ARABICDBPLAIN = 'arabicDbPlain'; + public const NUMERIC_EA1CHSPERIOD = 'ea1ChsPeriod'; + public const NUMERIC_EA1CHSPLAIN = 'ea1ChsPlain'; + public const NUMERIC_EA1CHTPERIOD = 'ea1ChtPeriod'; + public const NUMERIC_EA1CHTPLAIN = 'ea1ChtPlain'; + public const NUMERIC_EA1JPNCHSDBPERIOD = 'ea1JpnChsDbPeriod'; + public const NUMERIC_EA1JPNKORPLAIN = 'ea1JpnKorPlain'; + public const NUMERIC_EA1JPNKORPERIOD = 'ea1JpnKorPeriod'; + public const NUMERIC_ARABIC1MINUS = 'arabic1Minus'; + public const NUMERIC_ARABIC2MINUS = 'arabic2Minus'; + public const NUMERIC_HEBREW2MINUS = 'hebrew2Minus'; + public const NUMERIC_THAIALPHAPERIOD = 'thaiAlphaPeriod'; + public const NUMERIC_THAIALPHAPARENR = 'thaiAlphaParenR'; + public const NUMERIC_THAIALPHAPARENBOTH = 'thaiAlphaParenBoth'; + public const NUMERIC_THAINUMPERIOD = 'thaiNumPeriod'; + public const NUMERIC_THAINUMPARENR = 'thaiNumParenR'; + public const NUMERIC_THAINUMPARENBOTH = 'thaiNumParenBoth'; + public const NUMERIC_HINDIALPHAPERIOD = 'hindiAlphaPeriod'; + public const NUMERIC_HINDINUMPERIOD = 'hindiNumPeriod'; + public const NUMERIC_HINDINUMPARENR = 'hindiNumParenR'; + public const NUMERIC_HINDIALPHA1PERIOD = 'hindiAlpha1Period'; /** - * Bullet type + * Bullet type. * * @var string */ private $bulletType = self::TYPE_NONE; /** - * Bullet font + * Bullet font. * * @var string */ private $bulletFont; /** - * Bullet char + * Bullet char. * * @var string */ private $bulletChar = '-'; /** - * Bullet char + * Bullet char. * * @var Color */ private $bulletColor; /** - * Bullet numeric style + * Bullet numeric style. * * @var string */ private $bulletNumericStyle = self::NUMERIC_DEFAULT; /** - * Bullet numeric start at + * Bullet numeric start at. + * + * @var int|string + */ + private $bulletNumericStartAt; + + /** + * Hash index. * * @var int */ - private $bulletNumericStartAt = 1; - - /** - * Hash index - * - * @var string - */ private $hashIndex; - /** - * Create a new \PhpOffice\PhpPresentation\Style\Bullet - */ public function __construct() { - // Initialise values - $this->bulletType = self::TYPE_NONE; - $this->bulletFont = 'Calibri'; - $this->bulletChar = '-'; - $this->bulletColor = new Color(); - $this->bulletNumericStyle = self::NUMERIC_DEFAULT; - $this->bulletNumericStartAt = 1; + $this->bulletType = self::TYPE_NONE; + $this->bulletFont = 'Calibri'; + $this->bulletChar = '-'; + $this->bulletColor = new Color(); + $this->bulletNumericStyle = self::NUMERIC_DEFAULT; + $this->bulletNumericStartAt = 1; } /** - * Get bullet type + * Get bullet type. * * @return string */ @@ -147,9 +146,10 @@ class Bullet implements ComparableInterface } /** - * Set bullet type + * Set bullet type. + * + * @param string $pValue * - * @param string $pValue * @return \PhpOffice\PhpPresentation\Style\Bullet */ public function setBulletType($pValue = self::TYPE_NONE) @@ -160,7 +160,7 @@ class Bullet implements ComparableInterface } /** - * Get bullet font + * Get bullet font. * * @return string */ @@ -170,14 +170,15 @@ class Bullet implements ComparableInterface } /** - * Set bullet font + * Set bullet font. + * + * @param string $pValue * - * @param string $pValue * @return \PhpOffice\PhpPresentation\Style\Bullet */ public function setBulletFont($pValue = 'Calibri') { - if ($pValue == '') { + if ('' == $pValue) { $pValue = 'Calibri'; } $this->bulletFont = $pValue; @@ -186,7 +187,7 @@ class Bullet implements ComparableInterface } /** - * Get bullet char + * Get bullet char. * * @return string */ @@ -196,9 +197,10 @@ class Bullet implements ComparableInterface } /** - * Set bullet char + * Set bullet char. + * + * @param string $pValue * - * @param string $pValue * @return \PhpOffice\PhpPresentation\Style\Bullet */ public function setBulletChar($pValue = '-') @@ -209,7 +211,7 @@ class Bullet implements ComparableInterface } /** - * Get bullet numeric style + * Get bullet numeric style. * * @return string */ @@ -219,9 +221,10 @@ class Bullet implements ComparableInterface } /** - * Set bullet numeric style + * Set bullet numeric style. + * + * @param string $pValue * - * @param string $pValue * @return \PhpOffice\PhpPresentation\Style\Bullet */ public function setBulletNumericStyle($pValue = self::NUMERIC_DEFAULT) @@ -232,9 +235,9 @@ class Bullet implements ComparableInterface } /** - * Get bullet numeric start at + * Get bullet numeric start at. * - * @return string + * @return int|string */ public function getBulletNumericStartAt() { @@ -242,9 +245,10 @@ class Bullet implements ComparableInterface } /** - * Set bullet numeric start at + * Set bullet numeric start at. * * @param int|string $pValue + * * @return \PhpOffice\PhpPresentation\Style\Bullet */ public function setBulletNumericStartAt($pValue = 1) @@ -255,11 +259,11 @@ class Bullet implements ComparableInterface } /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { return md5( $this->bulletType @@ -272,29 +276,33 @@ class Bullet implements ComparableInterface } /** - * Get hash index + * Get hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @return string Hash index + * @return int|null Hash index */ - public function getHashIndex() + public function getHashIndex(): ?int { return $this->hashIndex; } /** - * Set hash index + * Set hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @param string $value Hash index + * @param int $value Hash index + * + * @return $this */ - public function setHashIndex($value) + public function setHashIndex(int $value) { $this->hashIndex = $value; + + return $this; } /** @@ -306,12 +314,12 @@ class Bullet implements ComparableInterface } /** - * @param Color $bulletColor * @return Bullet */ public function setBulletColor(Color $bulletColor) { $this->bulletColor = $bulletColor; + return $this; } } diff --git a/PhpOffice/PhpPresentation/Style/Color.php b/PhpOffice/PhpPresentation/Style/Color.php old mode 100755 new mode 100644 index 76fddd8..56e5102 --- a/PhpOffice/PhpPresentation/Style/Color.php +++ b/PhpOffice/PhpPresentation/Style/Color.php @@ -10,59 +10,62 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Style; use PhpOffice\PhpPresentation\ComparableInterface; /** - * \PhpOffice\PhpPresentation\Style\Color + * \PhpOffice\PhpPresentation\Style\Color. */ class Color implements ComparableInterface { /* Colors */ - const COLOR_BLACK = 'FF000000'; - const COLOR_WHITE = 'FFFFFFFF'; - const COLOR_RED = 'FFFF0000'; - const COLOR_DARKRED = 'FF800000'; - const COLOR_BLUE = 'FF0000FF'; - const COLOR_DARKBLUE = 'FF000080'; - const COLOR_GREEN = 'FF00FF00'; - const COLOR_DARKGREEN = 'FF008000'; - const COLOR_YELLOW = 'FFFFFF00'; - const COLOR_DARKYELLOW = 'FF808000'; + public const COLOR_BLACK = 'FF000000'; + public const COLOR_WHITE = 'FFFFFFFF'; + public const COLOR_RED = 'FFFF0000'; + public const COLOR_DARKRED = 'FF800000'; + public const COLOR_BLUE = 'FF0000FF'; + public const COLOR_DARKBLUE = 'FF000080'; + public const COLOR_GREEN = 'FF00FF00'; + public const COLOR_DARKGREEN = 'FF008000'; + public const COLOR_YELLOW = 'FFFFFF00'; + public const COLOR_DARKYELLOW = 'FF808000'; /** - * ARGB - Alpha RGB + * ARGB - Alpha RGB. * * @var string */ private $argb; /** - * Hash index + * Hash index. * - * @var string + * @var int */ private $hashIndex; /** - * Create a new \PhpOffice\PhpPresentation\Style\Color + * Create a new \PhpOffice\PhpPresentation\Style\Color. * * @param string $pARGB */ public function __construct($pARGB = self::COLOR_BLACK) { // Initialise values - $this->argb = $pARGB; + $this->argb = $pARGB; } /** - * Get ARGB + * Get ARGB. * * @return string */ @@ -72,14 +75,15 @@ class Color implements ComparableInterface } /** - * Set ARGB + * Set ARGB. + * + * @param string $pValue * - * @param string $pValue * @return \PhpOffice\PhpPresentation\Style\Color */ public function setARGB($pValue = self::COLOR_BLACK) { - if ($pValue == '') { + if ('' == $pValue) { $pValue = self::COLOR_BLACK; } $this->argb = $pValue; @@ -89,25 +93,29 @@ class Color implements ComparableInterface /** * Get the alpha % of the ARGB - * Will return 100 if no ARGB - * @return integer + * Will return 100 if no ARGB. + * + * @return int */ - public function getAlpha() + public function getAlpha(): int { $alpha = 100; if (strlen($this->argb) >= 6) { $dec = hexdec(substr($this->argb, 0, 2)); - $alpha = number_format(($dec/255) * 100, 2); + $alpha = (int) number_format(($dec / 255) * 100, 0); } + return $alpha; } /** - * Set the alpha % of the ARGB + * Set the alpha % of the ARGB. + * * @param int $alpha + * * @return $this */ - public function setAlpha($alpha = 100) + public function setAlpha(int $alpha = 100): self { if ($alpha < 0) { $alpha = 0; @@ -116,20 +124,21 @@ class Color implements ComparableInterface $alpha = 100; } $alpha = round(($alpha / 100) * 255); - $alpha = dechex($alpha); + $alpha = dechex((int) $alpha); $alpha = str_pad($alpha, 2, '0', STR_PAD_LEFT); $this->argb = $alpha . substr($this->argb, 2); + return $this; } /** - * Get RGB + * Get RGB. * * @return string */ public function getRGB() { - if (strlen($this->argb) == 6) { + if (6 == strlen($this->argb)) { return $this->argb; } else { return substr($this->argb, 2); @@ -137,18 +146,19 @@ class Color implements ComparableInterface } /** - * Set RGB + * Set RGB. + * + * @param string $pValue + * @param string $pAlpha * - * @param string $pValue - * @param string $pAlpha * @return \PhpOffice\PhpPresentation\Style\Color */ public function setRGB($pValue = '000000', $pAlpha = 'FF') { - if ($pValue == '') { + if ('' == $pValue) { $pValue = '000000'; } - if ($pAlpha == '') { + if ('' == $pAlpha) { $pAlpha = 'FF'; } $this->argb = $pAlpha . $pValue; @@ -157,11 +167,11 @@ class Color implements ComparableInterface } /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { return md5( $this->argb @@ -170,28 +180,32 @@ class Color implements ComparableInterface } /** - * Get hash index + * Get hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @return string Hash index + * @return int|null Hash index */ - public function getHashIndex() + public function getHashIndex(): ?int { return $this->hashIndex; } /** - * Set hash index + * Set hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @param string $value Hash index + * @param int $value Hash index + * + * @return $this */ - public function setHashIndex($value) + public function setHashIndex(int $value) { $this->hashIndex = $value; + + return $this; } } diff --git a/PhpOffice/PhpPresentation/Style/ColorMap.php b/PhpOffice/PhpPresentation/Style/ColorMap.php old mode 100755 new mode 100644 index 77f66b9..f95a3bc --- a/PhpOffice/PhpPresentation/Style/ColorMap.php +++ b/PhpOffice/PhpPresentation/Style/ColorMap.php @@ -10,39 +10,45 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Style; /** - * PhpOffice\PhpPresentation\Style\ColorMap + * PhpOffice\PhpPresentation\Style\ColorMap. */ class ColorMap { - const COLOR_BG1 = 'bg1'; - const COLOR_BG2 = 'bg2'; - const COLOR_TX1 = 'tx1'; - const COLOR_TX2 = 'tx2'; - const COLOR_ACCENT1 = 'accent1'; - const COLOR_ACCENT2 = 'accent2'; - const COLOR_ACCENT3 = 'accent3'; - const COLOR_ACCENT4 = 'accent4'; - const COLOR_ACCENT5 = 'accent5'; - const COLOR_ACCENT6 = 'accent6'; - const COLOR_HLINK = 'hlink'; - const COLOR_FOLHLINK = 'folHlink'; + public const COLOR_BG1 = 'bg1'; + public const COLOR_BG2 = 'bg2'; + public const COLOR_TX1 = 'tx1'; + public const COLOR_TX2 = 'tx2'; + public const COLOR_ACCENT1 = 'accent1'; + public const COLOR_ACCENT2 = 'accent2'; + public const COLOR_ACCENT3 = 'accent3'; + public const COLOR_ACCENT4 = 'accent4'; + public const COLOR_ACCENT5 = 'accent5'; + public const COLOR_ACCENT6 = 'accent6'; + public const COLOR_HLINK = 'hlink'; + public const COLOR_FOLHLINK = 'folHlink'; /** - * Mapping - Stores the mapping betweenSlide and theme + * Mapping - Stores the mapping betweenSlide and theme. * - * @var array + * @var array */ - protected $mapping = array(); + protected $mapping = []; - public static $mappingDefault = array( + /** + * @var array + */ + public static $mappingDefault = [ self::COLOR_BG1 => 'lt1', self::COLOR_TX1 => 'dk1', self::COLOR_BG2 => 'lt2', @@ -54,12 +60,12 @@ class ColorMap self::COLOR_ACCENT5 => 'accent5', self::COLOR_ACCENT6 => 'accent6', self::COLOR_HLINK => 'hlink', - self::COLOR_FOLHLINK => 'folHlink' - ); + self::COLOR_FOLHLINK => 'folHlink', + ]; /** * ColorMap constructor. - * Create a new ColorMap with standard values + * Create a new ColorMap with standard values. */ public function __construct() { @@ -67,36 +73,33 @@ class ColorMap } /** - * Change the color of one of the elements in the map - * - * @param string $item - * @param string $newThemeColor - * @return ColorMap + * Change the color of one of the elements in the map. */ - public function changeColor($item, $newThemeColor) + public function changeColor(string $item, string $newThemeColor): self { $this->mapping[$item] = $newThemeColor; + return $this; } /** - * Store a new map. For use with the reader + * Store a new map. For use with the reader. * - * @param array $arrayMapping - * @return ColorMap + * @param array $arrayMapping */ - public function setMapping(array $arrayMapping = array()) + public function setMapping(array $arrayMapping = []): self { $this->mapping = $arrayMapping; + return $this; } /** - * Get the whole mapping as an array + * Get the whole mapping as an array. * - * @return array + * @return array */ - public function getMapping() + public function getMapping(): array { return $this->mapping; } diff --git a/PhpOffice/PhpPresentation/Style/Fill.php b/PhpOffice/PhpPresentation/Style/Fill.php old mode 100755 new mode 100644 index b252762..19b92db --- a/PhpOffice/PhpPresentation/Style/Fill.php +++ b/PhpOffice/PhpPresentation/Style/Fill.php @@ -10,107 +10,105 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Style; use PhpOffice\PhpPresentation\ComparableInterface; -/** - * \PhpOffice\PhpPresentation\Style\Fill - */ class Fill implements ComparableInterface { /* Fill types */ - const FILL_NONE = 'none'; - const FILL_SOLID = 'solid'; - const FILL_GRADIENT_LINEAR = 'linear'; - const FILL_GRADIENT_PATH = 'path'; - const FILL_PATTERN_DARKDOWN = 'darkDown'; - const FILL_PATTERN_DARKGRAY = 'darkGray'; - const FILL_PATTERN_DARKGRID = 'darkGrid'; - const FILL_PATTERN_DARKHORIZONTAL = 'darkHorizontal'; - const FILL_PATTERN_DARKTRELLIS = 'darkTrellis'; - const FILL_PATTERN_DARKUP = 'darkUp'; - const FILL_PATTERN_DARKVERTICAL = 'darkVertical'; - const FILL_PATTERN_GRAY0625 = 'gray0625'; - const FILL_PATTERN_GRAY125 = 'gray125'; - const FILL_PATTERN_LIGHTDOWN = 'lightDown'; - const FILL_PATTERN_LIGHTGRAY = 'lightGray'; - const FILL_PATTERN_LIGHTGRID = 'lightGrid'; - const FILL_PATTERN_LIGHTHORIZONTAL = 'lightHorizontal'; - const FILL_PATTERN_LIGHTTRELLIS = 'lightTrellis'; - const FILL_PATTERN_LIGHTUP = 'lightUp'; - const FILL_PATTERN_LIGHTVERTICAL = 'lightVertical'; - const FILL_PATTERN_MEDIUMGRAY = 'mediumGray'; + public const FILL_NONE = 'none'; + public const FILL_SOLID = 'solid'; + public const FILL_GRADIENT_LINEAR = 'linear'; + public const FILL_GRADIENT_PATH = 'path'; + public const FILL_PATTERN_DARKDOWN = 'darkDown'; + public const FILL_PATTERN_DARKGRAY = 'darkGray'; + public const FILL_PATTERN_DARKGRID = 'darkGrid'; + public const FILL_PATTERN_DARKHORIZONTAL = 'darkHorizontal'; + public const FILL_PATTERN_DARKTRELLIS = 'darkTrellis'; + public const FILL_PATTERN_DARKUP = 'darkUp'; + public const FILL_PATTERN_DARKVERTICAL = 'darkVertical'; + public const FILL_PATTERN_GRAY0625 = 'gray0625'; + public const FILL_PATTERN_GRAY125 = 'gray125'; + public const FILL_PATTERN_LIGHTDOWN = 'lightDown'; + public const FILL_PATTERN_LIGHTGRAY = 'lightGray'; + public const FILL_PATTERN_LIGHTGRID = 'lightGrid'; + public const FILL_PATTERN_LIGHTHORIZONTAL = 'lightHorizontal'; + public const FILL_PATTERN_LIGHTTRELLIS = 'lightTrellis'; + public const FILL_PATTERN_LIGHTUP = 'lightUp'; + public const FILL_PATTERN_LIGHTVERTICAL = 'lightVertical'; + public const FILL_PATTERN_MEDIUMGRAY = 'mediumGray'; /** - * Fill type + * Fill type. * * @var string */ - private $fillType; + private $fillType = self::FILL_NONE; /** - * Rotation + * Rotation. * - * @var double + * @var float */ - private $rotation; + private $rotation = 0.0; /** - * Start color + * Start color. * - * @var \PhpOffice\PhpPresentation\Style\Color + * @var Color */ private $startColor; /** - * End color + * End color. * - * @var \PhpOffice\PhpPresentation\Style\Color + * @var Color */ private $endColor; /** - * Hash index + * Hash index. * - * @var string + * @var int */ private $hashIndex; /** - * Create a new \PhpOffice\PhpPresentation\Style\Fill + * Create a new \PhpOffice\PhpPresentation\Style\Fill. */ public function __construct() { - // Initialise values - $this->fillType = self::FILL_NONE; - $this->rotation = (double)0; - $this->startColor = new Color(Color::COLOR_WHITE); - $this->endColor = new Color(Color::COLOR_BLACK); + $this->startColor = new Color(Color::COLOR_BLACK); + $this->endColor = new Color(Color::COLOR_WHITE); } /** - * Get Fill Type + * Get Fill Type. * * @return string */ - public function getFillType() + public function getFillType(): string { return $this->fillType; } /** - * Set Fill Type + * Set Fill Type. * - * @param string $pValue \PhpOffice\PhpPresentation\Style\Fill fill type - * @return \PhpOffice\PhpPresentation\Style\Fill + * @param string $pValue Fill type + * + * @return self */ - public function setFillType($pValue = self::FILL_NONE) + public function setFillType(string $pValue = self::FILL_NONE): self { $this->fillType = $pValue; @@ -118,34 +116,35 @@ class Fill implements ComparableInterface } /** - * Get Rotation + * Get Rotation. * - * @return double + * @return float */ - public function getRotation() + public function getRotation(): float { return $this->rotation; } /** - * Set Rotation + * Set Rotation. * - * @param float|int $pValue - * @return \PhpOffice\PhpPresentation\Style\Fill + * @param float $pValue + * + * @return self */ - public function setRotation($pValue = 0) + public function setRotation(float $pValue = 0): self { - $this->rotation = (double)$pValue; + $this->rotation = $pValue; return $this; } /** - * Get Start Color + * Get Start Color. * - * @return \PhpOffice\PhpPresentation\Style\Color + * @return Color */ - public function getStartColor() + public function getStartColor(): Color { // It's a get but it may lead to a modified color which we won't detect but in which case we must bind. // So bind as an assurance. @@ -153,13 +152,13 @@ class Fill implements ComparableInterface } /** - * Set Start Color + * Set Start Color. * - * @param \PhpOffice\PhpPresentation\Style\Color $pValue - * @throws \Exception - * @return \PhpOffice\PhpPresentation\Style\Fill + * @param Color $pValue + * + * @return self */ - public function setStartColor(Color $pValue = null) + public function setStartColor(Color $pValue): self { $this->startColor = $pValue; @@ -167,11 +166,11 @@ class Fill implements ComparableInterface } /** - * Get End Color + * Get End Color. * - * @return \PhpOffice\PhpPresentation\Style\Color + * @return Color */ - public function getEndColor() + public function getEndColor(): Color { // It's a get but it may lead to a modified color which we won't detect but in which case we must bind. // So bind as an assurance. @@ -179,13 +178,13 @@ class Fill implements ComparableInterface } /** - * Set End Color + * Set End Color. * - * @param \PhpOffice\PhpPresentation\Style\Color $pValue - * @throws \Exception - * @return \PhpOffice\PhpPresentation\Style\Fill + * @param Color $pValue + * + * @return self */ - public function setEndColor(Color $pValue = null) + public function setEndColor(Color $pValue): self { $this->endColor = $pValue; @@ -193,11 +192,11 @@ class Fill implements ComparableInterface } /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { return md5( $this->getFillType() @@ -209,28 +208,32 @@ class Fill implements ComparableInterface } /** - * Get hash index + * Get hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @return string Hash index + * @return int|null Hash index */ - public function getHashIndex() + public function getHashIndex(): ?int { return $this->hashIndex; } /** - * Set hash index + * Set hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @param string $value Hash index + * @param int $value Hash index + * + * @return $this */ - public function setHashIndex($value) + public function setHashIndex(int $value) { $this->hashIndex = $value; + + return $this; } } diff --git a/PhpOffice/PhpPresentation/Style/Font.php b/PhpOffice/PhpPresentation/Style/Font.php old mode 100755 new mode 100644 index e4b49d0..2e25b25 --- a/PhpOffice/PhpPresentation/Style/Font.php +++ b/PhpOffice/PhpPresentation/Style/Font.php @@ -10,133 +10,134 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Style; use PhpOffice\PhpPresentation\ComparableInterface; /** - * \PhpOffice\PhpPresentation\Style\Font + * \PhpOffice\PhpPresentation\Style\Font. */ class Font implements ComparableInterface { /* Underline types */ - const UNDERLINE_NONE = 'none'; - const UNDERLINE_DASH = 'dash'; - const UNDERLINE_DASHHEAVY = 'dashHeavy'; - const UNDERLINE_DASHLONG = 'dashLong'; - const UNDERLINE_DASHLONGHEAVY = 'dashLongHeavy'; - const UNDERLINE_DOUBLE = 'dbl'; - const UNDERLINE_DOTHASH = 'dotDash'; - const UNDERLINE_DOTHASHHEAVY = 'dotDashHeavy'; - const UNDERLINE_DOTDOTDASH = 'dotDotDash'; - const UNDERLINE_DOTDOTDASHHEAVY = 'dotDotDashHeavy'; - const UNDERLINE_DOTTED = 'dotted'; - const UNDERLINE_DOTTEDHEAVY = 'dottedHeavy'; - const UNDERLINE_HEAVY = 'heavy'; - const UNDERLINE_SINGLE = 'sng'; - const UNDERLINE_WAVY = 'wavy'; - const UNDERLINE_WAVYDOUBLE = 'wavyDbl'; - const UNDERLINE_WAVYHEAVY = 'wavyHeavy'; - const UNDERLINE_WORDS = 'words'; + public const UNDERLINE_NONE = 'none'; + public const UNDERLINE_DASH = 'dash'; + public const UNDERLINE_DASHHEAVY = 'dashHeavy'; + public const UNDERLINE_DASHLONG = 'dashLong'; + public const UNDERLINE_DASHLONGHEAVY = 'dashLongHeavy'; + public const UNDERLINE_DOUBLE = 'dbl'; + public const UNDERLINE_DOTHASH = 'dotDash'; + public const UNDERLINE_DOTHASHHEAVY = 'dotDashHeavy'; + public const UNDERLINE_DOTDOTDASH = 'dotDotDash'; + public const UNDERLINE_DOTDOTDASHHEAVY = 'dotDotDashHeavy'; + public const UNDERLINE_DOTTED = 'dotted'; + public const UNDERLINE_DOTTEDHEAVY = 'dottedHeavy'; + public const UNDERLINE_HEAVY = 'heavy'; + public const UNDERLINE_SINGLE = 'sng'; + public const UNDERLINE_WAVY = 'wavy'; + public const UNDERLINE_WAVYDOUBLE = 'wavyDbl'; + public const UNDERLINE_WAVYHEAVY = 'wavyHeavy'; + public const UNDERLINE_WORDS = 'words'; + + public const FORMAT_LATIN = 'latin'; + public const FORMAT_EAST_ASIAN = 'ea'; + public const FORMAT_COMPLEX_SCRIPT = 'cs'; /** - * Name + * Name. * * @var string */ - private $name; - - /** - * Font Size - * - * @var float|int - */ - private $size; - - /** - * Bold - * - * @var boolean - */ - private $bold; + private $name = 'Calibri'; /** - * Italic + * Font Size. * - * @var boolean + * @var int */ - private $italic; + private $size = 10; /** - * Superscript + * Bold. * - * @var boolean + * @var bool */ - private $superScript; + private $bold = false; /** - * Subscript + * Italic. * - * @var boolean + * @var bool */ - private $subScript; + private $italic = false; /** - * Underline + * Superscript. + * + * @var bool + */ + private $superScript = false; + + /** + * Subscript. + * + * @var bool + */ + private $subScript = false; + + /** + * Underline. * * @var string */ - private $underline; + private $underline = self::UNDERLINE_NONE; /** - * Strikethrough + * Strikethrough. * - * @var boolean + * @var bool */ - private $strikethrough; + private $strikethrough = false; /** - * Foreground color + * Foreground color. * - * @var \PhpOffice\PhpPresentation\Style\Color + * @var Color */ private $color; /** - * Character Spacing + * Character Spacing. * - * @var int + * @var float */ - private $characterSpacing; + private $characterSpacing = 0; /** - * Hash index + * Format * * @var string */ - private $hashIndex; + private $format = self::FORMAT_LATIN; /** - * Create a new \PhpOffice\PhpPresentation\Style\Font + * Hash index. + * + * @var int */ + private $hashIndex; + public function __construct() { - // Initialise values - $this->name = 'Calibri'; - $this->size = 10; - $this->characterSpacing = 0; - $this->bold = false; - $this->italic = false; - $this->superScript = false; - $this->subScript = false; - $this->underline = self::UNDERLINE_NONE; - $this->strikethrough = false; - $this->color = new Color(Color::COLOR_BLACK); + $this->color = new Color(Color::COLOR_BLACK); } /** @@ -144,7 +145,7 @@ class Font implements ComparableInterface * * @return string */ - public function getName() + public function getName(): string { return $this->name; } @@ -152,181 +153,139 @@ class Font implements ComparableInterface /** * Set Name * - * @param string $pValue - * @return \PhpOffice\PhpPresentation\Style\Font + * @param string $pValue + * + * @return self */ - public function setName($pValue = 'Calibri') + public function setName(string $pValue = 'Calibri'): self { - if ($pValue == '') { + if ('' == $pValue) { $pValue = 'Calibri'; } $this->name = $pValue; return $this; } - + /** - * Get Character Spacing + * Get Character Spacing. * - * @return double + * @return float */ - public function getCharacterSpacing() + public function getCharacterSpacing(): float { return $this->characterSpacing; } - + /** * Set Character Spacing - * Value in pt - * @param float|int $pValue - * @return \PhpOffice\PhpPresentation\Style\Font + * Value in pt. + * + * @param float $pValue + * + * @return self */ - public function setCharacterSpacing($pValue = 0) + public function setCharacterSpacing(float $pValue = 0): self { - if ($pValue == '') { - $pValue = 0; - } $this->characterSpacing = $pValue * 100; - + return $this; } /** - * Get Size - * - * @return double + * Get Size. */ - public function getSize() + public function getSize(): int { return $this->size; } /** - * Set Size - * - * @param float|int $pValue - * @return \PhpOffice\PhpPresentation\Style\Font + * Set Size. */ - public function setSize($pValue = 10) + public function setSize(int $pValue = 10): self { - if ($pValue == '') { - $pValue = 10; - } $this->size = $pValue; return $this; } /** - * Get Bold + * Get Bold. * - * @return boolean + * @return bool */ - public function isBold() + public function isBold(): bool { return $this->bold; } /** - * Set Bold - * - * @param boolean $pValue - * @return \PhpOffice\PhpPresentation\Style\Font + * Set Bold. */ - public function setBold($pValue = false) + public function setBold(bool $pValue = false): self { - if ($pValue == '') { - $pValue = false; - } $this->bold = $pValue; return $this; } /** - * Get Italic + * Get Italic. * - * @return boolean + * @return bool */ - public function isItalic() + public function isItalic(): bool { return $this->italic; } /** - * Set Italic - * - * @param boolean $pValue - * @return \PhpOffice\PhpPresentation\Style\Font + * Set Italic. */ - public function setItalic($pValue = false) + public function setItalic(bool $pValue = false): self { - if ($pValue == '') { - $pValue = false; - } $this->italic = $pValue; return $this; } /** - * Get SuperScript + * Get SuperScript. * - * @return boolean + * @return bool */ - public function isSuperScript() + public function isSuperScript(): bool { return $this->superScript; } /** - * Set SuperScript - * - * @param boolean $pValue - * @return \PhpOffice\PhpPresentation\Style\Font + * Set SuperScript. */ - public function setSuperScript($pValue = false) + public function setSuperScript(bool $pValue = false): self { - if ($pValue == '') { - $pValue = false; - } - $this->superScript = $pValue; // Set SubScript at false only if SuperScript is true - if ($pValue === true) { + if (true === $pValue) { $this->subScript = false; } return $this; } - /** - * Get SubScript - * - * @return boolean - */ - public function isSubScript() + public function isSubScript(): bool { return $this->subScript; } - /** - * Set SubScript - * - * @param boolean $pValue - * @return \PhpOffice\PhpPresentation\Style\Font - */ - public function setSubScript($pValue = false) + public function setSubScript(bool $pValue = false): self { - if ($pValue == '') { - $pValue = false; - } - $this->subScript = $pValue; // Set SuperScript at false only if SubScript is true - if ($pValue === true) { + if (true === $pValue) { $this->superScript = false; } @@ -334,24 +293,25 @@ class Font implements ComparableInterface } /** - * Get Underline + * Get Underline. * * @return string */ - public function getUnderline() + public function getUnderline(): string { return $this->underline; } /** - * Set Underline + * Set Underline. * - * @param string $pValue \PhpOffice\PhpPresentation\Style\Font underline type - * @return \PhpOffice\PhpPresentation\Style\Font + * @param string $pValue Underline type + * + * @return self */ - public function setUnderline($pValue = self::UNDERLINE_NONE) + public function setUnderline(string $pValue = self::UNDERLINE_NONE): self { - if ($pValue == '') { + if ('' == $pValue) { $pValue = self::UNDERLINE_NONE; } $this->underline = $pValue; @@ -360,91 +320,122 @@ class Font implements ComparableInterface } /** - * Get Strikethrough + * Get Strikethrough. * - * @return boolean + * @return bool */ - public function isStrikethrough() + public function isStrikethrough(): bool { return $this->strikethrough; } /** - * Set Strikethrough - * - * @param boolean $pValue - * @return \PhpOffice\PhpPresentation\Style\Font + * Set Strikethrough. */ - public function setStrikethrough($pValue = false) + public function setStrikethrough(bool $pValue = false): self { - if ($pValue == '') { - $pValue = false; - } $this->strikethrough = $pValue; return $this; } /** - * Get Color - * - * @return \PhpOffice\PhpPresentation\Style\Color|\PhpOffice\PhpPresentation\Style\SchemeColor + * Get Color. */ - public function getColor() + public function getColor(): Color { return $this->color; } /** - * Set Color - * - * @param \PhpOffice\PhpPresentation\Style\Color|\PhpOffice\PhpPresentation\Style\SchemeColor $pValue - * @throws \Exception - * @return \PhpOffice\PhpPresentation\Style\Font + * Set Color. */ - public function setColor($pValue = null) + public function setColor(Color $pValue): self { - if (!$pValue instanceof Color) { - throw new \Exception('$pValue must be an instance of \PhpOffice\PhpPresentation\Style\Color'); - } $this->color = $pValue; return $this; } /** - * Get hash code + * Get format * - * @return string Hash code + * @return string */ - public function getHashCode() + public function getFormat(): string { - return md5($this->name . $this->size . ($this->bold ? 't' : 'f') . ($this->italic ? 't' : 'f') . ($this->superScript ? 't' : 'f') . ($this->subScript ? 't' : 'f') . $this->underline . ($this->strikethrough ? 't' : 'f') . $this->color->getHashCode() . __CLASS__); + return $this->format; } /** - * Get hash index + * Set format + * + * @param string $value + * + * @return self + */ + public function setFormat(string $value = self::FORMAT_LATIN): self + { + if (in_array($value, [ + self::FORMAT_COMPLEX_SCRIPT, + self::FORMAT_EAST_ASIAN, + self::FORMAT_LATIN, + ])) { + $this->format = $value; + } + + return $this; + } + + /** + * Get hash code. + * + * @return string Hash code + */ + public function getHashCode(): string + { + return md5( + $this->name + . $this->size + . ($this->bold ? 't' : 'f') + . ($this->italic ? 't' : 'f') + . ($this->superScript ? 't' : 'f') + . ($this->subScript ? 't' : 'f') + . $this->underline + . ($this->strikethrough ? 't' : 'f') + . $this->format + . $this->color->getHashCode() + . __CLASS__ + ); + } + + /** + * Get hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @return string Hash index + * @return int|null Hash index */ - public function getHashIndex() + public function getHashIndex(): ?int { return $this->hashIndex; } /** - * Set hash index + * Set hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @param string $value Hash index + * @param int $value Hash index + * + * @return $this */ - public function setHashIndex($value) + public function setHashIndex(int $value) { $this->hashIndex = $value; + + return $this; } } diff --git a/PhpOffice/PhpPresentation/Style/Outline.php b/PhpOffice/PhpPresentation/Style/Outline.php old mode 100755 new mode 100644 index 03c7d21..99714e2 --- a/PhpOffice/PhpPresentation/Style/Outline.php +++ b/PhpOffice/PhpPresentation/Style/Outline.php @@ -10,15 +10,18 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Style; /** - * \PhpOffice\PhpPresentation\Style\Outline + * \PhpOffice\PhpPresentation\Style\Outline. */ class Outline { @@ -26,54 +29,41 @@ class Outline * @var Fill */ protected $fill; - /** - * @var int - */ - protected $width; - /** - * Outline constructor. + * @var float */ + protected $width = 1; + public function __construct() { $this->fill = new Fill(); } - /** - * @return Fill - */ - public function getFill() + public function getFill(): Fill { return $this->fill; } - /** - * @param Fill $fill - * @return Outline - */ - public function setFill(Fill $fill) + public function setFill(Fill $fill): self { $this->fill = $fill; + return $this; } - /** - * @return int - */ - public function getWidth() + public function getWidth(): float { return $this->width; } /** - * Value in points - * @param int $width - * @return Outline + * Value in points. */ - public function setWidth($width) + public function setWidth(float $pValue = 1): self { - $this->width = intval($width); + $this->width = $pValue; + return $this; } } diff --git a/PhpOffice/PhpPresentation/Style/SchemeColor.php b/PhpOffice/PhpPresentation/Style/SchemeColor.php old mode 100755 new mode 100644 index 7b8c7fb..040e103 --- a/PhpOffice/PhpPresentation/Style/SchemeColor.php +++ b/PhpOffice/PhpPresentation/Style/SchemeColor.php @@ -10,30 +10,32 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Style; class SchemeColor extends Color { + /** + * @var string + */ protected $value; - /** - * @return string - */ - public function getValue() + public function getValue(): string { return $this->value; } - /** - * @param string $value - */ - public function setValue($value) + public function setValue(string $value): self { $this->value = $value; + + return $this; } } diff --git a/PhpOffice/PhpPresentation/Style/Shadow.php b/PhpOffice/PhpPresentation/Style/Shadow.php old mode 100755 new mode 100644 index acf672e..826dd47 --- a/PhpOffice/PhpPresentation/Style/Shadow.php +++ b/PhpOffice/PhpPresentation/Style/Shadow.php @@ -10,122 +10,105 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Style; use PhpOffice\PhpPresentation\ComparableInterface; /** - * \PhpOffice\PhpPresentation\Style\Shadow + * \PhpOffice\PhpPresentation\Style\Shadow. */ class Shadow implements ComparableInterface { /* Shadow alignment */ - const SHADOW_BOTTOM = 'b'; - const SHADOW_BOTTOM_LEFT = 'bl'; - const SHADOW_BOTTOM_RIGHT = 'br'; - const SHADOW_CENTER = 'ctr'; - const SHADOW_LEFT = 'l'; - const SHADOW_TOP = 't'; - const SHADOW_TOP_LEFT = 'tl'; - const SHADOW_TOP_RIGHT = 'tr'; + public const SHADOW_BOTTOM = 'b'; + public const SHADOW_BOTTOM_LEFT = 'bl'; + public const SHADOW_BOTTOM_RIGHT = 'br'; + public const SHADOW_CENTER = 'ctr'; + public const SHADOW_LEFT = 'l'; + public const SHADOW_TOP = 't'; + public const SHADOW_TOP_LEFT = 'tl'; + public const SHADOW_TOP_RIGHT = 'tr'; /** - * Visible + * Visible. * - * @var boolean + * @var bool */ - private $visible; + private $visible = false; /** - * Blur radius - * - * Defaults to 6 + * Blur radius. * * @var int */ - private $blurRadius; + private $blurRadius = 6; /** - * Shadow distance - * - * Defaults to 2 + * Shadow distance. * * @var int */ - private $distance; + private $distance = 2; /** - * Shadow direction (in degrees) + * Shadow direction (in degrees). * * @var int */ - private $direction; + private $direction = 0; /** - * Shadow alignment + * Shadow alignment. * * @var string */ - private $alignment; + private $alignment = self::SHADOW_BOTTOM_RIGHT; /** - * Color - * - * @var \PhpOffice\PhpPresentation\Style\Color + * @var Color|null */ private $color; /** - * Alpha - * * @var int */ - private $alpha; + private $alpha = 50; /** - * Hash index + * Hash index. * - * @var string + * @var int */ private $hashIndex; /** - * Create a new \PhpOffice\PhpPresentation\Style\Shadow + * Create a new \PhpOffice\PhpPresentation\Style\Shadow. */ public function __construct() { - // Initialise values - $this->visible = false; - $this->blurRadius = 6; - $this->distance = 2; - $this->direction = 0; - $this->alignment = self::SHADOW_BOTTOM_RIGHT; - $this->color = new Color(Color::COLOR_BLACK); - $this->alpha = 50; + $this->color = new Color(Color::COLOR_BLACK); } /** - * Get Visible - * - * @return boolean + * Get Visible. */ - public function isVisible() + public function isVisible(): bool { return $this->visible; } /** - * Set Visible - * - * @param boolean $pValue - * @return $this + * Set Visible. */ - public function setVisible($pValue = false) + public function setVisible(bool $pValue = false): self { $this->visible = $pValue; @@ -133,22 +116,17 @@ class Shadow implements ComparableInterface } /** - * Get Blur radius - * - * @return int + * Get Blur radius. */ - public function getBlurRadius() + public function getBlurRadius(): int { return $this->blurRadius; } /** - * Set Blur radius - * - * @param int $pValue - * @return $this + * Set Blur radius. */ - public function setBlurRadius($pValue = 6) + public function setBlurRadius(int $pValue = 6): self { $this->blurRadius = $pValue; @@ -156,22 +134,19 @@ class Shadow implements ComparableInterface } /** - * Get Shadow distance - * - * @return int + * Get Shadow distance. */ - public function getDistance() + public function getDistance(): int { return $this->distance; } /** - * Set Shadow distance + * Set Shadow distance. * - * @param int $pValue * @return $this */ - public function setDistance($pValue = 2) + public function setDistance(int $pValue = 2): self { $this->distance = $pValue; @@ -179,22 +154,17 @@ class Shadow implements ComparableInterface } /** - * Get Shadow direction (in degrees) - * - * @return int + * Get Shadow direction (in degrees). */ - public function getDirection() + public function getDirection(): int { return $this->direction; } /** - * Set Shadow direction (in degrees) - * - * @param int $pValue - * @return $this + * Set Shadow direction (in degrees). */ - public function setDirection($pValue = 0) + public function setDirection(int $pValue = 0): self { $this->direction = $pValue; @@ -202,22 +172,17 @@ class Shadow implements ComparableInterface } /** - * Get Shadow alignment - * - * @return int + * Get Shadow alignment. */ - public function getAlignment() + public function getAlignment(): string { return $this->alignment; } /** - * Set Shadow alignment - * - * @param string $pValue - * @return $this + * Set Shadow alignment. */ - public function setAlignment($pValue = self::SHADOW_BOTTOM_RIGHT) + public function setAlignment(string $pValue = self::SHADOW_BOTTOM_RIGHT): self { $this->alignment = $pValue; @@ -225,23 +190,17 @@ class Shadow implements ComparableInterface } /** - * Get Color - * - * @return \PhpOffice\PhpPresentation\Style\Color + * Get Color. */ - public function getColor() + public function getColor(): ?Color { return $this->color; } /** - * Set Color - * - * @param \PhpOffice\PhpPresentation\Style\Color $pValue - * @throws \Exception - * @return $this + * Set Color. */ - public function setColor(Color $pValue = null) + public function setColor(Color $pValue = null): self { $this->color = $pValue; @@ -249,22 +208,17 @@ class Shadow implements ComparableInterface } /** - * Get Alpha - * - * @return int + * Get Alpha. */ - public function getAlpha() + public function getAlpha(): int { return $this->alpha; } /** - * Set Alpha - * - * @param int $pValue - * @return $this + * Set Alpha. */ - public function setAlpha($pValue = 0) + public function setAlpha(int $pValue = 0): self { $this->alpha = $pValue; @@ -272,38 +226,42 @@ class Shadow implements ComparableInterface } /** - * Get hash code + * Get hash code. * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { return md5(($this->visible ? 't' : 'f') . $this->blurRadius . $this->distance . $this->direction . $this->alignment . $this->color->getHashCode() . $this->alpha . __CLASS__); } /** - * Get hash index + * Get hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @return string Hash index + * @return int|null Hash index */ - public function getHashIndex() + public function getHashIndex(): ?int { return $this->hashIndex; } /** - * Set hash index + * Set hash index. * * Note that this index may vary during script execution! Only reliable moment is * while doing a write of a workbook and when changes are not allowed. * - * @param string $value Hash index + * @param int $value Hash index + * + * @return $this */ - public function setHashIndex($value) + public function setHashIndex(int $value) { $this->hashIndex = $value; + + return $this; } } diff --git a/PhpOffice/PhpPresentation/Style/TextStyle.php b/PhpOffice/PhpPresentation/Style/TextStyle.php old mode 100755 new mode 100644 index 2f41c7a..64d7c5a --- a/PhpOffice/PhpPresentation/Style/TextStyle.php +++ b/PhpOffice/PhpPresentation/Style/TextStyle.php @@ -10,39 +10,39 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Style; use PhpOffice\PhpPresentation\Shape\RichText\Paragraph as RichTextParagraph; -/** - * Class TextStyle - */ class TextStyle { /** - * @var array + * @var array */ - protected $bodyStyle = array(); + protected $bodyStyle = []; /** - * @var array + * @var array */ - protected $titleStyle = array(); + protected $titleStyle = []; /** - * @var array + * @var array */ - protected $otherStyle = array(); + protected $otherStyle = []; /** * TextStyle constructor. + * * @param bool $default - * @throws \Exception */ - public function __construct($default = true) + public function __construct(bool $default = true) { if ($default) { $oColorLT1 = new SchemeColor(); @@ -70,116 +70,92 @@ class TextStyle } } - /** - * @param $lvl - * @return bool - */ - private function checkLvl($lvl) + private function checkLvl(?int $lvl): bool { - if (!is_int($lvl)) { - return false; - } - if ($lvl > 9) { + if (is_null($lvl) || $lvl > 9) { return false; } + return true; } - /** - * @param RichTextParagraph $style - * @param $lvl - * @return TextStyle - */ - public function setBodyStyleAtLvl(RichTextParagraph $style, $lvl) + public function setBodyStyleAtLvl(RichTextParagraph $style, ?int $lvl): self { if ($this->checkLvl($lvl)) { $this->bodyStyle[$lvl] = $style; } + return $this; } - /** - * @param RichTextParagraph $style - * @param $lvl - * @return TextStyle - */ - public function setTitleStyleAtLvl(RichTextParagraph $style, $lvl) + public function setTitleStyleAtLvl(RichTextParagraph $style, ?int $lvl): self { if ($this->checkLvl($lvl)) { $this->titleStyle[$lvl] = $style; } + return $this; } /** - * @param RichTextParagraph $style - * @param $lvl * @return TextStyle */ - public function setOtherStyleAtLvl(RichTextParagraph $style, $lvl) + public function setOtherStyleAtLvl(RichTextParagraph $style, ?int $lvl): self { if ($this->checkLvl($lvl)) { $this->otherStyle[$lvl] = $style; } + return $this; } - /** - * @param $lvl - * @return mixed - */ - public function getBodyStyleAtLvl($lvl) + public function getBodyStyleAtLvl(?int $lvl): ?RichTextParagraph { if ($this->checkLvl($lvl) && !empty($this->bodyStyle[$lvl])) { return $this->bodyStyle[$lvl]; } + return null; } - /** - * @param $lvl - * @return mixed - */ - public function getTitleStyleAtLvl($lvl) + public function getTitleStyleAtLvl(?int $lvl): ?RichTextParagraph { if ($this->checkLvl($lvl) && !empty($this->titleStyle[$lvl])) { return $this->titleStyle[$lvl]; } + return null; } - /** - * @param $lvl - * @return mixed - */ - public function getOtherStyleAtLvl($lvl) + public function getOtherStyleAtLvl(?int $lvl): ?RichTextParagraph { if ($this->checkLvl($lvl) && !empty($this->otherStyle[$lvl])) { return $this->otherStyle[$lvl]; } + return null; } /** - * @return array + * @return array */ - public function getBodyStyle() + public function getBodyStyle(): array { return $this->bodyStyle; } /** - * @return array + * @return array */ - public function getTitleStyle() + public function getTitleStyle(): array { return $this->titleStyle; } /** - * @return array + * @return array */ - public function getOtherStyle() + public function getOtherStyle(): array { return $this->otherStyle; } diff --git a/PhpOffice/PhpPresentation/Writer/AbstractDecoratorWriter.php b/PhpOffice/PhpPresentation/Writer/AbstractDecoratorWriter.php old mode 100755 new mode 100644 index 1d17404..9b12bf9 --- a/PhpOffice/PhpPresentation/Writer/AbstractDecoratorWriter.php +++ b/PhpOffice/PhpPresentation/Writer/AbstractDecoratorWriter.php @@ -1,4 +1,22 @@ oHashTable = $hashTable; + return $this; } @@ -47,12 +65,12 @@ abstract class AbstractDecoratorWriter } /** - * @param PhpPresentation $oPresentation * @return $this */ public function setPresentation(PhpPresentation $oPresentation) { $this->oPresentation = $oPresentation; + return $this; } @@ -65,12 +83,12 @@ abstract class AbstractDecoratorWriter } /** - * @param ZipInterface $oZip * @return $this */ public function setZip(ZipInterface $oZip) { $this->oZip = $oZip; + return $this; } diff --git a/PhpOffice/PhpPresentation/Writer/AbstractWriter.php b/PhpOffice/PhpPresentation/Writer/AbstractWriter.php old mode 100755 new mode 100644 index b17dcc1..14179ef --- a/PhpOffice/PhpPresentation/Writer/AbstractWriter.php +++ b/PhpOffice/PhpPresentation/Writer/AbstractWriter.php @@ -1,8 +1,29 @@ oDrawingHashTable; } /** - * Get PhpPresentation object - * - * @return PhpPresentation - * @throws \Exception + * Get PhpPresentation object. */ - public function getPhpPresentation() + public function getPhpPresentation(): ?PhpPresentation { - if (empty($this->oPresentation)) { - throw new \Exception("No PhpPresentation assigned."); - } return $this->oPresentation; } /** - * Get PhpPresentation object + * Get PhpPresentation object. * - * @param PhpPresentation $pPhpPresentation PhpPresentation object - * @throws \Exception - * @return \PhpOffice\PhpPresentation\Writer\AbstractWriter + * @param PhpPresentation|null $pPhpPresentation PhpPresentation object + * + * @return self */ public function setPhpPresentation(PhpPresentation $pPhpPresentation = null) { $this->oPresentation = $pPhpPresentation; + return $this; } - - /** - * @param ZipInterface $oZipAdapter - * @return $this - */ - public function setZipAdapter(ZipInterface $oZipAdapter) + public function setZipAdapter(ZipInterface $oZipAdapter): self { $this->oZipAdapter = $oZipAdapter; + return $this; } - /** - * @return ZipInterface - */ - public function getZipAdapter() + public function getZipAdapter(): ?ZipInterface { return $this->oZipAdapter; } /** - * Get an array of all drawings + * Get an array of all drawings. * - * @return \PhpOffice\PhpPresentation\Shape\AbstractDrawing[] All drawings in PhpPresentation - * @throws \Exception + * @return array */ - protected function allDrawings() + protected function allDrawings(): array { // Get an array of all drawings - $aDrawings = array(); + $aDrawings = []; // Get an array of all master slides $aSlideMasters = $this->getPhpPresentation()->getAllMasterSlides(); @@ -104,7 +110,7 @@ abstract class AbstractWriter }, $aSlideMasters); // Get an array of all slide layouts - $aSlideLayouts = array(); + $aSlideLayouts = []; array_walk_recursive($aSlideMasterLayouts, function ($oSlideLayout) use (&$aSlideLayouts) { $aSlideLayouts[] = $oSlideLayout; }); @@ -118,9 +124,14 @@ abstract class AbstractWriter return $aDrawings; } - private function iterateCollection(\ArrayIterator $oIterator) + /** + * @param ArrayIterator $oIterator + * + * @return array + */ + private function iterateCollection(ArrayIterator $oIterator): array { - $arrayReturn = array(); + $arrayReturn = []; if ($oIterator->count() <= 0) { return $arrayReturn; } @@ -137,6 +148,7 @@ abstract class AbstractWriter } $oIterator->next(); } + return $arrayReturn; } } diff --git a/PhpOffice/PhpPresentation/Writer/ODPresentation.php b/PhpOffice/PhpPresentation/Writer/ODPresentation.php old mode 100755 new mode 100644 index 027afdf..a963259 --- a/PhpOffice/PhpPresentation/Writer/ODPresentation.php +++ b/PhpOffice/PhpPresentation/Writer/ODPresentation.php @@ -10,54 +10,58 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Writer; +use DirectoryIterator; use PhpOffice\Common\Adapter\Zip\ZipArchiveAdapter; +use PhpOffice\PhpPresentation\Exception\DirectoryNotFoundException; +use PhpOffice\PhpPresentation\Exception\FileCopyException; +use PhpOffice\PhpPresentation\Exception\FileRemoveException; +use PhpOffice\PhpPresentation\Exception\InvalidParameterException; use PhpOffice\PhpPresentation\HashTable; use PhpOffice\PhpPresentation\PhpPresentation; -use PhpOffice\PhpPresentation\Shape\AbstractDrawing; -use PhpOffice\PhpPresentation\Shape\Table; -use DirectoryIterator; /** - * ODPresentation writer + * ODPresentation writer. */ class ODPresentation extends AbstractWriter implements WriterInterface { /** * @var \PhpOffice\PhpPresentation\Shape\Chart[] */ - public $chartArray = array(); + public $chartArray = []; /** - * Use disk caching where possible? - * - * @var boolean - */ + * Use disk caching where possible? + * + * @var bool + */ private $useDiskCaching = false; /** - * Disk caching directory + * Disk caching directory. * * @var string */ private $diskCachingDirectory; /** - * Create a new \PhpOffice\PhpPresentation\Writer\ODPresentation + * Create a new \PhpOffice\PhpPresentation\Writer\ODPresentation. * * @param PhpPresentation $pPhpPresentation - * @throws \Exception */ public function __construct(PhpPresentation $pPhpPresentation = null) { // Assign PhpPresentation - $this->setPhpPresentation($pPhpPresentation); + $this->setPhpPresentation($pPhpPresentation ?? new PhpPresentation()); // Set up disk caching location $this->diskCachingDirectory = './'; @@ -69,21 +73,22 @@ class ODPresentation extends AbstractWriter implements WriterInterface } /** - * Save PhpPresentation to file + * Save PhpPresentation to file. * - * @param string $pFilename - * @throws \Exception + * @throws FileCopyException + * @throws FileRemoveException + * @throws InvalidParameterException */ - public function save($pFilename) + public function save(string $pFilename): void { if (empty($pFilename)) { - throw new \Exception("Filename is empty"); + throw new InvalidParameterException('pFilename', ''); } // If $pFilename is php://output or php://stdout, make it a temporary file... $originalFilename = $pFilename; - if (strtolower($pFilename) == 'php://output' || strtolower($pFilename) == 'php://stdout') { + if ('php://output' == strtolower($pFilename) || 'php://stdout' == strtolower($pFilename)) { $pFilename = @tempnam('./', 'phppttmp'); - if ($pFilename == '') { + if ('' == $pFilename) { $pFilename = $originalFilename; } } @@ -97,22 +102,22 @@ class ODPresentation extends AbstractWriter implements WriterInterface // Variables $oPresentation = $this->getPhpPresentation(); - $arrayChart = array(); + $arrayChart = []; - $arrayFiles = array(); - $oDir = new DirectoryIterator(dirname(__FILE__).DIRECTORY_SEPARATOR.'ODPresentation'); + $arrayFiles = []; + $oDir = new DirectoryIterator(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'ODPresentation'); foreach ($oDir as $oFile) { if (!$oFile->isFile()) { continue; } $class = __NAMESPACE__ . '\\ODPresentation\\' . $oFile->getBasename('.php'); - $o = new \ReflectionClass($class); + $class = new \ReflectionClass($class); - if ($o->isAbstract() || !$o->isSubclassOf('PhpOffice\PhpPresentation\Writer\ODPresentation\AbstractDecoratorWriter')) { + if ($class->isAbstract() || !$class->isSubclassOf('PhpOffice\PhpPresentation\Writer\ODPresentation\AbstractDecoratorWriter')) { continue; } - $arrayFiles[$oFile->getBasename('.php')] = $o; + $arrayFiles[$oFile->getBasename('.php')] = $class; } ksort($arrayFiles); @@ -133,11 +138,11 @@ class ODPresentation extends AbstractWriter implements WriterInterface // If a temporary file was used, copy it to the correct file stream if ($originalFilename != $pFilename) { - if (copy($pFilename, $originalFilename) === false) { - throw new \Exception("Could not copy temporary zip file $pFilename to $originalFilename."); + if (false === copy($pFilename, $originalFilename)) { + throw new FileCopyException($pFilename, $originalFilename); } - if (@unlink($pFilename) === false) { - throw new \Exception('The file ' . $pFilename . ' could not be removed.'); + if (false === @unlink($pFilename)) { + throw new FileRemoveException($pFilename); } } } @@ -145,7 +150,7 @@ class ODPresentation extends AbstractWriter implements WriterInterface /** * Get use disk caching where possible? * - * @return boolean + * @return bool */ public function hasDiskCaching() { @@ -155,27 +160,29 @@ class ODPresentation extends AbstractWriter implements WriterInterface /** * Set use disk caching where possible? * - * @param boolean $pValue - * @param string $pDirectory Disk caching directory - * @throws \Exception + * @param bool $pValue + * @param string $directory Disk caching directory + * + * @throws DirectoryNotFoundException + * * @return \PhpOffice\PhpPresentation\Writer\ODPresentation */ - public function setUseDiskCaching($pValue = false, $pDirectory = null) + public function setUseDiskCaching(bool $pValue = false, string $directory = null) { $this->useDiskCaching = $pValue; - if (!is_null($pDirectory)) { - if (!is_dir($pDirectory)) { - throw new \Exception("Directory does not exist: $pDirectory"); + if (!is_null($directory)) { + if (!is_dir($directory)) { + throw new DirectoryNotFoundException($directory); } - $this->diskCachingDirectory = $pDirectory; + $this->diskCachingDirectory = $directory; } return $this; } /** - * Get disk caching directory + * Get disk caching directory. * * @return string */ diff --git a/PhpOffice/PhpPresentation/Writer/ODPresentation/AbstractDecoratorWriter.php b/PhpOffice/PhpPresentation/Writer/ODPresentation/AbstractDecoratorWriter.php old mode 100755 new mode 100644 index c5e390e..7683106 --- a/PhpOffice/PhpPresentation/Writer/ODPresentation/AbstractDecoratorWriter.php +++ b/PhpOffice/PhpPresentation/Writer/ODPresentation/AbstractDecoratorWriter.php @@ -1,4 +1,22 @@ arrayChart = $arrayChart; + return $this; } } diff --git a/PhpOffice/PhpPresentation/Writer/ODPresentation/Content.php b/PhpOffice/PhpPresentation/Writer/ODPresentation/Content.php old mode 100755 new mode 100644 index cb399ab..bf9fc5b --- a/PhpOffice/PhpPresentation/Writer/ODPresentation/Content.php +++ b/PhpOffice/PhpPresentation/Writer/ODPresentation/Content.php @@ -1,4 +1,22 @@ > */ - protected $arrStyleBullet = array(); + protected $arrStyleBullet = []; /** * Stores paragraph information for text shapes. * - * @var array + * @var array */ - protected $arrStyleParagraph = array(); + protected $arrStyleParagraph = []; /** * Stores font styles for text shapes that include lists. * - * @var Run[] + * @var array */ - protected $arrStyleTextFont = array(); + protected $arrStyleTextFont = []; /** * Used to track the current shape ID. * - * @var integer + * @var int */ protected $shapeId; - /** - * @return ZipInterface - * @throws \Exception - */ - public function render() + public function render(): ZipInterface { $this->getZip()->addFromString('content.xml', $this->writeContent()); + return $this->getZip(); } - - /** - * Write content file to XML format + * Write content file to XML format. * - * @return string XML Output - * @throws \Exception + * @return string XML Output */ - public function writeContent() + protected function writeContent(): string { // Create XML writer $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); @@ -113,12 +128,13 @@ class Content extends AbstractDecoratorWriter $objWriter->writeAttribute('xmlns:rdfa', 'http://docs.oasis-open.org/opendocument/meta/rdfa#'); $objWriter->writeAttribute('xmlns:field', 'urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0'); $objWriter->writeAttribute('xmlns:officeooo', 'http://openoffice.org/2009/office'); + $objWriter->writeAttribute('xmlns:loext', 'urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0'); $objWriter->writeAttribute('office:version', '1.2'); // office:automatic-styles $objWriter->startElement('office:automatic-styles'); - $this->shapeId = 0; + $this->shapeId = 0; $incSlide = 0; foreach ($this->getPresentation()->getAllSlides() as $pSlide) { // Slides @@ -148,31 +164,31 @@ class Content extends AbstractDecoratorWriter } } - $incSlide++; + ++$incSlide; } // Style : Bullet if (!empty($this->arrStyleBullet)) { foreach ($this->arrStyleBullet as $key => $item) { - $oStyle = $item['oStyle']; + $oStyle = $item['oStyle']; $arrLevel = explode(';', $item['level']); // style:style $objWriter->startElement('text:list-style'); $objWriter->writeAttribute('style:name', 'L_' . $key); foreach ($arrLevel as $level) { - if ($level != '') { + if ('' != $level) { $oAlign = $item['oAlign_' . $level]; // text:list-level-style-bullet $objWriter->startElement('text:list-level-style-bullet'); - $objWriter->writeAttribute('text:level', $level + 1); + $objWriter->writeAttribute('text:level', intval($level) + 1); $objWriter->writeAttribute('text:bullet-char', $oStyle->getBulletChar()); // style:list-level-properties $objWriter->startElement('style:list-level-properties'); if ($oAlign->getIndent() < 0) { - $objWriter->writeAttribute('text:space-before', CommonDrawing::pixelsToCentimeters($oAlign->getMarginLeft() - (-1 * $oAlign->getIndent())) . 'cm'); - $objWriter->writeAttribute('text:min-label-width', CommonDrawing::pixelsToCentimeters(-1 * $oAlign->getIndent()) . 'cm'); + $objWriter->writeAttribute('text:space-before', CommonDrawing::pixelsToCentimeters((int) ($oAlign->getMarginLeft() - (-1 * $oAlign->getIndent()))) . 'cm'); + $objWriter->writeAttribute('text:min-label-width', CommonDrawing::pixelsToCentimeters((int) (-1 * $oAlign->getIndent())) . 'cm'); } else { - $objWriter->writeAttribute('text:space-before', (CommonDrawing::pixelsToCentimeters($oAlign->getMarginLeft() - $oAlign->getIndent())) . 'cm'); - $objWriter->writeAttribute('text:min-label-width', CommonDrawing::pixelsToCentimeters($oAlign->getIndent()) . 'cm'); + $objWriter->writeAttribute('text:space-before', (CommonDrawing::pixelsToCentimeters((int) ($oAlign->getMarginLeft() - $oAlign->getIndent()))) . 'cm'); + $objWriter->writeAttribute('text:min-label-width', CommonDrawing::pixelsToCentimeters((int) $oAlign->getIndent()) . 'cm'); } $objWriter->endElement(); @@ -198,6 +214,24 @@ class Content extends AbstractDecoratorWriter $objWriter->writeAttribute('style:family', 'paragraph'); // style:paragraph-properties $objWriter->startElement('style:paragraph-properties'); + $objWriter->writeAttributeIf( + $item->getLineSpacingMode() === Paragraph::LINE_SPACING_MODE_PERCENT, + 'fo:line-height', + $item->getLineSpacing() . '%' + ); + $objWriter->writeAttributeIf( + $item->getLineSpacingMode() === Paragraph::LINE_SPACING_MODE_POINT, + 'fo:line-height', + $item->getLineSpacing() . 'pt' + ); + $objWriter->writeAttribute( + 'fo:margin-top', + Text::numberFormat(CommonDrawing::pointstoCentimeters($item->getSpacingBefore()), 3) . 'cm' + ); + $objWriter->writeAttribute( + 'fo:margin-bottom', + Text::numberFormat(CommonDrawing::pointstoCentimeters($item->getSpacingAfter()), 3) . 'cm' + ); switch ($item->getAlignment()->getHorizontal()) { case Alignment::HORIZONTAL_LEFT: $objWriter->writeAttribute('fo:text-align', 'left'); @@ -218,6 +252,10 @@ class Content extends AbstractDecoratorWriter $objWriter->writeAttribute('fo:text-align', 'left'); break; } + $objWriter->writeAttribute( + 'style:writing-mode', + $item->getAlignment()->isRTL() ? 'rl-tb' : 'lr-tb' + ); $objWriter->endElement(); $objWriter->endElement(); } @@ -229,19 +267,37 @@ class Content extends AbstractDecoratorWriter $objWriter->startElement('style:style'); $objWriter->writeAttribute('style:name', 'T_' . $key); $objWriter->writeAttribute('style:family', 'text'); - // style:text-properties + + // style:style > style:text-properties $objWriter->startElement('style:text-properties'); $objWriter->writeAttribute('fo:color', '#' . $item->getFont()->getColor()->getRGB()); - $objWriter->writeAttribute('fo:font-family', $item->getFont()->getName()); - $objWriter->writeAttribute('fo:font-size', $item->getFont()->getSize() . 'pt'); - // @todo : fo:font-style - if ($item->getFont()->isBold()) { - $objWriter->writeAttribute('fo:font-weight', 'bold'); + switch ($item->getFont()->getFormat()) { + case Font::FORMAT_LATIN: + $objWriter->writeAttribute('fo:font-family', $item->getFont()->getName()); + $objWriter->writeAttribute('fo:font-size', $item->getFont()->getSize() . 'pt'); + $objWriter->writeAttributeIf($item->getFont()->isBold(), 'fo:font-weight', 'bold'); + $objWriter->writeAttribute('fo:language', ($item->getLanguage() ? $item->getLanguage() : 'en')); + $objWriter->writeAttribute('style:script-type', 'latin'); + break; + case Font::FORMAT_EAST_ASIAN: + $objWriter->writeAttribute('style:font-family-asian', $item->getFont()->getName()); + $objWriter->writeAttribute('style:font-size-asian', $item->getFont()->getSize() . 'pt'); + $objWriter->writeAttributeIf($item->getFont()->isBold(), 'style:font-weight-asian', 'bold'); + $objWriter->writeAttribute('style:language-asian', ($item->getLanguage() ? $item->getLanguage() : 'en')); + $objWriter->writeAttribute('style:script-type', 'asian'); + break; + case Font::FORMAT_COMPLEX_SCRIPT: + $objWriter->writeAttribute('style:font-family-complex', $item->getFont()->getName()); + $objWriter->writeAttribute('style:font-size-complex', $item->getFont()->getSize() . 'pt'); + $objWriter->writeAttributeIf($item->getFont()->isBold(), 'style:font-weight-complex', 'bold'); + $objWriter->writeAttribute('style:language-complex', ($item->getLanguage() ? $item->getLanguage() : 'en')); + $objWriter->writeAttribute('style:script-type', 'complex'); + break; } - $objWriter->writeAttribute('fo:language', ($item->getLanguage() ? $item->getLanguage() : 'en')); - // @todo : style:text-underline-style + // > style:style > style:text-properties $objWriter->endElement(); + // > style:style $objWriter->endElement(); } } @@ -252,12 +308,12 @@ class Content extends AbstractDecoratorWriter //=============================================== // office:body $objWriter->startElement('office:body'); - // office:presentation + // office:body > office:presentation $objWriter->startElement('office:presentation'); // Write slides $slideCount = $this->getPresentation()->getSlideCount(); - $this->shapeId = 0; + $this->shapeId = 0; for ($i = 0; $i < $slideCount; ++$i) { $pSlide = $this->getPresentation()->getSlide($i); $objWriter->startElement('draw:page'); @@ -300,13 +356,18 @@ class Content extends AbstractDecoratorWriter $objWriter->endElement(); } + // office:document-content > office:body > office:presentation > presentation:settings + $objWriter->startElement('presentation:settings'); if ($this->getPresentation()->getPresentationProperties()->isLoopContinuouslyUntilEsc()) { - $objWriter->startElement('presentation:settings'); $objWriter->writeAttribute('presentation:endless', 'true'); - $objWriter->writeAttribute('presentation:pause', 'P0s'); + $objWriter->writeAttribute('presentation:pause', 'PT0S'); $objWriter->writeAttribute('presentation:mouse-visible', 'false'); - $objWriter->endElement(); } + if ($this->getPresentation()->getPresentationProperties()->getSlideshowType() === PresentationProperties::SLIDESHOW_TYPE_BROWSE) { + $objWriter->writeAttribute('presentation:full-screen', 'false'); + } + $objWriter->endElement(); + // > office:presentation $objWriter->endElement(); // > office:body @@ -319,20 +380,17 @@ class Content extends AbstractDecoratorWriter } /** - * Write picture - * - * @param \PhpOffice\Common\XMLWriter $objWriter - * @param \PhpOffice\PhpPresentation\Shape\Media $shape + * Write picture. */ - public function writeShapeMedia(XMLWriter $objWriter, Media $shape) + protected function writeShapeMedia(XMLWriter $objWriter, Media $shape): void { // draw:frame $objWriter->startElement('draw:frame'); $objWriter->writeAttribute('draw:name', $shape->getName()); - $objWriter->writeAttribute('svg:width', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getWidth()), 3) . 'cm'); - $objWriter->writeAttribute('svg:height', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getHeight()), 3) . 'cm'); - $objWriter->writeAttribute('svg:x', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getOffsetX()), 3) . 'cm'); - $objWriter->writeAttribute('svg:y', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getOffsetY()), 3) . 'cm'); + $objWriter->writeAttribute('svg:width', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getWidth()), 3) . 'cm'); + $objWriter->writeAttribute('svg:height', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getHeight()), 3) . 'cm'); + $objWriter->writeAttribute('svg:x', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getOffsetX()), 3) . 'cm'); + $objWriter->writeAttribute('svg:y', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getOffsetY()), 3) . 'cm'); $objWriter->writeAttribute('draw:style-name', 'gr' . $this->shapeId); // draw:frame > draw:plugin $objWriter->startElement('draw:plugin'); @@ -367,21 +425,19 @@ class Content extends AbstractDecoratorWriter } /** - * Write picture + * Write picture. * - * @param \PhpOffice\Common\XMLWriter $objWriter * @param AbstractDrawingAdapter $shape - * @throws \Exception */ - public function writeShapeDrawing(XMLWriter $objWriter, ShapeDrawing\AbstractDrawingAdapter $shape) + protected function writeShapeDrawing(XMLWriter $objWriter, ShapeDrawing\AbstractDrawingAdapter $shape): void { // draw:frame $objWriter->startElement('draw:frame'); $objWriter->writeAttribute('draw:name', $shape->getName()); - $objWriter->writeAttribute('svg:width', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getWidth()), 3) . 'cm'); - $objWriter->writeAttribute('svg:height', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getHeight()), 3) . 'cm'); - $objWriter->writeAttribute('svg:x', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getOffsetX()), 3) . 'cm'); - $objWriter->writeAttribute('svg:y', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getOffsetY()), 3) . 'cm'); + $objWriter->writeAttribute('svg:width', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getWidth()), 3) . 'cm'); + $objWriter->writeAttribute('svg:height', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getHeight()), 3) . 'cm'); + $objWriter->writeAttribute('svg:x', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getOffsetX()), 3) . 'cm'); + $objWriter->writeAttribute('svg:y', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getOffsetY()), 3) . 'cm'); $objWriter->writeAttribute('draw:style-name', 'gr' . $this->shapeId); // draw:image $objWriter->startElement('draw:image'); @@ -391,6 +447,7 @@ class Content extends AbstractDecoratorWriter $objWriter->writeAttribute('xlink:type', 'simple'); $objWriter->writeAttribute('xlink:show', 'embed'); $objWriter->writeAttribute('xlink:actuate', 'onLoad'); + $objWriter->writeAttribute('loext:mime-type', $shape->getMimeType()); $objWriter->writeElement('text:p'); $objWriter->endElement(); @@ -415,34 +472,30 @@ class Content extends AbstractDecoratorWriter } /** - * Write text - * - * @param \PhpOffice\Common\XMLWriter $objWriter - * @param \PhpOffice\PhpPresentation\Shape\RichText $shape - * @throws \Exception + * Write text. */ - public function writeShapeTxt(XMLWriter $objWriter, RichText $shape) + protected function writeShapeTxt(XMLWriter $objWriter, RichText $shape): void { // draw:frame $objWriter->startElement('draw:frame'); $objWriter->writeAttribute('draw:style-name', 'gr' . $this->shapeId); - $objWriter->writeAttribute('svg:width', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getWidth()), 3) . 'cm'); - $objWriter->writeAttribute('svg:height', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getHeight()), 3) . 'cm'); - $objWriter->writeAttribute('svg:x', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getOffsetX()), 3) . 'cm'); - $objWriter->writeAttribute('svg:y', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getOffsetY()), 3) . 'cm'); + $objWriter->writeAttribute('svg:width', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getWidth()), 3) . 'cm'); + $objWriter->writeAttribute('svg:height', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getHeight()), 3) . 'cm'); + $objWriter->writeAttribute('svg:x', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getOffsetX()), 3) . 'cm'); + $objWriter->writeAttribute('svg:y', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getOffsetY()), 3) . 'cm'); // draw:text-box $objWriter->startElement('draw:text-box'); - $paragraphs = $shape->getParagraphs(); - $paragraphId = 0; - $sCstShpLastBullet = ''; - $iCstShpLastBulletLvl = 0; - $bCstShpHasBullet = false; + $paragraphs = $shape->getParagraphs(); + $paragraphId = 0; + $sCstShpLastBullet = ''; + $iCstShpLastBulletLvl = 0; + $bCstShpHasBullet = false; foreach ($paragraphs as $paragraph) { // Close the bullet list - if ($sCstShpLastBullet != 'bullet' && $bCstShpHasBullet === true) { - for ($iInc = $iCstShpLastBulletLvl; $iInc >= 0; $iInc--) { + if ('bullet' != $sCstShpLastBullet && true === $bCstShpHasBullet) { + for ($iInc = $iCstShpLastBulletLvl; $iInc >= 0; --$iInc) { // text:list-item $objWriter->endElement(); // text:list @@ -452,24 +505,24 @@ class Content extends AbstractDecoratorWriter //=============================================== // Paragraph //=============================================== - if ($paragraph->getBulletStyle()->getBulletType() == 'none') { + if ('none' == $paragraph->getBulletStyle()->getBulletType()) { ++$paragraphId; // text:p $objWriter->startElement('text:p'); $objWriter->writeAttribute('text:style-name', 'P_' . $paragraph->getHashCode()); // Loop trough rich text elements - $richtexts = $paragraph->getRichTextElements(); + $richtexts = $paragraph->getRichTextElements(); $richtextId = 0; foreach ($richtexts as $richtext) { ++$richtextId; - if ($richtext instanceof TextElement || $richtext instanceof Run) { + if ($richtext instanceof TextElement) { // text:span $objWriter->startElement('text:span'); if ($richtext instanceof Run) { $objWriter->writeAttribute('text:style-name', 'T_' . $richtext->getHashCode()); } - if ($richtext->hasHyperlink() === true && $richtext->getHyperlink()->getUrl() != '') { + if (true === $richtext->hasHyperlink() && '' != $richtext->getHyperlink()->getUrl()) { // text:a $objWriter->startElement('text:a'); $objWriter->writeAttribute('xlink:type', 'simple'); @@ -490,18 +543,18 @@ class Content extends AbstractDecoratorWriter } } $objWriter->endElement(); - //=============================================== - // Bullet list - //=============================================== - } elseif ($paragraph->getBulletStyle()->getBulletType() == 'bullet') { + //=============================================== + // Bullet list + //=============================================== + } elseif ('bullet' == $paragraph->getBulletStyle()->getBulletType()) { $bCstShpHasBullet = true; // Open the bullet list - if ($sCstShpLastBullet != 'bullet' || ($sCstShpLastBullet == $paragraph->getBulletStyle()->getBulletType() && $iCstShpLastBulletLvl < $paragraph->getAlignment()->getLevel())) { + if ('bullet' != $sCstShpLastBullet || ($sCstShpLastBullet == $paragraph->getBulletStyle()->getBulletType() && $iCstShpLastBulletLvl < $paragraph->getAlignment()->getLevel())) { // text:list $objWriter->startElement('text:list'); $objWriter->writeAttribute('text:style-name', 'L_' . $paragraph->getBulletStyle()->getHashCode()); } - if ($sCstShpLastBullet == 'bullet') { + if ('bullet' == $sCstShpLastBullet) { if ($iCstShpLastBulletLvl == $paragraph->getAlignment()->getLevel()) { // text:list-item $objWriter->endElement(); @@ -523,17 +576,17 @@ class Content extends AbstractDecoratorWriter $objWriter->writeAttribute('text:style-name', 'P_' . $paragraph->getHashCode()); // Loop trough rich text elements - $richtexts = $paragraph->getRichTextElements(); + $richtexts = $paragraph->getRichTextElements(); $richtextId = 0; foreach ($richtexts as $richtext) { ++$richtextId; - if ($richtext instanceof TextElement || $richtext instanceof Run) { + if ($richtext instanceof TextElement) { // text:span $objWriter->startElement('text:span'); if ($richtext instanceof Run) { $objWriter->writeAttribute('text:style-name', 'T_' . $richtext->getHashCode()); } - if ($richtext->hasHyperlink() === true && $richtext->getHyperlink()->getUrl() != '') { + if (true === $richtext->hasHyperlink() && '' != $richtext->getHyperlink()->getUrl()) { // text:a $objWriter->startElement('text:a'); $objWriter->writeAttribute('xlink:type', 'simple'); @@ -555,13 +608,13 @@ class Content extends AbstractDecoratorWriter } $objWriter->endElement(); } - $sCstShpLastBullet = $paragraph->getBulletStyle()->getBulletType(); + $sCstShpLastBullet = $paragraph->getBulletStyle()->getBulletType(); $iCstShpLastBulletLvl = $paragraph->getAlignment()->getLevel(); } // Close the bullet list - if ($sCstShpLastBullet == 'bullet' && $bCstShpHasBullet === true) { - for ($iInc = $iCstShpLastBulletLvl; $iInc >= 0; $iInc--) { + if ('bullet' == $sCstShpLastBullet && true === $bCstShpHasBullet) { + for ($iInc = $iCstShpLastBulletLvl; $iInc >= 0; --$iInc) { // text:list-item $objWriter->endElement(); // text:list @@ -574,20 +627,19 @@ class Content extends AbstractDecoratorWriter // > draw:frame $objWriter->endElement(); } + /** - * Write Comment - * @param XMLWriter $objWriter - * @param Comment $oShape + * Write Comment. */ - public function writeShapeComment(XMLWriter $objWriter, Comment $oShape) + protected function writeShapeComment(XMLWriter $objWriter, Comment $oShape): void { - /** + /* * Note : This element is not valid in the Schema 1.2 */ // officeooo:annotation $objWriter->startElement('officeooo:annotation'); - $objWriter->writeAttribute('svg:x', number_format(CommonDrawing::pixelsToCentimeters($oShape->getOffsetX()), 2, '.', '').'cm'); - $objWriter->writeAttribute('svg:y', number_format(CommonDrawing::pixelsToCentimeters($oShape->getOffsetY()), 2, '.', '').'cm'); + $objWriter->writeAttribute('svg:x', number_format(CommonDrawing::pixelsToCentimeters((int) $oShape->getOffsetX()), 2, '.', '') . 'cm'); + $objWriter->writeAttribute('svg:y', number_format(CommonDrawing::pixelsToCentimeters((int) $oShape->getOffsetY()), 2, '.', '') . 'cm'); if ($oShape->getAuthor() instanceof Comment\Author) { $objWriter->writeElement('dc:creator', $oShape->getAuthor()->getName()); @@ -599,19 +651,15 @@ class Content extends AbstractDecoratorWriter $objWriter->endElement(); } - /** - * @param XMLWriter $objWriter - * @param Line $shape - */ - public function writeShapeLine(XMLWriter $objWriter, Line $shape) + protected function writeShapeLine(XMLWriter $objWriter, Line $shape): void { // draw:line $objWriter->startElement('draw:line'); $objWriter->writeAttribute('draw:style-name', 'gr' . $this->shapeId); - $objWriter->writeAttribute('svg:x1', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getOffsetX()), 3) . 'cm'); - $objWriter->writeAttribute('svg:y1', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getOffsetY()), 3) . 'cm'); - $objWriter->writeAttribute('svg:x2', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getOffsetX()+$shape->getWidth()), 3) . 'cm'); - $objWriter->writeAttribute('svg:y2', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getOffsetY()+$shape->getHeight()), 3) . 'cm'); + $objWriter->writeAttribute('svg:x1', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getOffsetX()), 3) . 'cm'); + $objWriter->writeAttribute('svg:y1', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getOffsetY()), 3) . 'cm'); + $objWriter->writeAttribute('svg:x2', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getOffsetX() + $shape->getWidth()), 3) . 'cm'); + $objWriter->writeAttribute('svg:y2', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getOffsetY() + $shape->getHeight()), 3) . 'cm'); // text:p $objWriter->writeElement('text:p'); @@ -620,19 +668,16 @@ class Content extends AbstractDecoratorWriter } /** - * Write table Shape - * @param XMLWriter $objWriter - * @param Table $shape - * @throws \Exception + * Write table Shape. */ - public function writeShapeTable(XMLWriter $objWriter, Table $shape) + protected function writeShapeTable(XMLWriter $objWriter, Table $shape): void { // draw:frame $objWriter->startElement('draw:frame'); - $objWriter->writeAttribute('svg:x', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getOffsetX()), 3) . 'cm'); - $objWriter->writeAttribute('svg:y', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getOffsetY()), 3) . 'cm'); - $objWriter->writeAttribute('svg:height', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getHeight()), 3) . 'cm'); - $objWriter->writeAttribute('svg:width', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getWidth()), 3) . 'cm'); + $objWriter->writeAttribute('svg:x', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getOffsetX()), 3) . 'cm'); + $objWriter->writeAttribute('svg:y', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getOffsetY()), 3) . 'cm'); + $objWriter->writeAttribute('svg:height', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getHeight()), 3) . 'cm'); + $objWriter->writeAttribute('svg:width', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getWidth()), 3) . 'cm'); $arrayRows = $shape->getRows(); if (!empty($arrayRows)) { @@ -647,15 +692,15 @@ class Content extends AbstractDecoratorWriter foreach ($arrayRows as $keyRow => $shapeRow) { // table:table-row $objWriter->startElement('table:table-row'); - $objWriter->writeAttribute('table:style-name', 'gr'.$this->shapeId.'r'.$keyRow); + $objWriter->writeAttribute('table:style-name', 'gr' . $this->shapeId . 'r' . $keyRow); //@todo getFill $numColspan = 0; foreach ($shapeRow->getCells() as $keyCell => $shapeCell) { - if ($numColspan == 0) { + if (0 == $numColspan) { // table:table-cell $objWriter->startElement('table:table-cell'); - $objWriter->writeAttribute('table:style-name', 'gr' . $this->shapeId.'r'.$keyRow.'c'.$keyCell); + $objWriter->writeAttribute('table:style-name', 'gr' . $this->shapeId . 'r' . $keyRow . 'c' . $keyCell); if ($shapeCell->getColspan() > 1) { $objWriter->writeAttribute('table:number-columns-spanned', $shapeCell->getColspan()); $numColspan = $shapeCell->getColspan() - 1; @@ -673,7 +718,7 @@ class Content extends AbstractDecoratorWriter if ($shapeRichText instanceof Run) { $objWriter->writeAttribute('text:style-name', 'T_' . $shapeRichText->getHashCode()); } - if ($shapeRichText->hasHyperlink() === true && $shapeRichText->getHyperlink()->getUrl() !== '') { + if (true === $shapeRichText->hasHyperlink() && '' !== $shapeRichText->getHyperlink()->getUrl()) { // text:a $objWriter->startElement('text:a'); $objWriter->writeAttribute('xlink:type', 'simple'); @@ -703,7 +748,7 @@ class Content extends AbstractDecoratorWriter } else { // table:covered-table-cell $objWriter->writeElement('table:covered-table-cell'); - $numColspan--; + --$numColspan; } } // > table:table-row @@ -717,12 +762,9 @@ class Content extends AbstractDecoratorWriter } /** - * Write table Chart - * @param XMLWriter $objWriter - * @param Chart $shape - * @throws \Exception + * Write table Chart. */ - public function writeShapeChart(XMLWriter $objWriter, Chart $shape) + protected function writeShapeChart(XMLWriter $objWriter, Chart $shape): void { $arrayChart = $this->getArrayChart(); $arrayChart[$this->shapeId] = $shape; @@ -731,14 +773,14 @@ class Content extends AbstractDecoratorWriter // draw:frame $objWriter->startElement('draw:frame'); $objWriter->writeAttribute('draw:name', $shape->getTitle()->getText()); - $objWriter->writeAttribute('svg:x', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getOffsetX()), 3) . 'cm'); - $objWriter->writeAttribute('svg:y', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getOffsetY()), 3) . 'cm'); - $objWriter->writeAttribute('svg:height', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getHeight()), 3) . 'cm'); - $objWriter->writeAttribute('svg:width', Text::numberFormat(CommonDrawing::pixelsToCentimeters($shape->getWidth()), 3) . 'cm'); + $objWriter->writeAttribute('svg:x', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getOffsetX()), 3) . 'cm'); + $objWriter->writeAttribute('svg:y', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getOffsetY()), 3) . 'cm'); + $objWriter->writeAttribute('svg:height', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getHeight()), 3) . 'cm'); + $objWriter->writeAttribute('svg:width', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $shape->getWidth()), 3) . 'cm'); // draw:object $objWriter->startElement('draw:object'); - $objWriter->writeAttribute('xlink:href', './Object '.$this->shapeId); + $objWriter->writeAttribute('xlink:href', './Object ' . $this->shapeId); $objWriter->writeAttribute('xlink:type', 'simple'); $objWriter->writeAttribute('xlink:show', 'embed'); @@ -749,13 +791,9 @@ class Content extends AbstractDecoratorWriter } /** - * Writes a group of shapes - * - * @param XMLWriter $objWriter - * @param Group $group - * @throws \Exception + * Writes a group of shapes. */ - public function writeShapeGroup(XMLWriter $objWriter, Group $group) + protected function writeShapeGroup(XMLWriter $objWriter, Group $group): void { // draw:g $objWriter->startElement('draw:g'); @@ -785,12 +823,9 @@ class Content extends AbstractDecoratorWriter } /** - * Writes the style information for a group of shapes - * - * @param XMLWriter $objWriter - * @param Group $group + * Writes the style information for a group of shapes. */ - public function writeGroupStyle(XMLWriter $objWriter, Group $group) + protected function writeGroupStyle(XMLWriter $objWriter, Group $group): void { $shapes = $group->getShapeCollection(); foreach ($shapes as $shape) { @@ -814,12 +849,9 @@ class Content extends AbstractDecoratorWriter } /** - * Write the default style information for a RichText shape - * - * @param \PhpOffice\Common\XMLWriter $objWriter - * @param \PhpOffice\PhpPresentation\Shape\RichText $shape + * Write the default style information for a RichText shape. */ - public function writeTxtStyle(XMLWriter $objWriter, RichText $shape) + protected function writeTxtStyle(XMLWriter $objWriter, RichText $shape): void { // style:style $objWriter->startElement('style:style'); @@ -841,24 +873,24 @@ class Content extends AbstractDecoratorWriter case Fill::FILL_GRADIENT_LINEAR: case Fill::FILL_GRADIENT_PATH: $objWriter->writeAttribute('draw:fill', 'gradient'); - $objWriter->writeAttribute('draw:fill-gradient-name', 'gradient_'.$shape->getFill()->getHashCode()); + $objWriter->writeAttribute('draw:fill-gradient-name', 'gradient_' . $shape->getFill()->getHashCode()); break; case Fill::FILL_SOLID: $objWriter->writeAttribute('draw:fill', 'solid'); - $objWriter->writeAttribute('draw:fill-color', '#'.$shape->getFill()->getStartColor()->getRGB()); + $objWriter->writeAttribute('draw:fill-color', '#' . $shape->getFill()->getStartColor()->getRGB()); break; case Fill::FILL_NONE: default: $objWriter->writeAttribute('draw:fill', 'none'); - $objWriter->writeAttribute('draw:fill-color', '#'.$shape->getFill()->getStartColor()->getRGB()); + $objWriter->writeAttribute('draw:fill-color', '#' . $shape->getFill()->getStartColor()->getRGB()); break; } // Border - if ($shape->getBorder()->getLineStyle() == Border::LINE_NONE) { + if (Border::LINE_NONE == $shape->getBorder()->getLineStyle()) { $objWriter->writeAttribute('draw:stroke', 'none'); } else { - $objWriter->writeAttribute('svg:stroke-color', '#'.$shape->getBorder()->getColor()->getRGB()); - $objWriter->writeAttribute('svg:stroke-width', number_format(CommonDrawing::pointsToCentimeters($shape->getBorder()->getLineWidth()), 3, '.', '').'cm'); + $objWriter->writeAttribute('svg:stroke-color', '#' . $shape->getBorder()->getColor()->getRGB()); + $objWriter->writeAttribute('svg:stroke-width', number_format(CommonDrawing::pointsToCentimeters($shape->getBorder()->getLineWidth()), 3, '.', '') . 'cm'); switch ($shape->getBorder()->getDashStyle()) { case Border::DASH_SOLID: $objWriter->writeAttribute('draw:stroke', 'solid'); @@ -874,7 +906,7 @@ class Content extends AbstractDecoratorWriter case Border::DASH_SYSDASHDOTDOT: case Border::DASH_SYSDOT: $objWriter->writeAttribute('draw:stroke', 'dash'); - $objWriter->writeAttribute('draw:stroke-dash', 'strokeDash_'.$shape->getBorder()->getDashStyle()); + $objWriter->writeAttribute('draw:stroke-dash', 'strokeDash_' . $shape->getBorder()->getDashStyle()); break; default: $objWriter->writeAttribute('draw:stroke', 'none'); @@ -888,7 +920,7 @@ class Content extends AbstractDecoratorWriter // > style:style $objWriter->endElement(); - $paragraphs = $shape->getParagraphs(); + $paragraphs = $shape->getParagraphs(); $paragraphId = 0; foreach ($paragraphs as $paragraph) { ++$paragraphId; @@ -902,14 +934,14 @@ class Content extends AbstractDecoratorWriter $bulletStyleHashCode = $paragraph->getBulletStyle()->getHashCode(); if (!isset($this->arrStyleBullet[$bulletStyleHashCode])) { $this->arrStyleBullet[$bulletStyleHashCode]['oStyle'] = $paragraph->getBulletStyle(); - $this->arrStyleBullet[$bulletStyleHashCode]['level'] = ''; + $this->arrStyleBullet[$bulletStyleHashCode]['level'] = ''; } - if (strpos($this->arrStyleBullet[$bulletStyleHashCode]['level'], ';' . $paragraph->getAlignment()->getLevel()) === false) { + if (false === strpos($this->arrStyleBullet[$bulletStyleHashCode]['level'], ';' . $paragraph->getAlignment()->getLevel())) { $this->arrStyleBullet[$bulletStyleHashCode]['level'] .= ';' . $paragraph->getAlignment()->getLevel(); $this->arrStyleBullet[$bulletStyleHashCode]['oAlign_' . $paragraph->getAlignment()->getLevel()] = $paragraph->getAlignment(); } - $richtexts = $paragraph->getRichTextElements(); + $richtexts = $paragraph->getRichTextElements(); $richtextId = 0; foreach ($richtexts as $richtext) { ++$richtextId; @@ -925,12 +957,9 @@ class Content extends AbstractDecoratorWriter } /** - * Write the default style information for an AbstractDrawingAdapter - * - * @param \PhpOffice\Common\XMLWriter $objWriter - * @param AbstractDrawingAdapter $shape + * Write the default style information for an AbstractDrawingAdapter. */ - public function writeDrawingStyle(XMLWriter $objWriter, AbstractDrawingAdapter $shape) + protected function writeDrawingStyle(XMLWriter $objWriter, AbstractDrawingAdapter $shape): void { // style:style $objWriter->startElement('style:style'); @@ -951,11 +980,8 @@ class Content extends AbstractDecoratorWriter /** * Write the default style information for a Line shape. - * - * @param XMLWriter $objWriter - * @param Line $shape */ - public function writeLineStyle(XMLWriter $objWriter, Line $shape) + protected function writeLineStyle(XMLWriter $objWriter, Line $shape): void { // style:style $objWriter->startElement('style:style'); @@ -977,30 +1003,27 @@ class Content extends AbstractDecoratorWriter $objWriter->writeAttribute('draw:stroke', 'none'); break; } - $objWriter->writeAttribute('svg:stroke-color', '#'.$shape->getBorder()->getColor()->getRGB()); - $objWriter->writeAttribute('svg:stroke-width', Text::numberFormat(CommonDrawing::pixelsToCentimeters((CommonDrawing::pointsToPixels($shape->getBorder()->getLineWidth()))), 3).'cm'); + $objWriter->writeAttribute('svg:stroke-color', '#' . $shape->getBorder()->getColor()->getRGB()); + $objWriter->writeAttribute('svg:stroke-width', Text::numberFormat(CommonDrawing::pointsToCentimeters($shape->getBorder()->getLineWidth()), 3) . 'cm'); $objWriter->endElement(); $objWriter->endElement(); } /** - * Write the default style information for a Table shape - * - * @param XMLWriter $objWriter - * @param Table $shape + * Write the default style information for a Table shape. */ - public function writeTableStyle(XMLWriter $objWriter, Table $shape) + protected function writeTableStyle(XMLWriter $objWriter, Table $shape): void { foreach ($shape->getRows() as $keyRow => $shapeRow) { // style:style $objWriter->startElement('style:style'); - $objWriter->writeAttribute('style:name', 'gr' . $this->shapeId.'r'.$keyRow); + $objWriter->writeAttribute('style:name', 'gr' . $this->shapeId . 'r' . $keyRow); $objWriter->writeAttribute('style:family', 'table-row'); // style:table-row-properties $objWriter->startElement('style:table-row-properties'); - $objWriter->writeAttribute('style:row-height', Text::numberFormat(CommonDrawing::pixelsToCentimeters(CommonDrawing::pointsToPixels($shapeRow->getHeight())), 3).'cm'); + $objWriter->writeAttribute('style:row-height', Text::numberFormat(CommonDrawing::pointsToCentimeters($shapeRow->getHeight()), 3) . 'cm'); $objWriter->endElement(); $objWriter->endElement(); @@ -1008,22 +1031,22 @@ class Content extends AbstractDecoratorWriter foreach ($shapeRow->getCells() as $keyCell => $shapeCell) { // style:style $objWriter->startElement('style:style'); - $objWriter->writeAttribute('style:name', 'gr' . $this->shapeId.'r'.$keyRow.'c'.$keyCell); + $objWriter->writeAttribute('style:name', 'gr' . $this->shapeId . 'r' . $keyRow . 'c' . $keyCell); $objWriter->writeAttribute('style:family', 'table-cell'); - /** + /* * Note : This element is not valid in the Schema 1.2 */ // style:graphic-properties - if ($shapeCell->getFill()->getFillType() != Fill::FILL_NONE) { + if (Fill::FILL_NONE != $shapeCell->getFill()->getFillType()) { $objWriter->startElement('style:graphic-properties'); - if ($shapeCell->getFill()->getFillType() == Fill::FILL_SOLID) { + if (Fill::FILL_SOLID == $shapeCell->getFill()->getFillType()) { $objWriter->writeAttribute('draw:fill', 'solid'); - $objWriter->writeAttribute('draw:fill-color', '#'.$shapeCell->getFill()->getStartColor()->getRGB()); + $objWriter->writeAttribute('draw:fill-color', '#' . $shapeCell->getFill()->getStartColor()->getRGB()); } - if ($shapeCell->getFill()->getFillType() == Fill::FILL_GRADIENT_LINEAR) { + if (Fill::FILL_GRADIENT_LINEAR == $shapeCell->getFill()->getFillType()) { $objWriter->writeAttribute('draw:fill', 'gradient'); - $objWriter->writeAttribute('draw:fill-gradient-name', 'gradient_'.$shapeCell->getFill()->getHashCode()); + $objWriter->writeAttribute('draw:fill-gradient-name', 'gradient_' . $shapeCell->getFill()->getHashCode()); } $objWriter->endElement(); } @@ -1043,7 +1066,7 @@ class Content extends AbstractDecoratorWriter case Border::LINE_SINGLE: $lineStyle = 'solid'; } - $objWriter->writeAttribute('fo:border', $lineWidth.'pt '.$lineStyle.' #'.$lineColor); + $objWriter->writeAttribute('fo:border', $lineWidth . 'pt ' . $lineStyle . ' #' . $lineColor); } else { $lineStyle = 'none'; $lineWidth = Text::numberFormat($cellBorders->getBottom()->getLineWidth() / 1.75, 2); @@ -1052,7 +1075,7 @@ class Content extends AbstractDecoratorWriter case Border::LINE_SINGLE: $lineStyle = 'solid'; } - $objWriter->writeAttribute('fo:border-bottom', $lineWidth.'pt '.$lineStyle.' #'.$lineColor); + $objWriter->writeAttribute('fo:border-bottom', $lineWidth . 'pt ' . $lineStyle . ' #' . $lineColor); // TOP $lineStyle = 'none'; $lineWidth = Text::numberFormat($cellBorders->getTop()->getLineWidth() / 1.75, 2); @@ -1061,7 +1084,7 @@ class Content extends AbstractDecoratorWriter case Border::LINE_SINGLE: $lineStyle = 'solid'; } - $objWriter->writeAttribute('fo:border-top', $lineWidth.'pt '.$lineStyle.' #'.$lineColor); + $objWriter->writeAttribute('fo:border-top', $lineWidth . 'pt ' . $lineStyle . ' #' . $lineColor); // RIGHT $lineStyle = 'none'; $lineWidth = Text::numberFormat($cellBorders->getRight()->getLineWidth() / 1.75, 2); @@ -1070,7 +1093,7 @@ class Content extends AbstractDecoratorWriter case Border::LINE_SINGLE: $lineStyle = 'solid'; } - $objWriter->writeAttribute('fo:border-right', $lineWidth.'pt '.$lineStyle.' #'.$lineColor); + $objWriter->writeAttribute('fo:border-right', $lineWidth . 'pt ' . $lineStyle . ' #' . $lineColor); // LEFT $lineStyle = 'none'; $lineWidth = Text::numberFormat($cellBorders->getLeft()->getLineWidth() / 1.75, 2); @@ -1079,7 +1102,7 @@ class Content extends AbstractDecoratorWriter case Border::LINE_SINGLE: $lineStyle = 'solid'; } - $objWriter->writeAttribute('fo:border-left', $lineWidth.'pt '.$lineStyle.' #'.$lineColor); + $objWriter->writeAttribute('fo:border-left', $lineWidth . 'pt ' . $lineStyle . ' #' . $lineColor); } // >style:paragraph-properties $objWriter->endElement(); @@ -1101,12 +1124,9 @@ class Content extends AbstractDecoratorWriter } /** - * Write the slide note - * @param XMLWriter $objWriter - * @param \PhpOffice\PhpPresentation\Slide\Note $note - * @throws \Exception + * Write the slide note. */ - public function writeSlideNote(XMLWriter $objWriter, Note $note) + protected function writeSlideNote(XMLWriter $objWriter, Note $note): void { $shapesNote = $note->getShapeCollection(); if (count($shapesNote) > 0) { @@ -1126,22 +1146,19 @@ class Content extends AbstractDecoratorWriter } /** - * Write style of a slide - * @param XMLWriter $objWriter - * @param Slide $slide - * @param int $incPage + * Write style of a slide. */ - public function writeStyleSlide(XMLWriter $objWriter, Slide $slide, $incPage) + protected function writeStyleSlide(XMLWriter $objWriter, Slide $slide, int $incPage): void { // style:style $objWriter->startElement('style:style'); $objWriter->writeAttribute('style:family', 'drawing-page'); - $objWriter->writeAttribute('style:name', 'stylePage'.$incPage); + $objWriter->writeAttribute('style:name', 'stylePage' . $incPage); // style:style/style:drawing-page-properties $objWriter->startElement('style:drawing-page-properties'); $objWriter->writeAttributeIf(!$slide->isVisible(), 'presentation:visibility', 'hidden'); if (!is_null($oTransition = $slide->getTransition())) { - $objWriter->writeAttribute('presentation:duration', 'PT'.number_format($oTransition->getAdvanceTimeTrigger() / 1000, 6, '.', '').'S'); + $objWriter->writeAttribute('presentation:duration', 'PT' . number_format($oTransition->getAdvanceTimeTrigger() / 1000, 6, '.', '') . 'S'); $objWriter->writeAttributeIf($oTransition->hasManualTrigger(), 'presentation:transition-type', 'manual'); $objWriter->writeAttributeIf($oTransition->hasTimeTrigger(), 'presentation:transition-type', 'automatic'); switch ($oTransition->getSpeed()) { @@ -1156,7 +1173,7 @@ class Content extends AbstractDecoratorWriter break; } - /** + /* * http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#property-presentation_transition-style */ switch ($oTransition->getTransitionType()) { @@ -1312,7 +1329,7 @@ class Content extends AbstractDecoratorWriter } if ($oBackground instanceof Slide\Background\Image) { $objWriter->writeAttribute('draw:fill', 'bitmap'); - $objWriter->writeAttribute('draw:fill-image-name', 'background_'.$incPage); + $objWriter->writeAttribute('draw:fill-image-name', 'background_' . $incPage); $objWriter->writeAttribute('style:repeat', 'stretch'); } } @@ -1321,14 +1338,9 @@ class Content extends AbstractDecoratorWriter $objWriter->endElement(); } - - /** - * @param XMLWriter $objWriter - * @param Fill $oFill - */ - protected function writeStylePartFill(XMLWriter $objWriter, $oFill) + protected function writeStylePartFill(XMLWriter $objWriter, ?Fill $oFill): void { - if (!($oFill instanceof Fill)) { + if (!$oFill) { return; } switch ($oFill->getFillType()) { @@ -1343,13 +1355,10 @@ class Content extends AbstractDecoratorWriter } } - /** - * @param XMLWriter $objWriter - * @param Shadow $oShadow * @todo Improve for supporting any direction (https://sinepost.wordpress.com/2012/02/16/theyve-got-atan-you-want-atan2/) */ - protected function writeStylePartShadow(XMLWriter $objWriter, Shadow $oShadow) + protected function writeStylePartShadow(XMLWriter $objWriter, Shadow $oShadow): void { if (!$oShadow->isVisible()) { return; @@ -1357,29 +1366,29 @@ class Content extends AbstractDecoratorWriter $objWriter->writeAttribute('draw:shadow', 'visible'); $objWriter->writeAttribute('draw:shadow-color', '#' . $oShadow->getColor()->getRGB()); - $distanceCms = CommonDrawing::pixelsToCentimeters($oShadow->getDistance()); - if ($oShadow->getDirection() == 0 || $oShadow->getDirection() == 360) { + $distanceCms = CommonDrawing::pixelsToCentimeters((int) $oShadow->getDistance()); + if (0 == $oShadow->getDirection() || 360 == $oShadow->getDirection()) { $objWriter->writeAttribute('draw:shadow-offset-x', $distanceCms . 'cm'); $objWriter->writeAttribute('draw:shadow-offset-y', '0cm'); - } elseif ($oShadow->getDirection() == 45) { + } elseif (45 == $oShadow->getDirection()) { $objWriter->writeAttribute('draw:shadow-offset-x', $distanceCms . 'cm'); $objWriter->writeAttribute('draw:shadow-offset-y', $distanceCms . 'cm'); - } elseif ($oShadow->getDirection() == 90) { + } elseif (90 == $oShadow->getDirection()) { $objWriter->writeAttribute('draw:shadow-offset-x', '0cm'); $objWriter->writeAttribute('draw:shadow-offset-y', $distanceCms . 'cm'); - } elseif ($oShadow->getDirection() == 135) { + } elseif (135 == $oShadow->getDirection()) { $objWriter->writeAttribute('draw:shadow-offset-x', '-' . $distanceCms . 'cm'); $objWriter->writeAttribute('draw:shadow-offset-y', $distanceCms . 'cm'); - } elseif ($oShadow->getDirection() == 180) { + } elseif (180 == $oShadow->getDirection()) { $objWriter->writeAttribute('draw:shadow-offset-x', '-' . $distanceCms . 'cm'); $objWriter->writeAttribute('draw:shadow-offset-y', '0cm'); - } elseif ($oShadow->getDirection() == 225) { + } elseif (225 == $oShadow->getDirection()) { $objWriter->writeAttribute('draw:shadow-offset-x', '-' . $distanceCms . 'cm'); $objWriter->writeAttribute('draw:shadow-offset-y', '-' . $distanceCms . 'cm'); - } elseif ($oShadow->getDirection() == 270) { + } elseif (270 == $oShadow->getDirection()) { $objWriter->writeAttribute('draw:shadow-offset-x', '0cm'); $objWriter->writeAttribute('draw:shadow-offset-y', '-' . $distanceCms . 'cm'); - } elseif ($oShadow->getDirection() == 315) { + } elseif (315 == $oShadow->getDirection()) { $objWriter->writeAttribute('draw:shadow-offset-x', $distanceCms . 'cm'); $objWriter->writeAttribute('draw:shadow-offset-y', '-' . $distanceCms . 'cm'); } diff --git a/PhpOffice/PhpPresentation/Writer/ODPresentation/Meta.php b/PhpOffice/PhpPresentation/Writer/ODPresentation/Meta.php old mode 100755 new mode 100644 index 751a420..d976503 --- a/PhpOffice/PhpPresentation/Writer/ODPresentation/Meta.php +++ b/PhpOffice/PhpPresentation/Writer/ODPresentation/Meta.php @@ -1,16 +1,35 @@ writeElement('meta:keyword', $this->getPresentation()->getDocumentProperties()->getKeywords()); + // meta:user-defined + $oDocumentProperties = $this->oPresentation->getDocumentProperties(); + foreach ($oDocumentProperties->getCustomProperties() as $customProperty) { + $propertyValue = $oDocumentProperties->getCustomPropertyValue($customProperty); + $propertyType = $oDocumentProperties->getCustomPropertyType($customProperty); + + $objWriter->startElement('meta:user-defined'); + $objWriter->writeAttribute('meta:name', $customProperty); + switch ($propertyType) { + case DocumentProperties::PROPERTY_TYPE_INTEGER: + case DocumentProperties::PROPERTY_TYPE_FLOAT: + $objWriter->writeAttribute('meta:value-type', 'float'); + $objWriter->writeRaw((string) $propertyValue); + break; + case DocumentProperties::PROPERTY_TYPE_BOOLEAN: + $objWriter->writeAttribute('meta:value-type', 'boolean'); + $objWriter->writeRaw($propertyValue ? 'true' : 'false'); + break; + case DocumentProperties::PROPERTY_TYPE_DATE: + $objWriter->writeAttribute('meta:value-type', 'date'); + $objWriter->writeRaw(date(DATE_W3C, (int) $propertyValue)); + break; + case DocumentProperties::PROPERTY_TYPE_STRING: + case DocumentProperties::PROPERTY_TYPE_UNKNOWN: + default: + $objWriter->writeAttribute('meta:value-type', 'string'); + $objWriter->writeRaw((string) $propertyValue); + break; + } + $objWriter->endElement(); + } + // @todo : Where these properties are written ? // $this->getPresentation()->getDocumentProperties()->getCategory() // $this->getPresentation()->getDocumentProperties()->getCompany() @@ -58,8 +109,9 @@ class Meta extends AbstractDecoratorWriter $objWriter->endElement(); $objWriter->endElement(); - + $this->getZip()->addFromString('meta.xml', $objWriter->getData()); + return $this->getZip(); } } diff --git a/PhpOffice/PhpPresentation/Writer/ODPresentation/MetaInfManifest.php b/PhpOffice/PhpPresentation/Writer/ODPresentation/MetaInfManifest.php old mode 100755 new mode 100644 index 9a263db..49f023e --- a/PhpOffice/PhpPresentation/Writer/ODPresentation/MetaInfManifest.php +++ b/PhpOffice/PhpPresentation/Writer/ODPresentation/MetaInfManifest.php @@ -1,4 +1,22 @@ getArrayChart() as $key => $shape) { $objWriter->startElement('manifest:file-entry'); $objWriter->writeAttribute('manifest:media-type', 'application/vnd.oasis.opendocument.chart'); - $objWriter->writeAttribute('manifest:full-path', 'Object '.$key.'/'); + $objWriter->writeAttribute('manifest:full-path', 'Object ' . $key . '/'); $objWriter->endElement(); $objWriter->startElement('manifest:file-entry'); $objWriter->writeAttribute('manifest:media-type', 'text/xml'); - $objWriter->writeAttribute('manifest:full-path', 'Object '.$key.'/content.xml'); + $objWriter->writeAttribute('manifest:full-path', 'Object ' . $key . '/content.xml'); $objWriter->endElement(); } - $arrMedia = array(); + $arrMedia = []; for ($i = 0; $i < $this->getDrawingHashTable()->count(); ++$i) { $shape = $this->getDrawingHashTable()->getByIndex($i); - if (! ($shape instanceof ShapeDrawing\AbstractDrawingAdapter)) { + if (!($shape instanceof ShapeDrawing\AbstractDrawingAdapter)) { continue; } $arrMedia[] = $shape->getIndexedFilename(); @@ -78,11 +94,11 @@ class MetaInfManifest extends AbstractDecoratorWriter $oBkgImage = $oSlide->getBackground(); if ($oBkgImage instanceof Image) { $arrayImage = getimagesize($oBkgImage->getPath()); - $mimeType = image_type_to_mime_type($arrayImage[2]); + $mimeType = image_type_to_mime_type($arrayImage[2]); $objWriter->startElement('manifest:file-entry'); $objWriter->writeAttribute('manifest:media-type', $mimeType); - $objWriter->writeAttribute('manifest:full-path', 'Pictures/' . str_replace(' ', '_', $oBkgImage->getIndexedFilename($numSlide))); + $objWriter->writeAttribute('manifest:full-path', 'Pictures/' . str_replace(' ', '_', $oBkgImage->getIndexedFilename((string) $numSlide))); $objWriter->endElement(); } } @@ -104,6 +120,7 @@ class MetaInfManifest extends AbstractDecoratorWriter $objWriter->endElement(); $this->getZip()->addFromString('META-INF/manifest.xml', $objWriter->getData()); + return $this->getZip(); } } diff --git a/PhpOffice/PhpPresentation/Writer/ODPresentation/Mimetype.php b/PhpOffice/PhpPresentation/Writer/ODPresentation/Mimetype.php old mode 100755 new mode 100644 index 1efd13e..8010124 --- a/PhpOffice/PhpPresentation/Writer/ODPresentation/Mimetype.php +++ b/PhpOffice/PhpPresentation/Writer/ODPresentation/Mimetype.php @@ -1,4 +1,22 @@ getZip()->addFromString('mimetype', 'application/vnd.oasis.opendocument.presentation'); + return $this->getZip(); } } diff --git a/PhpOffice/PhpPresentation/Writer/ODPresentation/ObjectsChart.php b/PhpOffice/PhpPresentation/Writer/ODPresentation/ObjectsChart.php old mode 100755 new mode 100644 index 2b827ba..12ded07 --- a/PhpOffice/PhpPresentation/Writer/ODPresentation/ObjectsChart.php +++ b/PhpOffice/PhpPresentation/Writer/ODPresentation/ObjectsChart.php @@ -1,4 +1,22 @@ getArrayChart() as $keyChart => $shapeChart) { $content = $this->writeContentPart($shapeChart); if (!empty($content)) { - $this->getZip()->addFromString('Object '.$keyChart.'/content.xml', $content); + $this->getZip()->addFromString('Object ' . $keyChart . '/content.xml', $content); } } return $this->getZip(); } - /** - * @param Chart $chart - * @return string - * @throws \Exception - */ - protected function writeContentPart(Chart $chart) + protected function writeContentPart(Chart $chart): string { $this->xmlContent = new XMLWriter(XMLWriter::STORAGE_MEMORY); $chartType = $chart->getPlotArea()->getType(); // Data - $this->arrayData = array(); - $this->arrayTitle = array(); + $this->arrayData = []; + $this->arrayTitle = []; $this->numData = 0; foreach ($chartType->getSeries() as $series) { $inc = 0; $this->arrayTitle[] = $series->getTitle(); foreach ($series->getValues() as $key => $value) { if (!isset($this->arrayData[$inc])) { - $this->arrayData[$inc] = array(); + $this->arrayData[$inc] = []; } if (empty($this->arrayData[$inc])) { $this->arrayData[$inc][] = $key; } $this->arrayData[$inc][] = $value; - $inc++; + ++$inc; } if ($inc > $this->numData) { $this->numData = $inc; @@ -145,7 +158,7 @@ class ObjectsChart extends AbstractDecoratorWriter $this->numSeries = 0; foreach ($chartType->getSeries() as $series) { $this->writeSeriesStyle($chart, $series); - $this->numSeries++; + ++$this->numSeries; } $this->writeFloorStyle(); $this->writeLegendStyle($chart); @@ -162,8 +175,8 @@ class ObjectsChart extends AbstractDecoratorWriter $this->xmlContent->startElement('office:chart'); // office:chart $this->xmlContent->startElement('chart:chart'); - $this->xmlContent->writeAttribute('svg:width', Text::numberFormat(CommonDrawing::pixelsToCentimeters($chart->getWidth()), 3) . 'cm'); - $this->xmlContent->writeAttribute('svg:height', Text::numberFormat(CommonDrawing::pixelsToCentimeters($chart->getHeight()), 3) . 'cm'); + $this->xmlContent->writeAttribute('svg:width', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $chart->getWidth()), 3) . 'cm'); + $this->xmlContent->writeAttribute('svg:height', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $chart->getHeight()), 3) . 'cm'); $this->xmlContent->writeAttribute('xlink:href', '.'); $this->xmlContent->writeAttribute('xlink:type', 'simple'); $this->xmlContent->writeAttribute('chart:style-name', 'styleChart'); @@ -174,6 +187,7 @@ class ObjectsChart extends AbstractDecoratorWriter } $this->xmlContent->writeAttributeIf($chartType instanceof Doughnut, 'chart:class', 'chart:ring'); $this->xmlContent->writeAttributeIf($chartType instanceof Line, 'chart:class', 'chart:line'); + $this->xmlContent->writeAttributeIf($chartType instanceof Radar, 'chart:class', 'chart:radar'); $this->xmlContent->writeAttributeIf($chartType instanceof Scatter, 'chart:class', 'chart:scatter'); $this->writeTitle($chart->getTitle()); @@ -193,11 +207,7 @@ class ObjectsChart extends AbstractDecoratorWriter return $this->xmlContent->getData(); } - /** - * @param Chart $chart - * @throws \Exception - */ - private function writeAxis(Chart $chart) + protected function writeAxis(Chart $chart): void { $chartType = $chart->getPlotArea()->getType(); @@ -206,9 +216,16 @@ class ObjectsChart extends AbstractDecoratorWriter $this->xmlContent->writeAttribute('chart:dimension', 'x'); $this->xmlContent->writeAttribute('chart:name', 'primary-x'); $this->xmlContent->writeAttribute('chart:style-name', 'styleAxisX'); + // chart:axis > chart:title + if ($chart->getPlotArea()->getAxisX()->isVisible()) { + $this->xmlContent->startElement('chart:title'); + $this->xmlContent->writeAttribute('chart:style-name', 'styleAxisXTitle'); + $this->xmlContent->writeElement('text:p', $chart->getPlotArea()->getAxisX()->getTitle()); + $this->xmlContent->endElement(); + } // chart:axis > chart:categories $this->xmlContent->startElement('chart:categories'); - $this->xmlContent->writeAttribute('table:cell-range-address', 'table-local.$A$2:.$A$'.($this->numData+1)); + $this->xmlContent->writeAttribute('table:cell-range-address', 'table-local.$A$2:.$A$' . ($this->numData + 1)); $this->xmlContent->endElement(); // chart:axis > chart:grid $this->writeGridline($chart->getPlotArea()->getAxisX()->getMajorGridlines(), 'styleAxisXGridlinesMajor', 'major'); @@ -222,6 +239,13 @@ class ObjectsChart extends AbstractDecoratorWriter $this->xmlContent->writeAttribute('chart:dimension', 'y'); $this->xmlContent->writeAttribute('chart:name', 'primary-y'); $this->xmlContent->writeAttribute('chart:style-name', 'styleAxisY'); + // chart:axis > chart:title + if ($chart->getPlotArea()->getAxisY()->isVisible()) { + $this->xmlContent->startElement('chart:title'); + $this->xmlContent->writeAttribute('chart:style-name', 'styleAxisYTitle'); + $this->xmlContent->writeElement('text:p', $chart->getPlotArea()->getAxisY()->getTitle()); + $this->xmlContent->endElement(); + } // chart:axis > chart:grid $this->writeGridline($chart->getPlotArea()->getAxisY()->getMajorGridlines(), 'styleAxisYGridlinesMajor', 'major'); // chart:axis > chart:grid @@ -239,10 +263,10 @@ class ObjectsChart extends AbstractDecoratorWriter } } - protected function writeGridline($oGridlines, $styleName, $chartClass) + protected function writeGridline(?Chart\Gridlines $oGridlines, string $styleName, string $chartClass): void { - if (!($oGridlines instanceof Chart\Gridlines)) { - return ; + if (!$oGridlines) { + return; } $this->xmlContent->startElement('chart:grid'); @@ -252,50 +276,17 @@ class ObjectsChart extends AbstractDecoratorWriter } /** - * @param Chart $chart - * @throws \Exception * @todo Set function in \PhpPresentation\Shape\Chart\Axis for defining width and color of the axis */ - protected function writeAxisStyle(Chart $chart) + protected function writeAxisStyle(Chart $chart): void { $chartType = $chart->getPlotArea()->getType(); // AxisX - // style:style - $this->xmlContent->startElement('style:style'); - $this->xmlContent->writeAttribute('style:name', 'styleAxisX'); - $this->xmlContent->writeAttribute('style:family', 'chart'); - // style:style > style:chart-properties - $this->xmlContent->startElement('style:chart-properties'); - $this->xmlContent->writeAttribute('chart:display-label', 'true'); - $this->xmlContent->writeAttribute('chart:tick-marks-major-inner', 'false'); - $this->xmlContent->writeAttribute('chart:tick-marks-major-outer', 'false'); - if ($chartType instanceof AbstractTypePie) { - $this->xmlContent->writeAttribute('chart:reverse-direction', 'true'); - } - if ($chart->getPlotArea()->getAxisX()->getMinBounds() != null) { - $this->xmlContent->writeAttribute('chart:minimum', $chart->getPlotArea()->getAxisX()->getMinBounds()); - } - if ($chart->getPlotArea()->getAxisX()->getMaxBounds() != null) { - $this->xmlContent->writeAttribute('chart:maximum', $chart->getPlotArea()->getAxisX()->getMaxBounds()); - } - $this->xmlContent->endElement(); - // style:style > style:graphic-properties - $this->xmlContent->startElement('style:graphic-properties'); - $this->xmlContent->writeAttribute('draw:stroke', 'solid'); - $this->xmlContent->writeAttribute('svg:stroke-width', '0.026cm'); - $this->xmlContent->writeAttribute('svg:stroke-color', '#878787'); - $this->xmlContent->endElement(); - // style:style > style:text-properties - $oFont = $chart->getPlotArea()->getAxisX()->getFont(); - $this->xmlContent->startElement('style:text-properties'); - $this->xmlContent->writeAttribute('fo:color', '#'.$oFont->getColor()->getRGB()); - $this->xmlContent->writeAttribute('fo:font-family', $oFont->getName()); - $this->xmlContent->writeAttribute('fo:font-size', $oFont->getSize().'pt'); - $this->xmlContent->writeAttribute('fo:font-style', $oFont->isItalic() ? 'italic' : 'normal'); - $this->xmlContent->endElement(); - // ##style:style - $this->xmlContent->endElement(); + $this->writeAxisMainStyle($chart->getPlotArea()->getAxisX(), 'styleAxisX', $chartType); + + // AxisX Title + $this->writeAxisTitleStyle($chart->getPlotArea()->getAxisX(), 'styleAxisXTitle'); // AxisX GridLines Major $this->writeGridlineStyle($chart->getPlotArea()->getAxisX()->getMajorGridlines(), 'styleAxisXGridlinesMajor'); @@ -304,41 +295,10 @@ class ObjectsChart extends AbstractDecoratorWriter $this->writeGridlineStyle($chart->getPlotArea()->getAxisX()->getMinorGridlines(), 'styleAxisXGridlinesMinor'); // AxisY - // style:style - $this->xmlContent->startElement('style:style'); - $this->xmlContent->writeAttribute('style:name', 'styleAxisY'); - $this->xmlContent->writeAttribute('style:family', 'chart'); - // style:style > style:chart-properties - $this->xmlContent->startElement('style:chart-properties'); - $this->xmlContent->writeAttribute('chart:display-label', 'true'); - $this->xmlContent->writeAttribute('chart:tick-marks-major-inner', 'false'); - $this->xmlContent->writeAttribute('chart:tick-marks-major-outer', 'false'); - if ($chartType instanceof AbstractTypePie) { - $this->xmlContent->writeAttribute('chart:reverse-direction', 'true'); - } - if ($chart->getPlotArea()->getAxisY()->getMinBounds() !== null) { - $this->xmlContent->writeAttribute('chart:minimum', $chart->getPlotArea()->getAxisY()->getMinBounds()); - } - if ($chart->getPlotArea()->getAxisY()->getMaxBounds() !== null) { - $this->xmlContent->writeAttribute('chart:maximum', $chart->getPlotArea()->getAxisY()->getMaxBounds()); - } - $this->xmlContent->endElement(); - // style:graphic-properties - $this->xmlContent->startElement('style:graphic-properties'); - $this->xmlContent->writeAttribute('draw:stroke', 'solid'); - $this->xmlContent->writeAttribute('svg:stroke-width', '0.026cm'); - $this->xmlContent->writeAttribute('svg:stroke-color', '#878787'); - $this->xmlContent->endElement(); - // style:style > style:text-properties - $oFont = $chart->getPlotArea()->getAxisY()->getFont(); - $this->xmlContent->startElement('style:text-properties'); - $this->xmlContent->writeAttribute('fo:color', '#'.$oFont->getColor()->getRGB()); - $this->xmlContent->writeAttribute('fo:font-family', $oFont->getName()); - $this->xmlContent->writeAttribute('fo:font-size', $oFont->getSize().'pt'); - $this->xmlContent->writeAttribute('fo:font-style', $oFont->isItalic() ? 'italic' : 'normal'); - $this->xmlContent->endElement(); - // ## style:style - $this->xmlContent->endElement(); + $this->writeAxisMainStyle($chart->getPlotArea()->getAxisY(), 'styleAxisY', $chartType); + + // AxisY Title + $this->writeAxisTitleStyle($chart->getPlotArea()->getAxisY(), 'styleAxisYTitle'); // AxisY GridLines Major $this->writeGridlineStyle($chart->getPlotArea()->getAxisY()->getMajorGridlines(), 'styleAxisYGridlinesMajor'); @@ -347,13 +307,82 @@ class ObjectsChart extends AbstractDecoratorWriter $this->writeGridlineStyle($chart->getPlotArea()->getAxisY()->getMinorGridlines(), 'styleAxisYGridlinesMinor'); } - /** - * @param Chart\Gridlines $oGridlines - * @param string $styleName - */ - protected function writeGridlineStyle($oGridlines, $styleName) + protected function writeAxisMainStyle(Chart\Axis $axis, string $styleName, AbstractType $chartType): void { - if (!($oGridlines instanceof Chart\Gridlines)) { + // style:style + $this->xmlContent->startElement('style:style'); + $this->xmlContent->writeAttribute('style:name', $styleName); + $this->xmlContent->writeAttribute('style:family', 'chart'); + // style:style > style:chart-properties + $this->xmlContent->startElement('style:chart-properties'); + $this->xmlContent->writeAttribute('chart:display-label', 'true'); + $this->xmlContent->writeAttribute('chart:tick-marks-major-inner', 'false'); + $this->xmlContent->writeAttribute('chart:tick-marks-major-outer', 'false'); + $this->xmlContent->writeAttributeIf($chartType instanceof AbstractTypePie, 'chart:reverse-direction', 'true'); + $this->xmlContent->writeAttributeIf(null !== $axis->getMinBounds(), 'chart:minimum', $axis->getMinBounds()); + $this->xmlContent->writeAttributeIf(null !== $axis->getMaxBounds(), 'chart:maximum', $axis->getMaxBounds()); + $this->xmlContent->writeAttributeIf(null !== $axis->getMajorUnit(), 'chart:interval-major', $axis->getMajorUnit()); + $this->xmlContent->writeAttributeIf(null !== $axis->getMinorUnit(), 'chart:interval-minor-divisor', $axis->getMinorUnit()); + switch ($axis->getTickLabelPosition()) { + case Axis::TICK_LABEL_POSITION_NEXT_TO: + $this->xmlContent->writeAttribute('chart:axis-label-position', 'near-axis'); + break; + case Axis::TICK_LABEL_POSITION_HIGH: + $this->xmlContent->writeAttribute('chart:axis-position', '0'); + $this->xmlContent->writeAttribute('chart:axis-label-position', 'outside-end'); + break; + case Axis::TICK_LABEL_POSITION_LOW: + $this->xmlContent->writeAttribute('chart:axis-position', '0'); + $this->xmlContent->writeAttribute('chart:axis-label-position', 'outside-start'); + $this->xmlContent->writeAttribute('chart:tick-mark-position', 'at-axis'); + break; + } + $this->xmlContent->writeAttributeIf($chartType instanceof Radar && $styleName == 'styleAxisX', 'chart:reverse-direction', 'true'); + $this->xmlContent->endElement(); + // style:graphic-properties + $this->xmlContent->startElement('style:graphic-properties'); + $this->xmlContent->writeAttribute('draw:stroke', $axis->getOutline()->getFill()->getFillType()); + $this->xmlContent->writeAttribute('svg:stroke-width', number_format(CommonDrawing::pointsToCentimeters($axis->getOutline()->getWidth()), 3, '.', '') . 'cm'); + $this->xmlContent->writeAttribute('svg:stroke-color', '#' . $axis->getOutline()->getFill()->getStartColor()->getRGB()); + $this->xmlContent->endElement(); + // style:style > style:text-properties + $this->xmlContent->startElement('style:text-properties'); + $this->xmlContent->writeAttribute('fo:color', '#' . $axis->getFont()->getColor()->getRGB()); + $this->xmlContent->writeAttribute('fo:font-family', $axis->getFont()->getName()); + $this->xmlContent->writeAttribute('fo:font-size', $axis->getFont()->getSize() . 'pt'); + $this->xmlContent->writeAttribute('fo:font-style', $axis->getFont()->isItalic() ? 'italic' : 'normal'); + $this->xmlContent->endElement(); + // ## style:style + $this->xmlContent->endElement(); + } + + protected function writeAxisTitleStyle(Chart\Axis $axis, string $styleName): void + { + // style:style + $this->xmlContent->startElement('style:style'); + $this->xmlContent->writeAttribute('style:name', $styleName); + $this->xmlContent->writeAttribute('style:family', 'chart'); + // style:chart-properties + $this->xmlContent->startElement('style:chart-properties'); + $this->xmlContent->writeAttribute('chart:auto-position', 'true'); + $this->xmlContent->writeAttributeIf($axis->getTitleRotation() != 0, 'style:rotation-angle', '-' . $axis->getTitleRotation()); + // > style:chart-properties + $this->xmlContent->endElement(); + // style:text-properties + $this->xmlContent->startElement('style:text-properties'); + $this->xmlContent->writeAttribute('fo:color', '#' . $axis->getFont()->getColor()->getRGB()); + $this->xmlContent->writeAttribute('fo:font-family', $axis->getFont()->getName()); + $this->xmlContent->writeAttribute('fo:font-size', $axis->getFont()->getSize() . 'pt'); + $this->xmlContent->writeAttribute('fo:font-style', $axis->getFont()->isItalic() ? 'italic' : 'normal'); + // > style:text-properties + $this->xmlContent->endElement(); + // > style:style + $this->xmlContent->endElement(); + } + + protected function writeGridlineStyle(?Chart\Gridlines $oGridlines, string $styleName): void + { + if (!$oGridlines) { return; } // style:style @@ -362,17 +391,14 @@ class ObjectsChart extends AbstractDecoratorWriter $this->xmlContent->writeAttribute('style:family', 'chart'); // style:style > style:graphic-properties $this->xmlContent->startElement('style:graphic-properties'); - $this->xmlContent->writeAttribute('svg:stroke-width', number_format(CommonDrawing::pointsToCentimeters($oGridlines->getOutline()->getWidth()), 2, '.', '').'cm'); - $this->xmlContent->writeAttribute('svg:stroke-color', '#'.$oGridlines->getOutline()->getFill()->getStartColor()->getRGB()); + $this->xmlContent->writeAttribute('svg:stroke-width', number_format(CommonDrawing::pointsToCentimeters($oGridlines->getOutline()->getWidth()), 2, '.', '') . 'cm'); + $this->xmlContent->writeAttribute('svg:stroke-color', '#' . $oGridlines->getOutline()->getFill()->getStartColor()->getRGB()); $this->xmlContent->endElement(); // ##style:style $this->xmlContent->endElement(); } - /** - * @param Chart $chart - */ - private function writeChartStyle(Chart $chart) + protected function writeChartStyle(Chart $chart): void { // style:style $this->xmlContent->startElement('style:style'); @@ -381,14 +407,14 @@ class ObjectsChart extends AbstractDecoratorWriter // style:graphic-properties $this->xmlContent->startElement('style:graphic-properties'); $this->xmlContent->writeAttribute('draw:stroke', $chart->getFill()->getFillType()); - $this->xmlContent->writeAttribute('draw:fill-color', '#'.$chart->getFill()->getStartColor()->getRGB()); + $this->xmlContent->writeAttribute('draw:fill-color', '#' . $chart->getFill()->getStartColor()->getRGB()); // > style:graphic-properties $this->xmlContent->endElement(); // > style:style $this->xmlContent->endElement(); } - private function writeFloor() + protected function writeFloor(): void { // chart:floor $this->xmlContent->startElement('chart:floor'); @@ -397,7 +423,7 @@ class ObjectsChart extends AbstractDecoratorWriter $this->xmlContent->endElement(); } - private function writeFloorStyle() + protected function writeFloorStyle(): void { // style:style $this->xmlContent->startElement('style:style'); @@ -416,10 +442,7 @@ class ObjectsChart extends AbstractDecoratorWriter $this->xmlContent->endElement(); } - /** - * @param Chart $chart - */ - private function writeLegend(Chart $chart) + protected function writeLegend(Chart $chart): void { // chart:legend $this->xmlContent->startElement('chart:legend'); @@ -442,18 +465,15 @@ class ObjectsChart extends AbstractDecoratorWriter break; } $this->xmlContent->writeAttribute('chart:legend-position', $position); - $this->xmlContent->writeAttribute('svg:x', Text::numberFormat(CommonDrawing::pixelsToCentimeters($chart->getLegend()->getOffsetX()), 3) . 'cm'); - $this->xmlContent->writeAttribute('svg:y', Text::numberFormat(CommonDrawing::pixelsToCentimeters($chart->getLegend()->getOffsetY()), 3) . 'cm'); + $this->xmlContent->writeAttribute('svg:x', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $chart->getLegend()->getOffsetX()), 3) . 'cm'); + $this->xmlContent->writeAttribute('svg:y', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $chart->getLegend()->getOffsetY()), 3) . 'cm'); $this->xmlContent->writeAttribute('style:legend-expansion', 'high'); $this->xmlContent->writeAttribute('chart:style-name', 'styleLegend'); // > chart:legend $this->xmlContent->endElement(); } - /** - * @param Chart $chart - */ - private function writeLegendStyle(Chart $chart) + protected function writeLegendStyle(Chart $chart): void { // style:style $this->xmlContent->startElement('style:style'); @@ -466,9 +486,9 @@ class ObjectsChart extends AbstractDecoratorWriter $this->xmlContent->endElement(); // style:text-properties $this->xmlContent->startElement('style:text-properties'); - $this->xmlContent->writeAttribute('fo:color', '#'.$chart->getLegend()->getFont()->getColor()->getRGB()); + $this->xmlContent->writeAttribute('fo:color', '#' . $chart->getLegend()->getFont()->getColor()->getRGB()); $this->xmlContent->writeAttribute('fo:font-family', $chart->getLegend()->getFont()->getName()); - $this->xmlContent->writeAttribute('fo:font-size', $chart->getLegend()->getFont()->getSize().'pt'); + $this->xmlContent->writeAttribute('fo:font-size', $chart->getLegend()->getFont()->getSize() . 'pt'); $this->xmlContent->writeAttribute('fo:font-style', $chart->getLegend()->getFont()->isItalic() ? 'italic' : 'normal'); // > style:text-properties $this->xmlContent->endElement(); @@ -476,11 +496,7 @@ class ObjectsChart extends AbstractDecoratorWriter $this->xmlContent->endElement(); } - /** - * @param Chart $chart - * @throws \Exception - */ - private function writePlotArea(Chart $chart) + protected function writePlotArea(Chart $chart): void { $chartType = $chart->getPlotArea()->getType(); @@ -493,15 +509,15 @@ class ObjectsChart extends AbstractDecoratorWriter } if ($chartType instanceof Bar3D || $chartType instanceof Pie3D) { // dr3d:light - $arrayLight = array( - array('#808080', '(0 0 1)', 'false', 'true'), - array('#666666', '(0.2 0.4 1)', 'true', 'false'), - array('#808080', '(0 0 1)', 'false', 'false'), - array('#808080', '(0 0 1)', 'false', 'false'), - array('#808080', '(0 0 1)', 'false', 'false'), - array('#808080', '(0 0 1)', 'false', 'false'), - array('#808080', '(0 0 1)', 'false', 'false'), - ); + $arrayLight = [ + ['#808080', '(0 0 1)', 'false', 'true'], + ['#666666', '(0.2 0.4 1)', 'true', 'false'], + ['#808080', '(0 0 1)', 'false', 'false'], + ['#808080', '(0 0 1)', 'false', 'false'], + ['#808080', '(0 0 1)', 'false', 'false'], + ['#808080', '(0 0 1)', 'false', 'false'], + ['#808080', '(0 0 1)', 'false', 'false'], + ]; foreach ($arrayLight as $light) { $this->xmlContent->startElement('dr3d:light'); $this->xmlContent->writeAttribute('dr3d:diffuse-color', $light[0]); @@ -520,8 +536,8 @@ class ObjectsChart extends AbstractDecoratorWriter $this->numSeries = 0; foreach ($chartType->getSeries() as $series) { $this->writeSeries($chart, $series); - $this->rangeCol++; - $this->numSeries++; + ++$this->rangeCol; + ++$this->numSeries; } //**** Wall **** @@ -533,11 +549,9 @@ class ObjectsChart extends AbstractDecoratorWriter } /** - * @param Chart $chart - * @throws \Exception - * @link : http://books.evc-cit.info/odbook/ch08.html#chart-plot-area-section + * @see : http://books.evc-cit.info/odbook/ch08.html#chart-plot-area-section */ - private function writePlotAreaStyle(Chart $chart) + protected function writePlotAreaStyle(Chart $chart): void { $chartType = $chart->getPlotArea()->getType(); @@ -553,20 +567,33 @@ class ObjectsChart extends AbstractDecoratorWriter } elseif ($chartType instanceof Pie3D) { $this->xmlContent->writeAttribute('chart:three-dimensional', 'true'); $this->xmlContent->writeAttribute('chart:right-angled-axes', 'true'); + } elseif ($chartType instanceof AbstractTypeLine) { + $this->xmlContent->writeAttributeIf($chartType->isSmooth(), 'chart:interpolation', 'cubic-spline'); + } + switch ($chart->getDisplayBlankAs()) { + case Chart::BLANKAS_ZERO: + $this->xmlContent->writeAttribute('chart:treat-empty-cells', 'use-zero'); + break; + case Chart::BLANKAS_GAP: + $this->xmlContent->writeAttribute('chart:treat-empty-cells', 'leave-gap'); + break; + case Chart::BLANKAS_SPAN: + $this->xmlContent->writeAttribute('chart:treat-empty-cells', 'ignore'); + break; } if ($chartType instanceof AbstractTypeBar) { $chartVertical = 'false'; - if ($chartType->getBarDirection() == AbstractTypeBar::DIRECTION_HORIZONTAL) { + if (AbstractTypeBar::DIRECTION_HORIZONTAL == $chartType->getBarDirection()) { $chartVertical = 'true'; } $this->xmlContent->writeAttribute('chart:vertical', $chartVertical); - if ($chartType->getBarGrouping() == Bar::GROUPING_CLUSTERED) { + if (Bar::GROUPING_CLUSTERED == $chartType->getBarGrouping()) { $this->xmlContent->writeAttribute('chart:stacked', 'false'); $this->xmlContent->writeAttribute('chart:overlap', '0'); - } elseif ($chartType->getBarGrouping() == Bar::GROUPING_STACKED) { + } elseif (Bar::GROUPING_STACKED == $chartType->getBarGrouping()) { $this->xmlContent->writeAttribute('chart:stacked', 'true'); $this->xmlContent->writeAttribute('chart:overlap', '100'); - } elseif ($chartType->getBarGrouping() == Bar::GROUPING_PERCENTSTACKED) { + } elseif (Bar::GROUPING_PERCENTSTACKED == $chartType->getBarGrouping()) { $this->xmlContent->writeAttribute('chart:stacked', 'true'); $this->xmlContent->writeAttribute('chart:overlap', '100'); $this->xmlContent->writeAttribute('chart:percentage', 'true'); @@ -574,7 +601,7 @@ class ObjectsChart extends AbstractDecoratorWriter } $labelFormat = 'value'; if ($chartType instanceof AbstractTypeBar) { - if ($chartType->getBarGrouping() == Bar::GROUPING_PERCENTSTACKED) { + if (Bar::GROUPING_PERCENTSTACKED == $chartType->getBarGrouping()) { $labelFormat = 'percentage'; } } @@ -586,33 +613,22 @@ class ObjectsChart extends AbstractDecoratorWriter $this->xmlContent->endElement(); } - /** - * @param Chart $chart - * @param Chart\Series $series - * @throws \Exception - */ - private function writeSeries(Chart $chart, Chart\Series $series) + protected function writeSeries(Chart $chart, Chart\Series $series): void { $chartType = $chart->getPlotArea()->getType(); $numRange = count($series->getValues()); // chart:series $this->xmlContent->startElement('chart:series'); - $this->xmlContent->writeAttribute('chart:values-cell-range-address', 'table-local.$'.$this->rangeCol.'$2:.$'.$this->rangeCol.'$'.($numRange+1)); - $this->xmlContent->writeAttribute('chart:label-cell-address', 'table-local.$'.$this->rangeCol.'$1'); - // if ($chartType instanceof Area) { - // $this->xmlContent->writeAttribute('chart:class', 'chart:area'); - // } elseif ($chartType instanceof AbstractTypeBar) { - // $this->xmlContent->writeAttribute('chart:class', 'chart:bar'); - // } elseif ($chartType instanceof Line) { - // $this->xmlContent->writeAttribute('chart:class', 'chart:line'); - // } elseif ($chartType instanceof AbstractTypePie) { - // $this->xmlContent->writeAttribute('chart:class', 'chart:circle'); - // } elseif ($chartType instanceof Scatter) { - // $this->xmlContent->writeAttribute('chart:class', 'chart:scatter'); - // } - $this->xmlContent->writeAttribute('chart:style-name', 'styleSeries'.$this->numSeries); - if ($chartType instanceof Area || $chartType instanceof AbstractTypeBar || $chartType instanceof Line || $chartType instanceof Scatter) { + $this->xmlContent->writeAttribute('chart:values-cell-range-address', 'table-local.$' . $this->rangeCol . '$2:.$' . $this->rangeCol . '$' . ($numRange + 1)); + $this->xmlContent->writeAttribute('chart:label-cell-address', 'table-local.$' . $this->rangeCol . '$1'); + $this->xmlContent->writeAttribute('chart:style-name', 'styleSeries' . $this->numSeries); + if ($chartType instanceof Area + || $chartType instanceof AbstractTypeBar + || $chartType instanceof Line + || $chartType instanceof Radar + || $chartType instanceof Scatter + ) { $dataPointFills = $series->getDataPointFills(); $incRepeat = $numRange; @@ -631,14 +647,14 @@ class ObjectsChart extends AbstractDecoratorWriter // chart:data-point $this->xmlContent->startElement('chart:data-point'); - $this->xmlContent->writeAttribute('chart:style-name', 'styleSeries'.$this->numSeries.'_'.$inc); + $this->xmlContent->writeAttribute('chart:style-name', 'styleSeries' . $this->numSeries . '_' . $inc); // > chart:data-point $this->xmlContent->endElement(); } - $inc++; - $incRepeat++; + ++$inc; + ++$incRepeat; } while ($inc < $numRange); - $incRepeat--; + --$incRepeat; } // chart:data-point $this->xmlContent->startElement('chart:data-point'); @@ -647,10 +663,10 @@ class ObjectsChart extends AbstractDecoratorWriter $this->xmlContent->endElement(); } elseif ($chartType instanceof AbstractTypePie) { $count = count($series->getDataPointFills()); - for ($inc = 0; $inc < $count; $inc++) { + for ($inc = 0; $inc < $count; ++$inc) { // chart:data-point $this->xmlContent->startElement('chart:data-point'); - $this->xmlContent->writeAttribute('chart:style-name', 'styleSeries'.$this->numSeries.'_'.$inc); + $this->xmlContent->writeAttribute('chart:style-name', 'styleSeries' . $this->numSeries . '_' . $inc); // > chart:data-point $this->xmlContent->endElement(); } @@ -660,18 +676,13 @@ class ObjectsChart extends AbstractDecoratorWriter $this->xmlContent->endElement(); } - /** - * @param Chart $chart - * @param Chart\Series $series - * @throws \Exception - */ - private function writeSeriesStyle(Chart $chart, Chart\Series $series) + protected function writeSeriesStyle(Chart $chart, Chart\Series $series): void { $chartType = $chart->getPlotArea()->getType(); // style:style $this->xmlContent->startElement('style:style'); - $this->xmlContent->writeAttribute('style:name', 'styleSeries'.$this->numSeries); + $this->xmlContent->writeAttribute('style:name', 'styleSeries' . $this->numSeries); $this->xmlContent->writeAttribute('style:family', 'chart'); // style:chart-properties $this->xmlContent->startElement('style:chart-properties'); @@ -695,15 +706,15 @@ class ObjectsChart extends AbstractDecoratorWriter } if ($chartType instanceof Line || $chartType instanceof Scatter) { $oMarker = $series->getMarker(); - /** + /* * @link : http://www.datypic.com/sc/odf/a-chart_symbol-type.html */ - $this->xmlContent->writeAttributeIf($oMarker->getSymbol() == Chart\Marker::SYMBOL_NONE, 'chart:symbol-type', 'none'); - /** + $this->xmlContent->writeAttributeIf(Chart\Marker::SYMBOL_NONE == $oMarker->getSymbol(), 'chart:symbol-type', 'none'); + /* * @link : http://www.datypic.com/sc/odf/a-chart_symbol-name.html */ - $this->xmlContent->writeAttributeIf($oMarker->getSymbol() != Chart\Marker::SYMBOL_NONE, 'chart:symbol-type', 'named-symbol'); - if ($oMarker->getSymbol() != Chart\Marker::SYMBOL_NONE) { + $this->xmlContent->writeAttributeIf(Chart\Marker::SYMBOL_NONE != $oMarker->getSymbol(), 'chart:symbol-type', 'named-symbol'); + if (Chart\Marker::SYMBOL_NONE != $oMarker->getSymbol()) { switch ($oMarker->getSymbol()) { case Chart\Marker::SYMBOL_DASH: $symbolName = 'horizontal-bar'; @@ -720,8 +731,8 @@ class ObjectsChart extends AbstractDecoratorWriter } $this->xmlContent->writeAttribute('chart:symbol-name', $symbolName); $symbolSize = number_format(CommonDrawing::pointsToCentimeters($oMarker->getSize()), 2, '.', ''); - $this->xmlContent->writeAttribute('chart:symbol-width', $symbolSize.'cm'); - $this->xmlContent->writeAttribute('chart:symbol-height', $symbolSize.'cm'); + $this->xmlContent->writeAttribute('chart:symbol-width', $symbolSize . 'cm'); + $this->xmlContent->writeAttribute('chart:symbol-height', $symbolSize . 'cm'); } } @@ -729,7 +740,7 @@ class ObjectsChart extends AbstractDecoratorWriter if (!empty($separator)) { // style:chart-properties/chart:label-separator $this->xmlContent->startElement('chart:label-separator'); - if ($separator == PHP_EOL) { + if (PHP_EOL == $separator) { $this->xmlContent->writeRaw(''); } else { $this->xmlContent->writeElement('text:p', $separator); @@ -741,7 +752,7 @@ class ObjectsChart extends AbstractDecoratorWriter $this->xmlContent->endElement(); // style:graphic-properties $this->xmlContent->startElement('style:graphic-properties'); - if ($chartType instanceof Line || $chartType instanceof Scatter) { + if ($chartType instanceof Line || $chartType instanceof Radar || $chartType instanceof Scatter) { $outlineWidth = ''; $outlineColor = ''; @@ -759,22 +770,22 @@ class ObjectsChart extends AbstractDecoratorWriter if (empty($outlineColor)) { $outlineColor = '4a7ebb'; } - $this->xmlContent->writeAttribute('svg:stroke-width', $outlineWidth.'cm'); - $this->xmlContent->writeAttribute('svg:stroke-color', '#'.$outlineColor); + $this->xmlContent->writeAttribute('svg:stroke-width', $outlineWidth . 'cm'); + $this->xmlContent->writeAttribute('svg:stroke-color', '#' . $outlineColor); } else { $this->xmlContent->writeAttribute('draw:stroke', 'none'); if (!($chartType instanceof Area)) { $this->xmlContent->writeAttribute('draw:fill', $series->getFill()->getFillType()); } } - $this->xmlContent->writeAttribute('draw:fill-color', '#'.$series->getFill()->getStartColor()->getRGB()); + $this->xmlContent->writeAttribute('draw:fill-color', '#' . $series->getFill()->getStartColor()->getRGB()); // > style:graphic-properties $this->xmlContent->endElement(); // style:text-properties $this->xmlContent->startElement('style:text-properties'); - $this->xmlContent->writeAttribute('fo:color', '#'.$series->getFont()->getColor()->getRGB()); + $this->xmlContent->writeAttribute('fo:color', '#' . $series->getFont()->getColor()->getRGB()); $this->xmlContent->writeAttribute('fo:font-family', $series->getFont()->getName()); - $this->xmlContent->writeAttribute('fo:font-size', $series->getFont()->getSize().'pt'); + $this->xmlContent->writeAttribute('fo:font-size', $series->getFont()->getSize() . 'pt'); // > style:text-properties $this->xmlContent->endElement(); @@ -784,12 +795,12 @@ class ObjectsChart extends AbstractDecoratorWriter foreach ($series->getDataPointFills() as $idx => $oFill) { // style:style $this->xmlContent->startElement('style:style'); - $this->xmlContent->writeAttribute('style:name', 'styleSeries'.$this->numSeries.'_'.$idx); + $this->xmlContent->writeAttribute('style:name', 'styleSeries' . $this->numSeries . '_' . $idx); $this->xmlContent->writeAttribute('style:family', 'chart'); // style:graphic-properties $this->xmlContent->startElement('style:graphic-properties'); $this->xmlContent->writeAttribute('draw:fill', $oFill->getFillType()); - $this->xmlContent->writeAttribute('draw:fill-color', '#'.$oFill->getStartColor()->getRGB()); + $this->xmlContent->writeAttribute('draw:fill-color', '#' . $oFill->getStartColor()->getRGB()); // > style:graphic-properties $this->xmlContent->endElement(); // > style:style @@ -797,9 +808,7 @@ class ObjectsChart extends AbstractDecoratorWriter } } - /** - */ - private function writeTable() + protected function writeTable(): void { // table:table $this->xmlContent->startElement('table:table'); @@ -825,40 +834,6 @@ class ObjectsChart extends AbstractDecoratorWriter // > table:table-header-columns $this->xmlContent->endElement(); - // table:table-rows - $this->xmlContent->startElement('table:table-rows'); - if (empty($this->arrayData)) { - $this->xmlContent->startElement('table:table-row'); - $this->xmlContent->startElement('table:table-cell'); - $this->xmlContent->endElement(); - $this->xmlContent->endElement(); - } else { - foreach ($this->arrayData as $row) { - // table:table-row - $this->xmlContent->startElement('table:table-row'); - foreach ($row as $cell) { - // table:table-cell - $this->xmlContent->startElement('table:table-cell'); - - $cellNumeric = is_numeric($cell); - $this->xmlContent->writeAttributeIf(!$cellNumeric, 'office:value-type', 'string'); - $this->xmlContent->writeAttributeIf($cellNumeric, 'office:value-type', 'float'); - $this->xmlContent->writeAttributeIf($cellNumeric, 'office:value', $cell); - // text:p - $this->xmlContent->startElement('text:p'); - $this->xmlContent->text($cell); - // > text:p - $this->xmlContent->endElement(); - // > table:table-cell - $this->xmlContent->endElement(); - } - // > table:table-row - $this->xmlContent->endElement(); - } - } - // > table:table-rows - $this->xmlContent->endElement(); - // table:table-header-rows $this->xmlContent->startElement('table:table-header-rows'); // table:table-row @@ -890,22 +865,52 @@ class ObjectsChart extends AbstractDecoratorWriter // > table:table-header-rows $this->xmlContent->endElement(); + // table:table-rows + $this->xmlContent->startElement('table:table-rows'); + if (empty($this->arrayData)) { + $this->xmlContent->startElement('table:table-row'); + $this->xmlContent->startElement('table:table-cell'); + $this->xmlContent->endElement(); + $this->xmlContent->endElement(); + } else { + foreach ($this->arrayData as $row) { + // table:table-row + $this->xmlContent->startElement('table:table-row'); + foreach ($row as $cell) { + // table:table-cell + $this->xmlContent->startElement('table:table-cell'); + + $cellValueTypeFloat = is_null($cell) ? true : is_numeric($cell); + $this->xmlContent->writeAttributeIf(!$cellValueTypeFloat, 'office:value-type', 'string'); + $this->xmlContent->writeAttributeIf($cellValueTypeFloat, 'office:value-type', 'float'); + $this->xmlContent->writeAttributeIf($cellValueTypeFloat, 'office:value', is_null($cell) ? 'NaN' : $cell); + // text:p + $this->xmlContent->startElement('text:p'); + $this->xmlContent->text(is_null($cell) ? 'NaN' : (string) $cell); + $this->xmlContent->endElement(); + // > table:table-cell + $this->xmlContent->endElement(); + } + // > table:table-row + $this->xmlContent->endElement(); + } + } + // > table:table-rows + $this->xmlContent->endElement(); + // > table:table $this->xmlContent->endElement(); } - /** - * @param Title $oTitle - */ - private function writeTitle(Title $oTitle) + protected function writeTitle(Title $oTitle): void { if (!$oTitle->isVisible()) { return; } // chart:title $this->xmlContent->startElement('chart:title'); - $this->xmlContent->writeAttribute('svg:x', Text::numberFormat(CommonDrawing::pixelsToCentimeters($oTitle->getOffsetX()), 3) . 'cm'); - $this->xmlContent->writeAttribute('svg:y', Text::numberFormat(CommonDrawing::pixelsToCentimeters($oTitle->getOffsetY()), 3) . 'cm'); + $this->xmlContent->writeAttribute('svg:x', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $oTitle->getOffsetX()), 3) . 'cm'); + $this->xmlContent->writeAttribute('svg:y', Text::numberFormat(CommonDrawing::pixelsToCentimeters((int) $oTitle->getOffsetY()), 3) . 'cm'); $this->xmlContent->writeAttribute('chart:style-name', 'styleTitle'); // > text:p $this->xmlContent->startElement('text:p'); @@ -915,10 +920,7 @@ class ObjectsChart extends AbstractDecoratorWriter $this->xmlContent->endElement(); } - /** - * @param Title $oTitle - */ - private function writeTitleStyle(Title $oTitle) + protected function writeTitleStyle(Title $oTitle): void { if (!$oTitle->isVisible()) { return; @@ -929,9 +931,9 @@ class ObjectsChart extends AbstractDecoratorWriter $this->xmlContent->writeAttribute('style:family', 'chart'); // style:text-properties $this->xmlContent->startElement('style:text-properties'); - $this->xmlContent->writeAttribute('fo:color', '#'.$oTitle->getFont()->getColor()->getRGB()); + $this->xmlContent->writeAttribute('fo:color', '#' . $oTitle->getFont()->getColor()->getRGB()); $this->xmlContent->writeAttribute('fo:font-family', $oTitle->getFont()->getName()); - $this->xmlContent->writeAttribute('fo:font-size', $oTitle->getFont()->getSize().'pt'); + $this->xmlContent->writeAttribute('fo:font-size', $oTitle->getFont()->getSize() . 'pt'); $this->xmlContent->writeAttribute('fo:font-style', $oTitle->getFont()->isItalic() ? 'italic' : 'normal'); // > style:text-properties $this->xmlContent->endElement(); @@ -939,7 +941,7 @@ class ObjectsChart extends AbstractDecoratorWriter $this->xmlContent->endElement(); } - private function writeWall() + protected function writeWall(): void { // chart:wall $this->xmlContent->startElement('chart:wall'); @@ -947,11 +949,7 @@ class ObjectsChart extends AbstractDecoratorWriter $this->xmlContent->endElement(); } - /** - * @param Chart $chart - * @throws \Exception - */ - private function writeWallStyle(Chart $chart) + protected function writeWallStyle(Chart $chart): void { $chartType = $chart->getPlotArea()->getType(); diff --git a/PhpOffice/PhpPresentation/Writer/ODPresentation/Pictures.php b/PhpOffice/PhpPresentation/Writer/ODPresentation/Pictures.php old mode 100755 new mode 100644 index 6ec4e54..ac8cfa9 --- a/PhpOffice/PhpPresentation/Writer/ODPresentation/Pictures.php +++ b/PhpOffice/PhpPresentation/Writer/ODPresentation/Pictures.php @@ -1,4 +1,22 @@ getDrawingHashTable()->count(); ++$i) { $shape = $this->getDrawingHashTable()->getByIndex($i); if (!($shape instanceof Drawing\AbstractDrawingAdapter)) { @@ -29,10 +45,10 @@ class Pictures extends AbstractDecoratorWriter // Add background image slide $oBkgImage = $oSlide->getBackground(); if ($oBkgImage instanceof Image) { - $this->getZip()->addFromString('Pictures/'.$oBkgImage->getIndexedFilename($keySlide), file_get_contents($oBkgImage->getPath())); + $this->getZip()->addFromString('Pictures/' . $oBkgImage->getIndexedFilename((string) $keySlide), file_get_contents($oBkgImage->getPath())); } } - + return $this->getZip(); } } diff --git a/PhpOffice/PhpPresentation/Writer/ODPresentation/Styles.php b/PhpOffice/PhpPresentation/Writer/ODPresentation/Styles.php old mode 100755 new mode 100644 index 3c7fd72..6bc8cbf --- a/PhpOffice/PhpPresentation/Writer/ODPresentation/Styles.php +++ b/PhpOffice/PhpPresentation/Writer/ODPresentation/Styles.php @@ -1,49 +1,64 @@ */ - protected $arrayGradient = array(); + protected $arrayGradient = []; /** - * Stores font styles draw:stroke-dash nodes + * Stores font styles draw:stroke-dash nodes. * - * @var array + * @var array */ - protected $arrayStrokeDash = array(); + protected $arrayStrokeDash = []; - /** - * @return \PhpOffice\Common\Adapter\Zip\ZipInterface - * @throws \Exception - */ - public function render() + public function render(): ZipInterface { $this->getZip()->addFromString('styles.xml', $this->writePart()); + return $this->getZip(); } /** - * Write Meta file to XML format + * Write Meta file to XML format. * - * @return string XML Output - * @throws \Exception + * @return string XML Output */ - protected function writePart() + protected function writePart(): string { // Create XML writer $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); @@ -134,8 +149,8 @@ class Styles extends AbstractDecoratorWriter $objWriter->writeAttribute('fo:margin-bottom', '0cm'); $objWriter->writeAttribute('fo:margin-left', '0cm'); $objWriter->writeAttribute('fo:margin-right', '0cm'); - $objWriter->writeAttribute('fo:page-width', Text::numberFormat(CommonDrawing::pixelsToCentimeters(CommonDrawing::emuToPixels($this->getPresentation()->getLayout()->getCX())), 1) . 'cm'); - $objWriter->writeAttribute('fo:page-height', Text::numberFormat(CommonDrawing::pixelsToCentimeters(CommonDrawing::emuToPixels($this->getPresentation()->getLayout()->getCY())), 1) . 'cm'); + $objWriter->writeAttribute('fo:page-width', Text::numberFormat(CommonDrawing::pixelsToCentimeters(CommonDrawing::emuToPixels((int) $this->getPresentation()->getLayout()->getCX())), 1) . 'cm'); + $objWriter->writeAttribute('fo:page-height', Text::numberFormat(CommonDrawing::pixelsToCentimeters(CommonDrawing::emuToPixels((int) $this->getPresentation()->getLayout()->getCY())), 1) . 'cm'); $printOrientation = 'portrait'; if ($this->getPresentation()->getLayout()->getCX() > $this->getPresentation()->getLayout()->getCY()) { $printOrientation = 'landscape'; @@ -163,24 +178,21 @@ class Styles extends AbstractDecoratorWriter } /** - * Write the default style information for a RichText shape - * - * @param XMLWriter $objWriter - * @param RichText $shape + * Write the default style information for a RichText shape. */ - protected function writeRichTextStyle(XMLWriter $objWriter, RichText $shape) + protected function writeRichTextStyle(XMLWriter $objWriter, RichText $shape): void { $oFill = $shape->getFill(); - if ($oFill->getFillType() == Fill::FILL_GRADIENT_LINEAR || $oFill->getFillType() == Fill::FILL_GRADIENT_PATH) { + if (Fill::FILL_GRADIENT_LINEAR == $oFill->getFillType() || Fill::FILL_GRADIENT_PATH == $oFill->getFillType()) { if (!in_array($oFill->getHashCode(), $this->arrayGradient)) { $this->writeGradientFill($objWriter, $oFill); } } $oBorder = $shape->getBorder(); - if ($oBorder->getDashStyle() != Border::DASH_SOLID) { + if (Border::DASH_SOLID != $oBorder->getDashStyle()) { if (!in_array($oBorder->getDashStyle(), $this->arrayStrokeDash)) { $objWriter->startElement('draw:stroke-dash'); - $objWriter->writeAttribute('draw:name', 'strokeDash_'.$oBorder->getDashStyle()); + $objWriter->writeAttribute('draw:name', 'strokeDash_' . $oBorder->getDashStyle()); $objWriter->writeAttribute('draw:style', 'rect'); switch ($oBorder->getDashStyle()) { case Border::DASH_DASH: @@ -251,16 +263,13 @@ class Styles extends AbstractDecoratorWriter } /** - * Write the default style information for a Table shape - * - * @param XMLWriter $objWriter - * @param Table $shape + * Write the default style information for a Table shape. */ - protected function writeTableStyle(XMLWriter $objWriter, Table $shape) + protected function writeTableStyle(XMLWriter $objWriter, Table $shape): void { foreach ($shape->getRows() as $row) { foreach ($row->getCells() as $cell) { - if ($cell->getFill()->getFillType() == Fill::FILL_GRADIENT_LINEAR) { + if (Fill::FILL_GRADIENT_LINEAR == $cell->getFill()->getFillType()) { if (!in_array($cell->getFill()->getHashCode(), $this->arrayGradient)) { $this->writeGradientFill($objWriter, $cell->getFill()); } @@ -270,12 +279,9 @@ class Styles extends AbstractDecoratorWriter } /** - * Writes the style information for a group of shapes - * - * @param XMLWriter $objWriter - * @param Group $group + * Writes the style information for a group of shapes. */ - protected function writeGroupStyle(XMLWriter $objWriter, Group $group) + protected function writeGroupStyle(XMLWriter $objWriter, Group $group): void { $shapes = $group->getShapeCollection(); foreach ($shapes as $shape) { @@ -288,20 +294,18 @@ class Styles extends AbstractDecoratorWriter } /** - * Write the gradient style - * @param XMLWriter $objWriter - * @param Fill $oFill + * Write the gradient style. */ - protected function writeGradientFill(XMLWriter $objWriter, Fill $oFill) + protected function writeGradientFill(XMLWriter $objWriter, Fill $oFill): void { $objWriter->startElement('draw:gradient'); - $objWriter->writeAttribute('draw:name', 'gradient_'.$oFill->getHashCode()); - $objWriter->writeAttribute('draw:display-name', 'gradient_'.$oFill->getHashCode()); + $objWriter->writeAttribute('draw:name', 'gradient_' . $oFill->getHashCode()); + $objWriter->writeAttribute('draw:display-name', 'gradient_' . $oFill->getHashCode()); $objWriter->writeAttribute('draw:style', 'linear'); $objWriter->writeAttribute('draw:start-intensity', '100%'); $objWriter->writeAttribute('draw:end-intensity', '100%'); - $objWriter->writeAttribute('draw:start-color', '#'.$oFill->getStartColor()->getRGB()); - $objWriter->writeAttribute('draw:end-color', '#'.$oFill->getEndColor()->getRGB()); + $objWriter->writeAttribute('draw:start-color', '#' . $oFill->getStartColor()->getRGB()); + $objWriter->writeAttribute('draw:end-color', '#' . $oFill->getEndColor()->getRGB()); $objWriter->writeAttribute('draw:border', '0%'); $objWriter->writeAttribute('draw:angle', $oFill->getRotation() - 90); $objWriter->endElement(); @@ -309,16 +313,13 @@ class Styles extends AbstractDecoratorWriter } /** - * Write the background image style - * @param XMLWriter $objWriter - * @param Image $oBkgImage - * @param $numSlide + * Write the background image style. */ - protected function writeBackgroundStyle(XMLWriter $objWriter, Image $oBkgImage, $numSlide) + protected function writeBackgroundStyle(XMLWriter $objWriter, Image $oBkgImage, int $numSlide): void { $objWriter->startElement('draw:fill-image'); - $objWriter->writeAttribute('draw:name', 'background_'.$numSlide); - $objWriter->writeAttribute('xlink:href', 'Pictures/'.str_replace(' ', '_', $oBkgImage->getIndexedFilename($numSlide))); + $objWriter->writeAttribute('draw:name', 'background_' . (string) $numSlide); + $objWriter->writeAttribute('xlink:href', 'Pictures/' . str_replace(' ', '_', $oBkgImage->getIndexedFilename((string) $numSlide))); $objWriter->writeAttribute('xlink:type', 'simple'); $objWriter->writeAttribute('xlink:show', 'embed'); $objWriter->writeAttribute('xlink:actuate', 'onLoad'); diff --git a/PhpOffice/PhpPresentation/Writer/ODPresentation/ThumbnailsThumbnail.php b/PhpOffice/PhpPresentation/Writer/ODPresentation/ThumbnailsThumbnail.php old mode 100755 new mode 100644 index b481a56..1d75433 --- a/PhpOffice/PhpPresentation/Writer/ODPresentation/ThumbnailsThumbnail.php +++ b/PhpOffice/PhpPresentation/Writer/ODPresentation/ThumbnailsThumbnail.php @@ -1,14 +1,30 @@ getPresentation()->getPresentationProperties()->getThumbnailPath(); if ($pathThumbnail) { @@ -37,6 +53,7 @@ class ThumbnailsThumbnail extends AbstractDecoratorWriter $this->getZip()->addFromString('Thumbnails/thumbnail.png', $imageContents); } } + return $this->getZip(); } } diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007.php old mode 100755 new mode 100644 index 2b6e2c2..e98a618 --- a/PhpOffice/PhpPresentation/Writer/PowerPoint2007.php +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007.php @@ -10,63 +10,59 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Writer; use DirectoryIterator; use PhpOffice\Common\Adapter\Zip\ZipArchiveAdapter; +use PhpOffice\PhpPresentation\Exception\DirectoryNotFoundException; +use PhpOffice\PhpPresentation\Exception\FileCopyException; +use PhpOffice\PhpPresentation\Exception\FileRemoveException; +use PhpOffice\PhpPresentation\Exception\InvalidParameterException; use PhpOffice\PhpPresentation\HashTable; use PhpOffice\PhpPresentation\PhpPresentation; -use PhpOffice\PhpPresentation\Writer\PowerPoint2007\LayoutPack\AbstractLayoutPack; -use PhpOffice\PhpPresentation\Writer\PowerPoint2007\LayoutPack\PackDefault; +use PhpOffice\PhpPresentation\Writer\PowerPoint2007\AbstractDecoratorWriter; +use ReflectionClass; /** - * \PhpOffice\PhpPresentation\Writer\PowerPoint2007 + * \PhpOffice\PhpPresentation\Writer\PowerPoint2007. */ class PowerPoint2007 extends AbstractWriter implements WriterInterface { /** * Use disk caching where possible? * - * @var boolean + * @var bool */ protected $useDiskCaching = false; /** - * Disk caching directory + * Disk caching directory. * * @var string */ protected $diskCachingDir; /** - * Layout pack to use - * @deprecated 0.7 - * @var \PhpOffice\PhpPresentation\Writer\PowerPoint2007\LayoutPack\AbstractLayoutPack - */ - protected $layoutPack; - - /** - * Create a new PowerPoint2007 file + * Create a new PowerPoint2007 file. * * @param PhpPresentation $pPhpPresentation - * @throws \Exception */ public function __construct(PhpPresentation $pPhpPresentation = null) { // Assign PhpPresentation - $this->setPhpPresentation($pPhpPresentation); + $this->setPhpPresentation($pPhpPresentation ?? new PhpPresentation()); // Set up disk caching location $this->diskCachingDir = './'; - // Set layout pack - $this->layoutPack = new PackDefault(); - // Set HashTable variables $this->oDrawingHashTable = new HashTable(); @@ -74,23 +70,24 @@ class PowerPoint2007 extends AbstractWriter implements WriterInterface } /** - * Save PhpPresentation to file + * Save PhpPresentation to file. * - * @param string $pFilename - * @throws \Exception + * @throws FileCopyException + * @throws FileRemoveException + * @throws InvalidParameterException */ - public function save($pFilename) + public function save(string $pFilename): void { if (empty($pFilename)) { - throw new \Exception("Filename is empty"); + throw new InvalidParameterException('pFilename', ''); } $oPresentation = $this->getPhpPresentation(); // If $pFilename is php://output or php://stdout, make it a temporary file... $originalFilename = $pFilename; - if (strtolower($pFilename) == 'php://output' || strtolower($pFilename) == 'php://stdout') { + if ('php://output' == strtolower($pFilename) || 'php://stdout' == strtolower($pFilename)) { $pFilename = @tempnam('./', 'phppttmp'); - if ($pFilename == '') { + if ('' == $pFilename) { $pFilename = $originalFilename; } } @@ -101,20 +98,20 @@ class PowerPoint2007 extends AbstractWriter implements WriterInterface $oZip = $this->getZipAdapter(); $oZip->open($pFilename); - $oDir = new DirectoryIterator(dirname(__FILE__).DIRECTORY_SEPARATOR.'PowerPoint2007'); - $arrayFiles = array(); + $oDir = new DirectoryIterator(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'PowerPoint2007'); + $arrayFiles = []; foreach ($oDir as $oFile) { if (!$oFile->isFile()) { continue; } $class = __NAMESPACE__ . '\\PowerPoint2007\\' . $oFile->getBasename('.php'); - $o = new \ReflectionClass($class); + $class = new ReflectionClass($class); - if ($o->isAbstract() || !$o->isSubclassOf('PhpOffice\PhpPresentation\Writer\PowerPoint2007\AbstractDecoratorWriter')) { + if ($class->isAbstract() || !$class->isSubclassOf(AbstractDecoratorWriter::class)) { continue; } - $arrayFiles[$oFile->getBasename('.php')] = $o; + $arrayFiles[$oFile->getBasename('.php')] = $class; } ksort($arrayFiles); @@ -133,11 +130,11 @@ class PowerPoint2007 extends AbstractWriter implements WriterInterface // If a temporary file was used, copy it to the correct file stream if ($originalFilename != $pFilename) { - if (copy($pFilename, $originalFilename) === false) { - throw new \Exception("Could not copy temporary zip file $pFilename to $originalFilename."); + if (false === copy($pFilename, $originalFilename)) { + throw new FileCopyException($pFilename, $originalFilename); } - if (@unlink($pFilename) === false) { - throw new \Exception('The file '.$pFilename.' could not be removed.'); + if (false === @unlink($pFilename)) { + throw new FileRemoveException($pFilename); } } } @@ -145,7 +142,7 @@ class PowerPoint2007 extends AbstractWriter implements WriterInterface /** * Get use disk caching where possible? * - * @return boolean + * @return bool */ public function hasDiskCaching() { @@ -155,27 +152,29 @@ class PowerPoint2007 extends AbstractWriter implements WriterInterface /** * Set use disk caching where possible? * - * @param boolean $pValue - * @param string $pDirectory Disk caching directory - * @throws \Exception + * @param bool $useDiskCaching + * @param string $directory Disk caching directory + * + * @throws DirectoryNotFoundException + * * @return \PhpOffice\PhpPresentation\Writer\PowerPoint2007 */ - public function setUseDiskCaching($pValue = false, $pDirectory = null) + public function setUseDiskCaching(bool $useDiskCaching = false, string $directory = null) { - $this->useDiskCaching = $pValue; + $this->useDiskCaching = $useDiskCaching; - if (!is_null($pDirectory)) { - if (!is_dir($pDirectory)) { - throw new \Exception("Directory does not exist: $pDirectory"); + if (!is_null($directory)) { + if (!is_dir($directory)) { + throw new DirectoryNotFoundException($directory); } - $this->diskCachingDir = $pDirectory; + $this->diskCachingDir = $directory; } return $this; } /** - * Get disk caching directory + * Get disk caching directory. * * @return string */ @@ -183,29 +182,4 @@ class PowerPoint2007 extends AbstractWriter implements WriterInterface { return $this->diskCachingDir; } - - /** - * Get layout pack to use - * - * @deprecated 0.7 - * @return \PhpOffice\PhpPresentation\Writer\PowerPoint2007\LayoutPack\AbstractLayoutPack - */ - public function getLayoutPack() - { - return $this->layoutPack; - } - - /** - * Set layout pack to use - * - * @deprecated 0.7 - * @param \PhpOffice\PhpPresentation\Writer\PowerPoint2007\LayoutPack\AbstractLayoutPack $pValue - * @return \PhpOffice\PhpPresentation\Writer\PowerPoint2007 - */ - public function setLayoutPack(AbstractLayoutPack $pValue = null) - { - $this->layoutPack = $pValue; - - return $this; - } } diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/AbstractDecoratorWriter.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/AbstractDecoratorWriter.php old mode 100755 new mode 100644 index c8d0b7a..362d941 --- a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/AbstractDecoratorWriter.php +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/AbstractDecoratorWriter.php @@ -1,4 +1,22 @@ startElement('Relationship'); - $objWriter->writeAttribute('Id', $pId); + $objWriter->writeAttribute('Id', 'rId' . (string) $pId); $objWriter->writeAttribute('Type', $pType); $objWriter->writeAttribute('Target', $pTarget); - if ($pTargetMode != '') { + if ('' != $pTargetMode) { $objWriter->writeAttribute('TargetMode', $pTargetMode); } @@ -44,26 +54,26 @@ abstract class AbstractDecoratorWriter extends \PhpOffice\PhpPresentation\Writer } /** - * Write Border + * Write Border. * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param \PhpOffice\PhpPresentation\Style\Border $pBorder Border - * @param string $pElementName Element name - * @throws \Exception + * @param XMLWriter $objWriter XML Writer + * @param Border $pBorder Border + * @param string $pElementName Element name + * @param bool $isMarker */ - protected function writeBorder(XMLWriter $objWriter, $pBorder, $pElementName = 'L') + protected function writeBorder(XMLWriter $objWriter, Border $pBorder, string $pElementName = 'L', bool $isMarker = false): void { if (!($pBorder instanceof Border)) { return; } - if ($pBorder->getLineStyle() == Border::LINE_NONE && $pElementName == '') { + if (Border::LINE_NONE == $pBorder->getLineStyle() && '' == $pElementName && !$isMarker) { return; } // Line style $lineStyle = $pBorder->getLineStyle(); - if ($lineStyle == Border::LINE_NONE) { + if (Border::LINE_NONE == $lineStyle) { $lineStyle = Border::LINE_SINGLE; } @@ -78,7 +88,7 @@ abstract class AbstractDecoratorWriter extends \PhpOffice\PhpPresentation\Writer $objWriter->writeAttribute('algn', 'ctr'); // Fill? - if ($pBorder->getLineStyle() == Border::LINE_NONE) { + if (Border::LINE_NONE == $pBorder->getLineStyle()) { // a:noFill $objWriter->writeElement('a:noFill', null); } else { @@ -113,12 +123,7 @@ abstract class AbstractDecoratorWriter extends \PhpOffice\PhpPresentation\Writer $objWriter->endElement(); } - /** - * @param XMLWriter $objWriter - * @param Color $color - * @param int|null $alpha - */ - protected function writeColor(XMLWriter $objWriter, Color $color, $alpha = null) + protected function writeColor(XMLWriter $objWriter, Color $color, ?int $alpha = null): void { if (is_null($alpha)) { $alpha = $color->getAlpha(); @@ -137,48 +142,49 @@ abstract class AbstractDecoratorWriter extends \PhpOffice\PhpPresentation\Writer } /** - * Write Fill + * Write Fill. * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param \PhpOffice\PhpPresentation\Style\Fill $pFill Fill style - * @throws \Exception + * @param XMLWriter $objWriter XML Writer + * @param Fill|null $pFill Fill style */ - protected function writeFill(XMLWriter $objWriter, $pFill) + protected function writeFill(XMLWriter $objWriter, ?Fill $pFill): void { - if (! $pFill instanceof Fill) { + if (!$pFill) { return; } // Is it a fill? - if ($pFill->getFillType() == Fill::FILL_NONE) { + if (Fill::FILL_NONE == $pFill->getFillType()) { $objWriter->writeElement('a:noFill'); + return; } // Is it a solid fill? - if ($pFill->getFillType() == Fill::FILL_SOLID) { + if (Fill::FILL_SOLID == $pFill->getFillType()) { $this->writeSolidFill($objWriter, $pFill); + return; } - // Check if this is a pattern type or gradient type - if ($pFill->getFillType() == Fill::FILL_GRADIENT_LINEAR || $pFill->getFillType() == Fill::FILL_GRADIENT_PATH) { - // Gradient fill + // Is it a gradient fill? + if (Fill::FILL_GRADIENT_LINEAR == $pFill->getFillType() || Fill::FILL_GRADIENT_PATH == $pFill->getFillType()) { $this->writeGradientFill($objWriter, $pFill); - } else { - // Pattern fill - $this->writePatternFill($objWriter, $pFill); + + return; } + + // Is it a pattern fill? + $this->writePatternFill($objWriter, $pFill); } /** - * Write Solid Fill + * Write Solid Fill. * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param \PhpOffice\PhpPresentation\Style\Fill $pFill Fill style - * @throws \Exception + * @param XMLWriter $objWriter XML Writer + * @param Fill $pFill Fill style */ - protected function writeSolidFill(XMLWriter $objWriter, Fill $pFill) + protected function writeSolidFill(XMLWriter $objWriter, Fill $pFill): void { // a:gradFill $objWriter->startElement('a:solidFill'); @@ -187,13 +193,12 @@ abstract class AbstractDecoratorWriter extends \PhpOffice\PhpPresentation\Writer } /** - * Write Gradient Fill + * Write Gradient Fill. * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param \PhpOffice\PhpPresentation\Style\Fill $pFill Fill style - * @throws \Exception + * @param XMLWriter $objWriter XML Writer + * @param Fill $pFill Fill style */ - protected function writeGradientFill(XMLWriter $objWriter, Fill $pFill) + protected function writeGradientFill(XMLWriter $objWriter, Fill $pFill): void { // a:gradFill $objWriter->startElement('a:gradFill'); @@ -216,7 +221,7 @@ abstract class AbstractDecoratorWriter extends \PhpOffice\PhpPresentation\Writer // a:lin $objWriter->startElement('a:lin'); - $objWriter->writeAttribute('ang', CommonDrawing::degreesToAngle($pFill->getRotation())); + $objWriter->writeAttribute('ang', CommonDrawing::degreesToAngle((int) $pFill->getRotation())); $objWriter->writeAttribute('scaled', '0'); $objWriter->endElement(); @@ -224,13 +229,12 @@ abstract class AbstractDecoratorWriter extends \PhpOffice\PhpPresentation\Writer } /** - * Write Pattern Fill + * Write Pattern Fill. * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param \PhpOffice\PhpPresentation\Style\Fill $pFill Fill style - * @throws \Exception + * @param XMLWriter $objWriter XML Writer + * @param Fill $pFill Fill style */ - protected function writePatternFill(XMLWriter $objWriter, Fill $pFill) + protected function writePatternFill(XMLWriter $objWriter, Fill $pFill): void { // a:pattFill $objWriter->startElement('a:pattFill'); @@ -254,21 +258,14 @@ abstract class AbstractDecoratorWriter extends \PhpOffice\PhpPresentation\Writer /** * Write Outline - * @param XMLWriter $objWriter - * @param Outline $oOutline - * @throws \Exception */ - protected function writeOutline(XMLWriter $objWriter, $oOutline) + protected function writeOutline(XMLWriter $objWriter, ?Outline $oOutline): void { - if (!$oOutline instanceof Outline) { + if (!$oOutline) { return; } // Width : pts - $width = $oOutline->getWidth(); - // Width : pts => px - $width = CommonDrawing::pointsToPixels($width); - // Width : px => emu - $width = CommonDrawing::pixelsToEmu($width); + $width = CommonDrawing::pointsToEmu($oOutline->getWidth()); // a:ln $objWriter->startElement('a:ln'); @@ -282,19 +279,18 @@ abstract class AbstractDecoratorWriter extends \PhpOffice\PhpPresentation\Writer } /** - * Determine absolute zip path - * - * @param string $path - * @return string + * Determine absolute zip path. */ - protected function absoluteZipPath($path) + protected function absoluteZipPath(string $path): string { - $path = str_replace(array( + $path = str_replace([ '/', - '\\' - ), DIRECTORY_SEPARATOR, $path); - $parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen'); - $absolutes = array(); + '\\', + ], DIRECTORY_SEPARATOR, $path); + $parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), function (string $var) { + return (bool) strlen($var); + }); + $absolutes = []; foreach ($parts as $part) { if ('.' == $part) { continue; diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/AbstractSlide.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/AbstractSlide.php old mode 100755 new mode 100644 index 0c220f0..c310379 --- a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/AbstractSlide.php +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/AbstractSlide.php @@ -10,48 +10,54 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ + +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Writer\PowerPoint2007; +use ArrayObject; use PhpOffice\Common\Drawing as CommonDrawing; use PhpOffice\Common\Text; use PhpOffice\Common\XMLWriter; +use PhpOffice\PhpPresentation\AbstractShape; +use PhpOffice\PhpPresentation\Exception\UndefinedChartTypeException; use PhpOffice\PhpPresentation\Shape\AbstractGraphic; +use PhpOffice\PhpPresentation\Shape\AutoShape; use PhpOffice\PhpPresentation\Shape\Chart as ShapeChart; use PhpOffice\PhpPresentation\Shape\Comment; -use PhpOffice\PhpPresentation\Shape\Drawing\Gd as ShapeDrawingGd; +use PhpOffice\PhpPresentation\Shape\Drawing\AbstractDrawingAdapter; use PhpOffice\PhpPresentation\Shape\Drawing\File as ShapeDrawingFile; +use PhpOffice\PhpPresentation\Shape\Drawing\Gd as ShapeDrawingGd; use PhpOffice\PhpPresentation\Shape\Group; use PhpOffice\PhpPresentation\Shape\Line; use PhpOffice\PhpPresentation\Shape\Media; use PhpOffice\PhpPresentation\Shape\Placeholder; use PhpOffice\PhpPresentation\Shape\RichText; use PhpOffice\PhpPresentation\Shape\RichText\BreakElement; +use PhpOffice\PhpPresentation\Shape\RichText\Paragraph; use PhpOffice\PhpPresentation\Shape\RichText\Run; use PhpOffice\PhpPresentation\Shape\RichText\TextElement; use PhpOffice\PhpPresentation\Shape\Table as ShapeTable; use PhpOffice\PhpPresentation\Slide; +use PhpOffice\PhpPresentation\Slide\AbstractSlide as AbstractSlideAlias; use PhpOffice\PhpPresentation\Slide\Note; use PhpOffice\PhpPresentation\Style\Alignment; -use PhpOffice\PhpPresentation\Style\Bullet; use PhpOffice\PhpPresentation\Style\Border; +use PhpOffice\PhpPresentation\Style\Bullet; use PhpOffice\PhpPresentation\Style\Color; use PhpOffice\PhpPresentation\Style\Shadow; -use PhpOffice\PhpPresentation\Slide\AbstractSlide as AbstractSlideAlias; abstract class AbstractSlide extends AbstractDecoratorWriter { /** - * @param AbstractSlideAlias $pSlideMaster - * @param $objWriter - * @param $relId * @return mixed - * @throws \Exception */ - protected function writeDrawingRelations(AbstractSlideAlias $pSlideMaster, $objWriter, $relId) + protected function writeDrawingRelations(AbstractSlideAlias $pSlideMaster, XMLWriter $objWriter, int $relId) { if ($pSlideMaster->getShapeCollection()->count() > 0) { // Loop trough images and write relationships @@ -114,14 +120,14 @@ abstract class AbstractSlide extends AbstractDecoratorWriter } /** - * @param XMLWriter $objWriter - * @param \ArrayObject|\PhpOffice\PhpPresentation\AbstractShape[] $shapes + * @param array|ArrayObject $shapes * @param int $shapeId - * @throws \Exception + * + * @throws UndefinedChartTypeException */ - protected function writeShapeCollection(XMLWriter $objWriter, $shapes = array(), &$shapeId = 0) + protected function writeShapeCollection(XMLWriter $objWriter, $shapes = [], &$shapeId = 0): void { - if (count($shapes) == 0) { + if (0 == count($shapes)) { return; } foreach ($shapes as $shape) { @@ -138,24 +144,25 @@ abstract class AbstractSlide extends AbstractDecoratorWriter $this->writeShapeChart($objWriter, $shape, $shapeId); } elseif ($shape instanceof AbstractGraphic) { $this->writeShapePic($objWriter, $shape, $shapeId); + } elseif ($shape instanceof AutoShape) { + $this->writeShapeAutoShape($objWriter, $shape, $shapeId); } elseif ($shape instanceof Group) { $this->writeShapeGroup($objWriter, $shape, $shapeId); } elseif ($shape instanceof Comment) { } else { - throw new \Exception("Unknown Shape type: {get_class($shape)}"); + throw new UndefinedChartTypeException(); } } } /** - * Write txt + * Write txt. * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param \PhpOffice\PhpPresentation\Shape\RichText $shape - * @param int $shapeId - * @throws \Exception + * @param XMLWriter $objWriter XML Writer + * @param RichText $shape + * @param int $shapeId */ - protected function writeShapeText(XMLWriter $objWriter, RichText $shape, $shapeId) + protected function writeShapeText(XMLWriter $objWriter, RichText $shape, int $shapeId): void { // p:sp $objWriter->startElement('p:sp'); @@ -200,7 +207,7 @@ abstract class AbstractSlide extends AbstractDecoratorWriter if (!$shape->isPlaceholder()) { // p:sp\p:spPr\a:xfrm $objWriter->startElement('a:xfrm'); - $objWriter->writeAttributeIf($shape->getRotation() != 0, 'rot', CommonDrawing::degreesToAngle($shape->getRotation())); + $objWriter->writeAttributeIf(0 != $shape->getRotation(), 'rot', CommonDrawing::degreesToAngle((int) $shape->getRotation())); // p:sp\p:spPr\a:xfrm\a:off $objWriter->startElement('a:off'); $objWriter->writeAttribute('x', CommonDrawing::pixelsToEmu($shape->getOffsetX())); @@ -235,17 +242,17 @@ abstract class AbstractSlide extends AbstractDecoratorWriter $objWriter->startElement('a:bodyPr'); if (!$shape->isPlaceholder()) { $verticalAlign = $shape->getActiveParagraph()->getAlignment()->getVertical(); - if ($verticalAlign != Alignment::VERTICAL_BASE && $verticalAlign != Alignment::VERTICAL_AUTO) { + if (Alignment::VERTICAL_BASE != $verticalAlign && Alignment::VERTICAL_AUTO != $verticalAlign) { $objWriter->writeAttribute('anchor', $verticalAlign); } - if ($shape->getWrap() != RichText::WRAP_SQUARE) { + if (RichText::WRAP_SQUARE != $shape->getWrap()) { $objWriter->writeAttribute('wrap', $shape->getWrap()); } $objWriter->writeAttribute('rtlCol', '0'); - if ($shape->getHorizontalOverflow() != RichText::OVERFLOW_OVERFLOW) { + if (RichText::OVERFLOW_OVERFLOW != $shape->getHorizontalOverflow()) { $objWriter->writeAttribute('horzOverflow', $shape->getHorizontalOverflow()); } - if ($shape->getVerticalOverflow() != RichText::OVERFLOW_OVERFLOW) { + if (RichText::OVERFLOW_OVERFLOW != $shape->getVerticalOverflow()) { $objWriter->writeAttribute('vertOverflow', $shape->getVerticalOverflow()); } if ($shape->isUpright()) { @@ -258,12 +265,13 @@ abstract class AbstractSlide extends AbstractDecoratorWriter $objWriter->writeAttribute('lIns', CommonDrawing::pixelsToEmu($shape->getInsetLeft())); $objWriter->writeAttribute('rIns', CommonDrawing::pixelsToEmu($shape->getInsetRight())); $objWriter->writeAttribute('tIns', CommonDrawing::pixelsToEmu($shape->getInsetTop())); - if ($shape->getColumns() <> 1) { + if (1 != $shape->getColumns()) { $objWriter->writeAttribute('numCol', $shape->getColumns()); + $objWriter->writeAttribute('spcCol', CommonDrawing::pixelsToEmu($shape->getColumnSpacing())); } // a:spAutoFit $objWriter->startElement('a:' . $shape->getAutoFit()); - if ($shape->getAutoFit() == RichText::AUTOFIT_NORMAL) { + if (RichText::AUTOFIT_NORMAL == $shape->getAutoFit()) { if (!is_null($shape->getFontScale())) { $objWriter->writeAttribute('fontScale', $shape->getFontScale() * 1000); } @@ -277,16 +285,18 @@ abstract class AbstractSlide extends AbstractDecoratorWriter // a:lstStyle $objWriter->writeElement('a:lstStyle', null); if ($shape->isPlaceholder() && - ($shape->getPlaceholder()->getType() == Placeholder::PH_TYPE_SLIDENUM || - $shape->getPlaceholder()->getType() == Placeholder::PH_TYPE_DATETIME) + (Placeholder::PH_TYPE_SLIDENUM == $shape->getPlaceholder()->getType() || + Placeholder::PH_TYPE_DATETIME == $shape->getPlaceholder()->getType()) ) { $objWriter->startElement('a:p'); $objWriter->startElement('a:fld'); $objWriter->writeAttribute('id', $this->getGUID()); $objWriter->writeAttribute('type', ( - $shape->getPlaceholder()->getType() == Placeholder::PH_TYPE_SLIDENUM ? 'slidenum' : 'datetime')); + Placeholder::PH_TYPE_SLIDENUM == $shape->getPlaceholder()->getType() ? 'slidenum' : 'datetime' + )); $objWriter->writeElement('a:t', ( - $shape->getPlaceholder()->getType() == Placeholder::PH_TYPE_SLIDENUM ? '' : '03-04-05')); + Placeholder::PH_TYPE_SLIDENUM == $shape->getPlaceholder()->getType() ? '' : '03-04-05' + )); $objWriter->endElement(); $objWriter->endElement(); } else { @@ -298,14 +308,13 @@ abstract class AbstractSlide extends AbstractDecoratorWriter } /** - * Write table + * Write table. * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param \PhpOffice\PhpPresentation\Shape\Table $shape - * @param int $shapeId - * @throws \Exception + * @param XMLWriter $objWriter XML Writer + * @param ShapeTable $shape + * @param int $shapeId */ - protected function writeShapeTable(XMLWriter $objWriter, ShapeTable $shape, $shapeId) + protected function writeShapeTable(XMLWriter $objWriter, ShapeTable $shape, int $shapeId): void { // p:graphicFrame $objWriter->startElement('p:graphicFrame'); @@ -365,12 +374,12 @@ abstract class AbstractSlide extends AbstractDecoratorWriter $objWriter->startElement('a:tblGrid'); // Write cell widths $countCells = count($shape->getRow(0)->getCells()); - for ($cell = 0; $cell < $countCells; $cell++) { + for ($cell = 0; $cell < $countCells; ++$cell) { // p:graphicFrame/a:graphic/a:graphicData/a:tbl/a:tblGrid/a:gridCol $objWriter->startElement('a:gridCol'); // Calculate column width $width = $shape->getRow(0)->getCell($cell)->getWidth(); - if ($width == 0) { + if (0 == $width) { $colCount = count($shape->getRow(0)->getCells()); $totalWidth = $shape->getWidth(); $width = $totalWidth / $colCount; @@ -381,29 +390,24 @@ abstract class AbstractSlide extends AbstractDecoratorWriter // p:graphicFrame/a:graphic/a:graphicData/a:tbl/a:tblGrid/ $objWriter->endElement(); // Colspan / rowspan containers - $colSpan = array(); - $rowSpan = array(); + $colSpan = $rowSpan = []; // Default border style $defaultBorder = new Border(); // Write rows $countRows = count($shape->getRows()); - for ($row = 0; $row < $countRows; $row++) { + for ($row = 0; $row < $countRows; ++$row) { // p:graphicFrame/a:graphic/a:graphicData/a:tbl/a:tr $objWriter->startElement('a:tr'); $objWriter->writeAttribute('h', CommonDrawing::pixelsToEmu($shape->getRow($row)->getHeight())); // Write cells $countCells = count($shape->getRow($row)->getCells()); - for ($cell = 0; $cell < $countCells; $cell++) { + for ($cell = 0; $cell < $countCells; ++$cell) { // Current cell $currentCell = $shape->getRow($row)->getCell($cell); // Next cell right - $nextCellRight = $shape->getRow($row)->getCell($cell + 1, true); + $hasNextCellRight = $shape->getRow($row)->hasCell($cell + 1); // Next cell below - $nextRowBelow = $shape->getRow($row + 1, true); - $nextCellBelow = null; - if ($nextRowBelow != null) { - $nextCellBelow = $nextRowBelow->getCell($cell, true); - } + $hasNextRowBelow = $shape->hasRow($row + 1); // a:tc $objWriter->startElement('a:tc'); // Colspan @@ -411,7 +415,7 @@ abstract class AbstractSlide extends AbstractDecoratorWriter $objWriter->writeAttribute('gridSpan', $currentCell->getColSpan()); $colSpan[$row] = $currentCell->getColSpan() - 1; } elseif (isset($colSpan[$row]) && $colSpan[$row] > 0) { - $colSpan[$row]--; + --$colSpan[$row]; $objWriter->writeAttribute('hMerge', '1'); } // Rowspan @@ -419,7 +423,7 @@ abstract class AbstractSlide extends AbstractDecoratorWriter $objWriter->writeAttribute('rowSpan', $currentCell->getRowSpan()); $rowSpan[$cell] = $currentCell->getRowSpan() - 1; } elseif (isset($rowSpan[$cell]) && $rowSpan[$cell] > 0) { - $rowSpan[$cell]--; + --$rowSpan[$cell]; $objWriter->writeAttribute('vMerge', '1'); } // a:txBody @@ -444,12 +448,12 @@ abstract class AbstractSlide extends AbstractDecoratorWriter // Text Direction $textDirection = $firstParagraphAlignment->getTextDirection(); - if ($textDirection != Alignment::TEXT_DIRECTION_HORIZONTAL) { + if (Alignment::TEXT_DIRECTION_HORIZONTAL != $textDirection) { $objWriter->writeAttribute('vert', $textDirection); } // Alignment (horizontal) $verticalAlign = $firstParagraphAlignment->getVertical(); - if ($verticalAlign != Alignment::VERTICAL_BASE && $verticalAlign != Alignment::VERTICAL_AUTO) { + if (Alignment::VERTICAL_BASE != $verticalAlign && Alignment::VERTICAL_AUTO != $verticalAlign) { $objWriter->writeAttribute('anchor', $verticalAlign); } @@ -467,15 +471,18 @@ abstract class AbstractSlide extends AbstractDecoratorWriter $borderDiagonalDown = $currentCell->getBorders()->getDiagonalDown(); $borderDiagonalUp = $currentCell->getBorders()->getDiagonalUp(); // Fix PowerPoint implementation - if (!is_null($nextCellRight) - && $nextCellRight->getBorders()->getRight()->getHashCode() != $defaultBorder->getHashCode() - ) { - $borderRight = $nextCellRight->getBorders()->getLeft(); + if ($hasNextCellRight) { + $nextCellRight = $shape->getRow($row)->getCell($cell + 1); + if ($nextCellRight->getBorders()->getRight()->getHashCode() != $defaultBorder->getHashCode()) { + $borderRight = $nextCellRight->getBorders()->getLeft(); + } } - if (!is_null($nextCellBelow) - && $nextCellBelow->getBorders()->getBottom()->getHashCode() != $defaultBorder->getHashCode() - ) { - $borderBottom = $nextCellBelow->getBorders()->getTop(); + if ($hasNextRowBelow) { + $nextRowBelow = $shape->getRow($row + 1); + $nextCellBelow = $nextRowBelow->getCell($cell); + if ($nextCellBelow->getBorders()->getBottom()->getHashCode() != $defaultBorder->getHashCode()) { + $borderBottom = $nextCellBelow->getBorders()->getTop(); + } } // Write borders $this->writeBorder($objWriter, $borderLeft, 'L'); @@ -498,14 +505,12 @@ abstract class AbstractSlide extends AbstractDecoratorWriter } /** - * Write paragraphs + * Write paragraphs. * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param \PhpOffice\PhpPresentation\Shape\RichText\Paragraph[] $paragraphs - * @param bool $bIsPlaceholder - * @throws \Exception + * @param XMLWriter $objWriter XML Writer + * @param array $paragraphs */ - protected function writeParagraphs(XMLWriter $objWriter, $paragraphs, $bIsPlaceholder = false) + protected function writeParagraphs(XMLWriter $objWriter, array $paragraphs, bool $bIsPlaceholder = false): void { // Loop trough paragraphs foreach ($paragraphs as $paragraph) { @@ -514,22 +519,44 @@ abstract class AbstractSlide extends AbstractDecoratorWriter // a:pPr if (!$bIsPlaceholder) { + // a:pPr $objWriter->startElement('a:pPr'); $objWriter->writeAttribute('algn', $paragraph->getAlignment()->getHorizontal()); + $objWriter->writeAttribute('rtl', $paragraph->getAlignment()->isRTL() ? '1' : '0'); $objWriter->writeAttribute('fontAlgn', $paragraph->getAlignment()->getVertical()); $objWriter->writeAttribute('marL', CommonDrawing::pixelsToEmu($paragraph->getAlignment()->getMarginLeft())); $objWriter->writeAttribute('marR', CommonDrawing::pixelsToEmu($paragraph->getAlignment()->getMarginRight())); $objWriter->writeAttribute('indent', CommonDrawing::pixelsToEmu($paragraph->getAlignment()->getIndent())); $objWriter->writeAttribute('lvl', $paragraph->getAlignment()->getLevel()); + // a:pPr:a:lnSpc $objWriter->startElement('a:lnSpc'); - $objWriter->startElement('a:spcPct'); - $objWriter->writeAttribute('val', $paragraph->getLineSpacing() * 1000); + if ($paragraph->getLineSpacingMode() == Paragraph::LINE_SPACING_MODE_POINT) { + $objWriter->startElement('a:spcPts'); + $objWriter->writeAttribute('val', $paragraph->getLineSpacing() * 100); + $objWriter->endElement(); + } else { + $objWriter->startElement('a:spcPct'); + $objWriter->writeAttribute('val', $paragraph->getLineSpacing() * 1000); + $objWriter->endElement(); + } + // >a:pPr:a:lnSpc + $objWriter->endElement(); + + $objWriter->startElement('a:spcBef'); + $objWriter->startElement('a:spcPts'); + $objWriter->writeAttribute('val', $paragraph->getSpacingBefore() * 100); + $objWriter->endElement(); + $objWriter->endElement(); + + $objWriter->startElement('a:spcAft'); + $objWriter->startElement('a:spcPts'); + $objWriter->writeAttribute('val', $paragraph->getSpacingAfter() * 100); $objWriter->endElement(); $objWriter->endElement(); // Bullet type specified? - if ($paragraph->getBulletStyle()->getBulletType() != Bullet::TYPE_NONE) { + if (Bullet::TYPE_NONE != $paragraph->getBulletStyle()->getBulletType()) { // Color // a:buClr must be before a:buFont (else PowerPoint crashes at launch) if ($paragraph->getBulletStyle()->getBulletColor() instanceof Color) { @@ -543,16 +570,16 @@ abstract class AbstractSlide extends AbstractDecoratorWriter $objWriter->writeAttribute('typeface', $paragraph->getBulletStyle()->getBulletFont()); $objWriter->endElement(); - if ($paragraph->getBulletStyle()->getBulletType() == Bullet::TYPE_BULLET) { + if (Bullet::TYPE_BULLET == $paragraph->getBulletStyle()->getBulletType()) { // a:buChar $objWriter->startElement('a:buChar'); $objWriter->writeAttribute('char', $paragraph->getBulletStyle()->getBulletChar()); $objWriter->endElement(); - } elseif ($paragraph->getBulletStyle()->getBulletType() == Bullet::TYPE_NUMERIC) { + } elseif (Bullet::TYPE_NUMERIC == $paragraph->getBulletStyle()->getBulletType()) { // a:buAutoNum $objWriter->startElement('a:buAutoNum'); $objWriter->writeAttribute('type', $paragraph->getBulletStyle()->getBulletNumericStyle()); - if ($paragraph->getBulletStyle()->getBulletNumericStartAt() != 1) { + if (1 != $paragraph->getBulletStyle()->getBulletNumericStartAt()) { $objWriter->writeAttribute('startAt', $paragraph->getBulletStyle()->getBulletNumericStartAt()); } $objWriter->endElement(); @@ -593,8 +620,11 @@ abstract class AbstractSlide extends AbstractDecoratorWriter $this->writeColor($objWriter, $element->getFont()->getColor()); $objWriter->endElement(); - // Font - a:latin - $objWriter->startElement('a:latin'); + // Font + // - a:latin + // - a:ea + // - a:cs + $objWriter->startElement('a:' . $element->getFont()->getFormat()); $objWriter->writeAttribute('typeface', $element->getFont()->getName()); $objWriter->endElement(); @@ -618,14 +648,11 @@ abstract class AbstractSlide extends AbstractDecoratorWriter } /** - * Write Line Shape + * Write Line Shape. * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param \PhpOffice\PhpPresentation\Shape\Line $shape - * @param int $shapeId - * @throws \Exception + * @param XMLWriter $objWriter XML Writer */ - protected function writeShapeLine(XMLWriter $objWriter, Line $shape, $shapeId) + protected function writeShapeLine(XMLWriter $objWriter, Line $shape, int $shapeId): void { // p:sp $objWriter->startElement('p:cxnSp'); @@ -713,11 +740,9 @@ abstract class AbstractSlide extends AbstractDecoratorWriter } /** - * Write Shadow - * @param XMLWriter $objWriter - * @param Shadow $oShadow + * Write Shadow. */ - protected function writeShadow(XMLWriter $objWriter, $oShadow) + protected function writeShadow(XMLWriter $objWriter, Shadow $oShadow): void { if (!($oShadow instanceof Shadow)) { return; @@ -734,7 +759,7 @@ abstract class AbstractSlide extends AbstractDecoratorWriter $objWriter->startElement('a:outerShdw'); $objWriter->writeAttribute('blurRad', CommonDrawing::pixelsToEmu($oShadow->getBlurRadius())); $objWriter->writeAttribute('dist', CommonDrawing::pixelsToEmu($oShadow->getDistance())); - $objWriter->writeAttribute('dir', CommonDrawing::degreesToAngle($oShadow->getDirection())); + $objWriter->writeAttribute('dir', CommonDrawing::degreesToAngle((int) $oShadow->getDirection())); $objWriter->writeAttribute('algn', $oShadow->getAlignment()); $objWriter->writeAttribute('rotWithShape', '0'); @@ -746,13 +771,12 @@ abstract class AbstractSlide extends AbstractDecoratorWriter } /** - * Write hyperlink + * Write hyperlink. * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param \PhpOffice\PhpPresentation\AbstractShape|\PhpOffice\PhpPresentation\Shape\RichText\TextElement $shape - * @throws \Exception + * @param XMLWriter $objWriter XML Writer + * @param AbstractShape|TextElement $shape */ - protected function writeHyperlink(XMLWriter $objWriter, $shape) + protected function writeHyperlink(XMLWriter $objWriter, $shape): void { if (!$shape->hasHyperlink()) { return; @@ -764,16 +788,26 @@ abstract class AbstractSlide extends AbstractDecoratorWriter if ($shape->getHyperlink()->isInternal()) { $objWriter->writeAttribute('action', $shape->getHyperlink()->getUrl()); } + + if ($shape->getHyperlink()->isTextColorUsed()) { + $objWriter->startElement('a:extLst'); + $objWriter->startElement('a:ext'); + $objWriter->writeAttribute('uri', '{A12FA001-AC4F-418D-AE19-62706E023703}'); + $objWriter->startElement('ahyp:hlinkClr'); + $objWriter->writeAttribute('xmlns:ahyp', 'http://schemas.microsoft.com/office/drawing/2018/hyperlinkcolor'); + $objWriter->writeAttribute('val', 'tx'); + $objWriter->endElement(); + $objWriter->endElement(); + $objWriter->endElement(); + } + $objWriter->endElement(); } /** - * Write Note Slide - * @param Note $pNote - * @throws \Exception - * @return string + * Write Note Slide. */ - protected function writeNote(Note $pNote) + protected function writeNote(Note $pNote): string { // Create XML writer $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); @@ -1054,14 +1088,109 @@ abstract class AbstractSlide extends AbstractDecoratorWriter return $objWriter->getData(); } + /** + * Write AutoShape + * + * @param XMLWriter $objWriter XML Writer + * @param AutoShape $shape + * @param int $shapeId + */ + protected function writeShapeAutoShape(XMLWriter $objWriter, AutoShape $shape, int $shapeId): void + { + // p:sp + $objWriter->startElement('p:sp'); + + // p:sp\p:nvSpPr + $objWriter->startElement('p:nvSpPr'); + // p:sp\p:nvSpPr\p:cNvPr + $objWriter->startElement('p:cNvPr'); + $objWriter->writeAttribute('id', $shapeId); + $objWriter->writeAttribute('name', ''); + $objWriter->writeAttribute('descr', ''); + // p:sp\p:nvSpPr\p:cNvPr\ + $objWriter->endElement(); + // p:sp\p:nvSpPr\p:cNvSpPr + $objWriter->writeElement('p:cNvSpPr'); + // p:sp\p:nvSpPr\p:nvPr + $objWriter->writeElement('p:nvPr'); + // p:sp\p:nvSpPr\ + $objWriter->endElement(); + + // p:sp\p:spPr + $objWriter->startElement('p:spPr'); + + // p:sp\p:spPr\a:xfrm + $objWriter->startElement('a:xfrm'); + $objWriter->writeAttributeIf($shape->getRotation() != 0, 'rot', CommonDrawing::degreesToAngle((int) $shape->getRotation())); + // p:sp\p:spPr\a:xfrm\a:off + $objWriter->startElement('a:off'); + $objWriter->writeAttribute('x', CommonDrawing::pixelsToEmu($shape->getOffsetX())); + $objWriter->writeAttribute('y', CommonDrawing::pixelsToEmu($shape->getOffsetY())); + $objWriter->endElement(); + // p:sp\p:spPr\a:xfrm\a:ext + $objWriter->startElement('a:ext'); + $objWriter->writeAttribute('cx', CommonDrawing::pixelsToEmu($shape->getWidth())); + $objWriter->writeAttribute('cy', CommonDrawing::pixelsToEmu($shape->getHeight())); + $objWriter->endElement(); + // p:sp\p:spPr\a:xfrm\ + $objWriter->endElement(); + + // p:sp\p:spPr\a:prstGeom + $objWriter->startElement('a:prstGeom'); + $objWriter->writeAttribute('prst', $shape->getType()); + // p:sp\p:spPr\a:prstGeom\a:avLst + $objWriter->writeElement('a:avLst'); + // p:sp\p:spPr\a:prstGeom\ + $objWriter->endElement(); + // Fill + $this->writeFill($objWriter, $shape->getFill()); + // Outline + $this->writeOutline($objWriter, $shape->getOutline()); + + // p:sp\p:spPr\ + $objWriter->endElement(); + // p:sp\p:txBody + $objWriter->startElement('p:txBody'); + // p:sp\p:txBody\a:bodyPr + $objWriter->startElement('a:bodyPr'); + $objWriter->writeAttribute('vertOverflow', 'clip'); + $objWriter->writeAttribute('rtlCol', '0'); + $objWriter->writeAttribute('anchor', 'ctr'); + // p:sp\p:txBody\a:bodyPr\ + $objWriter->endElement(); + + // p:sp\p:txBody\a:lstStyle + $objWriter->writeElement('a:lstStyle'); + + // p:sp\p:txBody\a:p + $objWriter->startElement('a:p'); + + // p:sp\p:txBody\a:p\a:pPr + $objWriter->writeElementBlock('a:pPr', [ + 'algn' => 'ctr', + ]); + // p:sp\p:txBody\a:p\a:r + $objWriter->startElement('a:r'); + // p:sp\p:txBody\a:p\a:r\a:t + $objWriter->startElement('a:t'); + $objWriter->writeCData(Text::controlCharacterPHP2OOXML($shape->getText())); + $objWriter->endElement(); + // p:sp\p:txBody\a:p\a:r\ + $objWriter->endElement(); + // p:sp\p:txBody\a:p\ + $objWriter->endElement(); + // p:sp\p:txBody\ + $objWriter->endElement(); + // p:sp\ + $objWriter->endElement(); + } + /** * Write chart * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param \PhpOffice\PhpPresentation\Shape\Chart $shape - * @param int $shapeId + * @param XMLWriter $objWriter XML Writer */ - protected function writeShapeChart(XMLWriter $objWriter, ShapeChart $shape, $shapeId) + protected function writeShapeChart(XMLWriter $objWriter, ShapeChart $shape, int $shapeId): void { // p:graphicFrame $objWriter->startElement('p:graphicFrame'); @@ -1086,7 +1215,7 @@ abstract class AbstractSlide extends AbstractDecoratorWriter $objWriter->endElement(); // p:xfrm $objWriter->startElement('p:xfrm'); - $objWriter->writeAttributeIf($shape->getRotation() != 0, 'rot', CommonDrawing::degreesToAngle($shape->getRotation())); + $objWriter->writeAttributeIf(0 != $shape->getRotation(), 'rot', CommonDrawing::degreesToAngle((int) $shape->getRotation())); // a:off $objWriter->startElement('a:off'); $objWriter->writeAttribute('x', CommonDrawing::pixelsToEmu($shape->getOffsetX())); @@ -1115,39 +1244,63 @@ abstract class AbstractSlide extends AbstractDecoratorWriter } /** - * Write pic + * Write pic. * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param \PhpOffice\PhpPresentation\Shape\AbstractGraphic $shape - * @param int $shapeId - * @throws \Exception + * @param XMLWriter $objWriter XML Writer */ - protected function writeShapePic(XMLWriter $objWriter, AbstractGraphic $shape, $shapeId) + protected function writeShapePic(XMLWriter $objWriter, AbstractGraphic $shape, int $shapeId): void { // p:pic $objWriter->startElement('p:pic'); + // p:nvPicPr $objWriter->startElement('p:nvPicPr'); + // p:cNvPr $objWriter->startElement('p:cNvPr'); $objWriter->writeAttribute('id', $shapeId); $objWriter->writeAttribute('name', $shape->getName()); $objWriter->writeAttribute('descr', $shape->getDescription()); + // a:hlinkClick if ($shape->hasHyperlink()) { $this->writeHyperlink($objWriter, $shape); } + + if ($shape instanceof AbstractDrawingAdapter && $shape->getExtension() == 'svg') { + $objWriter->startElement('a:extLst'); + $objWriter->startElement('a:ext'); + $objWriter->writeAttribute('uri', '{FF2B5EF4-FFF2-40B4-BE49-F238E27FC236}'); + $objWriter->startElement('a16:creationId'); + $objWriter->writeAttribute('xmlns:a16', 'http://schemas.microsoft.com/office/drawing/2014/main'); + $objWriter->writeAttribute('id', '{F8CFD691-5332-EB49-9B42-7D7B3DB9185D}'); + $objWriter->endElement(); + $objWriter->endElement(); + $objWriter->endElement(); + } + $objWriter->endElement(); + // p:cNvPicPr $objWriter->startElement('p:cNvPicPr'); + // a:picLocks $objWriter->startElement('a:picLocks'); $objWriter->writeAttribute('noChangeAspect', '1'); $objWriter->endElement(); + + // #p:cNvPicPr $objWriter->endElement(); + // p:nvPr $objWriter->startElement('p:nvPr'); - /** + // PlaceHolder + if ($shape->isPlaceholder()) { + $objWriter->startElement('p:ph'); + $objWriter->writeAttribute('type', $shape->getPlaceholder()->getType()); + $objWriter->endElement(); + } + /* * @link : https://github.com/stefslon/exportToPPTX/blob/master/exportToPPTX.m#L2128 */ if ($shape instanceof Media) { @@ -1162,7 +1315,7 @@ abstract class AbstractSlide extends AbstractDecoratorWriter $objWriter->writeAttribute('uri', '{DAA4B4D4-6D71-4841-9C94-3DE7FCFB9230}'); // p:nvPr > p:extLst > p:ext > p14:media $objWriter->startElement('p14:media'); - $objWriter->writeAttribute('r:embed', $shape->relationId); + $objWriter->writeAttribute('r:embed', ((int) $shape->relationId + 1)); $objWriter->writeAttribute('xmlns:p14', 'http://schemas.microsoft.com/office/powerpoint/2010/main'); // p:nvPr > p:extLst > p:ext > ##p14:media $objWriter->endElement(); @@ -1174,38 +1327,81 @@ abstract class AbstractSlide extends AbstractDecoratorWriter // ##p:nvPr $objWriter->endElement(); $objWriter->endElement(); + // p:blipFill $objWriter->startElement('p:blipFill'); + // a:blip $objWriter->startElement('a:blip'); $objWriter->writeAttribute('r:embed', $shape->relationId); + + if ($shape instanceof AbstractDrawingAdapter && $shape->getExtension() == 'svg') { + // a:extLst + $objWriter->startElement('a:extLst'); + + // a:extLst > a:ext + $objWriter->startElement('a:ext'); + $objWriter->writeAttribute('uri', '{28A0092B-C50C-407E-A947-70E740481C1C}'); + // a:extLst > a:ext > a14:useLocalDpi + $objWriter->startElement('a14:useLocalDpi'); + $objWriter->writeAttribute('xmlns:a14', 'http://schemas.microsoft.com/office/drawing/2010/main'); + $objWriter->writeAttribute('val', '0'); + // a:extLst > a:ext > ##a14:useLocalDpi + $objWriter->endElement(); + // a:extLst > ##a:ext + $objWriter->endElement(); + + // a:extLst > a:ext + $objWriter->startElement('a:ext'); + $objWriter->writeAttribute('uri', '{96DAC541-7B7A-43D3-8B79-37D633B846F1}'); + // a:extLst > a:ext > asvg:svgBlip + $objWriter->startElement('asvg:svgBlip'); + $objWriter->writeAttribute('xmlns:asvg', 'http://schemas.microsoft.com/office/drawing/2016/SVG/main'); + $objWriter->writeAttribute('r:embed', $shape->relationId); + // a:extLst > a:ext > ##asvg:svgBlip + $objWriter->endElement(); + // a:extLst > ##a:ext + $objWriter->endElement(); + + // ##a:extLst + $objWriter->endElement(); + } + $objWriter->endElement(); + // a:stretch $objWriter->startElement('a:stretch'); - $objWriter->writeElement('a:fillRect', null); + $objWriter->writeElement('a:fillRect'); $objWriter->endElement(); + $objWriter->endElement(); + // p:spPr $objWriter->startElement('p:spPr'); // a:xfrm $objWriter->startElement('a:xfrm'); - $objWriter->writeAttributeIf($shape->getRotation() != 0, 'rot', CommonDrawing::degreesToAngle($shape->getRotation())); + $objWriter->writeAttributeIf(0 != $shape->getRotation(), 'rot', CommonDrawing::degreesToAngle((int) $shape->getRotation())); + // a:off $objWriter->startElement('a:off'); $objWriter->writeAttribute('x', CommonDrawing::pixelsToEmu($shape->getOffsetX())); $objWriter->writeAttribute('y', CommonDrawing::pixelsToEmu($shape->getOffsetY())); $objWriter->endElement(); + // a:ext $objWriter->startElement('a:ext'); $objWriter->writeAttribute('cx', CommonDrawing::pixelsToEmu($shape->getWidth())); $objWriter->writeAttribute('cy', CommonDrawing::pixelsToEmu($shape->getHeight())); $objWriter->endElement(); + $objWriter->endElement(); + // a:prstGeom $objWriter->startElement('a:prstGeom'); $objWriter->writeAttribute('prst', 'rect'); - // a:avLst + // // a:prstGeom/a:avLst $objWriter->writeElement('a:avLst', null); + // ##a:prstGeom $objWriter->endElement(); $this->writeFill($objWriter, $shape->getFill()); @@ -1213,18 +1409,16 @@ abstract class AbstractSlide extends AbstractDecoratorWriter $this->writeShadow($objWriter, $shape->getShadow()); $objWriter->endElement(); + $objWriter->endElement(); } /** - * Write group + * Write group. * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param \PhpOffice\PhpPresentation\Shape\Group $group - * @param int $shapeId - * @throws \Exception + * @param XMLWriter $objWriter XML Writer */ - protected function writeShapeGroup(XMLWriter $objWriter, Group $group, &$shapeId) + protected function writeShapeGroup(XMLWriter $objWriter, Group $group, int &$shapeId): void { // p:grpSp $objWriter->startElement('p:grpSp'); @@ -1273,11 +1467,7 @@ abstract class AbstractSlide extends AbstractDecoratorWriter $objWriter->endElement(); // p:grpSp } - /** - * @param \PhpOffice\PhpPresentation\Slide\AbstractSlide $pSlide - * @param $objWriter - */ - protected function writeSlideBackground(AbstractSlideAlias $pSlide, XMLWriter $objWriter) + protected function writeSlideBackground(AbstractSlideAlias $pSlide, XMLWriter $objWriter): void { if (!($pSlide->getBackground() instanceof Slide\AbstractBackground)) { return; @@ -1324,7 +1514,7 @@ abstract class AbstractSlide extends AbstractDecoratorWriter // > p:bgPr $objWriter->endElement(); } - /** + /* * @link : http://www.officeopenxml.com/prSlide-background.php */ if ($oBackground instanceof Slide\Background\SchemeColor) { @@ -1342,16 +1532,14 @@ abstract class AbstractSlide extends AbstractDecoratorWriter $objWriter->endElement(); } - /** - * Write Transition Slide - * @link http://officeopenxml.com/prSlide-transitions.php - * @param XMLWriter $objWriter - * @param Slide\Transition $transition + * Write Transition Slide. + * + * @see http://officeopenxml.com/prSlide-transitions.php */ - protected function writeSlideTransition(XMLWriter $objWriter, $transition) + protected function writeSlideTransition(XMLWriter $objWriter, ?Slide\Transition $transition): void { - if (!$transition instanceof Slide\Transition) { + if (!$transition) { return; } $objWriter->startElement('p:transition'); @@ -1590,21 +1778,22 @@ abstract class AbstractSlide extends AbstractDecoratorWriter $objWriter->endElement(); } - private function getGUID() + private function getGUID(): string { if (function_exists('com_create_guid')) { return com_create_guid(); } else { - mt_srand((double)microtime() * 10000);//optional for php 4.2.0 and up. - $charid = strtoupper(md5(uniqid(rand(), true))); - $hyphen = chr(45);// "-" + mt_srand(intval(microtime(true) * 10000)); + $charid = strtoupper(md5(uniqid((string) rand(), true))); + $hyphen = chr(45); // "-" $uuid = chr(123)// "{" . substr($charid, 0, 8) . $hyphen . substr($charid, 8, 4) . $hyphen . substr($charid, 12, 4) . $hyphen . substr($charid, 16, 4) . $hyphen . substr($charid, 20, 12) - . chr(125);// "}" + . chr(125); // "}" + return $uuid; } } diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/CommentAuthors.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/CommentAuthors.php old mode 100755 new mode 100644 index 07e5d1d..35b90e1 --- a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/CommentAuthors.php +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/CommentAuthors.php @@ -1,4 +1,22 @@ getPresentation()->getAllSlides() as $oSlide) { foreach ($oSlide->getShapeCollection() as $oShape) { if (!($oShape instanceof Comment)) { @@ -43,6 +57,7 @@ class CommentAuthors extends AbstractDecoratorWriter /** * @param Author[] $arrayAuthors + * * @return string */ protected function writeCommentsAuthors($arrayAuthors) @@ -63,7 +78,7 @@ class CommentAuthors extends AbstractDecoratorWriter $objWriter->writeAttribute('id', $oAuthor->getIndex()); $objWriter->writeAttribute('name', $oAuthor->getName()); $objWriter->writeAttribute('initials', $oAuthor->getInitials()); - $objWriter->writeAttribute('lastIdx', "2"); + $objWriter->writeAttribute('lastIdx', '2'); $objWriter->writeAttribute('clrIdx', 0); $objWriter->endElement(); } diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/ContentTypes.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/ContentTypes.php old mode 100755 new mode 100644 index 34ca83e..6d0a6d7 --- a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/ContentTypes.php +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/ContentTypes.php @@ -10,29 +10,28 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Writer\PowerPoint2007; +use PhpOffice\Common\Adapter\Zip\ZipInterface; +use PhpOffice\Common\XMLWriter; use PhpOffice\PhpPresentation\Shape\Chart as ShapeChart; use PhpOffice\PhpPresentation\Shape\Comment; -use PhpOffice\PhpPresentation\Shape\Drawing as ShapeDrawing; -use PhpOffice\Common\XMLWriter; -use PhpOffice\PhpPresentation\Writer\PowerPoint2007; +use PhpOffice\PhpPresentation\Shape\Drawing\AbstractDrawingAdapter; /** - * \PhpOffice\PhpPresentation\Writer\PowerPoint2007\ContentTypes + * \PhpOffice\PhpPresentation\Writer\PowerPoint2007\ContentTypes. */ class ContentTypes extends AbstractDecoratorWriter { - /** - * @return \PhpOffice\Common\Adapter\Zip\ZipInterface - * @throws \Exception - */ - public function render() + public function render(): ZipInterface { // Create XML writer $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); @@ -50,6 +49,9 @@ class ContentTypes extends AbstractDecoratorWriter // XML $this->writeDefaultContentType($objWriter, 'xml', 'application/xml'); + // SVG + $this->writeDefaultContentType($objWriter, 'svg', 'image/svg+xml'); + // Presentation $this->writeOverrideContentType($objWriter, '/ppt/presentation.xml', 'application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml'); @@ -67,11 +69,12 @@ class ContentTypes extends AbstractDecoratorWriter $sldLayoutNr = 0; $sldLayoutId = time() + 689016272; // requires minimum value of 2 147 483 648 foreach ($this->oPresentation->getAllMasterSlides() as $idx => $oSlideMaster) { - $oSlideMaster->setRelsIndex($idx + 1); + $oSlideMaster->setRelsIndex((string) ($idx + 1)); $this->writeOverrideContentType($objWriter, '/ppt/slideMasters/slideMaster' . $oSlideMaster->getRelsIndex() . '.xml', 'application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml'); $this->writeOverrideContentType($objWriter, '/ppt/theme/theme' . $oSlideMaster->getRelsIndex() . '.xml', 'application/vnd.openxmlformats-officedocument.theme+xml'); foreach ($oSlideMaster->getAllSlideLayouts() as $oSlideLayout) { $oSlideLayout->layoutNr = ++$sldLayoutNr; + $oSlideLayout->setRelsIndex((string) $oSlideLayout->layoutNr); $oSlideLayout->layoutId = ++$sldLayoutId; $this->writeOverrideContentType($objWriter, '/ppt/slideLayouts/slideLayout' . $oSlideLayout->layoutNr . '.xml', 'application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml'); } @@ -100,13 +103,13 @@ class ContentTypes extends AbstractDecoratorWriter } // Add media content-types - $aMediaContentTypes = array(); + $aMediaContentTypes = []; // GIF, JPEG, PNG - $aMediaContentTypes['gif'] = 'image/gif'; - $aMediaContentTypes['jpg'] = 'image/jpeg'; + $aMediaContentTypes['gif'] = 'image/gif'; + $aMediaContentTypes['jpg'] = 'image/jpeg'; $aMediaContentTypes['jpeg'] = 'image/jpeg'; - $aMediaContentTypes['png'] = 'image/png'; + $aMediaContentTypes['png'] = 'image/png'; foreach ($aMediaContentTypes as $key => $value) { $this->writeDefaultContentType($objWriter, $key, $value); } @@ -120,8 +123,12 @@ class ContentTypes extends AbstractDecoratorWriter $shapeIndex = $this->getDrawingHashTable()->getByIndex($i); if ($shapeIndex instanceof ShapeChart) { // Chart content type - $this->writeOverrideContentType($objWriter, '/ppt/charts/chart' . $shapeIndex->getImageIndex() . '.xml', 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml'); - } else { + $this->writeOverrideContentType( + $objWriter, + '/ppt/charts/chart' . $shapeIndex->getImageIndex() . '.xml', + 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml' + ); + } elseif ($shapeIndex instanceof AbstractDrawingAdapter) { $extension = strtolower($shapeIndex->getExtension()); $mimeType = $shapeIndex->getMimeType(); @@ -141,18 +148,14 @@ class ContentTypes extends AbstractDecoratorWriter } /** - * Write Default content type + * Write Default content type. * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param string $pPartname Part name - * @param string $pContentType Content type - * @throws \Exception + * @param XMLWriter $objWriter XML Writer + * @param string $pPartname Part name + * @param string $pContentType Content type */ - private function writeDefaultContentType(XMLWriter $objWriter, $pPartname = '', $pContentType = '') + protected function writeDefaultContentType(XMLWriter $objWriter, string $pPartname, string $pContentType): void { - if ($pPartname == '' || $pContentType == '') { - throw new \Exception("Invalid parameters passed."); - } // Write content type $objWriter->startElement('Default'); $objWriter->writeAttribute('Extension', $pPartname); @@ -161,18 +164,14 @@ class ContentTypes extends AbstractDecoratorWriter } /** - * Write Override content type + * Write Override content type. * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param string $pPartname Part name - * @param string $pContentType Content type - * @throws \Exception + * @param XMLWriter $objWriter XML Writer + * @param string $pPartname Part name + * @param string $pContentType Content type */ - private function writeOverrideContentType(XMLWriter $objWriter, $pPartname = '', $pContentType = '') + protected function writeOverrideContentType(XMLWriter $objWriter, string $pPartname, string $pContentType): void { - if ($pPartname == '' || $pContentType == '') { - throw new \Exception("Invalid parameters passed."); - } // Write content type $objWriter->startElement('Override'); $objWriter->writeAttribute('PartName', $pPartname); diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsApp.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsApp.php old mode 100755 new mode 100644 index 5bb8714..fe81c50 --- a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsApp.php +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsApp.php @@ -1,16 +1,31 @@ writeElement('Application', 'Microsoft Office PowerPoint'); // Slides - $objWriter->writeElement('Slides', $this->getPresentation()->getSlideCount()); + $objWriter->writeElement('Slides', (string) $this->getPresentation()->getSlideCount()); // ScaleCrop $objWriter->writeElement('ScaleCrop', 'false'); diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsCore.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsCore.php old mode 100755 new mode 100644 index 591f42b..f1afb28 --- a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsCore.php +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsCore.php @@ -1,16 +1,31 @@ startElement('property'); $objWriter->writeAttribute('fmtid', '{D5CDD505-2E9C-101B-9397-08002B2CF9AE}'); - $objWriter->writeAttribute('pid', 2); + $objWriter->writeAttribute('pid', (++$pId) * 2); $objWriter->writeAttribute('name', '_MarkAsFinal'); // property > vt:bool @@ -37,6 +56,37 @@ class DocPropsCustom extends AbstractDecoratorWriter $objWriter->endElement(); } + $oDocumentProperties = $this->oPresentation->getDocumentProperties(); + foreach ($oDocumentProperties->getCustomProperties() as $customProperty) { + $propertyValue = $oDocumentProperties->getCustomPropertyValue($customProperty); + $propertyType = $oDocumentProperties->getCustomPropertyType($customProperty); + + $objWriter->startElement('property'); + $objWriter->writeAttribute('fmtid', '{D5CDD505-2E9C-101B-9397-08002B2CF9AE}'); + $objWriter->writeAttribute('pid', (++$pId) * 2); + $objWriter->writeAttribute('name', $customProperty); + switch ($propertyType) { + case DocumentProperties::PROPERTY_TYPE_INTEGER: + $objWriter->writeElement('vt:i4', (string) $propertyValue); + break; + case DocumentProperties::PROPERTY_TYPE_FLOAT: + $objWriter->writeElement('vt:r8', (string) $propertyValue); + break; + case DocumentProperties::PROPERTY_TYPE_BOOLEAN: + $objWriter->writeElement('vt:bool', $propertyValue ? 'true' : 'false'); + break; + case DocumentProperties::PROPERTY_TYPE_DATE: + $objWriter->startElement('vt:filetime'); + $objWriter->writeRaw(date(DATE_W3C, (int) $propertyValue)); + $objWriter->endElement(); + break; + default: + $objWriter->writeElement('vt:lpwstr', (string) $propertyValue); + break; + } + $objWriter->endElement(); + } + // > Properties $objWriter->endElement(); diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsThumbnail.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsThumbnail.php old mode 100755 new mode 100644 index 64308a4..6c54be1 --- a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsThumbnail.php +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/DocPropsThumbnail.php @@ -1,14 +1,30 @@ getPresentation()->getPresentationProperties()->getThumbnailPath(); diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/LayoutPack/AbstractLayoutPack.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/LayoutPack/AbstractLayoutPack.php deleted file mode 100755 index 8401aa4..0000000 --- a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/LayoutPack/AbstractLayoutPack.php +++ /dev/null @@ -1,225 +0,0 @@ -masterSlides; - } - - /** - * Get master slide relations - * - * @return array - */ - public function getMasterSlideRelations() - { - return $this->masterSlideRels; - } - - /** - * Get themes - * - * @return array - */ - public function getThemes() - { - return $this->themes; - } - - /** - * Get theme relations - * - * @return array - */ - public function getThemeRelations() - { - return $this->themeRelations; - } - - /** - * Get array of slide layouts - * - * @return array - */ - public function getLayouts() - { - return $this->layouts; - } - - /** - * Get array of slide layout relations - * - * @return array - */ - public function getLayoutRelations() - { - return $this->layoutRelations; - } - - /** - * Find specific slide layout. - * - * This is an array consisting of: - * - masterid - * - name (string) - * - body (string) - * - * @param string $name - * @param int $masterId - * @return array - * @throws \Exception - */ - public function findLayout($name = '', $masterId = 1) - { - foreach ($this->layouts as $layout) { - if ($layout['name'] == $name && $layout['masterid'] == $masterId) { - return $layout; - } - } - - throw new \Exception("Could not find slide layout $name in current layout pack."); - } - - /** - * Find specific slide layout id. - * - * @param string $name - * @return int - * @throws \Exception - */ - public function findLayoutId($name = '') - { - foreach ($this->layouts as $layoutId => $layout) { - if ($layout['name'] == $name) { - return $layoutId; - } - } - - throw new \Exception("Could not find slide layout $name in current layout pack."); - } - - /** - * Find specific slide layout name. - * - * @param int $idLayout - * @return int - * @throws \Exception - */ - public function findLayoutName($idLayout = null) - { - foreach ($this->layouts as $layoutId => $layout) { - if ($layoutId == $idLayout) { - return $layout['name']; - } - } - - throw new \Exception("Could not find slide layout $idLayout in current layout pack."); - } -} diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/LayoutPack/PackDefault.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/LayoutPack/PackDefault.php deleted file mode 100755 index 0df0c01..0000000 --- a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/LayoutPack/PackDefault.php +++ /dev/null @@ -1,3281 +0,0 @@ -masterSlides = array( - array( - 'masterid' => 1, - 'body' => ' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master title style - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master text styles - - - - - - - Second level - - - - - - - Third level - - - - - - - Fourth level - - - - - - - Fifth level - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 16/04/2009 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <#> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -')); - - // Master slide relations - $this->masterSlideRels = array( - //array('masterid' => '', 'id' => '', 'type' => '', 'contentType' => '', 'target' => '', 'contents' => '') - ); - - // Theme - $this->themes = array( - array( - 'masterid' => 1, - 'body' => ' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -')); - - // Theme relations - $this->themeRelations = array( - //array('masterid' => 1, 'id' => '', 'type' => '', 'contentType' => '', 'target' => '', 'contents' => '') - ); - - // Layouts - Layout::TITLE_SLIDE - $this->layouts[1] = array( - 'masterid' => 1, - 'name' => Layout::TITLE_SLIDE, - 'body' => ' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master title style - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master subtitle style - - - - - - - - - - - - - - - - - - - - - - - 16/04/2009 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ‹#› - - - - - - - - - - -'); - - // Layouts - Layout::TITLE_AND_CONTENT - $this->layouts[2] = array( - 'masterid' => 1, - 'name' => Layout::TITLE_AND_CONTENT, - 'body' => ' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master title style - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master text styles - - - - - - - Second level - - - - - - - Third level - - - - - - - Fourth level - - - - - - - Fifth level - - - - - - - - - - - - - - - - - - - - - - - 16/04/2009 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ‹#› - - - - - - - - - - -'); - - // Layouts - Layout::SECTION_HEADER - $this->layouts[3] = array( - 'masterid' => 1, - 'name' => Layout::SECTION_HEADER, - 'body' => ' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master title style - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master text styles - - - - - - - - - - - - - - - - - - - - - - 16/04/2009 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ‹#› - - - - - - - - - - -'); - - // Layouts - Layout::TWO_CONTENT - $this->layouts[4] = array( - 'masterid' => 1, - 'name' => Layout::TWO_CONTENT, - 'body' => ' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master title style - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master text styles - - - - - - - Second level - - - - - - - Third level - - - - - - - Fourth level - - - - - - - Fifth level - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master text styles - - - - - - - Second level - - - - - - - Third level - - - - - - - Fourth level - - - - - - - Fifth level - - - - - - - - - - - - - - - - - - - - - - - 16/04/2009 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ‹#› - - - - - - - - - - -'); - - // Layouts - Layout::COMPARISON - $this->layouts[5] = array( - 'masterid' => 1, - 'name' => Layout::COMPARISON, - 'body' => ' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master title style - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master text styles - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master text styles - - - - - - - Second level - - - - - - - Third level - - - - - - - Fourth level - - - - - - - Fifth level - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master text styles - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master text styles - - - - - - - Second level - - - - - - - Third level - - - - - - - Fourth level - - - - - - - Fifth level - - - - - - - - - - - - - - - - - - - - - - - 16/04/2009 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ‹#› - - - - - - - - - - -'); - - // Layouts - Layout::TITLE_ONLY - $this->layouts[6] = array( - 'masterid' => 1, - 'name' => Layout::TITLE_ONLY, - 'body' => ' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master title style - - - - - - - - - - - - - - - - - - - - - - - 16/04/2009 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ‹#› - - - - - - - - - - -'); - - // Layouts - Layout::BLANK - $this->layouts[7] = array( - 'masterid' => 1, - 'name' => Layout::BLANK, - 'body' => ' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 16/04/2009 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ‹#› - - - - - - - - - - -'); - - // Layouts - Layout::CONTENT_WITH_CAPTION - $this->layouts[8] = array( - 'masterid' => 1, - 'name' => Layout::CONTENT_WITH_CAPTION, - 'body' => ' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master title style - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master text styles - - - - - - - Second level - - - - - - - Third level - - - - - - - Fourth level - - - - - - - Fifth level - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master text styles - - - - - - - - - - - - - - - - - - - - - - 16/04/2009 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ‹#› - - - - - - - - - - -'); - - // Layouts - Layout::PICTURE_WITH_CAPTION - $this->layouts[9] = array( - 'masterid' => 1, - 'name' => Layout::PICTURE_WITH_CAPTION, - 'body' => ' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master title style - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master text styles - - - - - - - - - - - - - - - - - - - - - - 16/04/2009 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ‹#› - - - - - - - - - - -'); - - // Layouts - Layout::TITLE_AND_VERTICAL_TEXT - $this->layouts[10] = array( - 'masterid' => 1, - 'name' => Layout::TITLE_AND_VERTICAL_TEXT, - 'body' => ' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master title style - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master text styles - - - - - - - Second level - - - - - - - Third level - - - - - - - Fourth level - - - - - - - Fifth level - - - - - - - - - - - - - - - - - - - - - - - 16/04/2009 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ‹#› - - - - - - - - - - -'); - - // Layouts - Layout::VERTICAL_TITLE_AND_TEXT - $this->layouts[11] = array( - 'masterid' => 1, - 'name' => Layout::VERTICAL_TITLE_AND_TEXT, - 'body' => ' - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master title style - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Click to edit Master text styles - - - - - - - Second level - - - - - - - Third level - - - - - - - Fourth level - - - - - - - Fifth level - - - - - - - - - - - - - - - - - - - - - - - 16/04/2009 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ‹#› - - - - - - - - - - -'); - - // Layout relations - $this->layoutRelations = array( - //array('layoutId' => 0, 'id' => '', 'type' => '', 'contentType' => '', 'target' => '', 'contents' => '') - ); - } -} diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/LayoutPack/TemplateBased.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/LayoutPack/TemplateBased.php deleted file mode 100755 index 7a42c4c..0000000 --- a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/LayoutPack/TemplateBased.php +++ /dev/null @@ -1,203 +0,0 @@ -masterSlideRels = array(); - - // Theme relations - $this->themeRelations = array(); - - // Layout relations - $this->layoutRelations = array(); - - // Open package - $package = new \ZipArchive; - $package->open($fileName); - - // Read relations and search for officeDocument - $relations = simplexml_load_string($package->getFromName("_rels/.rels")); - foreach ($relations->Relationship as $rel) { - if ($rel["Type"] == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument") { - // Found office document! Search for master slide... - $presentationRels = simplexml_load_string($package->getFromName($this->absoluteZipPath(dirname($rel["Target"]) . "/_rels/" . basename($rel["Target"]) . ".rels"))); - foreach ($presentationRels->Relationship as $presRel) { - if ($presRel["Type"] == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster") { - // Found slide master! - $slideMasterId = str_replace('slideMaster', '', basename($presRel["Target"], '.xml')); - $this->masterSlides[] = array( - 'masterid' => $slideMasterId, - 'body' => $package->getFromName($this->absoluteZipPath(dirname($rel["Target"]) . "/" . dirname($presRel["Target"]) . "/" . basename($presRel["Target"]))) - ); - - // Search for theme & slide layouts - $masterRelations = simplexml_load_string($package->getFromName($this->absoluteZipPath(dirname($rel["Target"]) . "/" . dirname($presRel["Target"]) . "/_rels/" . basename($presRel["Target"]) . ".rels"))); - foreach ($masterRelations->Relationship as $masterRel) { - if ($masterRel["Type"] == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme") { - // Found theme! - $themeId = str_replace('theme', '', basename($masterRel["Target"], '.xml')); - $this->themes[$themeId - 1] = array( - 'masterid' => $slideMasterId, - 'body' => $package->getFromName($this->absoluteZipPath(dirname($rel["Target"]) . "/" . dirname($presRel["Target"]) . "/" . dirname($masterRel["Target"]) . "/" . basename($masterRel["Target"]))) - ); - - // Search for theme relations - $themeRelations = @simplexml_load_string($package->getFromName($this->absoluteZipPath(dirname($rel["Target"]) . "/" . dirname($presRel["Target"]) . "/" . dirname($masterRel["Target"]) . "/_rels/" . basename($masterRel["Target"]) . ".rels"))); - if ($themeRelations && $themeRelations->Relationship) { - foreach ($themeRelations->Relationship as $themeRel) { - if ($themeRel["Type"] != "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster" && $themeRel["Type"] != "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" && $themeRel["Type"] != "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme") { - // Theme relation - $this->themeRelations[] = array( - 'masterid' => $slideMasterId, - 'id' => $themeRel["Id"], - 'type' => $themeRel["Type"], - 'contentType' => '', - 'target' => $themeRel["Target"], - 'contents' => $package->getFromName($this->absoluteZipPath(dirname($rel["Target"]) . "/" . dirname($presRel["Target"]) . "/" . dirname($masterRel["Target"]) . "/" . dirname($themeRel["Target"]) . "/" . basename($themeRel["Target"]))) - ); - } - } - } - } elseif ($masterRel["Type"] == "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout") { - // Found slide layout! - $layoutId = str_replace('slideLayout', '', basename($masterRel["Target"], '.xml')); - $layout = array( - 'id' => $layoutId, - 'masterid' => $slideMasterId, - 'name' => '-unknown-', - 'body' => $package->getFromName($this->absoluteZipPath(dirname($rel["Target"]) . "/" . dirname($presRel["Target"]) . "/" . dirname($masterRel["Target"]) . "/" . basename($masterRel["Target"]))) - ); - if (utf8_encode(utf8_decode($layout['body'])) == $layout['body']) { - $layoutXml = simplexml_load_string($layout['body']); - } else { - $layoutXml = simplexml_load_string(utf8_encode($layout['body'])); - } - $layoutXml->registerXPathNamespace("p", "http://schemas.openxmlformats.org/presentationml/2006/main"); - $slide = $layoutXml->xpath('/p:sldLayout/p:cSld'); - $layout['name'] = (string) $slide[0]['name']; - $this->layouts[$layoutId] = $layout; - - // Search for slide layout relations - $layoutRelations = @simplexml_load_string($package->getFromName($this->absoluteZipPath(dirname($rel["Target"]) . "/" . dirname($presRel["Target"]) . "/" . dirname($masterRel["Target"]) . "/_rels/" . basename($masterRel["Target"]) . ".rels"))); - if ($layoutRelations && $layoutRelations->Relationship) { - foreach ($layoutRelations->Relationship as $layoutRel) { - if ($layoutRel["Type"] != "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster" && $layoutRel["Type"] != "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" && $layoutRel["Type"] != "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme") { - // Layout relation - $this->layoutRelations[] = array( - 'layoutId' => $layoutId, - 'id' => $layoutRel["Id"], - 'type' => $layoutRel["Type"], - 'contentType' => '', - 'target' => $layoutRel["Target"], - 'contents' => $package->getFromName($this->absoluteZipPath(dirname($rel["Target"]) . "/" . dirname($presRel["Target"]) . "/" . dirname($masterRel["Target"]) . "/" . dirname($layoutRel["Target"]) . "/" . basename($layoutRel["Target"]))) - ); - } - } - } - } else { - // Master slide relation - $this->masterSlideRels[] = array( - 'masterid' => $slideMasterId, - 'id' => $masterRel["Id"], - 'type' => $masterRel["Type"], - 'contentType' => '', - 'target' => $masterRel["Target"], - 'contents' => $package->getFromName($this->absoluteZipPath(dirname($rel["Target"]) . "/" . dirname($presRel["Target"]) . "/" . dirname($masterRel["Target"]) . "/" . basename($masterRel["Target"]))) - ); - } - } - } - } - - break; - } - } - - // Sort master slides - usort($this->masterSlides, array( - "\PhpOffice\PhpPresentation\Writer\PowerPoint2007\LayoutPack\TemplateBased", - "cmpMaster" - )); - - // Close package - $package->close(); - } - - /** - * Compare master slides - * - * @param array $firstSlide - * @param array $secondSlide - * @return int - */ - public static function cmpMaster($firstSlide, $secondSlide) - { - if ($firstSlide['masterid'] == $secondSlide['masterid']) { - return 0; - } - - return ($firstSlide['masterid'] < $secondSlide['masterid']) ? -1 : 1; - } - - /** - * Determine absolute zip path - * - * @param string $path - * @return string - */ - protected function absoluteZipPath($path) - { - $path = str_replace(array( - '/', - '\\' - ), DIRECTORY_SEPARATOR, $path); - $parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen'); - $absolutes = array(); - foreach ($parts as $part) { - if ('.' == $part) { - continue; - } - if ('..' == $part) { - array_pop($absolutes); - } else { - $absolutes[] = $part; - } - } - - return implode('/', $absolutes); - } -} diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptCharts.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptCharts.php old mode 100755 new mode 100644 index 7109881..4d03776 --- a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptCharts.php +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptCharts.php @@ -1,9 +1,30 @@ getDrawingHashTable()->count(); ++$i) { $shape = $this->getDrawingHashTable()->getByIndex($i); @@ -36,28 +60,26 @@ class PptCharts extends AbstractDecoratorWriter if ($shape->hasIncludedSpreadsheet()) { $this->getZip()->addFromString('ppt/charts/_rels/' . $shape->getIndexedFilename() . '.rels', $this->writeChartRelationships($shape)); - $pFilename = tempnam(sys_get_temp_dir(), 'PHPExcel'); + $pFilename = tempnam(sys_get_temp_dir(), 'PhpSpreadsheet'); $this->getZip()->addFromString('ppt/embeddings/' . $shape->getIndexedFilename() . '.xlsx', $this->writeSpreadsheet($this->getPresentation(), $shape, $pFilename . '.xlsx')); // remove temp file - if (@unlink($pFilename) === false) { - throw new \Exception('The file ' . $pFilename . ' could not removed.'); + if (false === @unlink($pFilename)) { + throw new FileRemoveException($pFilename); } } } } + return $this->getZip(); } - /** - * Write chart to XML format + * Write chart to XML format. * - * @param \PhpOffice\PhpPresentation\Shape\Chart $chart - * @return string XML Output - * @throws \Exception + * @return string XML Output */ - public function writeChart(Chart $chart) + protected function writeChart(Chart $chart): string { // Create XML writer $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); @@ -105,7 +127,7 @@ class PptCharts extends AbstractDecoratorWriter // c:hPercent $hPercent = $chart->getView3D()->getHeightPercent(); - $objWriter->writeElementIf($hPercent != null, 'c:hPercent', 'val', $hPercent); + $objWriter->writeElementIf(null != $hPercent, 'c:hPercent', 'val', $hPercent); // c:rotY $objWriter->startElement('c:rotY'); @@ -143,6 +165,11 @@ class PptCharts extends AbstractDecoratorWriter $objWriter->writeAttribute('val', '1'); $objWriter->endElement(); + // c:dispBlanksAs + $objWriter->startElement('c:dispBlanksAs'); + $objWriter->writeAttribute('val', $chart->getDisplayBlankAs()); + $objWriter->endElement(); + $objWriter->endElement(); // c:spPr @@ -152,7 +179,7 @@ class PptCharts extends AbstractDecoratorWriter $this->writeFill($objWriter, $chart->getFill()); // Border - if ($chart->getBorder()->getLineStyle() != Border::LINE_NONE) { + if (Border::LINE_NONE != $chart->getBorder()->getLineStyle()) { $this->writeBorder($objWriter, $chart->getBorder(), ''); } @@ -165,7 +192,7 @@ class PptCharts extends AbstractDecoratorWriter $objWriter->startElement('a:outerShdw'); $objWriter->writeAttribute('blurRad', CommonDrawing::pixelsToEmu($chart->getShadow()->getBlurRadius())); $objWriter->writeAttribute('dist', CommonDrawing::pixelsToEmu($chart->getShadow()->getDistance())); - $objWriter->writeAttribute('dir', CommonDrawing::degreesToAngle($chart->getShadow()->getDirection())); + $objWriter->writeAttribute('dir', CommonDrawing::degreesToAngle((int) $chart->getShadow()->getDirection())); $objWriter->writeAttribute('algn', $chart->getShadow()->getAlignment()); $objWriter->writeAttribute('rotWithShape', '0'); @@ -199,38 +226,31 @@ class PptCharts extends AbstractDecoratorWriter } /** - * Write chart to XML format + * Write chart to XML format. * - * @param PhpPresentation $presentation - * @param \PhpOffice\PhpPresentation\Shape\Chart $chart - * @param string $tempName - * @return string String output - * @throws \Exception + * @return string String output + * + * @throws FileRemoveException */ - public function writeSpreadsheet(PhpPresentation $presentation, $chart, $tempName) + protected function writeSpreadsheet(PhpPresentation $presentation, Chart $chart, string $tempName): string { - // Need output? - if (!$chart->hasIncludedSpreadsheet()) { - throw new \Exception('No spreadsheet output is required for the given chart.'); - } - - // Verify PHPExcel - if (!class_exists('PHPExcel')) { - throw new \Exception('PHPExcel has not been loaded. Include PHPExcel.php in your script, e.g. require_once \'PHPExcel.php\'.'); - } - // Create new spreadsheet - $workbook = new \PHPExcel(); + $spreadsheet = new Spreadsheet(); // Set properties $title = $chart->getTitle()->getText(); - if (strlen($title) == 0) { + if (0 == strlen($title)) { $title = 'Chart'; } - $workbook->getProperties()->setCreator($presentation->getDocumentProperties()->getCreator())->setLastModifiedBy($presentation->getDocumentProperties()->getLastModifiedBy())->setTitle($title); + $spreadsheet->getProperties() + ->setCreator( + $presentation->getDocumentProperties()->getCreator())->setLastModifiedBy( + $presentation->getDocumentProperties()->getLastModifiedBy() + ) + ->setTitle($title); // Add chart data - $sheet = $workbook->setActiveSheetIndex(0); + $sheet = $spreadsheet->setActiveSheetIndex(0); $sheet->setTitle('Sheet1'); // Write series @@ -242,14 +262,14 @@ class PptCharts extends AbstractDecoratorWriter // X-axis $axisXData = array_keys($series->getValues()); $numAxisXData = count($axisXData); - for ($i = 0; $i < $numAxisXData; $i++) { + for ($i = 0; $i < $numAxisXData; ++$i) { $sheet->setCellValueByColumnAndRow(0, $i + 2, $axisXData[$i]); } // Y-axis $axisYData = array_values($series->getValues()); $numAxisYData = count($axisYData); - for ($i = 0; $i < $numAxisYData; $i++) { + for ($i = 0; $i < $numAxisYData; ++$i) { $sheet->setCellValueByColumnAndRow(1 + $seriesIndex, $i + 2, $axisYData[$i]); } @@ -257,26 +277,24 @@ class PptCharts extends AbstractDecoratorWriter } // Save to string - $writer = \PHPExcel_IOFactory::createWriter($workbook, 'Excel2007'); + $writer = IOFactory::createWriter($spreadsheet, 'Xlsx'); $writer->save($tempName); // Load file in memory $returnValue = file_get_contents($tempName); - if (@unlink($tempName) === false) { - throw new \Exception('The file ' . $tempName . ' could not removed.'); + if (false === @unlink($tempName)) { + throw new FileRemoveException($tempName); } return $returnValue; } /** - * Write element with value attribute + * Write element with value attribute. * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param string $elementName - * @param string $value + * @param XMLWriter $objWriter XML Writer */ - protected function writeElementWithValAttribute($objWriter, $elementName, $value) + protected function writeElementWithValAttribute(XMLWriter $objWriter, string $elementName, string $value): void { $objWriter->startElement($elementName); $objWriter->writeAttribute('val', $value); @@ -284,54 +302,56 @@ class PptCharts extends AbstractDecoratorWriter } /** - * Write single value or reference + * Write single value or reference. * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param boolean $isReference - * @param mixed $value - * @param string $reference + * @param XMLWriter $objWriter XML Writer */ - protected function writeSingleValueOrReference($objWriter, $isReference, $value, $reference) + protected function writeSingleValueOrReference(XMLWriter $objWriter, bool $isReference, string $value, string $reference): void { if (!$isReference) { // Value $objWriter->writeElement('c:v', $value); + return; } // Reference and cache + // c:strRef $objWriter->startElement('c:strRef'); + // c:strRef/c:f $objWriter->writeElement('c:f', $reference); + // c:strRef/c:strCache $objWriter->startElement('c:strCache'); + // c:strRef/c:strCache/c:ptCount $objWriter->startElement('c:ptCount'); $objWriter->writeAttribute('val', '1'); $objWriter->endElement(); + // c:strRef/c:strCache/c:pt $objWriter->startElement('c:pt'); $objWriter->writeAttribute('idx', '0'); + // c:strRef/c:strCache/c:pt/c:v $objWriter->writeElement('c:v', $value); + // c:strRef/c:strCache/c:pt $objWriter->endElement(); + // c:strRef/c:strCache $objWriter->endElement(); + // c:strRef $objWriter->endElement(); } /** - * Write series value or reference + * Write series value or reference. * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param boolean $isReference - * @param mixed $values - * @param string $reference + * @param XMLWriter $objWriter XML Writer + * @param array $values */ - protected function writeMultipleValuesOrReference($objWriter, $isReference, $values, $reference) + protected function writeMultipleValuesOrReference(XMLWriter $objWriter, bool $isReference, array $values, string $reference): void { // c:strLit / c:numLit // c:strRef / c:numRef $referenceType = ($isReference ? 'Ref' : 'Lit'); - $dataType = 'str'; - if (is_int($values[0]) || is_float($values[0])) { - $dataType = 'num'; - } + $dataType = is_numeric($values[0]) ? 'num' : 'str'; $objWriter->startElement('c:' . $dataType . $referenceType); $numValues = count($values); @@ -344,11 +364,11 @@ class PptCharts extends AbstractDecoratorWriter $objWriter->endElement(); // Add points - for ($i = 0; $i < $numValues; $i++) { + for ($i = 0; $i < $numValues; ++$i) { // c:pt $objWriter->startElement('c:pt'); $objWriter->writeAttribute('idx', $i); - $objWriter->writeElement('c:v', $values[$i]); + $objWriter->writeElement('c:v', strval($values[$i])); $objWriter->endElement(); } } else { @@ -362,11 +382,11 @@ class PptCharts extends AbstractDecoratorWriter $objWriter->endElement(); // Add points - for ($i = 0; $i < $numValues; $i++) { + for ($i = 0; $i < $numValues; ++$i) { // c:pt $objWriter->startElement('c:pt'); $objWriter->writeAttribute('idx', $i); - $objWriter->writeElement('c:v', $values[$i]); + $objWriter->writeElement('c:v', strval($values[$i])); $objWriter->endElement(); } @@ -378,12 +398,8 @@ class PptCharts extends AbstractDecoratorWriter /** * Write Title - * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param \PhpOffice\PhpPresentation\Shape\Chart\Title $subject - * @throws \Exception */ - protected function writeTitle(XMLWriter $objWriter, Title $subject) + protected function writeTitle(XMLWriter $objWriter, Title $subject): void { // c:title $objWriter->startElement('c:title'); @@ -475,14 +491,13 @@ class PptCharts extends AbstractDecoratorWriter } /** - * Write Plot Area + * Write Plot Area. * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param \PhpOffice\PhpPresentation\Shape\Chart\PlotArea $subject - * @param \PhpOffice\PhpPresentation\Shape\Chart $chart - * @throws \Exception + * @param XMLWriter $objWriter XML Writer + * + * @throws UndefinedChartTypeException */ - protected function writePlotArea(XMLWriter $objWriter, PlotArea $subject, Chart $chart) + protected function writePlotArea(XMLWriter $objWriter, PlotArea $subject, Chart $chart): void { // c:plotArea $objWriter->startElement('c:plotArea'); @@ -506,10 +521,12 @@ class PptCharts extends AbstractDecoratorWriter $this->writeTypePie3D($objWriter, $chartType, $chart->hasIncludedSpreadsheet()); } elseif ($chartType instanceof Line) { $this->writeTypeLine($objWriter, $chartType, $chart->hasIncludedSpreadsheet()); + } elseif ($chartType instanceof Radar) { + $this->writeTypeRadar($objWriter, $chartType, $chart->hasIncludedSpreadsheet()); } elseif ($chartType instanceof Scatter) { $this->writeTypeScatter($objWriter, $chartType, $chart->hasIncludedSpreadsheet()); } else { - throw new \Exception('The chart type provided could not be rendered.'); + throw new UndefinedChartTypeException(); } // Write X axis? @@ -526,13 +543,12 @@ class PptCharts extends AbstractDecoratorWriter } /** - * Write Legend + * Write Legend. * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param \PhpOffice\PhpPresentation\Shape\Chart\Legend $subject - * @throws \Exception + * @param XMLWriter $objWriter XML Writer + * @param Chart\Legend $subject */ - protected function writeLegend(XMLWriter $objWriter, Legend $subject) + protected function writeLegend(XMLWriter $objWriter, Legend $subject): void { // c:legend $objWriter->startElement('c:legend'); @@ -557,7 +573,7 @@ class PptCharts extends AbstractDecoratorWriter $this->writeFill($objWriter, $subject->getFill()); // Border - if ($subject->getBorder()->getLineStyle() != Border::LINE_NONE) { + if (Border::LINE_NONE != $subject->getBorder()->getLineStyle()) { $this->writeBorder($objWriter, $subject->getBorder(), ''); } @@ -625,13 +641,12 @@ class PptCharts extends AbstractDecoratorWriter } /** - * Write Layout + * Write Layout. * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param mixed $subject - * @throws \Exception + * @param XMLWriter $objWriter XML Writer + * @param Legend|PlotArea|Title $subject */ - protected function writeLayout(XMLWriter $objWriter, $subject) + protected function writeLayout(XMLWriter $objWriter, $subject): void { // c:layout $objWriter->startElement('c:layout'); @@ -648,28 +663,28 @@ class PptCharts extends AbstractDecoratorWriter $objWriter->writeAttribute('val', 'edge'); $objWriter->endElement(); - if ($subject->getOffsetX() != 0) { + if (0 != $subject->getOffsetX()) { // c:x $objWriter->startElement('c:x'); $objWriter->writeAttribute('val', $subject->getOffsetX()); $objWriter->endElement(); } - if ($subject->getOffsetY() != 0) { + if (0 != $subject->getOffsetY()) { // c:y $objWriter->startElement('c:y'); $objWriter->writeAttribute('val', $subject->getOffsetY()); $objWriter->endElement(); } - if ($subject->getWidth() != 0) { + if (0 != $subject->getWidth()) { // c:w $objWriter->startElement('c:w'); $objWriter->writeAttribute('val', $subject->getWidth()); $objWriter->endElement(); } - if ($subject->getHeight() != 0) { + if (0 != $subject->getHeight()) { // c:h $objWriter->startElement('c:h'); $objWriter->writeAttribute('val', $subject->getHeight()); @@ -681,14 +696,13 @@ class PptCharts extends AbstractDecoratorWriter } /** - * Write Type Area + * Write Type Area. * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param \PhpOffice\PhpPresentation\Shape\Chart\Type\Area $subject - * @param boolean $includeSheet - * @throws \Exception + * @param XMLWriter $objWriter XML Writer + * @param Chart\Type\Area $subject + * @param bool $includeSheet */ - protected function writeTypeArea(XMLWriter $objWriter, Area $subject, $includeSheet = false) + protected function writeTypeArea(XMLWriter $objWriter, Area $subject, bool $includeSheet = false): void { // c:lineChart $objWriter->startElement('c:areaChart'); @@ -716,7 +730,7 @@ class PptCharts extends AbstractDecoratorWriter // c:ser > c:tx $objWriter->startElement('c:tx'); - $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex(1 + $seriesIndex) . '$1' : ''); + $coords = ($includeSheet ? 'Sheet1!$' . Coordinate::stringFromColumnIndex(1 + $seriesIndex) . '$1' : ''); $this->writeSingleValueOrReference($objWriter, $includeSheet, $series->getTitle(), $coords); $objWriter->endElement(); @@ -739,7 +753,7 @@ class PptCharts extends AbstractDecoratorWriter // c:ser > ##c:dLbls $objWriter->endElement(); - if ($series->getFill()->getFillType() != Fill::FILL_NONE) { + if (Fill::FILL_NONE != $series->getFill()->getFillType()) { // c:spPr $objWriter->startElement('c:spPr'); // Write fill @@ -761,7 +775,7 @@ class PptCharts extends AbstractDecoratorWriter // c:val $objWriter->startElement('c:val'); - $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$2:$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$' . (1 + count($axisYData)) : ''); + $coords = ($includeSheet ? 'Sheet1!$' . Coordinate::stringFromColumnIndex($seriesIndex + 1) . '$2:$' . Coordinate::stringFromColumnIndex($seriesIndex + 1) . '$' . (1 + count($axisYData)) : ''); $this->writeMultipleValuesOrReference($objWriter, $includeSheet, $axisYData, $coords); $objWriter->endElement(); @@ -784,14 +798,13 @@ class PptCharts extends AbstractDecoratorWriter } /** - * Write Type Bar + * Write Type Bar. * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param \PhpOffice\PhpPresentation\Shape\Chart\Type\Bar $subject - * @param boolean $includeSheet - * @throws \Exception + * @param XMLWriter $objWriter XML Writer + * @param Chart\Type\Bar $subject + * @param bool $includeSheet */ - protected function writeTypeBar(XMLWriter $objWriter, Bar $subject, $includeSheet = false) + protected function writeTypeBar(XMLWriter $objWriter, Bar $subject, bool $includeSheet = false): void { // c:barChart $objWriter->startElement('c:barChart'); @@ -824,7 +837,7 @@ class PptCharts extends AbstractDecoratorWriter // c:tx $objWriter->startElement('c:tx'); - $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex(1 + $seriesIndex) . '$1' : ''); + $coords = ($includeSheet ? 'Sheet1!$' . Coordinate::stringFromColumnIndex(1 + $seriesIndex) . '$1' : ''); $this->writeSingleValueOrReference($objWriter, $includeSheet, $series->getTitle(), $coords); $objWriter->endElement(); @@ -835,9 +848,9 @@ class PptCharts extends AbstractDecoratorWriter $objWriter->startElement('c:dPt'); // c:idx - $this->writeElementWithValAttribute($objWriter, 'c:idx', $key); + $this->writeElementWithValAttribute($objWriter, 'c:idx', (string) $key); - if ($value->getFillType() != Fill::FILL_NONE) { + if (Fill::FILL_NONE != $value->getFillType()) { // c:spPr $objWriter->startElement('c:spPr'); // Write fill @@ -938,7 +951,7 @@ class PptCharts extends AbstractDecoratorWriter $objWriter->endElement(); // c:spPr - if ($series->getFill()->getFillType() != Fill::FILL_NONE) { + if (Fill::FILL_NONE != $series->getFill()->getFillType()) { // c:spPr $objWriter->startElement('c:spPr'); // Write fill @@ -960,7 +973,7 @@ class PptCharts extends AbstractDecoratorWriter // c:val $objWriter->startElement('c:val'); - $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$2:$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$' . (1 + count($axisYData)) : ''); + $coords = ($includeSheet ? 'Sheet1!$' . Coordinate::stringFromColumnIndex($seriesIndex + 1) . '$2:$' . Coordinate::stringFromColumnIndex($seriesIndex + 1) . '$' . (1 + count($axisYData)) : ''); $this->writeMultipleValuesOrReference($objWriter, $includeSheet, $axisYData, $coords); $objWriter->endElement(); @@ -975,13 +988,8 @@ class PptCharts extends AbstractDecoratorWriter $objWriter->endElement(); // c:overlap - $barGrouping = $subject->getBarGrouping(); $objWriter->startElement('c:overlap'); - if ($barGrouping === Bar::GROUPING_CLUSTERED) { - $objWriter->writeAttribute('val', '0'); - } elseif ($barGrouping === Bar::GROUPING_STACKED || $barGrouping === Bar::GROUPING_PERCENTSTACKED) { - $objWriter->writeAttribute('val', '100000'); - } + $objWriter->writeAttribute('val', $subject->getOverlapWidthPercent()); $objWriter->endElement(); // c:axId @@ -1002,14 +1010,13 @@ class PptCharts extends AbstractDecoratorWriter } /** - * Write Type Bar3D + * Write Type Bar3D. * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param \PhpOffice\PhpPresentation\Shape\Chart\Type\Bar3D $subject - * @param boolean $includeSheet - * @throws \Exception + * @param XMLWriter $objWriter XML Writer + * @param Chart\Type\Bar3D $subject + * @param bool $includeSheet */ - protected function writeTypeBar3D(XMLWriter $objWriter, Bar3D $subject, $includeSheet = false) + protected function writeTypeBar3D(XMLWriter $objWriter, Bar3D $subject, bool $includeSheet = false): void { // c:bar3DChart $objWriter->startElement('c:bar3DChart'); @@ -1042,7 +1049,7 @@ class PptCharts extends AbstractDecoratorWriter // c:tx $objWriter->startElement('c:tx'); - $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex(1 + $seriesIndex) . '$1' : ''); + $coords = ($includeSheet ? 'Sheet1!$' . Coordinate::stringFromColumnIndex(1 + $seriesIndex) . '$1' : ''); $this->writeSingleValueOrReference($objWriter, $includeSheet, $series->getTitle(), $coords); $objWriter->endElement(); @@ -1053,9 +1060,9 @@ class PptCharts extends AbstractDecoratorWriter $objWriter->startElement('c:dPt'); // c:idx - $this->writeElementWithValAttribute($objWriter, 'c:idx', $key); + $this->writeElementWithValAttribute($objWriter, 'c:idx', (string) $key); - if ($value->getFillType() != Fill::FILL_NONE) { + if (Fill::FILL_NONE != $value->getFillType()) { // c:spPr $objWriter->startElement('c:spPr'); // Write fill @@ -1141,7 +1148,7 @@ class PptCharts extends AbstractDecoratorWriter $objWriter->endElement(); // c:spPr - if ($series->getFill()->getFillType() != Fill::FILL_NONE) { + if (Fill::FILL_NONE != $series->getFill()->getFillType()) { // c:spPr $objWriter->startElement('c:spPr'); // Write fill @@ -1163,7 +1170,7 @@ class PptCharts extends AbstractDecoratorWriter // c:val $objWriter->startElement('c:val'); - $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$2:$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$' . (1 + count($axisYData)) : ''); + $coords = ($includeSheet ? 'Sheet1!$' . Coordinate::stringFromColumnIndex($seriesIndex + 1) . '$2:$' . Coordinate::stringFromColumnIndex($seriesIndex + 1) . '$' . (1 + count($axisYData)) : ''); $this->writeMultipleValuesOrReference($objWriter, $includeSheet, $axisYData, $coords); $objWriter->endElement(); @@ -1196,14 +1203,13 @@ class PptCharts extends AbstractDecoratorWriter } /** - * Write Type Pie + * Write Type Pie. * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param \PhpOffice\PhpPresentation\Shape\Chart\Type\Doughnut $subject - * @param boolean $includeSheet - * @throws \Exception + * @param XMLWriter $objWriter XML Writer + * @param Doughnut $subject + * @param bool $includeSheet */ - protected function writeTypeDoughnut(XMLWriter $objWriter, Doughnut $subject, $includeSheet = false) + protected function writeTypeDoughnut(XMLWriter $objWriter, Doughnut $subject, bool $includeSheet = false): void { // c:pieChart $objWriter->startElement('c:doughnutChart'); @@ -1231,7 +1237,7 @@ class PptCharts extends AbstractDecoratorWriter // c:tx $objWriter->startElement('c:tx'); - $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex(1 + $seriesIndex) . '$1' : ''); + $coords = ($includeSheet ? 'Sheet1!$' . Coordinate::stringFromColumnIndex(1 + $seriesIndex) . '$1' : ''); $this->writeSingleValueOrReference($objWriter, $includeSheet, $series->getTitle(), $coords); $objWriter->endElement(); @@ -1240,7 +1246,7 @@ class PptCharts extends AbstractDecoratorWriter foreach ($dataPointFills as $key => $value) { // c:dPt $objWriter->startElement('c:dPt'); - $this->writeElementWithValAttribute($objWriter, 'c:idx', $key); + $this->writeElementWithValAttribute($objWriter, 'c:idx', (string) $key); // c:dPt/c:spPr $objWriter->startElement('c:spPr'); $this->writeFill($objWriter, $value); @@ -1263,7 +1269,7 @@ class PptCharts extends AbstractDecoratorWriter // c:val $objWriter->startElement('c:val'); - $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$2:$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$' . (1 + count($axisYData)) : ''); + $coords = ($includeSheet ? 'Sheet1!$' . Coordinate::stringFromColumnIndex($seriesIndex + 1) . '$2:$' . Coordinate::stringFromColumnIndex($seriesIndex + 1) . '$' . (1 + count($axisYData)) : ''); $this->writeMultipleValuesOrReference($objWriter, $includeSheet, $axisYData, $coords); $objWriter->endElement(); @@ -1272,96 +1278,97 @@ class PptCharts extends AbstractDecoratorWriter ++$seriesIndex; } - // c:dLbls - $objWriter->startElement('c:dLbls'); + if (isset($series) && is_object($series) && $series instanceof Chart\Series) { + // c:dLbls + $objWriter->startElement('c:dLbls'); - $this->writeElementWithValAttribute($objWriter, 'c:showLegendKey', $series->hasShowLegendKey() ? '1' : '0'); - $this->writeElementWithValAttribute($objWriter, 'c:showVal', $series->hasShowValue() ? '1' : '0'); - $this->writeElementWithValAttribute($objWriter, 'c:showCatName', $series->hasShowCategoryName() ? '1' : '0'); - $this->writeElementWithValAttribute($objWriter, 'c:showSerName', $series->hasShowSeriesName() ? '1' : '0'); - $this->writeElementWithValAttribute($objWriter, 'c:showPercent', $series->hasShowPercentage() ? '1' : '0'); - $this->writeElementWithValAttribute($objWriter, 'c:showBubbleSize', '0'); - $this->writeElementWithValAttribute($objWriter, 'c:showLeaderLines', $series->hasShowLeaderLines() ? '1' : '0'); + $this->writeElementWithValAttribute($objWriter, 'c:showLegendKey', $series->hasShowLegendKey() ? '1' : '0'); + $this->writeElementWithValAttribute($objWriter, 'c:showVal', $series->hasShowValue() ? '1' : '0'); + $this->writeElementWithValAttribute($objWriter, 'c:showCatName', $series->hasShowCategoryName() ? '1' : '0'); + $this->writeElementWithValAttribute($objWriter, 'c:showSerName', $series->hasShowSeriesName() ? '1' : '0'); + $this->writeElementWithValAttribute($objWriter, 'c:showPercent', $series->hasShowPercentage() ? '1' : '0'); + $this->writeElementWithValAttribute($objWriter, 'c:showBubbleSize', '0'); + $this->writeElementWithValAttribute($objWriter, 'c:showLeaderLines', $series->hasShowLeaderLines() ? '1' : '0'); - if ($series->hasDlblNumFormat()) { - //c:numFmt - $objWriter->startElement('c:numFmt'); - $objWriter->writeAttribute('formatCode', $series->getDlblNumFormat()); - $objWriter->writeAttribute('sourceLinked', '0'); + if ($series->hasDlblNumFormat()) { + //c:numFmt + $objWriter->startElement('c:numFmt'); + $objWriter->writeAttribute('formatCode', $series->getDlblNumFormat()); + $objWriter->writeAttribute('sourceLinked', '0'); + $objWriter->endElement(); + } + + // c:dLbls\c:txPr + $objWriter->startElement('c:txPr'); + $objWriter->writeElement('a:bodyPr', null); + $objWriter->writeElement('a:lstStyle', null); + + // c:dLbls\c:txPr\a:p + $objWriter->startElement('a:p'); + + // c:dLbls\c:txPr\a:p\a:pPr + $objWriter->startElement('a:pPr'); + + // c:dLbls\c:txPr\a:p\a:pPr\a:defRPr + $objWriter->startElement('a:defRPr'); + $objWriter->writeAttribute('b', ($series->getFont()->isBold() ? 'true' : 'false')); + $objWriter->writeAttribute('i', ($series->getFont()->isItalic() ? 'true' : 'false')); + $objWriter->writeAttribute('strike', ($series->getFont()->isStrikethrough() ? 'sngStrike' : 'noStrike')); + $objWriter->writeAttribute('sz', ($series->getFont()->getSize() * 100)); + $objWriter->writeAttribute('u', $series->getFont()->getUnderline()); + $objWriter->writeAttributeIf($series->getFont()->isSuperScript(), 'baseline', '300000'); + $objWriter->writeAttributeIf($series->getFont()->isSubScript(), 'baseline', '-250000'); + + // c:dLbls\c:txPr\a:p\a:pPr\a:defRPr\a:solidFill + $objWriter->startElement('a:solidFill'); + $this->writeColor($objWriter, $series->getFont()->getColor()); + $objWriter->endElement(); + + // c:dLbls\c:txPr\a:p\a:pPr\a:defRPr\a:latin + $objWriter->startElement('a:latin'); + $objWriter->writeAttribute('typeface', $series->getFont()->getName()); + $objWriter->endElement(); + + // c:dLbls\c:txPr\a:p\a:pPr\a:defRPr\ + $objWriter->endElement(); + // c:dLbls\c:txPr\a:p\a:pPr\ + $objWriter->endElement(); + + // c:dLbls\c:txPr\a:p\a:endParaRPr + $objWriter->startElement('a:endParaRPr'); + $objWriter->writeAttribute('lang', 'en-US'); + $objWriter->writeAttribute('dirty', '0'); + $objWriter->endElement(); + + // c:dLbls\c:txPr\a:p\ + $objWriter->endElement(); + // c:dLbls\c:txPr\ + $objWriter->endElement(); + + $separator = $series->getSeparator(); + if (!empty($separator) && PHP_EOL != $separator) { + // c:dLbls\c:separator + $objWriter->writeElement('c:separator', $separator); + } + + // c:dLbls\ $objWriter->endElement(); } - // c:dLbls\c:txPr - $objWriter->startElement('c:txPr'); - $objWriter->writeElement('a:bodyPr', null); - $objWriter->writeElement('a:lstStyle', null); - - // c:dLbls\c:txPr\a:p - $objWriter->startElement('a:p'); - - // c:dLbls\c:txPr\a:p\a:pPr - $objWriter->startElement('a:pPr'); - - // c:dLbls\c:txPr\a:p\a:pPr\a:defRPr - $objWriter->startElement('a:defRPr'); - $objWriter->writeAttribute('b', ($series->getFont()->isBold() ? 'true' : 'false')); - $objWriter->writeAttribute('i', ($series->getFont()->isItalic() ? 'true' : 'false')); - $objWriter->writeAttribute('strike', ($series->getFont()->isStrikethrough() ? 'sngStrike' : 'noStrike')); - $objWriter->writeAttribute('sz', ($series->getFont()->getSize() * 100)); - $objWriter->writeAttribute('u', $series->getFont()->getUnderline()); - $objWriter->writeAttributeIf($series->getFont()->isSuperScript(), 'baseline', '300000'); - $objWriter->writeAttributeIf($series->getFont()->isSubScript(), 'baseline', '-250000'); - - // c:dLbls\c:txPr\a:p\a:pPr\a:defRPr\a:solidFill - $objWriter->startElement('a:solidFill'); - $this->writeColor($objWriter, $series->getFont()->getColor()); - $objWriter->endElement(); - - // c:dLbls\c:txPr\a:p\a:pPr\a:defRPr\a:latin - $objWriter->startElement('a:latin'); - $objWriter->writeAttribute('typeface', $series->getFont()->getName()); - $objWriter->endElement(); - - // c:dLbls\c:txPr\a:p\a:pPr\a:defRPr\ - $objWriter->endElement(); - // c:dLbls\c:txPr\a:p\a:pPr\ - $objWriter->endElement(); - - // c:dLbls\c:txPr\a:p\a:endParaRPr - $objWriter->startElement('a:endParaRPr'); - $objWriter->writeAttribute('lang', 'en-US'); - $objWriter->writeAttribute('dirty', '0'); - $objWriter->endElement(); - - // c:dLbls\c:txPr\a:p\ - $objWriter->endElement(); - // c:dLbls\c:txPr\ - $objWriter->endElement(); - - $separator = $series->getSeparator(); - if (!empty($separator) && $separator != PHP_EOL) { - // c:dLbls\c:separator - $objWriter->writeElement('c:separator', $separator); - } - - // c:dLbls\ - $objWriter->endElement(); - $this->writeElementWithValAttribute($objWriter, 'c:firstSliceAng', '0'); - $this->writeElementWithValAttribute($objWriter, 'c:holeSize', $subject->getHoleSize()); + $this->writeElementWithValAttribute($objWriter, 'c:holeSize', (string) $subject->getHoleSize()); $objWriter->endElement(); } /** - * Write Type Pie + * Write Type Pie. * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param \PhpOffice\PhpPresentation\Shape\Chart\Type\Pie $subject - * @param boolean $includeSheet - * @throws \Exception + * @param XMLWriter $objWriter XML Writer + * @param Pie $subject + * @param bool $includeSheet */ - protected function writeTypePie(XMLWriter $objWriter, Pie $subject, $includeSheet = false) + protected function writeTypePie(XMLWriter $objWriter, Pie $subject, bool $includeSheet = false): void { // c:pieChart $objWriter->startElement('c:pieChart'); @@ -1389,7 +1396,7 @@ class PptCharts extends AbstractDecoratorWriter // c:tx $objWriter->startElement('c:tx'); - $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex(1 + $seriesIndex) . '$1' : ''); + $coords = ($includeSheet ? 'Sheet1!$' . Coordinate::stringFromColumnIndex(1 + $seriesIndex) . '$1' : ''); $this->writeSingleValueOrReference($objWriter, $includeSheet, $series->getTitle(), $coords); $objWriter->endElement(); @@ -1398,7 +1405,7 @@ class PptCharts extends AbstractDecoratorWriter foreach ($dataPointFills as $key => $value) { // c:dPt $objWriter->startElement('c:dPt'); - $this->writeElementWithValAttribute($objWriter, 'c:idx', $key); + $this->writeElementWithValAttribute($objWriter, 'c:idx', (string) $key); // c:dPt/c:spPr $objWriter->startElement('c:spPr'); $this->writeFill($objWriter, $value); @@ -1507,7 +1514,7 @@ class PptCharts extends AbstractDecoratorWriter // c:val $objWriter->startElement('c:val'); - $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$2:$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$' . (1 + count($axisYData)) : ''); + $coords = ($includeSheet ? 'Sheet1!$' . Coordinate::stringFromColumnIndex($seriesIndex + 1) . '$2:$' . Coordinate::stringFromColumnIndex($seriesIndex + 1) . '$' . (1 + count($axisYData)) : ''); $this->writeMultipleValuesOrReference($objWriter, $includeSheet, $axisYData, $coords); $objWriter->endElement(); @@ -1520,14 +1527,13 @@ class PptCharts extends AbstractDecoratorWriter } /** - * Write Type Pie3D + * Write Type Pie3D. * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param \PhpOffice\PhpPresentation\Shape\Chart\Type\Pie3D $subject - * @param boolean $includeSheet - * @throws \Exception + * @param XMLWriter $objWriter XML Writer + * @param Pie3D $subject + * @param bool $includeSheet */ - protected function writeTypePie3D(XMLWriter $objWriter, Pie3D $subject, $includeSheet = false) + protected function writeTypePie3D(XMLWriter $objWriter, Pie3D $subject, bool $includeSheet = false): void { // c:pie3DChart $objWriter->startElement('c:pie3DChart'); @@ -1555,7 +1561,7 @@ class PptCharts extends AbstractDecoratorWriter // c:tx $objWriter->startElement('c:tx'); - $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex(1 + $seriesIndex) . '$1' : ''); + $coords = ($includeSheet ? 'Sheet1!$' . Coordinate::stringFromColumnIndex(1 + $seriesIndex) . '$1' : ''); $this->writeSingleValueOrReference($objWriter, $includeSheet, $series->getTitle(), $coords); $objWriter->endElement(); @@ -1569,7 +1575,7 @@ class PptCharts extends AbstractDecoratorWriter foreach ($dataPointFills as $key => $value) { // c:dPt $objWriter->startElement('c:dPt'); - $this->writeElementWithValAttribute($objWriter, 'c:idx', $key); + $this->writeElementWithValAttribute($objWriter, 'c:idx', (string) $key); // c:dPt/c:spPr $objWriter->startElement('c:spPr'); $this->writeFill($objWriter, $value); @@ -1667,7 +1673,7 @@ class PptCharts extends AbstractDecoratorWriter // c:val $objWriter->startElement('c:val'); - $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$2:$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$' . (1 + count($axisYData)) : ''); + $coords = ($includeSheet ? 'Sheet1!$' . Coordinate::stringFromColumnIndex($seriesIndex + 1) . '$2:$' . Coordinate::stringFromColumnIndex($seriesIndex + 1) . '$' . (1 + count($axisYData)) : ''); $this->writeMultipleValuesOrReference($objWriter, $includeSheet, $axisYData, $coords); $objWriter->endElement(); @@ -1680,14 +1686,13 @@ class PptCharts extends AbstractDecoratorWriter } /** - * Write Type Line + * Write Type Line. * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param \PhpOffice\PhpPresentation\Shape\Chart\Type\Line $subject - * @param boolean $includeSheet - * @throws \Exception + * @param XMLWriter $objWriter XML Writer + * @param Line $subject + * @param bool $includeSheet */ - protected function writeTypeLine(XMLWriter $objWriter, Line $subject, $includeSheet = false) + protected function writeTypeLine(XMLWriter $objWriter, Line $subject, bool $includeSheet = false): void { // c:lineChart $objWriter->startElement('c:lineChart'); @@ -1715,7 +1720,7 @@ class PptCharts extends AbstractDecoratorWriter // c:tx $objWriter->startElement('c:tx'); - $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex(1 + $seriesIndex) . '$1' : ''); + $coords = ($includeSheet ? 'Sheet1!$' . Coordinate::stringFromColumnIndex(1 + $seriesIndex) . '$1' : ''); $this->writeSingleValueOrReference($objWriter, $includeSheet, $series->getTitle(), $coords); $objWriter->endElement(); @@ -1817,10 +1822,15 @@ class PptCharts extends AbstractDecoratorWriter // c:val $objWriter->startElement('c:val'); - $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$2:$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$' . (1 + count($axisYData)) : ''); + $coords = ($includeSheet ? 'Sheet1!$' . Coordinate::stringFromColumnIndex($seriesIndex + 1) . '$2:$' . Coordinate::stringFromColumnIndex($seriesIndex + 1) . '$' . (1 + count($axisYData)) : ''); $this->writeMultipleValuesOrReference($objWriter, $includeSheet, $axisYData, $coords); $objWriter->endElement(); + // c:smooth + $objWriter->startElement('c:smooth'); + $objWriter->writeAttribute('val', $subject->isSmooth() ? '1' : '0'); + $objWriter->endElement(); + $objWriter->endElement(); ++$seriesIndex; @@ -1831,11 +1841,177 @@ class PptCharts extends AbstractDecoratorWriter $objWriter->writeAttribute('val', '1'); $objWriter->endElement(); - // c:smooth - $objWriter->startElement('c:smooth'); + // c:axId + $objWriter->startElement('c:axId'); + $objWriter->writeAttribute('val', '52743552'); + $objWriter->endElement(); + + // c:axId + $objWriter->startElement('c:axId'); + $objWriter->writeAttribute('val', '52749440'); + $objWriter->endElement(); + + $objWriter->endElement(); + } + + /** + * Write Type Radar + * + * @param XMLWriter $objWriter XML Writer + * @param Radar $subject + * @param bool $includeSheet + */ + protected function writeTypeRadar(XMLWriter $objWriter, Radar $subject, bool $includeSheet = false): void + { + // c:scatterChart + $objWriter->startElement('c:radarChart'); + + // c:radarStyle + $objWriter->startElement('c:radarStyle'); + $objWriter->writeAttribute('val', 'marker'); + $objWriter->endElement(); + + // c:varyColors + $objWriter->startElement('c:varyColors'); $objWriter->writeAttribute('val', '0'); $objWriter->endElement(); + // Write series + $seriesIndex = 0; + foreach ($subject->getSeries() as $series) { + // c:ser + $objWriter->startElement('c:ser'); + + // c:idx + $objWriter->startElement('c:idx'); + $objWriter->writeAttribute('val', $seriesIndex); + $objWriter->endElement(); + + // c:order + $objWriter->startElement('c:order'); + $objWriter->writeAttribute('val', $seriesIndex); + $objWriter->endElement(); + + // c:tx + $objWriter->startElement('c:tx'); + $coords = ($includeSheet ? 'Sheet1!$' . Coordinate::stringFromColumnIndex(1 + $seriesIndex) . '$1' : ''); + $this->writeSingleValueOrReference($objWriter, $includeSheet, $series->getTitle(), $coords); + $objWriter->endElement(); + + // Marker + $this->writeSeriesMarker($objWriter, $series->getMarker()); + + // c:dLbls + $objWriter->startElement('c:dLbls'); + + // c:txPr + $objWriter->startElement('c:txPr'); + + // a:bodyPr + $objWriter->writeElement('a:bodyPr', null); + + // a:lstStyle + $objWriter->writeElement('a:lstStyle', null); + + // a:p + $objWriter->startElement('a:p'); + + // a:pPr + $objWriter->startElement('a:pPr'); + + // a:defRPr + $objWriter->startElement('a:defRPr'); + + $objWriter->writeAttribute('b', ($series->getFont()->isBold() ? 'true' : 'false')); + $objWriter->writeAttribute('i', ($series->getFont()->isItalic() ? 'true' : 'false')); + $objWriter->writeAttribute('strike', ($series->getFont()->isStrikethrough() ? 'sngStrike' : 'noStrike')); + $objWriter->writeAttribute('sz', ($series->getFont()->getSize() * 100)); + $objWriter->writeAttribute('u', $series->getFont()->getUnderline()); + $objWriter->writeAttributeIf($series->getFont()->isSuperScript(), 'baseline', '30000'); + $objWriter->writeAttributeIf($series->getFont()->isSubScript(), 'baseline', '-25000'); + + // Font - a:solidFill + $objWriter->startElement('a:solidFill'); + + $this->writeColor($objWriter, $series->getFont()->getColor()); + + $objWriter->endElement(); + + // Font - a:latin + $objWriter->startElement('a:latin'); + $objWriter->writeAttribute('typeface', $series->getFont()->getName()); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // a:endParaRPr + $objWriter->startElement('a:endParaRPr'); + $objWriter->writeAttribute('lang', 'en-US'); + $objWriter->writeAttribute('dirty', '0'); + $objWriter->endElement(); + + $objWriter->endElement(); + + $objWriter->endElement(); + + // c:showLegendKey + $this->writeElementWithValAttribute($objWriter, 'c:showLegendKey', $series->hasShowLegendKey() ? '1' : '0'); + + // c:showVal + $this->writeElementWithValAttribute($objWriter, 'c:showVal', $series->hasShowValue() ? '1' : '0'); + + // c:showCatName + $this->writeElementWithValAttribute($objWriter, 'c:showCatName', $series->hasShowCategoryName() ? '1' : '0'); + + // c:showSerName + $this->writeElementWithValAttribute($objWriter, 'c:showSerName', $series->hasShowSeriesName() ? '1' : '0'); + + // c:showPercent + $this->writeElementWithValAttribute($objWriter, 'c:showPercent', $series->hasShowPercentage() ? '1' : '0'); + + // c:showLeaderLines + $this->writeElementWithValAttribute($objWriter, 'c:showLeaderLines', $series->hasShowLeaderLines() ? '1' : '0'); + + $objWriter->endElement(); + + // c:spPr + $objWriter->startElement('c:spPr'); + // Write fill + $this->writeFill($objWriter, $series->getFill()); + // Write outline + $this->writeOutline($objWriter, $series->getOutline()); + // ## c:spPr + $objWriter->endElement(); + + // Write X axis data + $axisXData = array_keys($series->getValues()); + + // c:cat + $objWriter->startElement('c:cat'); + $this->writeMultipleValuesOrReference($objWriter, $includeSheet, $axisXData, 'Sheet1!$A$2:$A$' . (1 + count($axisXData))); + $objWriter->endElement(); + + // Write Y axis data + $axisYData = array_values($series->getValues()); + + // c:val + $objWriter->startElement('c:val'); + $coords = ($includeSheet ? 'Sheet1!$' . Coordinate::stringFromColumnIndex($seriesIndex + 1) . '$2:$' . Coordinate::stringFromColumnIndex($seriesIndex + 1) . '$' . (1 + count($axisYData)) : ''); + $this->writeMultipleValuesOrReference($objWriter, $includeSheet, $axisYData, $coords); + $objWriter->endElement(); + + // c:smooth + $objWriter->startElement('c:smooth'); + $objWriter->writeAttribute('val', '0'); + $objWriter->endElement(); + + $objWriter->endElement(); + + ++$seriesIndex; + } + // c:axId $objWriter->startElement('c:axId'); $objWriter->writeAttribute('val', '52743552'); @@ -1852,12 +2028,11 @@ class PptCharts extends AbstractDecoratorWriter /** * Write Type Scatter * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param \PhpOffice\PhpPresentation\Shape\Chart\Type\Scatter $subject - * @param boolean $includeSheet - * @throws \Exception + * @param XMLWriter $objWriter + * @param Scatter $subject + * @param bool $includeSheet */ - protected function writeTypeScatter(XMLWriter $objWriter, Scatter $subject, $includeSheet = false) + protected function writeTypeScatter(XMLWriter $objWriter, Scatter $subject, bool $includeSheet = false): void { // c:scatterChart $objWriter->startElement('c:scatterChart'); @@ -1890,7 +2065,7 @@ class PptCharts extends AbstractDecoratorWriter // c:tx $objWriter->startElement('c:tx'); - $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex(1 + $seriesIndex) . '$1' : ''); + $coords = ($includeSheet ? 'Sheet1!$' . Coordinate::stringFromColumnIndex(1 + $seriesIndex) . '$1' : ''); $this->writeSingleValueOrReference($objWriter, $includeSheet, $series->getTitle(), $coords); $objWriter->endElement(); @@ -1978,7 +2153,7 @@ class PptCharts extends AbstractDecoratorWriter // c:separator $separator = $series->getSeparator(); - if (!empty($separator) && $separator != PHP_EOL) { + if (!empty($separator) && PHP_EOL != $separator) { // c:dLbls\c:separator $objWriter->writeElement('c:separator', $separator); } @@ -2001,13 +2176,13 @@ class PptCharts extends AbstractDecoratorWriter // c:yVal $objWriter->startElement('c:yVal'); - $coords = ($includeSheet ? 'Sheet1!$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$2:$' . \PHPExcel_Cell::stringFromColumnIndex($seriesIndex + 1) . '$' . (1 + count($axisYData)) : ''); + $coords = ($includeSheet ? 'Sheet1!$' . Coordinate::stringFromColumnIndex($seriesIndex + 1) . '$2:$' . Coordinate::stringFromColumnIndex($seriesIndex + 1) . '$' . (1 + count($axisYData)) : ''); $this->writeMultipleValuesOrReference($objWriter, $includeSheet, $axisYData, $coords); $objWriter->endElement(); // c:smooth $objWriter->startElement('c:smooth'); - $objWriter->writeAttribute('val', '0'); + $objWriter->writeAttribute('val', $subject->isSmooth() ? '1' : '0'); $objWriter->endElement(); $objWriter->endElement(); @@ -2029,13 +2204,13 @@ class PptCharts extends AbstractDecoratorWriter } /** - * Write chart relationships to XML format + * Write chart relationships to XML format. * - * @param \PhpOffice\PhpPresentation\Shape\Chart $pChart - * @return string XML Output - * @throws \Exception + * @param Chart $pChart + * + * @return string XML Output */ - public function writeChartRelationships(Chart $pChart) + protected function writeChartRelationships(Chart $pChart): string { // Create XML writer $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); @@ -2060,20 +2235,20 @@ class PptCharts extends AbstractDecoratorWriter /** * @param XMLWriter $objWriter - * @param Chart\Marker $oMarker + * @param Chart\Marker $marker */ - protected function writeSeriesMarker(XMLWriter $objWriter, Chart\Marker $oMarker) + protected function writeSeriesMarker(XMLWriter $objWriter, Chart\Marker $marker): void { // c:marker $objWriter->startElement('c:marker'); // c:marker > c:symbol $objWriter->startElement('c:symbol'); - $objWriter->writeAttribute('val', $oMarker->getSymbol()); + $objWriter->writeAttribute('val', $marker->getSymbol()); $objWriter->endElement(); // Size if different of none - if ($oMarker->getSymbol() != Chart\Marker::SYMBOL_NONE) { - $markerSize = (int)$oMarker->getSize(); + if (Chart\Marker::SYMBOL_NONE != $marker->getSymbol()) { + $markerSize = (int) $marker->getSize(); if ($markerSize < 2) { $markerSize = 2; } @@ -2081,7 +2256,7 @@ class PptCharts extends AbstractDecoratorWriter $markerSize = 72; } - /** + /* * c:marker > c:size * Size in points * @link : https://msdn.microsoft.com/en-us/library/hh658135(v=office.12).aspx @@ -2090,31 +2265,41 @@ class PptCharts extends AbstractDecoratorWriter $objWriter->writeAttribute('val', $markerSize); $objWriter->endElement(); } + + // // c:marker > c:spPr + $objWriter->startElement('c:spPr'); + $this->writeFill($objWriter, $marker->getFill()); + $this->writeBorder($objWriter, $marker->getBorder(), '', true); + $objWriter->endElement(); + + // > c:marker $objWriter->endElement(); } /** * @param XMLWriter $objWriter * @param Chart\Axis $oAxis - * @param $typeAxis + * @param string $typeAxis * @param Chart\Type\AbstractType $typeChart - * @throws \Exception */ - protected function writeAxis(XMLWriter $objWriter, Chart\Axis $oAxis, $typeAxis, Chart\Type\AbstractType $typeChart) + protected function writeAxis(XMLWriter $objWriter, Chart\Axis $oAxis, string $typeAxis, Chart\Type\AbstractType $typeChart): void { - if ($typeAxis != Chart\Axis::AXIS_X && $typeAxis != Chart\Axis::AXIS_Y) { + if (Chart\Axis::AXIS_X != $typeAxis && Chart\Axis::AXIS_Y != $typeAxis) { return; } - if ($typeAxis == Chart\Axis::AXIS_X) { + $crossesAt = $oAxis->getCrossesAt(); + $orientation = $oAxis->isReversedOrder() ? 'maxMin' : 'minMax'; + + if (Chart\Axis::AXIS_X == $typeAxis) { $mainElement = 'c:catAx'; $axIdVal = '52743552'; - $axPosVal = 'b'; + $axPosVal = $crossesAt === 'max' ? 't' : 'b'; $crossAxVal = '52749440'; } else { $mainElement = 'c:valAx'; $axIdVal = '52749440'; - $axPosVal = 'l'; + $axPosVal = $crossesAt === 'max' ? 'r' : 'l'; $crossAxVal = '52743552'; } @@ -2131,16 +2316,16 @@ class PptCharts extends AbstractDecoratorWriter // $mainElement > c:scaling > c:orientation $objWriter->startElement('c:orientation'); - $objWriter->writeAttribute('val', 'minMax'); + $objWriter->writeAttribute('val', $orientation); $objWriter->endElement(); - if ($oAxis->getMaxBounds() != null) { + if (null != $oAxis->getMaxBounds()) { $objWriter->startElement('c:max'); $objWriter->writeAttribute('val', $oAxis->getMaxBounds()); $objWriter->endElement(); } - if ($oAxis->getMinBounds() != null) { + if (null != $oAxis->getMinBounds()) { $objWriter->startElement('c:min'); $objWriter->writeAttribute('val', $oAxis->getMinBounds()); $objWriter->endElement(); @@ -2177,7 +2362,7 @@ class PptCharts extends AbstractDecoratorWriter $objWriter->endElement(); } - if ($oAxis->getTitle() != '') { + if ('' != $oAxis->getTitle()) { // c:title $objWriter->startElement('c:title'); @@ -2188,7 +2373,9 @@ class PptCharts extends AbstractDecoratorWriter $objWriter->startElement('c:rich'); // a:bodyPr - $objWriter->writeElement('a:bodyPr', null); + $objWriter->startElement('a:bodyPr'); + $objWriter->writeAttributeIf($oAxis->getTitleRotation() != 0, 'rot', CommonDrawing::degreesToAngle((int) $oAxis->getTitleRotation())); + $objWriter->endElement(); // a:lstStyle $objWriter->writeElement('a:lstStyle', null); @@ -2277,7 +2464,7 @@ class PptCharts extends AbstractDecoratorWriter // c:tickLblPos $objWriter->startElement('c:tickLblPos'); - $objWriter->writeAttribute('val', 'nextTo'); + $objWriter->writeAttribute('val', $oAxis->getTickLabelPosition()); $objWriter->endElement(); // c:spPr @@ -2292,12 +2479,18 @@ class PptCharts extends AbstractDecoratorWriter $objWriter->writeAttribute('val', $crossAxVal); $objWriter->endElement(); - // c:crosses - $objWriter->startElement('c:crosses'); - $objWriter->writeAttribute('val', 'autoZero'); - $objWriter->endElement(); + // c:crosses "autoZero" | "min" | "max" | custom string value + if (in_array($crossesAt, ['autoZero', 'min', 'max'])) { + $objWriter->startElement('c:crosses'); + $objWriter->writeAttribute('val', $crossesAt); + $objWriter->endElement(); + } else { + $objWriter->startElement('c:crossesAt'); + $objWriter->writeAttribute('val', $crossesAt); + $objWriter->endElement(); + } - if ($typeAxis == Chart\Axis::AXIS_X) { + if (Chart\Axis::AXIS_X == $typeAxis) { // c:lblAlgn $objWriter->startElement('c:lblAlgn'); $objWriter->writeAttribute('val', 'ctr'); @@ -2307,9 +2500,16 @@ class PptCharts extends AbstractDecoratorWriter $objWriter->startElement('c:lblOffset'); $objWriter->writeAttribute('val', '100'); $objWriter->endElement(); + + // c:majorUnit + if ($oAxis->getMajorUnit() != null) { + $objWriter->startElement('c:tickLblSkip'); + $objWriter->writeAttribute('val', $oAxis->getMajorUnit()); + $objWriter->endElement(); + } } - if ($typeAxis == Chart\Axis::AXIS_Y) { + if (Chart\Axis::AXIS_Y == $typeAxis) { // c:crossBetween $objWriter->startElement('c:crossBetween'); // midCat : Position Axis On Tick Marks @@ -2322,14 +2522,14 @@ class PptCharts extends AbstractDecoratorWriter $objWriter->endElement(); // c:majorUnit - if ($oAxis->getMajorUnit() != null) { + if (null != $oAxis->getMajorUnit()) { $objWriter->startElement('c:majorUnit'); $objWriter->writeAttribute('val', $oAxis->getMajorUnit()); $objWriter->endElement(); } // c:minorUnit - if ($oAxis->getMinorUnit() != null) { + if (null != $oAxis->getMinorUnit()) { $objWriter->startElement('c:minorUnit'); $objWriter->writeAttribute('val', $oAxis->getMinorUnit()); $objWriter->endElement(); @@ -2342,9 +2542,8 @@ class PptCharts extends AbstractDecoratorWriter /** * @param XMLWriter $objWriter * @param Gridlines $oGridlines - * @throws \Exception */ - protected function writeAxisGridlines(XMLWriter $objWriter, Gridlines $oGridlines) + protected function writeAxisGridlines(XMLWriter $objWriter, Gridlines $oGridlines): void { // c:spPr $objWriter->startElement('c:spPr'); diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptComments.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptComments.php old mode 100755 new mode 100644 index 4c1cc19..5a30c08 --- a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptComments.php +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptComments.php @@ -1,4 +1,22 @@ getPresentation()->getAllSlides() as $numSlide => $oSlide) { $contentXml = $this->writeSlideComments($oSlide); if (empty($contentXml)) { continue; } - $this->getZip()->addFromString('ppt/comments/comment'.($numSlide + 1).'.xml', $contentXml); + $this->getZip()->addFromString('ppt/comments/comment' . ($numSlide + 1) . '.xml', $contentXml); } + return $this->getZip(); } /** - * @param Slide $oSlide * @return string */ - protected function writeSlideComments(Slide $oSlide) + protected function writeSlideComments(Slide $oSlide): string { /** * @var Comment[] */ - $arrayComment = array(); + $arrayComment = []; foreach ($oSlide->getShapeCollection() as $oShape) { if ($oShape instanceof Comment) { $arrayComment[] = $oShape; diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptMedia.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptMedia.php old mode 100755 new mode 100644 index 58121be..85a7ace --- a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptMedia.php +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptMedia.php @@ -1,16 +1,34 @@ getDrawingHashTable()->count(); ++$i) { $shape = $this->getDrawingHashTable()->getByIndex($i); diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptPresProps.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptPresProps.php old mode 100755 new mode 100644 index d434e03..c28f12d --- a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptPresProps.php +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptPresProps.php @@ -1,15 +1,34 @@ oPresentation->getPresentationProperties(); @@ -26,11 +45,18 @@ class PptPresProps extends AbstractDecoratorWriter $objWriter->writeAttribute('xmlns:p', 'http://schemas.openxmlformats.org/presentationml/2006/main'); // p:presentationPr > p:showPr + $objWriter->startElement('p:showPr'); if ($presentationPpts->isLoopContinuouslyUntilEsc()) { - $objWriter->startElement('p:showPr'); $objWriter->writeAttribute('loop', '1'); - $objWriter->endElement(); } + // Depends on the slideshow type + // p:presentationPr > p:showPr > p:present + // p:presentationPr > p:showPr > p:browse + // p:presentationPr > p:showPr > p:kiosk + $objWriter->writeElement('p:' . $presentationPpts->getSlideshowType()); + + // > p:presentationPr > p:showPr + $objWriter->endElement(); // p:extLst $objWriter->startElement('p:extLst'); diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptPresentation.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptPresentation.php old mode 100755 new mode 100644 index a38c016..a6aa3c1 --- a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptPresentation.php +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptPresentation.php @@ -1,17 +1,35 @@ startElement('p:sldMasterIdLst'); // Add slide masters - $relationId = 1; + $relationId = 1; $slideMasterId = 2147483648; $countMasterSlides = count($this->oPresentation->getAllMasterSlides()); - for ($inc = 1; $inc <= $countMasterSlides; $inc++) { + for ($inc = 1; $inc <= $countMasterSlides; ++$inc) { // p:sldMasterId $objWriter->startElement('p:sldMasterId'); $objWriter->writeAttribute('id', $slideMasterId); @@ -46,7 +64,7 @@ class PptPresentation extends AbstractDecoratorWriter $objWriter->endElement(); // theme - $relationId++; + ++$relationId; // p:sldIdLst $objWriter->startElement('p:sldIdLst'); @@ -65,7 +83,7 @@ class PptPresentation extends AbstractDecoratorWriter $objWriter->startElement('p:sldSz'); $objWriter->writeAttribute('cx', $this->oPresentation->getLayout()->getCX()); $objWriter->writeAttribute('cy', $this->oPresentation->getLayout()->getCY()); - if ($this->oPresentation->getLayout()->getDocumentLayout() != DocumentLayout::LAYOUT_CUSTOM) { + if (DocumentLayout::LAYOUT_CUSTOM != $this->oPresentation->getLayout()->getDocumentLayout()) { $objWriter->writeAttribute('type', $this->oPresentation->getLayout()->getDocumentLayout()); } $objWriter->endElement(); diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptSlideLayouts.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptSlideLayouts.php old mode 100755 new mode 100644 index fe7a694..b482579 --- a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptSlideLayouts.php +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptSlideLayouts.php @@ -1,21 +1,39 @@ oPresentation->getAllMasterSlides() as $oSlideMaster) { foreach ($oSlideMaster->getAllSlideLayouts() as $oSlideLayout) { @@ -34,13 +52,11 @@ class PptSlideLayouts extends AbstractSlide } /** - * Write slide layout relationships to XML format + * Write slide layout relationships to XML format. * - * @param \PhpOffice\PhpPresentation\Slide\SlideLayout $oSlideLayout - * @return string XML Output - * @throws \Exception + * @return string XML Output */ - public function writeSlideLayoutRelationships(SlideLayout $oSlideLayout) + protected function writeSlideLayoutRelationships(SlideLayout $oSlideLayout): string { // Create XML writer $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); @@ -66,7 +82,7 @@ class PptSlideLayouts extends AbstractSlide $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', '../media/' . $oBackground->getIndexedFilename($oSlideLayout->getRelsIndex())); $oBackground->relationId = 'rId' . $relId; - $relId++; + ++$relId; } $objWriter->endElement(); @@ -76,13 +92,11 @@ class PptSlideLayouts extends AbstractSlide } /** - * Write slide to XML format + * Write slide to XML format. * - * @param \PhpOffice\PhpPresentation\Slide\SlideLayout $pSlideLayout * @return string XML Output - * @throws \Exception */ - public function writeSlideLayout(SlideLayout $pSlideLayout) + protected function writeSlideLayout(SlideLayout $pSlideLayout): string { // Create XML writer $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); @@ -96,7 +110,7 @@ class PptSlideLayouts extends AbstractSlide $objWriter->writeAttribute('preserve', 1); // p:sldLayout\p:cSld $objWriter->startElement('p:cSld'); - $objWriter->writeAttributeIf($pSlideLayout->getLayoutName() != '', 'name', $pSlideLayout->getLayoutName()); + $objWriter->writeAttributeIf('' != $pSlideLayout->getLayoutName(), 'name', $pSlideLayout->getLayoutName()); // Background $this->writeSlideBackground($pSlideLayout, $objWriter); // p:sldLayout\p:cSld\p:spTree diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptSlideMasters.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptSlideMasters.php old mode 100755 new mode 100644 index 56379c2..a7758a8 --- a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptSlideMasters.php +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptSlideMasters.php @@ -1,25 +1,39 @@ oPresentation->getAllMasterSlides() as $oMasterSlide) { // Add the relations from the masterSlide to the ZIP file @@ -38,14 +52,13 @@ class PptSlideMasters extends AbstractSlide } /** - * Write slide master relationships to XML format + * Write slide master relationships to XML format. + * + * @todo Set method in protected * - * @param SlideMaster $oMasterSlide * @return string XML Output - * @throws \Exception - * @internal param int $masterId Master slide id */ - public function writeSlideMasterRelationships(SlideMaster $oMasterSlide) + public function writeSlideMasterRelationships(SlideMaster $oMasterSlide): string { // Create XML writer $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); @@ -72,7 +85,7 @@ class PptSlideMasters extends AbstractSlide $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', '../media/' . $oBackground->getIndexedFilename($oMasterSlide->getRelsIndex())); $oBackground->relationId = 'rId' . $relId; - $relId++; + ++$relId; } // TODO: Write hyperlink relationships? @@ -85,13 +98,11 @@ class PptSlideMasters extends AbstractSlide } /** - * Write slide to XML format + * Write slide to XML format. * - * @param \PhpOffice\PhpPresentation\Slide\SlideMaster $pSlide * @return string XML Output - * @throws \Exception */ - public function writeSlideMaster(SlideMaster $pSlide) + protected function writeSlideMaster(SlideMaster $pSlide): string { // Create XML writer $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); @@ -178,35 +189,35 @@ class PptSlideMasters extends AbstractSlide // p:sldMaster\p:txStyles $objWriter->startElement('p:txStyles'); - foreach (array( - 'p:titleStyle' => $pSlide->getTextStyles()->getTitleStyle(), - 'p:bodyStyle' => $pSlide->getTextStyles()->getBodyStyle(), - 'p:otherStyle' => $pSlide->getTextStyles()->getOtherStyle() - ) as $startElement => $stylesArray) { + foreach ([ + 'p:titleStyle' => $pSlide->getTextStyles()->getTitleStyle(), + 'p:bodyStyle' => $pSlide->getTextStyles()->getBodyStyle(), + 'p:otherStyle' => $pSlide->getTextStyles()->getOtherStyle(), + ] as $startElement => $stylesArray) { // titleStyle $objWriter->startElement($startElement); foreach ($stylesArray as $lvl => $oParagraph) { /** @var RichText\Paragraph $oParagraph */ - $elementName = ($lvl == 0 ? 'a:defPPr' : 'a:lvl' . $lvl . 'pPr'); + $elementName = (0 == $lvl ? 'a:defPPr' : 'a:lvl' . $lvl . 'pPr'); $objWriter->startElement($elementName); $objWriter->writeAttribute('algn', $oParagraph->getAlignment()->getHorizontal()); $objWriter->writeAttributeIf( - $oParagraph->getAlignment()->getMarginLeft() != 0, + 0 != $oParagraph->getAlignment()->getMarginLeft(), 'marL', CommonDrawing::pixelsToEmu($oParagraph->getAlignment()->getMarginLeft()) ); $objWriter->writeAttributeIf( - $oParagraph->getAlignment()->getMarginRight() != 0, + 0 != $oParagraph->getAlignment()->getMarginRight(), 'marR', CommonDrawing::pixelsToEmu($oParagraph->getAlignment()->getMarginRight()) ); $objWriter->writeAttributeIf( - $oParagraph->getAlignment()->getIndent() != 0, + 0 != $oParagraph->getAlignment()->getIndent(), 'indent', CommonDrawing::pixelsToEmu($oParagraph->getAlignment()->getIndent()) ); $objWriter->startElement('a:defRPr'); - $objWriter->writeAttributeIf($oParagraph->getFont()->getSize() != 10, 'sz', $oParagraph->getFont()->getSize() * 100); + $objWriter->writeAttributeIf(10 != $oParagraph->getFont()->getSize(), 'sz', $oParagraph->getFont()->getSize() * 100); $objWriter->writeAttributeIf($oParagraph->getFont()->isBold(), 'b', 1); $objWriter->writeAttributeIf($oParagraph->getFont()->isItalic(), 'i', 1); $objWriter->writeAttribute('kern', '1200'); diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptSlides.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptSlides.php old mode 100755 new mode 100644 index 14e73d8..8c2999d --- a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptSlides.php +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptSlides.php @@ -1,7 +1,26 @@ oPresentation->getAllSlides() as $idx => $oSlide) { $this->oZip->addFromString('ppt/slides/_rels/slide' . ($idx + 1) . '.xml.rels', $this->writeSlideRelationships($oSlide)); @@ -40,7 +59,7 @@ class PptSlides extends AbstractSlide // Add background image slide $oBkgImage = $oSlide->getBackground(); if ($oBkgImage instanceof Image) { - $this->oZip->addFromString('ppt/media/'.$oBkgImage->getIndexedFilename($idx), file_get_contents($oBkgImage->getPath())); + $this->oZip->addFromString('ppt/media/' . $oBkgImage->getIndexedFilename((string) $idx), file_get_contents($oBkgImage->getPath())); } } @@ -48,13 +67,11 @@ class PptSlides extends AbstractSlide } /** - * Write slide relationships to XML format + * Write slide relationships to XML format. * - * @param \PhpOffice\PhpPresentation\Slide $pSlide - * @return string XML Output - * @throws \Exception + * @return string XML Output */ - protected function writeSlideRelationships(Slide $pSlide) + protected function writeSlideRelationships(Slide $pSlide): string { //@todo Group all getShapeCollection()->getIterator @@ -110,9 +127,9 @@ class PptSlides extends AbstractSlide if ($iterator2->current() instanceof Media) { // Write relationship for image drawing $iterator2->current()->relationId = 'rId' . $relId; - $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/video', '../media/' . $iterator->current()->getIndexedFilename()); + $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/video', '../media/' . $iterator2->current()->getIndexedFilename()); ++$relId; - $this->writeRelationship($objWriter, $relId, 'http://schemas.microsoft.com/office/2007/relationships/media', '../media/' . $iterator->current()->getIndexedFilename()); + $this->writeRelationship($objWriter, $relId, 'http://schemas.microsoft.com/office/2007/relationships/media', '../media/' . $iterator2->current()->getIndexedFilename()); ++$relId; } elseif ($iterator2->current() instanceof ShapeDrawing\AbstractDrawingAdapter) { // Write relationship for image drawing @@ -138,7 +155,7 @@ class PptSlides extends AbstractSlide // Write background relationships? $oBackground = $pSlide->getBackground(); if ($oBackground instanceof Image) { - $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', '../media/' . $oBackground->getIndexedFilename($idxSlide)); + $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', '../media/' . $oBackground->getIndexedFilename((string) $idxSlide)); $oBackground->relationId = 'rId' . $relId; ++$relId; } @@ -151,7 +168,7 @@ class PptSlides extends AbstractSlide // Hyperlink on shape if ($iterator->current()->hasHyperlink()) { // Write relationship for hyperlink - $hyperlink = $iterator->current()->getHyperlink(); + $hyperlink = $iterator->current()->getHyperlink(); $hyperlink->relationId = 'rId' . $relId; if (!$hyperlink->isInternal()) { @@ -170,7 +187,7 @@ class PptSlides extends AbstractSlide if ($element instanceof Run || $element instanceof TextElement) { if ($element->hasHyperlink()) { // Write relationship for hyperlink - $hyperlink = $element->getHyperlink(); + $hyperlink = $element->getHyperlink(); $hyperlink->relationId = 'rId' . $relId; if (!$hyperlink->isInternal()) { @@ -190,10 +207,10 @@ class PptSlides extends AbstractSlide if ($iterator->current() instanceof ShapeTable) { // Rows $countRows = count($iterator->current()->getRows()); - for ($row = 0; $row < $countRows; $row++) { + for ($row = 0; $row < $countRows; ++$row) { // Cells in rows $countCells = count($iterator->current()->getRow($row)->getCells()); - for ($cell = 0; $cell < $countCells; $cell++) { + for ($cell = 0; $cell < $countCells; ++$cell) { $currentCell = $iterator->current()->getRow($row)->getCell($cell); // Paragraphs in cell foreach ($currentCell->getParagraphs() as $paragraph) { @@ -203,7 +220,7 @@ class PptSlides extends AbstractSlide if ($element instanceof Run || $element instanceof TextElement) { if ($element->hasHyperlink()) { // Write relationship for hyperlink - $hyperlink = $element->getHyperlink(); + $hyperlink = $element->getHyperlink(); $hyperlink->relationId = 'rId' . $relId; if (!$hyperlink->isInternal()) { @@ -227,7 +244,7 @@ class PptSlides extends AbstractSlide // Hyperlink on shape if ($iterator2->current()->hasHyperlink()) { // Write relationship for hyperlink - $hyperlink = $iterator2->current()->getHyperlink(); + $hyperlink = $iterator2->current()->getHyperlink(); $hyperlink->relationId = 'rId' . $relId; if (!$hyperlink->isInternal()) { @@ -246,7 +263,7 @@ class PptSlides extends AbstractSlide if ($element instanceof Run || $element instanceof TextElement) { if ($element->hasHyperlink()) { // Write relationship for hyperlink - $hyperlink = $element->getHyperlink(); + $hyperlink = $element->getHyperlink(); $hyperlink->relationId = 'rId' . $relId; if (!$hyperlink->isInternal()) { @@ -266,10 +283,10 @@ class PptSlides extends AbstractSlide if ($iterator2->current() instanceof ShapeTable) { // Rows $countRows = count($iterator2->current()->getRows()); - for ($row = 0; $row < $countRows; $row++) { + for ($row = 0; $row < $countRows; ++$row) { // Cells in rows $countCells = count($iterator2->current()->getRow($row)->getCells()); - for ($cell = 0; $cell < $countCells; $cell++) { + for ($cell = 0; $cell < $countCells; ++$cell) { $currentCell = $iterator2->current()->getRow($row)->getCell($cell); // Paragraphs in cell foreach ($currentCell->getParagraphs() as $paragraph) { @@ -279,7 +296,7 @@ class PptSlides extends AbstractSlide if ($element instanceof Run || $element instanceof TextElement) { if ($element->hasHyperlink()) { // Write relationship for hyperlink - $hyperlink = $element->getHyperlink(); + $hyperlink = $element->getHyperlink(); $hyperlink->relationId = 'rId' . $relId; if (!$hyperlink->isInternal()) { @@ -330,13 +347,13 @@ class PptSlides extends AbstractSlide } if ($hasSlideComment) { - $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments', '../comments/comment'.($idxSlide + 1).'.xml'); + $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments', '../comments/comment' . ($idxSlide + 1) . '.xml'); ++$relId; } } if ($pSlide->getNote()->getShapeCollection()->count() > 0) { - $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide', '../notesSlides/notesSlide'.($idxSlide + 1).'.xml'); + $this->writeRelationship($objWriter, $relId, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide', '../notesSlides/notesSlide' . ($idxSlide + 1) . '.xml'); } $objWriter->endElement(); @@ -346,13 +363,11 @@ class PptSlides extends AbstractSlide } /** - * Write slide to XML format + * Write slide to XML format. * - * @param \PhpOffice\PhpPresentation\Slide $pSlide - * @return string XML Output - * @throws \Exception + * @return string XML Output */ - public function writeSlide(Slide $pSlide) + protected function writeSlide(Slide $pSlide): string { // Create XML writer $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); @@ -499,11 +514,7 @@ class PptSlides extends AbstractSlide return $objWriter->getData(); } - /** - * @param XMLWriter $objWriter - * @param Slide $oSlide - */ - protected function writeSlideAnimations(XMLWriter $objWriter, Slide $oSlide) + protected function writeSlideAnimations(XMLWriter $objWriter, Slide $oSlide): void { $arrayAnimations = $oSlide->getAnimations(); if (empty($arrayAnimations)) { @@ -513,8 +524,8 @@ class PptSlides extends AbstractSlide // Variables $shapeId = 1; $idCount = 1; - $hashToIdMap = array(); - $arrayAnimationIds = array(); + $hashToIdMap = []; + $arrayAnimationIds = []; foreach ($oSlide->getShapeCollection() as $shape) { $hashToIdMap[$shape->getHashCode()] = ++$shapeId; @@ -746,127 +757,4 @@ class PptSlides extends AbstractSlide // ##p:timing $objWriter->endElement(); } - - /** - * Write pic - * - * @param \PhpOffice\Common\XMLWriter $objWriter XML Writer - * @param \PhpOffice\PhpPresentation\Shape\Drawing\AbstractDrawingAdapter $shape - * @param int $shapeId - * @throws \Exception - */ - protected function writeShapeDrawing(XMLWriter $objWriter, ShapeDrawing\AbstractDrawingAdapter $shape, $shapeId) - { - // p:pic - $objWriter->startElement('p:pic'); - - // p:nvPicPr - $objWriter->startElement('p:nvPicPr'); - - // p:cNvPr - $objWriter->startElement('p:cNvPr'); - $objWriter->writeAttribute('id', $shapeId); - $objWriter->writeAttribute('name', $shape->getName()); - $objWriter->writeAttribute('descr', $shape->getDescription()); - - // a:hlinkClick - if ($shape->hasHyperlink()) { - $this->writeHyperlink($objWriter, $shape); - } - - $objWriter->endElement(); - - // p:cNvPicPr - $objWriter->startElement('p:cNvPicPr'); - - // a:picLocks - $objWriter->startElement('a:picLocks'); - $objWriter->writeAttribute('noChangeAspect', '1'); - $objWriter->endElement(); - - $objWriter->endElement(); - - // p:nvPr - $objWriter->startElement('p:nvPr'); - // PlaceHolder - if ($shape->isPlaceholder()) { - $objWriter->startElement('p:ph'); - $objWriter->writeAttribute('type', $shape->getPlaceholder()->getType()); - $objWriter->endElement(); - } - /** - * @link : https://github.com/stefslon/exportToPPTX/blob/master/exportToPPTX.m#L2128 - */ - if ($shape instanceof Media) { - // p:nvPr > a:videoFile - $objWriter->startElement('a:videoFile'); - $objWriter->writeAttribute('r:link', $shape->relationId); - $objWriter->endElement(); - // p:nvPr > p:extLst - $objWriter->startElement('p:extLst'); - // p:nvPr > p:extLst > p:ext - $objWriter->startElement('p:ext'); - $objWriter->writeAttribute('uri', '{DAA4B4D4-6D71-4841-9C94-3DE7FCFB9230}'); - // p:nvPr > p:extLst > p:ext > p14:media - $objWriter->startElement('p14:media'); - $objWriter->writeAttribute('r:embed', ($shape->relationId + 1)); - $objWriter->writeAttribute('xmlns:p14', 'http://schemas.microsoft.com/office/powerpoint/2010/main'); - // p:nvPr > p:extLst > p:ext > ##p14:media - $objWriter->endElement(); - // p:nvPr > p:extLst > ##p:ext - $objWriter->endElement(); - // p:nvPr > ##p:extLst - $objWriter->endElement(); - } - // ##p:nvPr - $objWriter->endElement(); - $objWriter->endElement(); - - // p:blipFill - $objWriter->startElement('p:blipFill'); - - // a:blip - $objWriter->startElement('a:blip'); - $objWriter->writeAttribute('r:embed', $shape->relationId); - $objWriter->endElement(); - - // a:stretch - $objWriter->startElement('a:stretch'); - $objWriter->writeElement('a:fillRect'); - $objWriter->endElement(); - - $objWriter->endElement(); - - // p:spPr - $objWriter->startElement('p:spPr'); - // a:xfrm - $objWriter->startElement('a:xfrm'); - $objWriter->writeAttributeIf($shape->getRotation() != 0, 'rot', CommonDrawing::degreesToAngle($shape->getRotation())); - - // a:off - $objWriter->startElement('a:off'); - $objWriter->writeAttribute('x', CommonDrawing::pixelsToEmu($shape->getOffsetX())); - $objWriter->writeAttribute('y', CommonDrawing::pixelsToEmu($shape->getOffsetY())); - $objWriter->endElement(); - - // a:ext - $objWriter->startElement('a:ext'); - $objWriter->writeAttribute('cx', CommonDrawing::pixelsToEmu($shape->getWidth())); - $objWriter->writeAttribute('cy', CommonDrawing::pixelsToEmu($shape->getHeight())); - $objWriter->endElement(); - - $objWriter->endElement(); - - // a:prstGeom - $objWriter->startElement('a:prstGeom'); - $objWriter->writeAttribute('prst', 'rect'); - // // a:prstGeom/a:avLst - $objWriter->writeElement('a:avLst', null); - // ##a:prstGeom - $objWriter->endElement(); - - $objWriter->endElement(); - - $objWriter->endElement(); - } } diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptTableProps.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptTableProps.php old mode 100755 new mode 100644 index e9dd71d..ff2c5f8 --- a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptTableProps.php +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptTableProps.php @@ -1,15 +1,34 @@ oPresentation->getAllMasterSlides() as $oMasterSlide) { $this->getZip()->addFromString('ppt/theme/theme' . $oMasterSlide->getRelsIndex() . '.xml', $this->writeTheme($oMasterSlide)); @@ -20,16 +38,14 @@ class PptTheme extends AbstractDecoratorWriter return $this->getZip(); } - /** - * Write theme to XML format + * Write theme to XML format. * - * @param Slide\SlideMaster $oMasterSlide * @return string XML Output */ - protected function writeTheme(Slide\SlideMaster $oMasterSlide) + protected function writeTheme(Slide\SlideMaster $oMasterSlide): string { - $arrayFont = array( + $arrayFont = [ 'Jpan' => 'MS Pゴシック', 'Hang' => '맑은 고딕', 'Hans' => '宋体', @@ -59,12 +75,12 @@ class PptTheme extends AbstractDecoratorWriter 'Mong' => 'Mongolian Baiti', 'Viet' => 'Times New Roman', 'Uigh' => 'Microsoft Uighur', - ); + ]; // Create XML writer $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); - $name = 'Theme'.rand(1, 100); + $name = 'Theme' . rand(1, 100); // XML header $objWriter->startDocument('1.0', 'UTF-8', 'yes'); @@ -83,13 +99,13 @@ class PptTheme extends AbstractDecoratorWriter foreach ($oMasterSlide->getAllSchemeColors() as $oSchemeColor) { // a:theme/a:themeElements/a:clrScheme/a:* - $objWriter->startElement('a:'.$oSchemeColor->getValue()); + $objWriter->startElement('a:' . $oSchemeColor->getValue()); - if (in_array($oSchemeColor->getValue(), array( - 'dk1', 'lt1' - ))) { + if (in_array($oSchemeColor->getValue(), [ + 'dk1', 'lt1', + ])) { $objWriter->startElement('a:sysClr'); - $objWriter->writeAttribute('val', ($oSchemeColor->getValue() == 'dk1' ? 'windowText' : 'window')); + $objWriter->writeAttribute('val', ('dk1' == $oSchemeColor->getValue() ? 'windowText' : 'window')); $objWriter->writeAttribute('lastClr', $oSchemeColor->getRGB()); $objWriter->endElement(); } else { diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptViewProps.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptViewProps.php old mode 100755 new mode 100644 index f014fe7..2a89473 --- a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptViewProps.php +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/PptViewProps.php @@ -1,15 +1,34 @@ startElement('a:sx'); $objWriter->writeAttribute('d', '100'); - $objWriter->writeAttribute('n', (int)($this->getPresentation()->getPresentationProperties()->getZoom() * 100)); + $objWriter->writeAttribute('n', (int) ($this->getPresentation()->getPresentationProperties()->getZoom() * 100)); $objWriter->endElement(); $objWriter->startElement('a:sy'); $objWriter->writeAttribute('d', '100'); - $objWriter->writeAttribute('n', (int)($this->getPresentation()->getPresentationProperties()->getZoom() * 100)); + $objWriter->writeAttribute('n', (int) ($this->getPresentation()->getPresentationProperties()->getZoom() * 100)); $objWriter->endElement(); // > // p:viewPr > p:slideViewPr > p:cSldViewPr > p:cViewPr > p:scale diff --git a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/Relationships.php b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/Relationships.php old mode 100755 new mode 100644 index ccc5768..9069ed4 --- a/PhpOffice/PhpPresentation/Writer/PowerPoint2007/Relationships.php +++ b/PhpOffice/PhpPresentation/Writer/PowerPoint2007/Relationships.php @@ -1,19 +1,38 @@ getZip()->addFromString('_rels/.rels', $this->writeRelationships()); $this->getZip()->addFromString('ppt/_rels/presentation.xml.rels', $this->writePresentationRelationships()); @@ -22,12 +41,11 @@ class Relationships extends AbstractDecoratorWriter } /** - * Write relationships to XML format + * Write relationships to XML format. * - * @return string XML Output - * @throws \Exception + * @return string XML Output */ - public function writeRelationships() + protected function writeRelationships(): string { // Create XML writer $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); @@ -67,12 +85,11 @@ class Relationships extends AbstractDecoratorWriter } /** - * Write presentation relationships to XML format + * Write presentation relationships to XML format. * - * @return string XML Output - * @throws \Exception + * @return string XML Output */ - public function writePresentationRelationships() + protected function writePresentationRelationships(): string { // Create XML writer $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); @@ -106,7 +123,6 @@ class Relationships extends AbstractDecoratorWriter $this->writeRelationship($objWriter, $relationId++, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/viewProps', 'viewProps.xml'); $this->writeRelationship($objWriter, $relationId++, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/tableStyles', 'tableStyles.xml'); - // Comments Authors foreach ($this->getPresentation()->getAllSlides() as $oSlide) { foreach ($oSlide->getShapeCollection() as $oShape) { diff --git a/PhpOffice/PhpPresentation/Writer/Serialized.php b/PhpOffice/PhpPresentation/Writer/Serialized.php old mode 100755 new mode 100644 index 2d57e84..5482c1e --- a/PhpOffice/PhpPresentation/Writer/Serialized.php +++ b/PhpOffice/PhpPresentation/Writer/Serialized.php @@ -10,48 +10,56 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Writer; use PhpOffice\Common\Adapter\Zip\ZipArchiveAdapter; use PhpOffice\Common\XMLWriter; +use PhpOffice\PhpPresentation\Exception\DirectoryNotFoundException; +use PhpOffice\PhpPresentation\Exception\InvalidParameterException; use PhpOffice\PhpPresentation\PhpPresentation; use PhpOffice\PhpPresentation\Shape\Drawing\AbstractDrawingAdapter; +use PhpOffice\PhpPresentation\Shape\Drawing\File; /** - * \PhpOffice\PhpPresentation\Writer\Serialized + * \PhpOffice\PhpPresentation\Writer\Serialized. */ class Serialized extends AbstractWriter implements WriterInterface { /** - * Create a new \PhpOffice\PhpPresentation\Writer\Serialized + * Create a new \PhpOffice\PhpPresentation\Writer\Serialized. * * @param \PhpOffice\PhpPresentation\PhpPresentation $pPhpPresentation - * @throws \Exception */ public function __construct(PhpPresentation $pPhpPresentation = null) { // Set PhpPresentation - $this->setPhpPresentation($pPhpPresentation); + $this->setPhpPresentation($pPhpPresentation ?? new PhpPresentation()); // Set ZIP Adapter $this->setZipAdapter(new ZipArchiveAdapter()); } /** - * Save PhpPresentation to file + * Save PhpPresentation to file. * - * @param string $pFilename - * @throws \Exception + * @throws DirectoryNotFoundException + * @throws InvalidParameterException */ - public function save($pFilename) + public function save(string $pFilename): void { if (empty($pFilename)) { - throw new \Exception("Filename is empty."); + throw new InvalidParameterException('pFilename', ''); + } + if (!is_dir(dirname($pFilename))) { + throw new DirectoryNotFoundException(dirname($pFilename)); } $oPresentation = $this->getPhpPresentation(); @@ -67,7 +75,10 @@ class Serialized extends AbstractWriter implements WriterInterface for ($j = 0; $j < $oPresentation->getSlide($i)->getShapeCollection()->count(); ++$j) { if ($oPresentation->getSlide($i)->getShapeCollection()->offsetGet($j) instanceof AbstractDrawingAdapter) { $imgTemp = $oPresentation->getSlide($i)->getShapeCollection()->offsetGet($j); - $objZip->addFromString('media/' . $imgTemp->getImageIndex() . '/' . pathinfo($imgTemp->getPath(), PATHINFO_BASENAME), file_get_contents($imgTemp->getPath())); + $objZip->addFromString( + 'media/' . $imgTemp->getImageIndex() . '/' . pathinfo($imgTemp->getPath(), PATHINFO_BASENAME), + file_get_contents($imgTemp->getPath()) + ); } } } @@ -80,14 +91,14 @@ class Serialized extends AbstractWriter implements WriterInterface } /** - * Serialize PhpPresentation object to XML + * Serialize PhpPresentation object to XML. * - * @param PhpPresentation $pPhpPresentation - * @param string $pFilename - * @return string XML Output - * @throws \Exception + * @param PhpPresentation|null $pPhpPresentation + * @param string $pFilename + * + * @return string XML Output */ - private function writeSerialized(PhpPresentation $pPhpPresentation = null, $pFilename = '') + protected function writeSerialized(PhpPresentation $pPhpPresentation = null, $pFilename = '') { // Clone $pPhpPresentation $pPhpPresentation = clone $pPhpPresentation; @@ -98,7 +109,12 @@ class Serialized extends AbstractWriter implements WriterInterface for ($j = 0; $j < $pPhpPresentation->getSlide($i)->getShapeCollection()->count(); ++$j) { if ($pPhpPresentation->getSlide($i)->getShapeCollection()->offsetGet($j) instanceof AbstractDrawingAdapter) { $imgTemp = $pPhpPresentation->getSlide($i)->getShapeCollection()->offsetGet($j); - $imgTemp->setPath('zip://' . $pFilename . '#media/' . $imgTemp->getImageIndex() . '/' . pathinfo($imgTemp->getPath(), PATHINFO_BASENAME), false); + $imgPath = 'zip://' . $pFilename . '#media/' . $imgTemp->getImageIndex() . '/' . pathinfo($imgTemp->getPath(), PATHINFO_BASENAME); + if ($imgTemp instanceof File) { + $imgTemp->setPath($imgPath, false); + } else { + $imgTemp->setPath($imgPath); + } } } } diff --git a/PhpOffice/PhpPresentation/Writer/WriterInterface.php b/PhpOffice/PhpPresentation/Writer/WriterInterface.php old mode 100755 new mode 100644 index bf63a35..ab64192 --- a/PhpOffice/PhpPresentation/Writer/WriterInterface.php +++ b/PhpOffice/PhpPresentation/Writer/WriterInterface.php @@ -10,23 +10,23 @@ * file that was distributed with this source code. For the full list of * contributors, visit https://github.com/PHPOffice/PHPPresentation/contributors. * - * @link https://github.com/PHPOffice/PHPPresentation + * @see https://github.com/PHPOffice/PHPPresentation + * * @copyright 2009-2015 PHPPresentation contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ +declare(strict_types=1); + namespace PhpOffice\PhpPresentation\Writer; /** - * Writer interface + * Writer interface. */ interface WriterInterface { /** * Save PhpPresentation to file - * - * @param string $pFilename - * @throws \Exception */ - public function save($pFilename); + public function save(string $pFilename): void; } diff --git a/PhpOffice/PhpSpreadsheet/Calculation/ArrayEnabled.php b/PhpOffice/PhpSpreadsheet/Calculation/ArrayEnabled.php new file mode 100644 index 0000000..1e3f697 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/ArrayEnabled.php @@ -0,0 +1,133 @@ +initialise(($arguments === false) ? [] : $arguments); + } + + /** + * Handles array argument processing when the function accepts a single argument that can be an array argument. + * Example use for: + * DAYOFMONTH() or FACT(). + */ + protected static function evaluateSingleArgumentArray(callable $method, array $values): array + { + $result = []; + foreach ($values as $value) { + $result[] = $method($value); + } + + return $result; + } + + /** + * Handles array argument processing when the function accepts multiple arguments, + * and any of them can be an array argument. + * Example use for: + * ROUND() or DATE(). + * + * @param mixed ...$arguments + */ + protected static function evaluateArrayArguments(callable $method, ...$arguments): array + { + self::initialiseHelper($arguments); + $arguments = self::$arrayArgumentHelper->arguments(); + + return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments); + } + + /** + * Handles array argument processing when the function accepts multiple arguments, + * but only the first few (up to limit) can be an array arguments. + * Example use for: + * NETWORKDAYS() or CONCATENATE(), where the last argument is a matrix (or a series of values) that need + * to be treated as a such rather than as an array arguments. + * + * @param mixed ...$arguments + */ + protected static function evaluateArrayArgumentsSubset(callable $method, int $limit, ...$arguments): array + { + self::initialiseHelper(array_slice($arguments, 0, $limit)); + $trailingArguments = array_slice($arguments, $limit); + $arguments = self::$arrayArgumentHelper->arguments(); + $arguments = array_merge($arguments, $trailingArguments); + + return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments); + } + + /** + * @param mixed $value + */ + private static function testFalse($value): bool + { + return $value === false; + } + + /** + * Handles array argument processing when the function accepts multiple arguments, + * but only the last few (from start) can be an array arguments. + * Example use for: + * Z.TEST() or INDEX(), where the first argument 1 is a matrix that needs to be treated as a dataset + * rather than as an array argument. + * + * @param mixed ...$arguments + */ + protected static function evaluateArrayArgumentsSubsetFrom(callable $method, int $start, ...$arguments): array + { + $arrayArgumentsSubset = array_combine( + range($start, count($arguments) - $start), + array_slice($arguments, $start) + ); + if (self::testFalse($arrayArgumentsSubset)) { + return ['#VALUE!']; + } + + self::initialiseHelper($arrayArgumentsSubset); + $leadingArguments = array_slice($arguments, 0, $start); + $arguments = self::$arrayArgumentHelper->arguments(); + $arguments = array_merge($leadingArguments, $arguments); + + return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments); + } + + /** + * Handles array argument processing when the function accepts multiple arguments, + * and any of them can be an array argument except for the one specified by ignore. + * Example use for: + * HLOOKUP() and VLOOKUP(), where argument 1 is a matrix that needs to be treated as a database + * rather than as an array argument. + * + * @param mixed ...$arguments + */ + protected static function evaluateArrayArgumentsIgnore(callable $method, int $ignore, ...$arguments): array + { + $leadingArguments = array_slice($arguments, 0, $ignore); + $ignoreArgument = array_slice($arguments, $ignore, 1); + $trailingArguments = array_slice($arguments, $ignore + 1); + + self::initialiseHelper(array_merge($leadingArguments, [[null]], $trailingArguments)); + $arguments = self::$arrayArgumentHelper->arguments(); + + array_splice($arguments, $ignore, 1, $ignoreArgument); + + return ArrayArgumentProcessor::processArguments(self::$arrayArgumentHelper, $method, ...$arguments); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/BinaryComparison.php b/PhpOffice/PhpSpreadsheet/Calculation/BinaryComparison.php new file mode 100644 index 0000000..5d4f261 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/BinaryComparison.php @@ -0,0 +1,181 @@ + '' && $operand1[0] == Calculation::FORMULA_STRING_QUOTE) { + $operand1 = Calculation::unwrapResult($operand1); + } + if (is_string($operand2) && $operand2 > '' && $operand2[0] == Calculation::FORMULA_STRING_QUOTE) { + $operand2 = Calculation::unwrapResult($operand2); + } + + // Use case insensitive comparaison if not OpenOffice mode + if (Functions::getCompatibilityMode() != Functions::COMPATIBILITY_OPENOFFICE) { + if (is_string($operand1)) { + $operand1 = StringHelper::strToUpper($operand1); + } + if (is_string($operand2)) { + $operand2 = StringHelper::strToUpper($operand2); + } + } + + $useLowercaseFirstComparison = is_string($operand1) && + is_string($operand2) && + Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE; + + return self::evaluateComparison($operand1, $operand2, $operator, $useLowercaseFirstComparison); + } + + /** + * @param mixed $operand1 + * @param mixed $operand2 + */ + private static function evaluateComparison($operand1, $operand2, string $operator, bool $useLowercaseFirstComparison): bool + { + switch ($operator) { + // Equality + case '=': + return self::equal($operand1, $operand2); + // Greater than + case '>': + return self::greaterThan($operand1, $operand2, $useLowercaseFirstComparison); + // Less than + case '<': + return self::lessThan($operand1, $operand2, $useLowercaseFirstComparison); + // Greater than or equal + case '>=': + return self::greaterThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison); + // Less than or equal + case '<=': + return self::lessThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison); + // Inequality + case '<>': + return self::notEqual($operand1, $operand2); + default: + throw new Exception('Unsupported binary comparison operator'); + } + } + + /** + * @param mixed $operand1 + * @param mixed $operand2 + */ + private static function equal($operand1, $operand2): bool + { + if (is_numeric($operand1) && is_numeric($operand2)) { + $result = (abs($operand1 - $operand2) < self::DELTA); + } elseif (($operand1 === null && is_numeric($operand2)) || ($operand2 === null && is_numeric($operand1))) { + $result = $operand1 == $operand2; + } else { + $result = self::strcmpAllowNull($operand1, $operand2) == 0; + } + + return $result; + } + + /** + * @param mixed $operand1 + * @param mixed $operand2 + */ + private static function greaterThanOrEqual($operand1, $operand2, bool $useLowercaseFirstComparison): bool + { + if (is_numeric($operand1) && is_numeric($operand2)) { + $result = ((abs($operand1 - $operand2) < self::DELTA) || ($operand1 > $operand2)); + } elseif (($operand1 === null && is_numeric($operand2)) || ($operand2 === null && is_numeric($operand1))) { + $result = $operand1 >= $operand2; + } elseif ($useLowercaseFirstComparison) { + $result = self::strcmpLowercaseFirst($operand1, $operand2) >= 0; + } else { + $result = self::strcmpAllowNull($operand1, $operand2) >= 0; + } + + return $result; + } + + /** + * @param mixed $operand1 + * @param mixed $operand2 + */ + private static function lessThanOrEqual($operand1, $operand2, bool $useLowercaseFirstComparison): bool + { + if (is_numeric($operand1) && is_numeric($operand2)) { + $result = ((abs($operand1 - $operand2) < self::DELTA) || ($operand1 < $operand2)); + } elseif (($operand1 === null && is_numeric($operand2)) || ($operand2 === null && is_numeric($operand1))) { + $result = $operand1 <= $operand2; + } elseif ($useLowercaseFirstComparison) { + $result = self::strcmpLowercaseFirst($operand1, $operand2) <= 0; + } else { + $result = self::strcmpAllowNull($operand1, $operand2) <= 0; + } + + return $result; + } + + /** + * @param mixed $operand1 + * @param mixed $operand2 + */ + private static function greaterThan($operand1, $operand2, bool $useLowercaseFirstComparison): bool + { + return self::lessThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison) !== true; + } + + /** + * @param mixed $operand1 + * @param mixed $operand2 + */ + private static function lessThan($operand1, $operand2, bool $useLowercaseFirstComparison): bool + { + return self::greaterThanOrEqual($operand1, $operand2, $useLowercaseFirstComparison) !== true; + } + + /** + * @param mixed $operand1 + * @param mixed $operand2 + */ + private static function notEqual($operand1, $operand2): bool + { + return self::equal($operand1, $operand2) !== true; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Calculation.php b/PhpOffice/PhpSpreadsheet/Calculation/Calculation.php old mode 100755 new mode 100644 index b16caa1..616784d --- a/PhpOffice/PhpSpreadsheet/Calculation/Calculation.php +++ b/PhpOffice/PhpSpreadsheet/Calculation/Calculation.php @@ -2,15 +2,23 @@ namespace PhpOffice\PhpSpreadsheet\Calculation; +use PhpOffice\PhpSpreadsheet\Calculation\Engine\BranchPruner; use PhpOffice\PhpSpreadsheet\Calculation\Engine\CyclicReferenceStack; use PhpOffice\PhpSpreadsheet\Calculation\Engine\Logger; +use PhpOffice\PhpSpreadsheet\Calculation\Engine\Operands; use PhpOffice\PhpSpreadsheet\Calculation\Token\Stack; +use PhpOffice\PhpSpreadsheet\Cell\AddressRange; use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; -use PhpOffice\PhpSpreadsheet\NamedRange; +use PhpOffice\PhpSpreadsheet\Cell\DataType; +use PhpOffice\PhpSpreadsheet\DefinedName; +use PhpOffice\PhpSpreadsheet\ReferenceHelper; use PhpOffice\PhpSpreadsheet\Shared; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use ReflectionClassConstant; +use ReflectionMethod; +use ReflectionParameter; class Calculation { @@ -23,11 +31,21 @@ class Calculation // Opening bracket const CALCULATION_REGEXP_OPENBRACE = '\('; // Function (allow for the old @ symbol that could be used to prefix a function, but we'll ignore it) - const CALCULATION_REGEXP_FUNCTION = '@?(?:_xlfn\.)?([A-Z][A-Z0-9\.]*)[\s]*\('; + const CALCULATION_REGEXP_FUNCTION = '@?(?:_xlfn\.)?(?:_xlws\.)?([\p{L}][\p{L}\p{N}\.]*)[\s]*\('; // Cell reference (cell or range of cells, with or without a sheet reference) - const CALCULATION_REGEXP_CELLREF = '((([^\s,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?\$?([a-z]{1,3})\$?(\d{1,7})'; - // Named Range of cells - const CALCULATION_REGEXP_NAMEDRANGE = '((([^\s,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?([_A-Z][_A-Z0-9\.]*)'; + const CALCULATION_REGEXP_CELLREF = '((([^\s,!&%^\/\*\+<>=:`-]*)|(\'(?:[^\']|\'[^!])+?\')|(\"(?:[^\"]|\"[^!])+?\"))!)?\$?\b([a-z]{1,3})\$?(\d{1,7})(?![\w.])'; + // Cell reference (with or without a sheet reference) ensuring absolute/relative + const CALCULATION_REGEXP_CELLREF_RELATIVE = '((([^\s\(,!&%^\/\*\+<>=:`-]*)|(\'(?:[^\']|\'[^!])+?\')|(\"(?:[^\"]|\"[^!])+?\"))!)?(\$?\b[a-z]{1,3})(\$?\d{1,7})(?![\w.])'; + const CALCULATION_REGEXP_COLUMN_RANGE = '(((([^\s\(,!&%^\/\*\+<>=:`-]*)|(\'(?:[^\']|\'[^!])+?\')|(\".(?:[^\"]|\"[^!])?\"))!)?(\$?[a-z]{1,3})):(?![.*])'; + const CALCULATION_REGEXP_ROW_RANGE = '(((([^\s\(,!&%^\/\*\+<>=:`-]*)|(\'(?:[^\']|\'[^!])+?\')|(\"(?:[^\"]|\"[^!])+?\"))!)?(\$?[1-9][0-9]{0,6})):(?![.*])'; + // Cell reference (with or without a sheet reference) ensuring absolute/relative + // Cell ranges ensuring absolute/relative + const CALCULATION_REGEXP_COLUMNRANGE_RELATIVE = '(\$?[a-z]{1,3}):(\$?[a-z]{1,3})'; + const CALCULATION_REGEXP_ROWRANGE_RELATIVE = '(\$?\d{1,7}):(\$?\d{1,7})'; + // Defined Names: Named Range of cells, or Named Formulae + const CALCULATION_REGEXP_DEFINEDNAME = '((([^\s,!&%^\/\*\+<>=-]*)|(\'(?:[^\']|\'[^!])+?\')|(\"(?:[^\"]|\"[^!])+?\"))!)?([_\p{L}][_\p{L}\p{N}\.]*)'; + // Structured Reference (Fully Qualified and Unqualified) + const CALCULATION_REGEXP_STRUCTURED_REFERENCE = '([\p{L}_\\\\][\p{L}\p{N}\._]+)?(\[(?:[^\d\]+-])?)'; // Error const CALCULATION_REGEXP_ERROR = '\#[A-Z][A-Z0_\/]*[!\?]?'; @@ -36,19 +54,26 @@ class Calculation const RETURN_ARRAY_AS_VALUE = 'value'; const RETURN_ARRAY_AS_ARRAY = 'array'; + const FORMULA_OPEN_FUNCTION_BRACE = '('; + const FORMULA_CLOSE_FUNCTION_BRACE = ')'; + const FORMULA_OPEN_MATRIX_BRACE = '{'; + const FORMULA_CLOSE_MATRIX_BRACE = '}'; + const FORMULA_STRING_QUOTE = '"'; + + /** @var string */ private static $returnArrayAsType = self::RETURN_ARRAY_AS_VALUE; /** * Instance of this class. * - * @var Calculation + * @var ?Calculation */ private static $instance; /** * Instance of the spreadsheet this Calculation Engine is using. * - * @var Spreadsheet + * @var ?Spreadsheet */ private $spreadsheet; @@ -67,37 +92,35 @@ class Calculation private $calculationCacheEnabled = true; /** - * Used to generate unique store keys. - * - * @var int + * @var BranchPruner */ - private $branchStoreKeyCounter = 0; + private $branchPruner; + /** + * @var bool + */ private $branchPruningEnabled = true; /** * List of operators that can be used within formulae * The true/false value indicates whether it is a binary operator or a unary operator. - * - * @var array */ - private static $operators = [ + private const CALCULATION_OPERATORS = [ '+' => true, '-' => true, '*' => true, '/' => true, '^' => true, '&' => true, '%' => false, '~' => false, '>' => true, '<' => true, '=' => true, '>=' => true, - '<=' => true, '<>' => true, '|' => true, ':' => true, + '<=' => true, '<>' => true, '∩' => true, '∪' => true, + ':' => true, ]; /** * List of binary operators (those that expect two operands). - * - * @var array */ - private static $binaryOperators = [ + private const BINARY_OPERATORS = [ '+' => true, '-' => true, '*' => true, '/' => true, '^' => true, '&' => true, '>' => true, '<' => true, '=' => true, '>=' => true, '<=' => true, '<>' => true, - '|' => true, ':' => true, + '∩' => true, '∪' => true, ':' => true, ]; /** @@ -112,17 +135,29 @@ class Calculation * If true, then a user error will be triggered * If false, then an exception will be thrown. * - * @var bool + * @var ?bool + * + * @deprecated 1.25.2 use setSuppressFormulaErrors() instead */ - public $suppressFormulaErrors = false; + public $suppressFormulaErrors; + + /** @var bool */ + private $suppressFormulaErrorsNew = false; /** * Error message for any error that was raised/thrown by the calculation engine. * - * @var string + * @var null|string */ public $formulaError; + /** + * Reference Helper. + * + * @var ReferenceHelper + */ + private static $referenceHelper; + /** * An array of the nested cell references accessed by the calculation engine, used for the debug log. * @@ -130,6 +165,7 @@ class Calculation */ private $cyclicReferenceStack; + /** @var array */ private $cellStack = []; /** @@ -141,6 +177,7 @@ class Calculation */ private $cyclicFormulaCounter = 1; + /** @var string */ private $cyclicFormulaCell = ''; /** @@ -150,13 +187,6 @@ class Calculation */ public $cyclicFormulaCount = 1; - /** - * Epsilon Precision used for comparisons in calculations. - * - * @var float - */ - private $delta = 0.1e-12; - /** * The current locale setting. * @@ -181,24 +211,30 @@ class Calculation */ private static $localeArgumentSeparator = ','; + /** @var array */ private static $localeFunctions = []; /** * Locale-specific translations for Excel constants (True, False and Null). * - * @var string[] + * @var array */ - public static $localeBoolean = [ + private static $localeBoolean = [ 'TRUE' => 'TRUE', 'FALSE' => 'FALSE', 'NULL' => 'NULL', ]; + public static function getLocaleBoolean(string $index): string + { + return self::$localeBoolean[$index]; + } + /** * Excel constant string translations to their PHP equivalents * Constant conversion from text name/value to actual (datatyped) value. * - * @var string[] + * @var array */ private static $excelConstants = [ 'TRUE' => true, @@ -206,68 +242,105 @@ class Calculation 'NULL' => null, ]; - // PhpSpreadsheet functions + public static function keyInExcelConstants(string $key): bool + { + return array_key_exists($key, self::$excelConstants); + } + + /** @return mixed */ + public static function getExcelConstants(string $key) + { + return self::$excelConstants[$key]; + } + + /** + * Array of functions usable on Spreadsheet. + * In theory, this could be const rather than static; + * however, Phpstan breaks trying to analyze it when attempted. + * + *@var array + */ private static $phpSpreadsheetFunctions = [ 'ABS' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'abs', + 'functionCall' => [MathTrig\Absolute::class, 'evaluate'], 'argumentCount' => '1', ], 'ACCRINT' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'ACCRINT'], - 'argumentCount' => '4-7', + 'functionCall' => [Financial\Securities\AccruedInterest::class, 'periodic'], + 'argumentCount' => '4-8', ], 'ACCRINTM' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'ACCRINTM'], + 'functionCall' => [Financial\Securities\AccruedInterest::class, 'atMaturity'], 'argumentCount' => '3-5', ], 'ACOS' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'acos', + 'functionCall' => [MathTrig\Trig\Cosine::class, 'acos'], 'argumentCount' => '1', ], 'ACOSH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'acosh', + 'functionCall' => [MathTrig\Trig\Cosine::class, 'acosh'], 'argumentCount' => '1', ], 'ACOT' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'ACOT'], + 'functionCall' => [MathTrig\Trig\Cotangent::class, 'acot'], 'argumentCount' => '1', ], 'ACOTH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'ACOTH'], + 'functionCall' => [MathTrig\Trig\Cotangent::class, 'acoth'], 'argumentCount' => '1', ], 'ADDRESS' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, - 'functionCall' => [LookupRef::class, 'cellAddress'], + 'functionCall' => [LookupRef\Address::class, 'cell'], 'argumentCount' => '2-5', ], + 'AGGREGATE' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '3+', + ], 'AMORDEGRC' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'AMORDEGRC'], + 'functionCall' => [Financial\Amortization::class, 'AMORDEGRC'], 'argumentCount' => '6,7', ], 'AMORLINC' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'AMORLINC'], + 'functionCall' => [Financial\Amortization::class, 'AMORLINC'], 'argumentCount' => '6,7', ], + 'ANCHORARRAY' => [ + 'category' => Category::CATEGORY_UNCATEGORISED, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '*', + ], 'AND' => [ 'category' => Category::CATEGORY_LOGICAL, - 'functionCall' => [Logical::class, 'logicalAnd'], + 'functionCall' => [Logical\Operations::class, 'logicalAnd'], 'argumentCount' => '1+', ], + 'ARABIC' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Arabic::class, 'evaluate'], + 'argumentCount' => '1', + ], 'AREAS' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '1', ], + 'ARRAYTOTEXT' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Text::class, 'fromArray'], + 'argumentCount' => '1,2', + ], 'ASC' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [Functions::class, 'DUMMY'], @@ -275,52 +348,52 @@ class Calculation ], 'ASIN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'asin', + 'functionCall' => [MathTrig\Trig\Sine::class, 'asin'], 'argumentCount' => '1', ], 'ASINH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'asinh', + 'functionCall' => [MathTrig\Trig\Sine::class, 'asinh'], 'argumentCount' => '1', ], 'ATAN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'atan', + 'functionCall' => [MathTrig\Trig\Tangent::class, 'atan'], 'argumentCount' => '1', ], 'ATAN2' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'ATAN2'], + 'functionCall' => [MathTrig\Trig\Tangent::class, 'atan2'], 'argumentCount' => '2', ], 'ATANH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'atanh', + 'functionCall' => [MathTrig\Trig\Tangent::class, 'atanh'], 'argumentCount' => '1', ], 'AVEDEV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'AVEDEV'], + 'functionCall' => [Statistical\Averages::class, 'averageDeviations'], 'argumentCount' => '1+', ], 'AVERAGE' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'AVERAGE'], + 'functionCall' => [Statistical\Averages::class, 'average'], 'argumentCount' => '1+', ], 'AVERAGEA' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'AVERAGEA'], + 'functionCall' => [Statistical\Averages::class, 'averageA'], 'argumentCount' => '1+', ], 'AVERAGEIF' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'AVERAGEIF'], + 'functionCall' => [Statistical\Conditional::class, 'AVERAGEIF'], 'argumentCount' => '2,3', ], 'AVERAGEIFS' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Functions::class, 'DUMMY'], + 'functionCall' => [Statistical\Conditional::class, 'AVERAGEIFS'], 'argumentCount' => '3+', ], 'BAHTTEXT' => [ @@ -328,85 +401,135 @@ class Calculation 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '1', ], + 'BASE' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Base::class, 'evaluate'], + 'argumentCount' => '2,3', + ], 'BESSELI' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'BESSELI'], + 'functionCall' => [Engineering\BesselI::class, 'BESSELI'], 'argumentCount' => '2', ], 'BESSELJ' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'BESSELJ'], + 'functionCall' => [Engineering\BesselJ::class, 'BESSELJ'], 'argumentCount' => '2', ], 'BESSELK' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'BESSELK'], + 'functionCall' => [Engineering\BesselK::class, 'BESSELK'], 'argumentCount' => '2', ], 'BESSELY' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'BESSELY'], + 'functionCall' => [Engineering\BesselY::class, 'BESSELY'], 'argumentCount' => '2', ], 'BETADIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'BETADIST'], + 'functionCall' => [Statistical\Distributions\Beta::class, 'distribution'], 'argumentCount' => '3-5', ], + 'BETA.DIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '4-6', + ], 'BETAINV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'BETAINV'], + 'functionCall' => [Statistical\Distributions\Beta::class, 'inverse'], + 'argumentCount' => '3-5', + ], + 'BETA.INV' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Beta::class, 'inverse'], 'argumentCount' => '3-5', ], 'BIN2DEC' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'BINTODEC'], + 'functionCall' => [Engineering\ConvertBinary::class, 'toDecimal'], 'argumentCount' => '1', ], 'BIN2HEX' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'BINTOHEX'], + 'functionCall' => [Engineering\ConvertBinary::class, 'toHex'], 'argumentCount' => '1,2', ], 'BIN2OCT' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'BINTOOCT'], + 'functionCall' => [Engineering\ConvertBinary::class, 'toOctal'], 'argumentCount' => '1,2', ], 'BINOMDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'BINOMDIST'], + 'functionCall' => [Statistical\Distributions\Binomial::class, 'distribution'], 'argumentCount' => '4', ], + 'BINOM.DIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Binomial::class, 'distribution'], + 'argumentCount' => '4', + ], + 'BINOM.DIST.RANGE' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Binomial::class, 'range'], + 'argumentCount' => '3,4', + ], + 'BINOM.INV' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Binomial::class, 'inverse'], + 'argumentCount' => '3', + ], 'BITAND' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'BITAND'], + 'functionCall' => [Engineering\BitWise::class, 'BITAND'], 'argumentCount' => '2', ], 'BITOR' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'BITOR'], + 'functionCall' => [Engineering\BitWise::class, 'BITOR'], 'argumentCount' => '2', ], 'BITXOR' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'BITOR'], + 'functionCall' => [Engineering\BitWise::class, 'BITXOR'], 'argumentCount' => '2', ], 'BITLSHIFT' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'BITLSHIFT'], + 'functionCall' => [Engineering\BitWise::class, 'BITLSHIFT'], 'argumentCount' => '2', ], 'BITRSHIFT' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'BITRSHIFT'], + 'functionCall' => [Engineering\BitWise::class, 'BITRSHIFT'], 'argumentCount' => '2', ], + 'BYCOL' => [ + 'category' => Category::CATEGORY_LOGICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '*', + ], + 'BYROW' => [ + 'category' => Category::CATEGORY_LOGICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '*', + ], 'CEILING' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'CEILING'], - 'argumentCount' => '2', + 'functionCall' => [MathTrig\Ceiling::class, 'ceiling'], + 'argumentCount' => '1-2', // 2 for Excel, 1-2 for Ods/Gnumeric + ], + 'CEILING.MATH' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Ceiling::class, 'math'], + 'argumentCount' => '1-3', + ], + 'CEILING.PRECISE' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Ceiling::class, 'precise'], + 'argumentCount' => '1,2', ], 'CELL' => [ 'category' => Category::CATEGORY_INFORMATION, @@ -415,178 +538,239 @@ class Calculation ], 'CHAR' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'CHARACTER'], + 'functionCall' => [TextData\CharacterConvert::class, 'character'], 'argumentCount' => '1', ], 'CHIDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'CHIDIST'], + 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'distributionRightTail'], + 'argumentCount' => '2', + ], + 'CHISQ.DIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'distributionLeftTail'], + 'argumentCount' => '3', + ], + 'CHISQ.DIST.RT' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'distributionRightTail'], 'argumentCount' => '2', ], 'CHIINV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'CHIINV'], + 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'inverseRightTail'], + 'argumentCount' => '2', + ], + 'CHISQ.INV' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'inverseLeftTail'], + 'argumentCount' => '2', + ], + 'CHISQ.INV.RT' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'inverseRightTail'], 'argumentCount' => '2', ], 'CHITEST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Functions::class, 'DUMMY'], + 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'test'], + 'argumentCount' => '2', + ], + 'CHISQ.TEST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'test'], 'argumentCount' => '2', ], 'CHOOSE' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, - 'functionCall' => [LookupRef::class, 'CHOOSE'], + 'functionCall' => [LookupRef\Selection::class, 'CHOOSE'], + 'argumentCount' => '2+', + ], + 'CHOOSECOLS' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2+', + ], + 'CHOOSEROWS' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '2+', ], 'CLEAN' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'TRIMNONPRINTABLE'], + 'functionCall' => [TextData\Trim::class, 'nonPrintable'], 'argumentCount' => '1', ], 'CODE' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'ASCIICODE'], + 'functionCall' => [TextData\CharacterConvert::class, 'code'], 'argumentCount' => '1', ], 'COLUMN' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, - 'functionCall' => [LookupRef::class, 'COLUMN'], + 'functionCall' => [LookupRef\RowColumnInformation::class, 'COLUMN'], 'argumentCount' => '-1', + 'passCellReference' => true, 'passByReference' => [true], ], 'COLUMNS' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, - 'functionCall' => [LookupRef::class, 'COLUMNS'], + 'functionCall' => [LookupRef\RowColumnInformation::class, 'COLUMNS'], 'argumentCount' => '1', ], 'COMBIN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'COMBIN'], + 'functionCall' => [MathTrig\Combinations::class, 'withoutRepetition'], + 'argumentCount' => '2', + ], + 'COMBINA' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Combinations::class, 'withRepetition'], 'argumentCount' => '2', ], 'COMPLEX' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'COMPLEX'], + 'functionCall' => [Engineering\Complex::class, 'COMPLEX'], 'argumentCount' => '2,3', ], 'CONCAT' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'CONCATENATE'], + 'functionCall' => [TextData\Concatenate::class, 'CONCATENATE'], 'argumentCount' => '1+', ], 'CONCATENATE' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'CONCATENATE'], + 'functionCall' => [TextData\Concatenate::class, 'CONCATENATE'], 'argumentCount' => '1+', ], 'CONFIDENCE' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'CONFIDENCE'], + 'functionCall' => [Statistical\Confidence::class, 'CONFIDENCE'], + 'argumentCount' => '3', + ], + 'CONFIDENCE.NORM' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Confidence::class, 'CONFIDENCE'], + 'argumentCount' => '3', + ], + 'CONFIDENCE.T' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '3', ], 'CONVERT' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'CONVERTUOM'], + 'functionCall' => [Engineering\ConvertUOM::class, 'CONVERT'], 'argumentCount' => '3', ], 'CORREL' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'CORREL'], + 'functionCall' => [Statistical\Trends::class, 'CORREL'], 'argumentCount' => '2', ], 'COS' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'cos', + 'functionCall' => [MathTrig\Trig\Cosine::class, 'cos'], 'argumentCount' => '1', ], 'COSH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'cosh', + 'functionCall' => [MathTrig\Trig\Cosine::class, 'cosh'], 'argumentCount' => '1', ], 'COT' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'COT'], + 'functionCall' => [MathTrig\Trig\Cotangent::class, 'cot'], 'argumentCount' => '1', ], 'COTH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'COTH'], + 'functionCall' => [MathTrig\Trig\Cotangent::class, 'coth'], 'argumentCount' => '1', ], 'COUNT' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'COUNT'], + 'functionCall' => [Statistical\Counts::class, 'COUNT'], 'argumentCount' => '1+', ], 'COUNTA' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'COUNTA'], + 'functionCall' => [Statistical\Counts::class, 'COUNTA'], 'argumentCount' => '1+', ], 'COUNTBLANK' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'COUNTBLANK'], + 'functionCall' => [Statistical\Counts::class, 'COUNTBLANK'], 'argumentCount' => '1', ], 'COUNTIF' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'COUNTIF'], + 'functionCall' => [Statistical\Conditional::class, 'COUNTIF'], 'argumentCount' => '2', ], 'COUNTIFS' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'COUNTIFS'], + 'functionCall' => [Statistical\Conditional::class, 'COUNTIFS'], 'argumentCount' => '2+', ], 'COUPDAYBS' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'COUPDAYBS'], + 'functionCall' => [Financial\Coupons::class, 'COUPDAYBS'], 'argumentCount' => '3,4', ], 'COUPDAYS' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'COUPDAYS'], + 'functionCall' => [Financial\Coupons::class, 'COUPDAYS'], 'argumentCount' => '3,4', ], 'COUPDAYSNC' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'COUPDAYSNC'], + 'functionCall' => [Financial\Coupons::class, 'COUPDAYSNC'], 'argumentCount' => '3,4', ], 'COUPNCD' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'COUPNCD'], + 'functionCall' => [Financial\Coupons::class, 'COUPNCD'], 'argumentCount' => '3,4', ], 'COUPNUM' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'COUPNUM'], + 'functionCall' => [Financial\Coupons::class, 'COUPNUM'], 'argumentCount' => '3,4', ], 'COUPPCD' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'COUPPCD'], + 'functionCall' => [Financial\Coupons::class, 'COUPPCD'], 'argumentCount' => '3,4', ], 'COVAR' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'COVAR'], + 'functionCall' => [Statistical\Trends::class, 'COVAR'], + 'argumentCount' => '2', + ], + 'COVARIANCE.P' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Trends::class, 'COVAR'], + 'argumentCount' => '2', + ], + 'COVARIANCE.S' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '2', ], 'CRITBINOM' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'CRITBINOM'], + 'functionCall' => [Statistical\Distributions\Binomial::class, 'inverse'], 'argumentCount' => '3', ], 'CSC' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'CSC'], + 'functionCall' => [MathTrig\Trig\Cosecant::class, 'csc'], 'argumentCount' => '1', ], 'CSCH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'CSCH'], + 'functionCall' => [MathTrig\Trig\Cosecant::class, 'csch'], 'argumentCount' => '1', ], 'CUBEKPIMEMBER' => [ @@ -626,152 +810,172 @@ class Calculation ], 'CUMIPMT' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'CUMIPMT'], + 'functionCall' => [Financial\CashFlow\Constant\Periodic\Cumulative::class, 'interest'], 'argumentCount' => '6', ], 'CUMPRINC' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'CUMPRINC'], + 'functionCall' => [Financial\CashFlow\Constant\Periodic\Cumulative::class, 'principal'], 'argumentCount' => '6', ], 'DATE' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'DATE'], + 'functionCall' => [DateTimeExcel\Date::class, 'fromYMD'], 'argumentCount' => '3', ], 'DATEDIF' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'DATEDIF'], + 'functionCall' => [DateTimeExcel\Difference::class, 'interval'], 'argumentCount' => '2,3', ], + 'DATESTRING' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], 'DATEVALUE' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'DATEVALUE'], + 'functionCall' => [DateTimeExcel\DateValue::class, 'fromString'], 'argumentCount' => '1', ], 'DAVERAGE' => [ 'category' => Category::CATEGORY_DATABASE, - 'functionCall' => [Database::class, 'DAVERAGE'], + 'functionCall' => [Database\DAverage::class, 'evaluate'], 'argumentCount' => '3', ], 'DAY' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'DAYOFMONTH'], + 'functionCall' => [DateTimeExcel\DateParts::class, 'day'], 'argumentCount' => '1', ], 'DAYS' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'DAYS'], + 'functionCall' => [DateTimeExcel\Days::class, 'between'], 'argumentCount' => '2', ], 'DAYS360' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'DAYS360'], + 'functionCall' => [DateTimeExcel\Days360::class, 'between'], 'argumentCount' => '2,3', ], 'DB' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'DB'], + 'functionCall' => [Financial\Depreciation::class, 'DB'], 'argumentCount' => '4,5', ], + 'DBCS' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '1', + ], 'DCOUNT' => [ 'category' => Category::CATEGORY_DATABASE, - 'functionCall' => [Database::class, 'DCOUNT'], + 'functionCall' => [Database\DCount::class, 'evaluate'], 'argumentCount' => '3', ], 'DCOUNTA' => [ 'category' => Category::CATEGORY_DATABASE, - 'functionCall' => [Database::class, 'DCOUNTA'], + 'functionCall' => [Database\DCountA::class, 'evaluate'], 'argumentCount' => '3', ], 'DDB' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'DDB'], + 'functionCall' => [Financial\Depreciation::class, 'DDB'], 'argumentCount' => '4,5', ], 'DEC2BIN' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'DECTOBIN'], + 'functionCall' => [Engineering\ConvertDecimal::class, 'toBinary'], 'argumentCount' => '1,2', ], 'DEC2HEX' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'DECTOHEX'], + 'functionCall' => [Engineering\ConvertDecimal::class, 'toHex'], 'argumentCount' => '1,2', ], 'DEC2OCT' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'DECTOOCT'], + 'functionCall' => [Engineering\ConvertDecimal::class, 'toOctal'], 'argumentCount' => '1,2', ], + 'DECIMAL' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2', + ], 'DEGREES' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'rad2deg', + 'functionCall' => [MathTrig\Angle::class, 'toDegrees'], 'argumentCount' => '1', ], 'DELTA' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'DELTA'], + 'functionCall' => [Engineering\Compare::class, 'DELTA'], 'argumentCount' => '1,2', ], 'DEVSQ' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'DEVSQ'], + 'functionCall' => [Statistical\Deviations::class, 'sumSquares'], 'argumentCount' => '1+', ], 'DGET' => [ 'category' => Category::CATEGORY_DATABASE, - 'functionCall' => [Database::class, 'DGET'], + 'functionCall' => [Database\DGet::class, 'evaluate'], 'argumentCount' => '3', ], 'DISC' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'DISC'], + 'functionCall' => [Financial\Securities\Rates::class, 'discount'], 'argumentCount' => '4,5', ], 'DMAX' => [ 'category' => Category::CATEGORY_DATABASE, - 'functionCall' => [Database::class, 'DMAX'], + 'functionCall' => [Database\DMax::class, 'evaluate'], 'argumentCount' => '3', ], 'DMIN' => [ 'category' => Category::CATEGORY_DATABASE, - 'functionCall' => [Database::class, 'DMIN'], + 'functionCall' => [Database\DMin::class, 'evaluate'], 'argumentCount' => '3', ], 'DOLLAR' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'DOLLAR'], + 'functionCall' => [TextData\Format::class, 'DOLLAR'], 'argumentCount' => '1,2', ], 'DOLLARDE' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'DOLLARDE'], + 'functionCall' => [Financial\Dollar::class, 'decimal'], 'argumentCount' => '2', ], 'DOLLARFR' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'DOLLARFR'], + 'functionCall' => [Financial\Dollar::class, 'fractional'], 'argumentCount' => '2', ], 'DPRODUCT' => [ 'category' => Category::CATEGORY_DATABASE, - 'functionCall' => [Database::class, 'DPRODUCT'], + 'functionCall' => [Database\DProduct::class, 'evaluate'], 'argumentCount' => '3', ], + 'DROP' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2-3', + ], 'DSTDEV' => [ 'category' => Category::CATEGORY_DATABASE, - 'functionCall' => [Database::class, 'DSTDEV'], + 'functionCall' => [Database\DStDev::class, 'evaluate'], 'argumentCount' => '3', ], 'DSTDEVP' => [ 'category' => Category::CATEGORY_DATABASE, - 'functionCall' => [Database::class, 'DSTDEVP'], + 'functionCall' => [Database\DStDevP::class, 'evaluate'], 'argumentCount' => '3', ], 'DSUM' => [ 'category' => Category::CATEGORY_DATABASE, - 'functionCall' => [Database::class, 'DSUM'], + 'functionCall' => [Database\DSum::class, 'evaluate'], 'argumentCount' => '3', ], 'DURATION' => [ @@ -781,87 +985,107 @@ class Calculation ], 'DVAR' => [ 'category' => Category::CATEGORY_DATABASE, - 'functionCall' => [Database::class, 'DVAR'], + 'functionCall' => [Database\DVar::class, 'evaluate'], 'argumentCount' => '3', ], 'DVARP' => [ 'category' => Category::CATEGORY_DATABASE, - 'functionCall' => [Database::class, 'DVARP'], + 'functionCall' => [Database\DVarP::class, 'evaluate'], 'argumentCount' => '3', ], + 'ECMA.CEILING' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '1,2', + ], 'EDATE' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'EDATE'], + 'functionCall' => [DateTimeExcel\Month::class, 'adjust'], 'argumentCount' => '2', ], 'EFFECT' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'EFFECT'], + 'functionCall' => [Financial\InterestRate::class, 'effective'], 'argumentCount' => '2', ], + 'ENCODEURL' => [ + 'category' => Category::CATEGORY_WEB, + 'functionCall' => [Web\Service::class, 'urlEncode'], + 'argumentCount' => '1', + ], 'EOMONTH' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'EOMONTH'], + 'functionCall' => [DateTimeExcel\Month::class, 'lastDay'], 'argumentCount' => '2', ], 'ERF' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'ERF'], + 'functionCall' => [Engineering\Erf::class, 'ERF'], 'argumentCount' => '1,2', ], 'ERF.PRECISE' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'ERFPRECISE'], + 'functionCall' => [Engineering\Erf::class, 'ERFPRECISE'], 'argumentCount' => '1', ], 'ERFC' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'ERFC'], + 'functionCall' => [Engineering\ErfC::class, 'ERFC'], 'argumentCount' => '1', ], 'ERFC.PRECISE' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'ERFC'], + 'functionCall' => [Engineering\ErfC::class, 'ERFC'], 'argumentCount' => '1', ], 'ERROR.TYPE' => [ 'category' => Category::CATEGORY_INFORMATION, - 'functionCall' => [Functions::class, 'errorType'], + 'functionCall' => [Information\ExcelError::class, 'type'], 'argumentCount' => '1', ], 'EVEN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'EVEN'], + 'functionCall' => [MathTrig\Round::class, 'even'], 'argumentCount' => '1', ], 'EXACT' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'EXACT'], + 'functionCall' => [TextData\Text::class, 'exact'], 'argumentCount' => '2', ], 'EXP' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'exp', + 'functionCall' => [MathTrig\Exp::class, 'evaluate'], 'argumentCount' => '1', ], + 'EXPAND' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2-4', + ], 'EXPONDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'EXPONDIST'], + 'functionCall' => [Statistical\Distributions\Exponential::class, 'distribution'], + 'argumentCount' => '3', + ], + 'EXPON.DIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Exponential::class, 'distribution'], 'argumentCount' => '3', ], 'FACT' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'FACT'], + 'functionCall' => [MathTrig\Factorial::class, 'fact'], 'argumentCount' => '1', ], 'FACTDOUBLE' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'FACTDOUBLE'], + 'functionCall' => [MathTrig\Factorial::class, 'factDouble'], 'argumentCount' => '1', ], 'FALSE' => [ 'category' => Category::CATEGORY_LOGICAL, - 'functionCall' => [Logical::class, 'FALSE'], + 'functionCall' => [Logical\Boolean::class, 'FALSE'], 'argumentCount' => '0', ], 'FDIST' => [ @@ -869,14 +1093,34 @@ class Calculation 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '3', ], + 'F.DIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\F::class, 'distribution'], + 'argumentCount' => '4', + ], + 'F.DIST.RT' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '3', + ], + 'FILTER' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [LookupRef\Filter::class, 'filter'], + 'argumentCount' => '2-3', + ], + 'FILTERXML' => [ + 'category' => Category::CATEGORY_WEB, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2', + ], 'FIND' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'SEARCHSENSITIVE'], + 'functionCall' => [TextData\Search::class, 'sensitive'], 'argumentCount' => '2,3', ], 'FINDB' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'SEARCHSENSITIVE'], + 'functionCall' => [TextData\Search::class, 'sensitive'], 'argumentCount' => '2,3', ], 'FINV' => [ @@ -884,34 +1128,79 @@ class Calculation 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '3', ], + 'F.INV' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '3', + ], + 'F.INV.RT' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '3', + ], 'FISHER' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'FISHER'], + 'functionCall' => [Statistical\Distributions\Fisher::class, 'distribution'], 'argumentCount' => '1', ], 'FISHERINV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'FISHERINV'], + 'functionCall' => [Statistical\Distributions\Fisher::class, 'inverse'], 'argumentCount' => '1', ], 'FIXED' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'FIXEDFORMAT'], + 'functionCall' => [TextData\Format::class, 'FIXEDFORMAT'], 'argumentCount' => '1-3', ], 'FLOOR' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'FLOOR'], - 'argumentCount' => '2', + 'functionCall' => [MathTrig\Floor::class, 'floor'], + 'argumentCount' => '1-2', // Excel requries 2, Ods/Gnumeric 1-2 + ], + 'FLOOR.MATH' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Floor::class, 'math'], + 'argumentCount' => '1-3', + ], + 'FLOOR.PRECISE' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Floor::class, 'precise'], + 'argumentCount' => '1-2', ], 'FORECAST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'FORECAST'], + 'functionCall' => [Statistical\Trends::class, 'FORECAST'], + 'argumentCount' => '3', + ], + 'FORECAST.ETS' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '3-6', + ], + 'FORECAST.ETS.CONFINT' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '3-6', + ], + 'FORECAST.ETS.SEASONALITY' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2-4', + ], + 'FORECAST.ETS.STAT' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '3-6', + ], + 'FORECAST.LINEAR' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Trends::class, 'FORECAST'], 'argumentCount' => '3', ], 'FORMULATEXT' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, - 'functionCall' => [LookupRef::class, 'FORMULATEXT'], + 'functionCall' => [LookupRef\Formula::class, 'text'], 'argumentCount' => '1', 'passCellReference' => true, 'passByReference' => [true], @@ -926,44 +1215,74 @@ class Calculation 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '2', ], + 'F.TEST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2', + ], 'FV' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'FV'], + 'functionCall' => [Financial\CashFlow\Constant\Periodic::class, 'futureValue'], 'argumentCount' => '3-5', ], 'FVSCHEDULE' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'FVSCHEDULE'], + 'functionCall' => [Financial\CashFlow\Single::class, 'futureValue'], 'argumentCount' => '2', ], + 'GAMMA' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Gamma::class, 'gamma'], + 'argumentCount' => '1', + ], 'GAMMADIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'GAMMADIST'], + 'functionCall' => [Statistical\Distributions\Gamma::class, 'distribution'], + 'argumentCount' => '4', + ], + 'GAMMA.DIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Gamma::class, 'distribution'], 'argumentCount' => '4', ], 'GAMMAINV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'GAMMAINV'], + 'functionCall' => [Statistical\Distributions\Gamma::class, 'inverse'], + 'argumentCount' => '3', + ], + 'GAMMA.INV' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Gamma::class, 'inverse'], 'argumentCount' => '3', ], 'GAMMALN' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'GAMMALN'], + 'functionCall' => [Statistical\Distributions\Gamma::class, 'ln'], + 'argumentCount' => '1', + ], + 'GAMMALN.PRECISE' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Gamma::class, 'ln'], + 'argumentCount' => '1', + ], + 'GAUSS' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'gauss'], 'argumentCount' => '1', ], 'GCD' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'GCD'], + 'functionCall' => [MathTrig\Gcd::class, 'evaluate'], 'argumentCount' => '1+', ], 'GEOMEAN' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'GEOMEAN'], + 'functionCall' => [Statistical\Averages\Mean::class, 'geometric'], 'argumentCount' => '1+', ], 'GESTEP' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'GESTEP'], + 'functionCall' => [Engineering\Compare::class, 'GESTEP'], 'argumentCount' => '1,2', ], 'GETPIVOTDATA' => [ @@ -973,198 +1292,213 @@ class Calculation ], 'GROWTH' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'GROWTH'], + 'functionCall' => [Statistical\Trends::class, 'GROWTH'], 'argumentCount' => '1-4', ], 'HARMEAN' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'HARMEAN'], + 'functionCall' => [Statistical\Averages\Mean::class, 'harmonic'], 'argumentCount' => '1+', ], 'HEX2BIN' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'HEXTOBIN'], + 'functionCall' => [Engineering\ConvertHex::class, 'toBinary'], 'argumentCount' => '1,2', ], 'HEX2DEC' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'HEXTODEC'], + 'functionCall' => [Engineering\ConvertHex::class, 'toDecimal'], 'argumentCount' => '1', ], 'HEX2OCT' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'HEXTOOCT'], + 'functionCall' => [Engineering\ConvertHex::class, 'toOctal'], 'argumentCount' => '1,2', ], 'HLOOKUP' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, - 'functionCall' => [LookupRef::class, 'HLOOKUP'], + 'functionCall' => [LookupRef\HLookup::class, 'lookup'], 'argumentCount' => '3,4', ], 'HOUR' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'HOUROFDAY'], + 'functionCall' => [DateTimeExcel\TimeParts::class, 'hour'], 'argumentCount' => '1', ], + 'HSTACK' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '1+', + ], 'HYPERLINK' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, - 'functionCall' => [LookupRef::class, 'HYPERLINK'], + 'functionCall' => [LookupRef\Hyperlink::class, 'set'], 'argumentCount' => '1,2', 'passCellReference' => true, ], 'HYPGEOMDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'HYPGEOMDIST'], + 'functionCall' => [Statistical\Distributions\HyperGeometric::class, 'distribution'], 'argumentCount' => '4', ], + 'HYPGEOM.DIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '5', + ], 'IF' => [ 'category' => Category::CATEGORY_LOGICAL, - 'functionCall' => [Logical::class, 'statementIf'], + 'functionCall' => [Logical\Conditional::class, 'statementIf'], 'argumentCount' => '1-3', ], 'IFERROR' => [ 'category' => Category::CATEGORY_LOGICAL, - 'functionCall' => [Logical::class, 'IFERROR'], + 'functionCall' => [Logical\Conditional::class, 'IFERROR'], 'argumentCount' => '2', ], 'IFNA' => [ 'category' => Category::CATEGORY_LOGICAL, - 'functionCall' => [Logical::class, 'IFNA'], + 'functionCall' => [Logical\Conditional::class, 'IFNA'], 'argumentCount' => '2', ], + 'IFS' => [ + 'category' => Category::CATEGORY_LOGICAL, + 'functionCall' => [Logical\Conditional::class, 'IFS'], + 'argumentCount' => '2+', + ], 'IMABS' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMABS'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMABS'], 'argumentCount' => '1', ], 'IMAGINARY' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMAGINARY'], + 'functionCall' => [Engineering\Complex::class, 'IMAGINARY'], 'argumentCount' => '1', ], 'IMARGUMENT' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMARGUMENT'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMARGUMENT'], 'argumentCount' => '1', ], 'IMCONJUGATE' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMCONJUGATE'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMCONJUGATE'], 'argumentCount' => '1', ], 'IMCOS' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMCOS'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMCOS'], 'argumentCount' => '1', ], 'IMCOSH' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMCOSH'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMCOSH'], 'argumentCount' => '1', ], 'IMCOT' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMCOT'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMCOT'], 'argumentCount' => '1', ], 'IMCSC' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMCSC'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMCSC'], 'argumentCount' => '1', ], 'IMCSCH' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMCSCH'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMCSCH'], 'argumentCount' => '1', ], 'IMDIV' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMDIV'], + 'functionCall' => [Engineering\ComplexOperations::class, 'IMDIV'], 'argumentCount' => '2', ], 'IMEXP' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMEXP'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMEXP'], 'argumentCount' => '1', ], 'IMLN' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMLN'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMLN'], 'argumentCount' => '1', ], 'IMLOG10' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMLOG10'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMLOG10'], 'argumentCount' => '1', ], 'IMLOG2' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMLOG2'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMLOG2'], 'argumentCount' => '1', ], 'IMPOWER' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMPOWER'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMPOWER'], 'argumentCount' => '2', ], 'IMPRODUCT' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMPRODUCT'], + 'functionCall' => [Engineering\ComplexOperations::class, 'IMPRODUCT'], 'argumentCount' => '1+', ], 'IMREAL' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMREAL'], + 'functionCall' => [Engineering\Complex::class, 'IMREAL'], 'argumentCount' => '1', ], 'IMSEC' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMSEC'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMSEC'], 'argumentCount' => '1', ], 'IMSECH' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMSECH'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMSECH'], 'argumentCount' => '1', ], 'IMSIN' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMSIN'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMSIN'], 'argumentCount' => '1', ], 'IMSINH' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMSINH'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMSINH'], 'argumentCount' => '1', ], 'IMSQRT' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMSQRT'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMSQRT'], 'argumentCount' => '1', ], 'IMSUB' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMSUB'], + 'functionCall' => [Engineering\ComplexOperations::class, 'IMSUB'], 'argumentCount' => '2', ], 'IMSUM' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMSUM'], + 'functionCall' => [Engineering\ComplexOperations::class, 'IMSUM'], 'argumentCount' => '1+', ], 'IMTAN' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'IMTAN'], + 'functionCall' => [Engineering\ComplexFunctions::class, 'IMTAN'], 'argumentCount' => '1', ], 'INDEX' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, - 'functionCall' => [LookupRef::class, 'INDEX'], - 'argumentCount' => '1-4', + 'functionCall' => [LookupRef\Matrix::class, 'index'], + 'argumentCount' => '2-4', ], 'INDIRECT' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, - 'functionCall' => [LookupRef::class, 'INDIRECT'], + 'functionCall' => [LookupRef\Indirect::class, 'INDIRECT'], 'argumentCount' => '1,2', 'passCellReference' => true, ], @@ -1175,101 +1509,118 @@ class Calculation ], 'INT' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'INT'], + 'functionCall' => [MathTrig\IntClass::class, 'evaluate'], 'argumentCount' => '1', ], 'INTERCEPT' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'INTERCEPT'], + 'functionCall' => [Statistical\Trends::class, 'INTERCEPT'], 'argumentCount' => '2', ], 'INTRATE' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'INTRATE'], + 'functionCall' => [Financial\Securities\Rates::class, 'interest'], 'argumentCount' => '4,5', ], 'IPMT' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'IPMT'], + 'functionCall' => [Financial\CashFlow\Constant\Periodic\Interest::class, 'payment'], 'argumentCount' => '4-6', ], 'IRR' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'IRR'], + 'functionCall' => [Financial\CashFlow\Variable\Periodic::class, 'rate'], 'argumentCount' => '1,2', ], 'ISBLANK' => [ 'category' => Category::CATEGORY_INFORMATION, - 'functionCall' => [Functions::class, 'isBlank'], + 'functionCall' => [Information\Value::class, 'isBlank'], 'argumentCount' => '1', ], 'ISERR' => [ 'category' => Category::CATEGORY_INFORMATION, - 'functionCall' => [Functions::class, 'isErr'], + 'functionCall' => [Information\ErrorValue::class, 'isErr'], 'argumentCount' => '1', ], 'ISERROR' => [ 'category' => Category::CATEGORY_INFORMATION, - 'functionCall' => [Functions::class, 'isError'], + 'functionCall' => [Information\ErrorValue::class, 'isError'], 'argumentCount' => '1', ], 'ISEVEN' => [ 'category' => Category::CATEGORY_INFORMATION, - 'functionCall' => [Functions::class, 'isEven'], + 'functionCall' => [Information\Value::class, 'isEven'], 'argumentCount' => '1', ], 'ISFORMULA' => [ 'category' => Category::CATEGORY_INFORMATION, - 'functionCall' => [Functions::class, 'isFormula'], + 'functionCall' => [Information\Value::class, 'isFormula'], 'argumentCount' => '1', 'passCellReference' => true, 'passByReference' => [true], ], 'ISLOGICAL' => [ 'category' => Category::CATEGORY_INFORMATION, - 'functionCall' => [Functions::class, 'isLogical'], + 'functionCall' => [Information\Value::class, 'isLogical'], 'argumentCount' => '1', ], 'ISNA' => [ 'category' => Category::CATEGORY_INFORMATION, - 'functionCall' => [Functions::class, 'isNa'], + 'functionCall' => [Information\ErrorValue::class, 'isNa'], 'argumentCount' => '1', ], 'ISNONTEXT' => [ 'category' => Category::CATEGORY_INFORMATION, - 'functionCall' => [Functions::class, 'isNonText'], + 'functionCall' => [Information\Value::class, 'isNonText'], 'argumentCount' => '1', ], 'ISNUMBER' => [ 'category' => Category::CATEGORY_INFORMATION, - 'functionCall' => [Functions::class, 'isNumber'], + 'functionCall' => [Information\Value::class, 'isNumber'], 'argumentCount' => '1', ], + 'ISO.CEILING' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '1,2', + ], 'ISODD' => [ 'category' => Category::CATEGORY_INFORMATION, - 'functionCall' => [Functions::class, 'isOdd'], + 'functionCall' => [Information\Value::class, 'isOdd'], 'argumentCount' => '1', ], + 'ISOMITTED' => [ + 'category' => Category::CATEGORY_INFORMATION, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '*', + ], 'ISOWEEKNUM' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'ISOWEEKNUM'], + 'functionCall' => [DateTimeExcel\Week::class, 'isoWeekNumber'], 'argumentCount' => '1', ], 'ISPMT' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'ISPMT'], + 'functionCall' => [Financial\CashFlow\Constant\Periodic\Interest::class, 'schedulePayment'], 'argumentCount' => '4', ], 'ISREF' => [ 'category' => Category::CATEGORY_INFORMATION, - 'functionCall' => [Functions::class, 'DUMMY'], + 'functionCall' => [Information\Value::class, 'isRef'], 'argumentCount' => '1', + 'passCellReference' => true, + 'passByReference' => [true], ], 'ISTEXT' => [ 'category' => Category::CATEGORY_INFORMATION, - 'functionCall' => [Functions::class, 'isText'], + 'functionCall' => [Information\Value::class, 'isText'], 'argumentCount' => '1', ], + 'ISTHAIDIGIT' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], 'JIS' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, 'functionCall' => [Functions::class, 'DUMMY'], @@ -1277,107 +1628,137 @@ class Calculation ], 'KURT' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'KURT'], + 'functionCall' => [Statistical\Deviations::class, 'kurtosis'], 'argumentCount' => '1+', ], + 'LAMBDA' => [ + 'category' => Category::CATEGORY_LOGICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '*', + ], 'LARGE' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'LARGE'], + 'functionCall' => [Statistical\Size::class, 'large'], 'argumentCount' => '2', ], 'LCM' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'LCM'], + 'functionCall' => [MathTrig\Lcm::class, 'evaluate'], 'argumentCount' => '1+', ], 'LEFT' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'LEFT'], + 'functionCall' => [TextData\Extract::class, 'left'], 'argumentCount' => '1,2', ], 'LEFTB' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'LEFT'], + 'functionCall' => [TextData\Extract::class, 'left'], 'argumentCount' => '1,2', ], 'LEN' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'STRINGLENGTH'], + 'functionCall' => [TextData\Text::class, 'length'], 'argumentCount' => '1', ], 'LENB' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'STRINGLENGTH'], + 'functionCall' => [TextData\Text::class, 'length'], 'argumentCount' => '1', ], + 'LET' => [ + 'category' => Category::CATEGORY_LOGICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '*', + ], 'LINEST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'LINEST'], + 'functionCall' => [Statistical\Trends::class, 'LINEST'], 'argumentCount' => '1-4', ], 'LN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'log', + 'functionCall' => [MathTrig\Logarithms::class, 'natural'], 'argumentCount' => '1', ], 'LOG' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'logBase'], + 'functionCall' => [MathTrig\Logarithms::class, 'withBase'], 'argumentCount' => '1,2', ], 'LOG10' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'log10', + 'functionCall' => [MathTrig\Logarithms::class, 'base10'], 'argumentCount' => '1', ], 'LOGEST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'LOGEST'], + 'functionCall' => [Statistical\Trends::class, 'LOGEST'], 'argumentCount' => '1-4', ], 'LOGINV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'LOGINV'], + 'functionCall' => [Statistical\Distributions\LogNormal::class, 'inverse'], 'argumentCount' => '3', ], 'LOGNORMDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'LOGNORMDIST'], + 'functionCall' => [Statistical\Distributions\LogNormal::class, 'cumulative'], + 'argumentCount' => '3', + ], + 'LOGNORM.DIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\LogNormal::class, 'distribution'], + 'argumentCount' => '4', + ], + 'LOGNORM.INV' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\LogNormal::class, 'inverse'], 'argumentCount' => '3', ], 'LOOKUP' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, - 'functionCall' => [LookupRef::class, 'LOOKUP'], + 'functionCall' => [LookupRef\Lookup::class, 'lookup'], 'argumentCount' => '2,3', ], 'LOWER' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'LOWERCASE'], + 'functionCall' => [TextData\CaseConvert::class, 'lower'], 'argumentCount' => '1', ], + 'MAKEARRAY' => [ + 'category' => Category::CATEGORY_LOGICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '*', + ], + 'MAP' => [ + 'category' => Category::CATEGORY_LOGICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '*', + ], 'MATCH' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, - 'functionCall' => [LookupRef::class, 'MATCH'], + 'functionCall' => [LookupRef\ExcelMatch::class, 'MATCH'], 'argumentCount' => '2,3', ], 'MAX' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'MAX'], + 'functionCall' => [Statistical\Maximum::class, 'max'], 'argumentCount' => '1+', ], 'MAXA' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'MAXA'], + 'functionCall' => [Statistical\Maximum::class, 'maxA'], 'argumentCount' => '1+', ], 'MAXIFS' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'MAXIFS'], + 'functionCall' => [Statistical\Conditional::class, 'MAXIFS'], 'argumentCount' => '3+', ], 'MDETERM' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'MDETERM'], + 'functionCall' => [MathTrig\MatrixFunctions::class, 'determinant'], 'argumentCount' => '1', ], 'MDURATION' => [ @@ -1387,7 +1768,7 @@ class Calculation ], 'MEDIAN' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'MEDIAN'], + 'functionCall' => [Statistical\Averages::class, 'median'], 'argumentCount' => '1+', ], 'MEDIANIF' => [ @@ -1397,167 +1778,212 @@ class Calculation ], 'MID' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'MID'], + 'functionCall' => [TextData\Extract::class, 'mid'], 'argumentCount' => '3', ], 'MIDB' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'MID'], + 'functionCall' => [TextData\Extract::class, 'mid'], 'argumentCount' => '3', ], 'MIN' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'MIN'], + 'functionCall' => [Statistical\Minimum::class, 'min'], 'argumentCount' => '1+', ], 'MINA' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'MINA'], + 'functionCall' => [Statistical\Minimum::class, 'minA'], 'argumentCount' => '1+', ], 'MINIFS' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'MINIFS'], + 'functionCall' => [Statistical\Conditional::class, 'MINIFS'], 'argumentCount' => '3+', ], 'MINUTE' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'MINUTE'], + 'functionCall' => [DateTimeExcel\TimeParts::class, 'minute'], 'argumentCount' => '1', ], 'MINVERSE' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'MINVERSE'], + 'functionCall' => [MathTrig\MatrixFunctions::class, 'inverse'], 'argumentCount' => '1', ], 'MIRR' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'MIRR'], + 'functionCall' => [Financial\CashFlow\Variable\Periodic::class, 'modifiedRate'], 'argumentCount' => '3', ], 'MMULT' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'MMULT'], + 'functionCall' => [MathTrig\MatrixFunctions::class, 'multiply'], 'argumentCount' => '2', ], 'MOD' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'MOD'], + 'functionCall' => [MathTrig\Operations::class, 'mod'], 'argumentCount' => '2', ], 'MODE' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'MODE'], + 'functionCall' => [Statistical\Averages::class, 'mode'], + 'argumentCount' => '1+', + ], + 'MODE.MULT' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '1+', ], 'MODE.SNGL' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'MODE'], + 'functionCall' => [Statistical\Averages::class, 'mode'], 'argumentCount' => '1+', ], 'MONTH' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'MONTHOFYEAR'], + 'functionCall' => [DateTimeExcel\DateParts::class, 'month'], 'argumentCount' => '1', ], 'MROUND' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'MROUND'], + 'functionCall' => [MathTrig\Round::class, 'multiple'], 'argumentCount' => '2', ], 'MULTINOMIAL' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'MULTINOMIAL'], + 'functionCall' => [MathTrig\Factorial::class, 'multinomial'], 'argumentCount' => '1+', ], + 'MUNIT' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\MatrixFunctions::class, 'identity'], + 'argumentCount' => '1', + ], 'N' => [ 'category' => Category::CATEGORY_INFORMATION, - 'functionCall' => [Functions::class, 'n'], + 'functionCall' => [Information\Value::class, 'asNumber'], 'argumentCount' => '1', ], 'NA' => [ 'category' => Category::CATEGORY_INFORMATION, - 'functionCall' => [Functions::class, 'NA'], + 'functionCall' => [Information\ExcelError::class, 'NA'], 'argumentCount' => '0', ], 'NEGBINOMDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'NEGBINOMDIST'], + 'functionCall' => [Statistical\Distributions\Binomial::class, 'negative'], 'argumentCount' => '3', ], + 'NEGBINOM.DIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '4', + ], 'NETWORKDAYS' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'NETWORKDAYS'], - 'argumentCount' => '2+', + 'functionCall' => [DateTimeExcel\NetworkDays::class, 'count'], + 'argumentCount' => '2-3', + ], + 'NETWORKDAYS.INTL' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2-4', ], 'NOMINAL' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'NOMINAL'], + 'functionCall' => [Financial\InterestRate::class, 'nominal'], 'argumentCount' => '2', ], 'NORMDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'NORMDIST'], + 'functionCall' => [Statistical\Distributions\Normal::class, 'distribution'], + 'argumentCount' => '4', + ], + 'NORM.DIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Normal::class, 'distribution'], 'argumentCount' => '4', ], 'NORMINV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'NORMINV'], + 'functionCall' => [Statistical\Distributions\Normal::class, 'inverse'], + 'argumentCount' => '3', + ], + 'NORM.INV' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Normal::class, 'inverse'], 'argumentCount' => '3', ], 'NORMSDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'NORMSDIST'], + 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'cumulative'], 'argumentCount' => '1', ], + 'NORM.S.DIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'distribution'], + 'argumentCount' => '1,2', + ], 'NORMSINV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'NORMSINV'], + 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'inverse'], + 'argumentCount' => '1', + ], + 'NORM.S.INV' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'inverse'], 'argumentCount' => '1', ], 'NOT' => [ 'category' => Category::CATEGORY_LOGICAL, - 'functionCall' => [Logical::class, 'NOT'], + 'functionCall' => [Logical\Operations::class, 'NOT'], 'argumentCount' => '1', ], 'NOW' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'DATETIMENOW'], + 'functionCall' => [DateTimeExcel\Current::class, 'now'], 'argumentCount' => '0', ], 'NPER' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'NPER'], + 'functionCall' => [Financial\CashFlow\Constant\Periodic::class, 'periods'], 'argumentCount' => '3-5', ], 'NPV' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'NPV'], + 'functionCall' => [Financial\CashFlow\Variable\Periodic::class, 'presentValue'], 'argumentCount' => '2+', ], + 'NUMBERSTRING' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], 'NUMBERVALUE' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'NUMBERVALUE'], + 'functionCall' => [TextData\Format::class, 'NUMBERVALUE'], 'argumentCount' => '1+', ], 'OCT2BIN' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'OCTTOBIN'], + 'functionCall' => [Engineering\ConvertOctal::class, 'toBinary'], 'argumentCount' => '1,2', ], 'OCT2DEC' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'OCTTODEC'], + 'functionCall' => [Engineering\ConvertOctal::class, 'toDecimal'], 'argumentCount' => '1', ], 'OCT2HEX' => [ 'category' => Category::CATEGORY_ENGINEERING, - 'functionCall' => [Engineering::class, 'OCTTOHEX'], + 'functionCall' => [Engineering\ConvertOctal::class, 'toHex'], 'argumentCount' => '1,2', ], 'ODD' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'ODD'], + 'functionCall' => [MathTrig\Round::class, 'odd'], 'argumentCount' => '1', ], 'ODDFPRICE' => [ @@ -1582,39 +2008,64 @@ class Calculation ], 'OFFSET' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, - 'functionCall' => [LookupRef::class, 'OFFSET'], + 'functionCall' => [LookupRef\Offset::class, 'OFFSET'], 'argumentCount' => '3-5', 'passCellReference' => true, 'passByReference' => [true], ], 'OR' => [ 'category' => Category::CATEGORY_LOGICAL, - 'functionCall' => [Logical::class, 'logicalOr'], + 'functionCall' => [Logical\Operations::class, 'logicalOr'], 'argumentCount' => '1+', ], 'PDURATION' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'PDURATION'], + 'functionCall' => [Financial\CashFlow\Single::class, 'periods'], 'argumentCount' => '3', ], 'PEARSON' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'CORREL'], + 'functionCall' => [Statistical\Trends::class, 'CORREL'], 'argumentCount' => '2', ], 'PERCENTILE' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'PERCENTILE'], + 'functionCall' => [Statistical\Percentiles::class, 'PERCENTILE'], + 'argumentCount' => '2', + ], + 'PERCENTILE.EXC' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2', + ], + 'PERCENTILE.INC' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Percentiles::class, 'PERCENTILE'], 'argumentCount' => '2', ], 'PERCENTRANK' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'PERCENTRANK'], + 'functionCall' => [Statistical\Percentiles::class, 'PERCENTRANK'], + 'argumentCount' => '2,3', + ], + 'PERCENTRANK.EXC' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2,3', + ], + 'PERCENTRANK.INC' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Percentiles::class, 'PERCENTRANK'], 'argumentCount' => '2,3', ], 'PERMUT' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'PERMUT'], + 'functionCall' => [Statistical\Permutations::class, 'PERMUT'], + 'argumentCount' => '2', + ], + 'PERMUTATIONA' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Permutations::class, 'PERMUTATIONA'], 'argumentCount' => '2', ], 'PHONETIC' => [ @@ -1622,6 +2073,11 @@ class Calculation 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '1', ], + 'PHI' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '1', + ], 'PI' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, 'functionCall' => 'pi', @@ -1629,37 +2085,42 @@ class Calculation ], 'PMT' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'PMT'], + 'functionCall' => [Financial\CashFlow\Constant\Periodic\Payments::class, 'annuity'], 'argumentCount' => '3-5', ], 'POISSON' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'POISSON'], + 'functionCall' => [Statistical\Distributions\Poisson::class, 'distribution'], + 'argumentCount' => '3', + ], + 'POISSON.DIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Poisson::class, 'distribution'], 'argumentCount' => '3', ], 'POWER' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'POWER'], + 'functionCall' => [MathTrig\Operations::class, 'power'], 'argumentCount' => '2', ], 'PPMT' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'PPMT'], + 'functionCall' => [Financial\CashFlow\Constant\Periodic\Payments::class, 'interestPayment'], 'argumentCount' => '4-6', ], 'PRICE' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'PRICE'], + 'functionCall' => [Financial\Securities\Price::class, 'price'], 'argumentCount' => '6,7', ], 'PRICEDISC' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'PRICEDISC'], + 'functionCall' => [Financial\Securities\Price::class, 'priceDiscounted'], 'argumentCount' => '4,5', ], 'PRICEMAT' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'PRICEMAT'], + 'functionCall' => [Financial\Securities\Price::class, 'priceAtMaturity'], 'argumentCount' => '5,6', ], 'PROB' => [ @@ -1669,123 +2130,164 @@ class Calculation ], 'PRODUCT' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'PRODUCT'], + 'functionCall' => [MathTrig\Operations::class, 'product'], 'argumentCount' => '1+', ], 'PROPER' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'PROPERCASE'], + 'functionCall' => [TextData\CaseConvert::class, 'proper'], 'argumentCount' => '1', ], 'PV' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'PV'], + 'functionCall' => [Financial\CashFlow\Constant\Periodic::class, 'presentValue'], 'argumentCount' => '3-5', ], 'QUARTILE' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'QUARTILE'], + 'functionCall' => [Statistical\Percentiles::class, 'QUARTILE'], + 'argumentCount' => '2', + ], + 'QUARTILE.EXC' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2', + ], + 'QUARTILE.INC' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Percentiles::class, 'QUARTILE'], 'argumentCount' => '2', ], 'QUOTIENT' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'QUOTIENT'], + 'functionCall' => [MathTrig\Operations::class, 'quotient'], 'argumentCount' => '2', ], 'RADIANS' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'deg2rad', + 'functionCall' => [MathTrig\Angle::class, 'toRadians'], 'argumentCount' => '1', ], 'RAND' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'RAND'], + 'functionCall' => [MathTrig\Random::class, 'rand'], 'argumentCount' => '0', ], + 'RANDARRAY' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\Random::class, 'randArray'], + 'argumentCount' => '0-5', + ], 'RANDBETWEEN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'RAND'], + 'functionCall' => [MathTrig\Random::class, 'randBetween'], 'argumentCount' => '2', ], 'RANK' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'RANK'], + 'functionCall' => [Statistical\Percentiles::class, 'RANK'], + 'argumentCount' => '2,3', + ], + 'RANK.AVG' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2,3', + ], + 'RANK.EQ' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Percentiles::class, 'RANK'], 'argumentCount' => '2,3', ], 'RATE' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'RATE'], + 'functionCall' => [Financial\CashFlow\Constant\Periodic\Interest::class, 'rate'], 'argumentCount' => '3-6', ], 'RECEIVED' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'RECEIVED'], + 'functionCall' => [Financial\Securities\Price::class, 'received'], 'argumentCount' => '4-5', ], + 'REDUCE' => [ + 'category' => Category::CATEGORY_LOGICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '*', + ], 'REPLACE' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'REPLACE'], + 'functionCall' => [TextData\Replace::class, 'replace'], 'argumentCount' => '4', ], 'REPLACEB' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'REPLACE'], + 'functionCall' => [TextData\Replace::class, 'replace'], 'argumentCount' => '4', ], 'REPT' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => 'str_repeat', + 'functionCall' => [TextData\Concatenate::class, 'builtinREPT'], 'argumentCount' => '2', ], 'RIGHT' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'RIGHT'], + 'functionCall' => [TextData\Extract::class, 'right'], 'argumentCount' => '1,2', ], 'RIGHTB' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'RIGHT'], + 'functionCall' => [TextData\Extract::class, 'right'], 'argumentCount' => '1,2', ], 'ROMAN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'ROMAN'], + 'functionCall' => [MathTrig\Roman::class, 'evaluate'], 'argumentCount' => '1,2', ], 'ROUND' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'round', + 'functionCall' => [MathTrig\Round::class, 'round'], 'argumentCount' => '2', ], + 'ROUNDBAHTDOWN' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], + 'ROUNDBAHTUP' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], 'ROUNDDOWN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'ROUNDDOWN'], + 'functionCall' => [MathTrig\Round::class, 'down'], 'argumentCount' => '2', ], 'ROUNDUP' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'ROUNDUP'], + 'functionCall' => [MathTrig\Round::class, 'up'], 'argumentCount' => '2', ], 'ROW' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, - 'functionCall' => [LookupRef::class, 'ROW'], + 'functionCall' => [LookupRef\RowColumnInformation::class, 'ROW'], 'argumentCount' => '-1', + 'passCellReference' => true, 'passByReference' => [true], ], 'ROWS' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, - 'functionCall' => [LookupRef::class, 'ROWS'], + 'functionCall' => [LookupRef\RowColumnInformation::class, 'ROWS'], 'argumentCount' => '1', ], 'RRI' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'RRI'], + 'functionCall' => [Financial\CashFlow\Single::class, 'interestRate'], 'argumentCount' => '3', ], 'RSQ' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'RSQ'], + 'functionCall' => [Statistical\Trends::class, 'RSQ'], 'argumentCount' => '2', ], 'RTD' => [ @@ -1795,273 +2297,403 @@ class Calculation ], 'SEARCH' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'SEARCHINSENSITIVE'], + 'functionCall' => [TextData\Search::class, 'insensitive'], 'argumentCount' => '2,3', ], + 'SCAN' => [ + 'category' => Category::CATEGORY_LOGICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '*', + ], 'SEARCHB' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'SEARCHINSENSITIVE'], + 'functionCall' => [TextData\Search::class, 'insensitive'], 'argumentCount' => '2,3', ], 'SEC' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'SEC'], + 'functionCall' => [MathTrig\Trig\Secant::class, 'sec'], 'argumentCount' => '1', ], 'SECH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'SECH'], + 'functionCall' => [MathTrig\Trig\Secant::class, 'sech'], 'argumentCount' => '1', ], 'SECOND' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'SECOND'], + 'functionCall' => [DateTimeExcel\TimeParts::class, 'second'], 'argumentCount' => '1', ], + 'SEQUENCE' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [MathTrig\MatrixFunctions::class, 'sequence'], + 'argumentCount' => '1-4', + ], 'SERIESSUM' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'SERIESSUM'], + 'functionCall' => [MathTrig\SeriesSum::class, 'evaluate'], 'argumentCount' => '4', ], + 'SHEET' => [ + 'category' => Category::CATEGORY_INFORMATION, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '0,1', + ], + 'SHEETS' => [ + 'category' => Category::CATEGORY_INFORMATION, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '0,1', + ], 'SIGN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'SIGN'], + 'functionCall' => [MathTrig\Sign::class, 'evaluate'], 'argumentCount' => '1', ], 'SIN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'sin', + 'functionCall' => [MathTrig\Trig\Sine::class, 'sin'], 'argumentCount' => '1', ], + 'SINGLE' => [ + 'category' => Category::CATEGORY_UNCATEGORISED, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '*', + ], 'SINH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'sinh', + 'functionCall' => [MathTrig\Trig\Sine::class, 'sinh'], 'argumentCount' => '1', ], 'SKEW' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'SKEW'], + 'functionCall' => [Statistical\Deviations::class, 'skew'], + 'argumentCount' => '1+', + ], + 'SKEW.P' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '1+', ], 'SLN' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'SLN'], + 'functionCall' => [Financial\Depreciation::class, 'SLN'], 'argumentCount' => '3', ], 'SLOPE' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'SLOPE'], + 'functionCall' => [Statistical\Trends::class, 'SLOPE'], 'argumentCount' => '2', ], 'SMALL' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'SMALL'], + 'functionCall' => [Statistical\Size::class, 'small'], 'argumentCount' => '2', ], + 'SORT' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [LookupRef\Sort::class, 'sort'], + 'argumentCount' => '1-4', + ], + 'SORTBY' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [LookupRef\Sort::class, 'sortBy'], + 'argumentCount' => '2+', + ], 'SQRT' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'sqrt', + 'functionCall' => [MathTrig\Sqrt::class, 'sqrt'], 'argumentCount' => '1', ], 'SQRTPI' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'SQRTPI'], + 'functionCall' => [MathTrig\Sqrt::class, 'pi'], 'argumentCount' => '1', ], 'STANDARDIZE' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'STANDARDIZE'], + 'functionCall' => [Statistical\Standardize::class, 'execute'], 'argumentCount' => '3', ], 'STDEV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'STDEV'], + 'functionCall' => [Statistical\StandardDeviations::class, 'STDEV'], 'argumentCount' => '1+', ], 'STDEV.S' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'STDEV'], + 'functionCall' => [Statistical\StandardDeviations::class, 'STDEV'], 'argumentCount' => '1+', ], 'STDEV.P' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'STDEVP'], + 'functionCall' => [Statistical\StandardDeviations::class, 'STDEVP'], 'argumentCount' => '1+', ], 'STDEVA' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'STDEVA'], + 'functionCall' => [Statistical\StandardDeviations::class, 'STDEVA'], 'argumentCount' => '1+', ], 'STDEVP' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'STDEVP'], + 'functionCall' => [Statistical\StandardDeviations::class, 'STDEVP'], 'argumentCount' => '1+', ], 'STDEVPA' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'STDEVPA'], + 'functionCall' => [Statistical\StandardDeviations::class, 'STDEVPA'], 'argumentCount' => '1+', ], 'STEYX' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'STEYX'], + 'functionCall' => [Statistical\Trends::class, 'STEYX'], 'argumentCount' => '2', ], 'SUBSTITUTE' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'SUBSTITUTE'], + 'functionCall' => [TextData\Replace::class, 'substitute'], 'argumentCount' => '3,4', ], 'SUBTOTAL' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'SUBTOTAL'], + 'functionCall' => [MathTrig\Subtotal::class, 'evaluate'], 'argumentCount' => '2+', 'passCellReference' => true, ], 'SUM' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'SUM'], + 'functionCall' => [MathTrig\Sum::class, 'sumErroringStrings'], 'argumentCount' => '1+', ], 'SUMIF' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'SUMIF'], + 'functionCall' => [Statistical\Conditional::class, 'SUMIF'], 'argumentCount' => '2,3', ], 'SUMIFS' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'SUMIFS'], + 'functionCall' => [Statistical\Conditional::class, 'SUMIFS'], 'argumentCount' => '3+', ], 'SUMPRODUCT' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'SUMPRODUCT'], + 'functionCall' => [MathTrig\Sum::class, 'product'], 'argumentCount' => '1+', ], 'SUMSQ' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'SUMSQ'], + 'functionCall' => [MathTrig\SumSquares::class, 'sumSquare'], 'argumentCount' => '1+', ], 'SUMX2MY2' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'SUMX2MY2'], + 'functionCall' => [MathTrig\SumSquares::class, 'sumXSquaredMinusYSquared'], 'argumentCount' => '2', ], 'SUMX2PY2' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'SUMX2PY2'], + 'functionCall' => [MathTrig\SumSquares::class, 'sumXSquaredPlusYSquared'], 'argumentCount' => '2', ], 'SUMXMY2' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'SUMXMY2'], + 'functionCall' => [MathTrig\SumSquares::class, 'sumXMinusYSquared'], 'argumentCount' => '2', ], 'SWITCH' => [ 'category' => Category::CATEGORY_LOGICAL, - 'functionCall' => [Logical::class, 'statementSwitch'], + 'functionCall' => [Logical\Conditional::class, 'statementSwitch'], 'argumentCount' => '3+', ], 'SYD' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'SYD'], + 'functionCall' => [Financial\Depreciation::class, 'SYD'], 'argumentCount' => '4', ], 'T' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'RETURNSTRING'], + 'functionCall' => [TextData\Text::class, 'test'], 'argumentCount' => '1', ], + 'TAKE' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2-3', + ], 'TAN' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'tan', + 'functionCall' => [MathTrig\Trig\Tangent::class, 'tan'], 'argumentCount' => '1', ], 'TANH' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => 'tanh', + 'functionCall' => [MathTrig\Trig\Tangent::class, 'tanh'], 'argumentCount' => '1', ], 'TBILLEQ' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'TBILLEQ'], + 'functionCall' => [Financial\TreasuryBill::class, 'bondEquivalentYield'], 'argumentCount' => '3', ], 'TBILLPRICE' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'TBILLPRICE'], + 'functionCall' => [Financial\TreasuryBill::class, 'price'], 'argumentCount' => '3', ], 'TBILLYIELD' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'TBILLYIELD'], + 'functionCall' => [Financial\TreasuryBill::class, 'yield'], 'argumentCount' => '3', ], 'TDIST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'TDIST'], + 'functionCall' => [Statistical\Distributions\StudentT::class, 'distribution'], 'argumentCount' => '3', ], + 'T.DIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '3', + ], + 'T.DIST.2T' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2', + ], + 'T.DIST.RT' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2', + ], 'TEXT' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'TEXTFORMAT'], + 'functionCall' => [TextData\Format::class, 'TEXTFORMAT'], 'argumentCount' => '2', ], + 'TEXTAFTER' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Extract::class, 'after'], + 'argumentCount' => '2-6', + ], + 'TEXTBEFORE' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Extract::class, 'before'], + 'argumentCount' => '2-6', + ], 'TEXTJOIN' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'TEXTJOIN'], + 'functionCall' => [TextData\Concatenate::class, 'TEXTJOIN'], 'argumentCount' => '3+', ], + 'TEXTSPLIT' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Text::class, 'split'], + 'argumentCount' => '2-6', + ], + 'THAIDAYOFWEEK' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], + 'THAIDIGIT' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], + 'THAIMONTHOFYEAR' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], + 'THAINUMSOUND' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], + 'THAINUMSTRING' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], + 'THAISTRINGLENGTH' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], + 'THAIYEAR' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '?', + ], 'TIME' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'TIME'], + 'functionCall' => [DateTimeExcel\Time::class, 'fromHMS'], 'argumentCount' => '3', ], 'TIMEVALUE' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'TIMEVALUE'], + 'functionCall' => [DateTimeExcel\TimeValue::class, 'fromString'], 'argumentCount' => '1', ], 'TINV' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'TINV'], + 'functionCall' => [Statistical\Distributions\StudentT::class, 'inverse'], + 'argumentCount' => '2', + ], + 'T.INV' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\StudentT::class, 'inverse'], + 'argumentCount' => '2', + ], + 'T.INV.2T' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '2', ], 'TODAY' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'DATENOW'], + 'functionCall' => [DateTimeExcel\Current::class, 'today'], 'argumentCount' => '0', ], + 'TOCOL' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '1-3', + ], + 'TOROW' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '1-3', + ], 'TRANSPOSE' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, - 'functionCall' => [LookupRef::class, 'TRANSPOSE'], + 'functionCall' => [LookupRef\Matrix::class, 'transpose'], 'argumentCount' => '1', ], 'TREND' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'TREND'], + 'functionCall' => [Statistical\Trends::class, 'TREND'], 'argumentCount' => '1-4', ], 'TRIM' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'TRIMSPACES'], + 'functionCall' => [TextData\Trim::class, 'spaces'], 'argumentCount' => '1', ], 'TRIMMEAN' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'TRIMMEAN'], + 'functionCall' => [Statistical\Averages\Mean::class, 'trim'], 'argumentCount' => '2', ], 'TRUE' => [ 'category' => Category::CATEGORY_LOGICAL, - 'functionCall' => [Logical::class, 'TRUE'], + 'functionCall' => [Logical\Boolean::class, 'TRUE'], 'argumentCount' => '0', ], 'TRUNC' => [ 'category' => Category::CATEGORY_MATH_AND_TRIG, - 'functionCall' => [MathTrig::class, 'TRUNC'], + 'functionCall' => [MathTrig\Trunc::class, 'evaluate'], 'argumentCount' => '1,2', ], 'TTEST' => [ @@ -2069,64 +2701,79 @@ class Calculation 'functionCall' => [Functions::class, 'DUMMY'], 'argumentCount' => '4', ], + 'T.TEST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '4', + ], 'TYPE' => [ 'category' => Category::CATEGORY_INFORMATION, - 'functionCall' => [Functions::class, 'TYPE'], + 'functionCall' => [Information\Value::class, 'type'], 'argumentCount' => '1', ], 'UNICHAR' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'CHARACTER'], + 'functionCall' => [TextData\CharacterConvert::class, 'character'], 'argumentCount' => '1', ], 'UNICODE' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'ASCIICODE'], + 'functionCall' => [TextData\CharacterConvert::class, 'code'], 'argumentCount' => '1', ], + 'UNIQUE' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [LookupRef\Unique::class, 'unique'], + 'argumentCount' => '1+', + ], 'UPPER' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'UPPERCASE'], + 'functionCall' => [TextData\CaseConvert::class, 'upper'], 'argumentCount' => '1', ], 'USDOLLAR' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Functions::class, 'DUMMY'], + 'functionCall' => [Financial\Dollar::class, 'format'], 'argumentCount' => '2', ], 'VALUE' => [ 'category' => Category::CATEGORY_TEXT_AND_DATA, - 'functionCall' => [TextData::class, 'VALUE'], + 'functionCall' => [TextData\Format::class, 'VALUE'], 'argumentCount' => '1', ], + 'VALUETOTEXT' => [ + 'category' => Category::CATEGORY_TEXT_AND_DATA, + 'functionCall' => [TextData\Format::class, 'valueToText'], + 'argumentCount' => '1,2', + ], 'VAR' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'VARFunc'], + 'functionCall' => [Statistical\Variances::class, 'VAR'], 'argumentCount' => '1+', ], 'VAR.P' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'VARP'], + 'functionCall' => [Statistical\Variances::class, 'VARP'], 'argumentCount' => '1+', ], 'VAR.S' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'VARFunc'], + 'functionCall' => [Statistical\Variances::class, 'VAR'], 'argumentCount' => '1+', ], 'VARA' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'VARA'], + 'functionCall' => [Statistical\Variances::class, 'VARA'], 'argumentCount' => '1+', ], 'VARP' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'VARP'], + 'functionCall' => [Statistical\Variances::class, 'VARP'], 'argumentCount' => '1+', ], 'VARPA' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'VARPA'], + 'functionCall' => [Statistical\Variances::class, 'VARPA'], 'argumentCount' => '1+', ], 'VDB' => [ @@ -2136,52 +2783,92 @@ class Calculation ], 'VLOOKUP' => [ 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, - 'functionCall' => [LookupRef::class, 'VLOOKUP'], + 'functionCall' => [LookupRef\VLookup::class, 'lookup'], 'argumentCount' => '3,4', ], + 'VSTACK' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '1+', + ], + 'WEBSERVICE' => [ + 'category' => Category::CATEGORY_WEB, + 'functionCall' => [Web\Service::class, 'webService'], + 'argumentCount' => '1', + ], 'WEEKDAY' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'WEEKDAY'], + 'functionCall' => [DateTimeExcel\Week::class, 'day'], 'argumentCount' => '1,2', ], 'WEEKNUM' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'WEEKNUM'], + 'functionCall' => [DateTimeExcel\Week::class, 'number'], 'argumentCount' => '1,2', ], 'WEIBULL' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'WEIBULL'], + 'functionCall' => [Statistical\Distributions\Weibull::class, 'distribution'], + 'argumentCount' => '4', + ], + 'WEIBULL.DIST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\Weibull::class, 'distribution'], 'argumentCount' => '4', ], 'WORKDAY' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'WORKDAY'], - 'argumentCount' => '2+', + 'functionCall' => [DateTimeExcel\WorkDay::class, 'date'], + 'argumentCount' => '2-3', + ], + 'WORKDAY.INTL' => [ + 'category' => Category::CATEGORY_DATE_AND_TIME, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2-4', + ], + 'WRAPCOLS' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2-3', + ], + 'WRAPROWS' => [ + 'category' => Category::CATEGORY_MATH_AND_TRIG, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2-3', ], 'XIRR' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'XIRR'], + 'functionCall' => [Financial\CashFlow\Variable\NonPeriodic::class, 'rate'], 'argumentCount' => '2,3', ], + 'XLOOKUP' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '3-6', + ], 'XNPV' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'XNPV'], + 'functionCall' => [Financial\CashFlow\Variable\NonPeriodic::class, 'presentValue'], 'argumentCount' => '3', ], + 'XMATCH' => [ + 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE, + 'functionCall' => [Functions::class, 'DUMMY'], + 'argumentCount' => '2,3', + ], 'XOR' => [ 'category' => Category::CATEGORY_LOGICAL, - 'functionCall' => [Logical::class, 'logicalXor'], + 'functionCall' => [Logical\Operations::class, 'logicalXor'], 'argumentCount' => '1+', ], 'YEAR' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'YEAR'], + 'functionCall' => [DateTimeExcel\DateParts::class, 'year'], 'argumentCount' => '1', ], 'YEARFRAC' => [ 'category' => Category::CATEGORY_DATE_AND_TIME, - 'functionCall' => [DateTime::class, 'YEARFRAC'], + 'functionCall' => [DateTimeExcel\YearFrac::class, 'fraction'], 'argumentCount' => '2,3', ], 'YIELD' => [ @@ -2191,42 +2878,60 @@ class Calculation ], 'YIELDDISC' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'YIELDDISC'], + 'functionCall' => [Financial\Securities\Yields::class, 'yieldDiscounted'], 'argumentCount' => '4,5', ], 'YIELDMAT' => [ 'category' => Category::CATEGORY_FINANCIAL, - 'functionCall' => [Financial::class, 'YIELDMAT'], + 'functionCall' => [Financial\Securities\Yields::class, 'yieldAtMaturity'], 'argumentCount' => '5,6', ], 'ZTEST' => [ 'category' => Category::CATEGORY_STATISTICAL, - 'functionCall' => [Statistical::class, 'ZTEST'], + 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'zTest'], + 'argumentCount' => '2-3', + ], + 'Z.TEST' => [ + 'category' => Category::CATEGORY_STATISTICAL, + 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'zTest'], 'argumentCount' => '2-3', ], ]; - // Internal functions used for special control purposes + /** + * Internal functions used for special control purposes. + * + * @var array + */ private static $controlFunctions = [ 'MKMATRIX' => [ 'argumentCount' => '*', - 'functionCall' => 'self::mkMatrix', + 'functionCall' => [Internal\MakeMatrix::class, 'make'], + ], + 'NAME.ERROR' => [ + 'argumentCount' => '*', + 'functionCall' => [Functions::class, 'NAME'], + ], + 'WILDCARDMATCH' => [ + 'argumentCount' => '2', + 'functionCall' => [Internal\WildcardMatch::class, 'compare'], ], ]; - public function __construct(Spreadsheet $spreadsheet = null) + public function __construct(?Spreadsheet $spreadsheet = null) { - $this->delta = 1 * pow(10, 0 - ini_get('precision')); - $this->spreadsheet = $spreadsheet; $this->cyclicReferenceStack = new CyclicReferenceStack(); $this->debugLog = new Logger($this->cyclicReferenceStack); + $this->branchPruner = new BranchPruner($this->branchPruningEnabled); + self::$referenceHelper = ReferenceHelper::getInstance(); } - private static function loadLocales() + private static function loadLocales(): void { $localeFileDirectory = __DIR__ . '/locale/'; - foreach (glob($localeFileDirectory . '*', GLOB_ONLYDIR) as $filename) { + $localeFileNames = glob($localeFileDirectory . '*', GLOB_ONLYDIR) ?: []; + foreach ($localeFileNames as $filename) { $filename = substr($filename, strlen($localeFileDirectory)); if ($filename != 'en') { self::$validLocaleLanguages[] = $filename; @@ -2237,12 +2942,10 @@ class Calculation /** * Get an instance of this class. * - * @param Spreadsheet $spreadsheet Injected spreadsheet for working with a PhpSpreadsheet Spreadsheet object, - * or NULL to create a standalone claculation engine - * - * @return Calculation + * @param ?Spreadsheet $spreadsheet Injected spreadsheet for working with a PhpSpreadsheet Spreadsheet object, + * or NULL to create a standalone calculation engine */ - public static function getInstance(Spreadsheet $spreadsheet = null) + public static function getInstance(?Spreadsheet $spreadsheet = null): self { if ($spreadsheet !== null) { $instance = $spreadsheet->getCalculationEngine(); @@ -2262,10 +2965,10 @@ class Calculation * Flush the calculation cache for any existing instance of this class * but only if a Calculation instance exists. */ - public function flushInstance() + public function flushInstance(): void { $this->clearCalculationCache(); - $this->clearBranchStore(); + $this->branchPruner->clearBranchStore(); } /** @@ -2280,8 +2983,6 @@ class Calculation /** * __clone implementation. Cloning should not be allowed in a Singleton! - * - * @throws Exception */ final public function __clone() { @@ -2293,7 +2994,7 @@ class Calculation * * @return string locale-specific translation of TRUE */ - public static function getTRUE() + public static function getTRUE(): string { return self::$localeBoolean['TRUE']; } @@ -2303,7 +3004,7 @@ class Calculation * * @return string locale-specific translation of FALSE */ - public static function getFALSE() + public static function getFALSE(): string { return self::$localeBoolean['FALSE']; } @@ -2317,9 +3018,11 @@ class Calculation */ public static function setArrayReturnType($returnType) { - if (($returnType == self::RETURN_ARRAY_AS_VALUE) || + if ( + ($returnType == self::RETURN_ARRAY_AS_VALUE) || ($returnType == self::RETURN_ARRAY_AS_ERROR) || - ($returnType == self::RETURN_ARRAY_AS_ARRAY)) { + ($returnType == self::RETURN_ARRAY_AS_ARRAY) + ) { self::$returnArrayAsType = $returnType; return true; @@ -2351,18 +3054,18 @@ class Calculation /** * Enable/disable calculation cache. * - * @param bool $pValue + * @param bool $calculationCacheEnabled */ - public function setCalculationCacheEnabled($pValue) + public function setCalculationCacheEnabled($calculationCacheEnabled): void { - $this->calculationCacheEnabled = $pValue; + $this->calculationCacheEnabled = $calculationCacheEnabled; $this->clearCalculationCache(); } /** * Enable calculation cache. */ - public function enableCalculationCache() + public function enableCalculationCache(): void { $this->setCalculationCacheEnabled(true); } @@ -2370,7 +3073,7 @@ class Calculation /** * Disable calculation cache. */ - public function disableCalculationCache() + public function disableCalculationCache(): void { $this->setCalculationCacheEnabled(false); } @@ -2378,7 +3081,7 @@ class Calculation /** * Clear calculation cache. */ - public function clearCalculationCache() + public function clearCalculationCache(): void { $this->calculationCache = []; } @@ -2388,7 +3091,7 @@ class Calculation * * @param string $worksheetName */ - public function clearCalculationCacheForWorksheet($worksheetName) + public function clearCalculationCacheForWorksheet($worksheetName): void { if (isset($this->calculationCache[$worksheetName])) { unset($this->calculationCache[$worksheetName]); @@ -2401,7 +3104,7 @@ class Calculation * @param string $fromWorksheetName * @param string $toWorksheetName */ - public function renameCalculationCacheForWorksheet($fromWorksheetName, $toWorksheetName) + public function renameCalculationCacheForWorksheet($fromWorksheetName, $toWorksheetName): void { if (isset($this->calculationCache[$fromWorksheetName])) { $this->calculationCache[$toWorksheetName] = &$this->calculationCache[$fromWorksheetName]; @@ -2412,29 +3115,24 @@ class Calculation /** * Enable/disable calculation cache. * - * @param bool $pValue * @param mixed $enabled */ - public function setBranchPruningEnabled($enabled) + public function setBranchPruningEnabled($enabled): void { $this->branchPruningEnabled = $enabled; + $this->branchPruner = new BranchPruner($this->branchPruningEnabled); } - public function enableBranchPruning() + public function enableBranchPruning(): void { $this->setBranchPruningEnabled(true); } - public function disableBranchPruning() + public function disableBranchPruning(): void { $this->setBranchPruningEnabled(false); } - public function clearBranchStore() - { - $this->branchStoreKeyCounter = 0; - } - /** * Get the currently defined locale code. * @@ -2445,6 +3143,21 @@ class Calculation return self::$localeLanguage; } + private function getLocaleFile(string $localeDir, string $locale, string $language, string $file): string + { + $localeFileName = $localeDir . str_replace('_', DIRECTORY_SEPARATOR, $locale) . + DIRECTORY_SEPARATOR . $file; + if (!file_exists($localeFileName)) { + // If there isn't a locale specific file, look for a language specific file + $localeFileName = $localeDir . $language . DIRECTORY_SEPARATOR . $file; + if (!file_exists($localeFileName)) { + throw new Exception('Locale file not found'); + } + } + + return $localeFileName; + } + /** * Set the locale code. * @@ -2452,7 +3165,7 @@ class Calculation * * @return bool */ - public function setLocale($locale) + public function setLocale(string $locale) { // Identify our locale and language $language = $locale = strtolower($locale); @@ -2462,32 +3175,31 @@ class Calculation if (count(self::$validLocaleLanguages) == 1) { self::loadLocales(); } + // Test whether we have any language data for this language (any locale) - if (in_array($language, self::$validLocaleLanguages)) { + if (in_array($language, self::$validLocaleLanguages, true)) { // initialise language/locale settings self::$localeFunctions = []; self::$localeArgumentSeparator = ','; self::$localeBoolean = ['TRUE' => 'TRUE', 'FALSE' => 'FALSE', 'NULL' => 'NULL']; - // Default is English, if user isn't requesting english, then read the necessary data from the locale files - if ($locale != 'en_us') { + + // Default is US English, if user isn't requesting US english, then read the necessary data from the locale files + if ($locale !== 'en_us') { + $localeDir = implode(DIRECTORY_SEPARATOR, [__DIR__, 'locale', null]); // Search for a file with a list of function names for locale - $functionNamesFile = __DIR__ . '/locale/' . str_replace('_', DIRECTORY_SEPARATOR, $locale) . DIRECTORY_SEPARATOR . 'functions'; - if (!file_exists($functionNamesFile)) { - // If there isn't a locale specific function file, look for a language specific function file - $functionNamesFile = __DIR__ . '/locale/' . $language . DIRECTORY_SEPARATOR . 'functions'; - if (!file_exists($functionNamesFile)) { - return false; - } + try { + $functionNamesFile = $this->getLocaleFile($localeDir, $locale, $language, 'functions'); + } catch (Exception $e) { + return false; } + // Retrieve the list of locale or language specific function names - $localeFunctions = file($functionNamesFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + $localeFunctions = file($functionNamesFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: []; foreach ($localeFunctions as $localeFunction) { [$localeFunction] = explode('##', $localeFunction); // Strip out comments if (strpos($localeFunction, '=') !== false) { - [$fName, $lfName] = explode('=', $localeFunction); - $fName = trim($fName); - $lfName = trim($lfName); - if ((isset(self::$phpSpreadsheetFunctions[$fName])) && ($lfName != '') && ($fName != $lfName)) { + [$fName, $lfName] = array_map('trim', explode('=', $localeFunction)); + if ((substr($fName, 0, 1) === '*' || isset(self::$phpSpreadsheetFunctions[$fName])) && ($lfName != '') && ($fName != $lfName)) { self::$localeFunctions[$fName] = $lfName; } } @@ -2500,20 +3212,22 @@ class Calculation self::$localeBoolean['FALSE'] = self::$localeFunctions['FALSE']; } - $configFile = __DIR__ . '/locale/' . str_replace('_', DIRECTORY_SEPARATOR, $locale) . DIRECTORY_SEPARATOR . 'config'; - if (!file_exists($configFile)) { - $configFile = __DIR__ . '/locale/' . $language . DIRECTORY_SEPARATOR . 'config'; + try { + $configFile = $this->getLocaleFile($localeDir, $locale, $language, 'config'); + } catch (Exception $e) { + return false; } - if (file_exists($configFile)) { - $localeSettings = file($configFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); - foreach ($localeSettings as $localeSetting) { - [$localeSetting] = explode('##', $localeSetting); // Strip out comments - if (strpos($localeSetting, '=') !== false) { - [$settingName, $settingValue] = explode('=', $localeSetting); - $settingName = strtoupper(trim($settingName)); + + $localeSettings = file($configFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: []; + foreach ($localeSettings as $localeSetting) { + [$localeSetting] = explode('##', $localeSetting); // Strip out comments + if (strpos($localeSetting, '=') !== false) { + [$settingName, $settingValue] = array_map('trim', explode('=', $localeSetting)); + $settingName = strtoupper($settingName); + if ($settingValue !== '') { switch ($settingName) { case 'ARGUMENTSEPARATOR': - self::$localeArgumentSeparator = trim($settingValue); + self::$localeArgumentSeparator = $settingValue; break; } @@ -2532,30 +3246,28 @@ class Calculation return false; } - /** - * @param string $fromSeparator - * @param string $toSeparator - * @param string $formula - * @param bool $inBraces - * - * @return string - */ - public static function translateSeparator($fromSeparator, $toSeparator, $formula, &$inBraces) - { + public static function translateSeparator( + string $fromSeparator, + string $toSeparator, + string $formula, + int &$inBracesLevel, + string $openBrace = self::FORMULA_OPEN_FUNCTION_BRACE, + string $closeBrace = self::FORMULA_CLOSE_FUNCTION_BRACE + ): string { $strlen = mb_strlen($formula); for ($i = 0; $i < $strlen; ++$i) { $chr = mb_substr($formula, $i, 1); switch ($chr) { - case '{': - $inBraces = true; + case $openBrace: + ++$inBracesLevel; break; - case '}': - $inBraces = false; + case $closeBrace: + --$inBracesLevel; break; case $fromSeparator: - if (!$inBraces) { + if ($inBracesLevel > 0) { $formula = mb_substr($formula, 0, $i) . $toSeparator . mb_substr($formula, $i + 1); } } @@ -2564,59 +3276,83 @@ class Calculation return $formula; } - /** - * @param string[] $from - * @param string[] $to - * @param string $formula - * @param string $fromSeparator - * @param string $toSeparator - * - * @return string - */ - private static function translateFormula(array $from, array $to, $formula, $fromSeparator, $toSeparator) + private static function translateFormulaBlock( + array $from, + array $to, + string $formula, + int &$inFunctionBracesLevel, + int &$inMatrixBracesLevel, + string $fromSeparator, + string $toSeparator + ): string { + // Function Names + $formula = (string) preg_replace($from, $to, $formula); + + // Temporarily adjust matrix separators so that they won't be confused with function arguments + $formula = self::translateSeparator(';', '|', $formula, $inMatrixBracesLevel, self::FORMULA_OPEN_MATRIX_BRACE, self::FORMULA_CLOSE_MATRIX_BRACE); + $formula = self::translateSeparator(',', '!', $formula, $inMatrixBracesLevel, self::FORMULA_OPEN_MATRIX_BRACE, self::FORMULA_CLOSE_MATRIX_BRACE); + // Function Argument Separators + $formula = self::translateSeparator($fromSeparator, $toSeparator, $formula, $inFunctionBracesLevel); + // Restore matrix separators + $formula = self::translateSeparator('|', ';', $formula, $inMatrixBracesLevel, self::FORMULA_OPEN_MATRIX_BRACE, self::FORMULA_CLOSE_MATRIX_BRACE); + $formula = self::translateSeparator('!', ',', $formula, $inMatrixBracesLevel, self::FORMULA_OPEN_MATRIX_BRACE, self::FORMULA_CLOSE_MATRIX_BRACE); + + return $formula; + } + + private static function translateFormula(array $from, array $to, string $formula, string $fromSeparator, string $toSeparator): string { - // Convert any Excel function names to the required language + // Convert any Excel function names and constant names to the required language; + // and adjust function argument separators if (self::$localeLanguage !== 'en_us') { - $inBraces = false; - // If there is the possibility of braces within a quoted string, then we don't treat those as matrix indicators - if (strpos($formula, '"') !== false) { - // So instead we skip replacing in any quoted strings by only replacing in every other array element after we've exploded - // the formula - $temp = explode('"', $formula); - $i = false; + $inFunctionBracesLevel = 0; + $inMatrixBracesLevel = 0; + // If there is the possibility of separators within a quoted string, then we treat them as literals + if (strpos($formula, self::FORMULA_STRING_QUOTE) !== false) { + // So instead we skip replacing in any quoted strings by only replacing in every other array element + // after we've exploded the formula + $temp = explode(self::FORMULA_STRING_QUOTE, $formula); + $notWithinQuotes = false; foreach ($temp as &$value) { - // Only count/replace in alternating array entries - if ($i = !$i) { - $value = preg_replace($from, $to, $value); - $value = self::translateSeparator($fromSeparator, $toSeparator, $value, $inBraces); + // Only adjust in alternating array entries + $notWithinQuotes = $notWithinQuotes === false; + if ($notWithinQuotes === true) { + $value = self::translateFormulaBlock($from, $to, $value, $inFunctionBracesLevel, $inMatrixBracesLevel, $fromSeparator, $toSeparator); } } unset($value); // Then rebuild the formula string - $formula = implode('"', $temp); + $formula = implode(self::FORMULA_STRING_QUOTE, $temp); } else { // If there's no quoted strings, then we do a simple count/replace - $formula = preg_replace($from, $to, $formula); - $formula = self::translateSeparator($fromSeparator, $toSeparator, $formula, $inBraces); + $formula = self::translateFormulaBlock($from, $to, $formula, $inFunctionBracesLevel, $inMatrixBracesLevel, $fromSeparator, $toSeparator); } } return $formula; } - private static $functionReplaceFromExcel = null; + /** @var ?array */ + private static $functionReplaceFromExcel; - private static $functionReplaceToLocale = null; + /** @var ?array */ + private static $functionReplaceToLocale; + /** + * @param string $formula + * + * @return string + */ public function _translateFormulaToLocale($formula) { + // Build list of function names and constants for translation if (self::$functionReplaceFromExcel === null) { self::$functionReplaceFromExcel = []; foreach (array_keys(self::$localeFunctions) as $excelFunctionName) { - self::$functionReplaceFromExcel[] = '/(@?[^\w\.])' . preg_quote($excelFunctionName, '/') . '([\s]*\()/Ui'; + self::$functionReplaceFromExcel[] = '/(@?[^\w\.])' . preg_quote($excelFunctionName, '/') . '([\s]*\()/ui'; } foreach (array_keys(self::$localeBoolean) as $excelBoolean) { - self::$functionReplaceFromExcel[] = '/(@?[^\w\.])' . preg_quote($excelBoolean, '/') . '([^\w\.])/Ui'; + self::$functionReplaceFromExcel[] = '/(@?[^\w\.])' . preg_quote($excelBoolean, '/') . '([^\w\.])/ui'; } } @@ -2630,22 +3366,35 @@ class Calculation } } - return self::translateFormula(self::$functionReplaceFromExcel, self::$functionReplaceToLocale, $formula, ',', self::$localeArgumentSeparator); + return self::translateFormula( + self::$functionReplaceFromExcel, + self::$functionReplaceToLocale, + $formula, + ',', + self::$localeArgumentSeparator + ); } - private static $functionReplaceFromLocale = null; + /** @var ?array */ + private static $functionReplaceFromLocale; - private static $functionReplaceToExcel = null; + /** @var ?array */ + private static $functionReplaceToExcel; + /** + * @param string $formula + * + * @return string + */ public function _translateFormulaToEnglish($formula) { if (self::$functionReplaceFromLocale === null) { self::$functionReplaceFromLocale = []; foreach (self::$localeFunctions as $localeFunctionName) { - self::$functionReplaceFromLocale[] = '/(@?[^\w\.])' . preg_quote($localeFunctionName, '/') . '([\s]*\()/Ui'; + self::$functionReplaceFromLocale[] = '/(@?[^\w\.])' . preg_quote($localeFunctionName, '/') . '([\s]*\()/ui'; } foreach (self::$localeBoolean as $excelBoolean) { - self::$functionReplaceFromLocale[] = '/(@?[^\w\.])' . preg_quote($excelBoolean, '/') . '([^\w\.])/Ui'; + self::$functionReplaceFromLocale[] = '/(@?[^\w\.])' . preg_quote($excelBoolean, '/') . '([^\w\.])/ui'; } } @@ -2662,6 +3411,11 @@ class Calculation return self::translateFormula(self::$functionReplaceFromLocale, self::$functionReplaceToExcel, $formula, self::$localeArgumentSeparator, ','); } + /** + * @param string $function + * + * @return string + */ public static function localeFunc($function) { if (self::$localeLanguage !== 'en_us') { @@ -2693,11 +3447,12 @@ class Calculation // Return Excel errors "as is" return $value; } + // Return strings wrapped in quotes - return '"' . $value . '"'; - // Convert numeric errors to NaN error + return self::FORMULA_STRING_QUOTE . $value . self::FORMULA_STRING_QUOTE; } elseif ((is_float($value)) && ((is_nan($value)) || (is_infinite($value)))) { - return Functions::NAN(); + // Convert numeric errors to NaN error + return Information\ExcelError::NAN(); } return $value; @@ -2713,12 +3468,12 @@ class Calculation public static function unwrapResult($value) { if (is_string($value)) { - if ((isset($value[0])) && ($value[0] == '"') && (substr($value, -1) == '"')) { + if ((isset($value[0])) && ($value[0] == self::FORMULA_STRING_QUOTE) && (substr($value, -1) == self::FORMULA_STRING_QUOTE)) { return substr($value, 1, -1); } // Convert numeric errors to NAN error } elseif ((is_float($value)) && ((is_nan($value)) || (is_infinite($value)))) { - return Functions::NAN(); + return Information\ExcelError::NAN(); } return $value; @@ -2728,16 +3483,14 @@ class Calculation * Calculate cell value (using formula from a cell ID) * Retained for backward compatibility. * - * @param Cell $pCell Cell to calculate - * - * @throws Exception + * @param Cell $cell Cell to calculate * * @return mixed */ - public function calculate(Cell $pCell = null) + public function calculate(?Cell $cell = null) { try { - return $this->calculateCellValue($pCell); + return $this->calculateCellValue($cell); } catch (\Exception $e) { throw new Exception($e->getMessage()); } @@ -2746,16 +3499,14 @@ class Calculation /** * Calculate the value of a cell formula. * - * @param Cell $pCell Cell to calculate + * @param Cell $cell Cell to calculate * @param bool $resetLog Flag indicating whether the debug log should be reset or not * - * @throws \PhpOffice\PhpSpreadsheet\Exception - * * @return mixed */ - public function calculateCellValue(Cell $pCell = null, $resetLog = true) + public function calculateCellValue(?Cell $cell = null, $resetLog = true) { - if ($pCell === null) { + if ($cell === null) { return null; } @@ -2772,17 +3523,38 @@ class Calculation // Execute the calculation for the cell formula $this->cellStack[] = [ - 'sheet' => $pCell->getWorksheet()->getTitle(), - 'cell' => $pCell->getCoordinate(), + 'sheet' => $cell->getWorksheet()->getTitle(), + 'cell' => $cell->getCoordinate(), ]; + $cellAddressAttempted = false; + $cellAddress = null; + try { - $result = self::unwrapResult($this->_calculateFormulaValue($pCell->getValue(), $pCell->getCoordinate(), $pCell)); + $result = self::unwrapResult($this->_calculateFormulaValue($cell->getValue(), $cell->getCoordinate(), $cell)); + if ($this->spreadsheet === null) { + throw new Exception('null spreadsheet in calculateCellValue'); + } + $cellAddressAttempted = true; $cellAddress = array_pop($this->cellStack); - $this->spreadsheet->getSheetByName($cellAddress['sheet'])->getCell($cellAddress['cell']); + if ($cellAddress === null) { + throw new Exception('null cellAddress in calculateCellValue'); + } + $testSheet = $this->spreadsheet->getSheetByName($cellAddress['sheet']); + if ($testSheet === null) { + throw new Exception('worksheet not found in calculateCellValue'); + } + $testSheet->getCell($cellAddress['cell']); } catch (\Exception $e) { - $cellAddress = array_pop($this->cellStack); - $this->spreadsheet->getSheetByName($cellAddress['sheet'])->getCell($cellAddress['cell']); + if (!$cellAddressAttempted) { + $cellAddress = array_pop($this->cellStack); + } + if ($this->spreadsheet !== null && is_array($cellAddress) && array_key_exists('sheet', $cellAddress)) { + $testSheet = $this->spreadsheet->getSheetByName($cellAddress['sheet']); + if ($testSheet !== null && array_key_exists('cell', $cellAddress)) { + $testSheet->getCell($cellAddress['cell']); + } + } throw new Exception($e->getMessage()); } @@ -2791,7 +3563,7 @@ class Calculation self::$returnArrayAsType = $returnArrayAsType; $testResult = Functions::flattenArray($result); if (self::$returnArrayAsType == self::RETURN_ARRAY_AS_ERROR) { - return Functions::VALUE(); + return Information\ExcelError::VALUE(); } // If there's only a single cell in the array, then we allow it if (count($testResult) != 1) { @@ -2799,13 +3571,13 @@ class Calculation $r = array_keys($result); $r = array_shift($r); if (!is_numeric($r)) { - return Functions::VALUE(); + return Information\ExcelError::VALUE(); } if (is_array($result[$r])) { $c = array_keys($result[$r]); $c = array_shift($c); if (!is_numeric($c)) { - return Functions::VALUE(); + return Information\ExcelError::VALUE(); } } } @@ -2813,10 +3585,10 @@ class Calculation } self::$returnArrayAsType = $returnArrayAsType; - if ($result === null) { + if ($result === null && $cell->getWorksheet()->getSheetView()->getShowZeros()) { return 0; } elseif ((is_float($result)) && ((is_nan($result)) || (is_infinite($result)))) { - return Functions::NAN(); + return Information\ExcelError::NAN(); } return $result; @@ -2843,7 +3615,7 @@ class Calculation } // Parse the formula and return the token stack - return $this->_parseFormula($formula); + return $this->internalParseFormula($formula); } /** @@ -2851,32 +3623,30 @@ class Calculation * * @param string $formula Formula to parse * @param string $cellID Address of the cell to calculate - * @param Cell $pCell Cell to calculate - * - * @throws \PhpOffice\PhpSpreadsheet\Exception + * @param Cell $cell Cell to calculate * * @return mixed */ - public function calculateFormula($formula, $cellID = null, Cell $pCell = null) + public function calculateFormula($formula, $cellID = null, ?Cell $cell = null) { // Initialise the logging settings $this->formulaError = null; $this->debugLog->clearLog(); $this->cyclicReferenceStack->clear(); - if ($this->spreadsheet !== null && $cellID === null && $pCell === null) { + $resetCache = $this->getCalculationCacheEnabled(); + if ($this->spreadsheet !== null && $cellID === null && $cell === null) { $cellID = 'A1'; - $pCell = $this->spreadsheet->getActiveSheet()->getCell($cellID); + $cell = $this->spreadsheet->getActiveSheet()->getCell($cellID); } else { // Disable calculation cacheing because it only applies to cell calculations, not straight formulae // But don't actually flush any cache - $resetCache = $this->getCalculationCacheEnabled(); $this->calculationCacheEnabled = false; } // Execute the calculation try { - $result = self::unwrapResult($this->_calculateFormulaValue($formula, $cellID, $pCell)); + $result = self::unwrapResult($this->_calculateFormulaValue($formula, $cellID, $cell)); } catch (\Exception $e) { throw new Exception($e->getMessage()); } @@ -2890,18 +3660,15 @@ class Calculation } /** - * @param string $cellReference * @param mixed $cellValue - * - * @return bool */ - public function getValueFromCache($cellReference, &$cellValue) + public function getValueFromCache(string $cellReference, &$cellValue): bool { + $this->debugLog->writeDebugLog('Testing cache value for cell %s', $cellReference); // Is calculation cacheing enabled? - // Is the value present in calculation cache? - $this->debugLog->writeDebugLog('Testing cache value for cell ', $cellReference); + // If so, is the required value present in calculation cache? if (($this->calculationCacheEnabled) && (isset($this->calculationCache[$cellReference]))) { - $this->debugLog->writeDebugLog('Retrieving value for cell ', $cellReference, ' from cache'); + $this->debugLog->writeDebugLog('Retrieving value for cell %s from cache', $cellReference); // Return the cached result $cellValue = $this->calculationCache[$cellReference]; @@ -2916,7 +3683,7 @@ class Calculation * @param string $cellReference * @param mixed $cellValue */ - public function saveValueToCache($cellReference, $cellValue) + public function saveValueToCache($cellReference, $cellValue): void { if ($this->calculationCacheEnabled) { $this->calculationCache[$cellReference] = $cellValue; @@ -2928,18 +3695,16 @@ class Calculation * * @param string $formula The formula to parse and calculate * @param string $cellID The ID (e.g. A3) of the cell that we are calculating - * @param Cell $pCell Cell to calculate - * - * @throws Exception + * @param Cell $cell Cell to calculate * * @return mixed */ - public function _calculateFormulaValue($formula, $cellID = null, Cell $pCell = null) + public function _calculateFormulaValue($formula, $cellID = null, ?Cell $cell = null) { $cellValue = null; // Quote-Prefixed cell values cannot be formulae, but are treated as strings - if ($pCell !== null && $pCell->getStyle()->getQuotePrefix() === true) { + if ($cell !== null && $cell->getStyle()->getQuotePrefix() === true) { return self::wrapResult((string) $formula); } @@ -2958,13 +3723,14 @@ class Calculation return self::wrapResult($formula); } - $pCellParent = ($pCell !== null) ? $pCell->getWorksheet() : null; + $pCellParent = ($cell !== null) ? $cell->getWorksheet() : null; $wsTitle = ($pCellParent !== null) ? $pCellParent->getTitle() : "\x00Wrk"; $wsCellReference = $wsTitle . '!' . $cellID; if (($cellID !== null) && ($this->getValueFromCache($wsCellReference, $cellValue))) { return $cellValue; } + $this->debugLog->writeDebugLog('Evaluating formula for cell %s', $wsCellReference); if (($wsTitle[0] !== "\x00") && ($this->cyclicReferenceStack->onStack($wsCellReference))) { if ($this->cyclicFormulaCount <= 0) { @@ -2986,9 +3752,11 @@ class Calculation } } + $this->debugLog->writeDebugLog('Formula for cell %s is %s', $wsCellReference, $formula); // Parse the formula onto the token stack and calculate the value $this->cyclicReferenceStack->push($wsCellReference); - $cellValue = $this->processTokenStack($this->_parseFormula($formula, $pCell), $cellID, $pCell); + + $cellValue = $this->processTokenStack($this->internalParseFormula($formula, $cell), $cellID, $cell); $this->cyclicReferenceStack->pop(); // Save to calculation cache @@ -3003,8 +3771,8 @@ class Calculation /** * Ensure that paired matrix operands are both matrices and of the same size. * - * @param mixed &$operand1 First matrix operand - * @param mixed &$operand2 Second matrix operand + * @param mixed $operand1 First matrix operand + * @param mixed $operand2 Second matrix operand * @param int $resize Flag indicating whether the matrices should be resized to match * and (if so), whether the smaller dimension should grow or the * larger should shrink. @@ -3048,7 +3816,7 @@ class Calculation /** * Read the dimensions of a matrix, and re-index it with straight numeric keys starting from row 0, column 0. * - * @param array &$matrix matrix operand + * @param array $matrix matrix operand * * @return int[] An array comprising the number of rows, and number of columns */ @@ -3073,14 +3841,14 @@ class Calculation /** * Ensure that paired matrix operands are both matrices of the same size. * - * @param mixed &$matrix1 First matrix operand - * @param mixed &$matrix2 Second matrix operand + * @param mixed $matrix1 First matrix operand + * @param mixed $matrix2 Second matrix operand * @param int $matrix1Rows Row size of first matrix operand * @param int $matrix1Columns Column size of first matrix operand * @param int $matrix2Rows Row size of second matrix operand * @param int $matrix2Columns Column size of second matrix operand */ - private static function resizeMatricesShrink(&$matrix1, &$matrix2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns) + private static function resizeMatricesShrink(&$matrix1, &$matrix2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns): void { if (($matrix2Columns < $matrix1Columns) || ($matrix2Rows < $matrix1Rows)) { if ($matrix2Rows < $matrix1Rows) { @@ -3116,14 +3884,14 @@ class Calculation /** * Ensure that paired matrix operands are both matrices of the same size. * - * @param mixed &$matrix1 First matrix operand - * @param mixed &$matrix2 Second matrix operand + * @param mixed $matrix1 First matrix operand + * @param mixed $matrix2 Second matrix operand * @param int $matrix1Rows Row size of first matrix operand * @param int $matrix1Columns Column size of first matrix operand * @param int $matrix2Rows Row size of second matrix operand * @param int $matrix2Columns Column size of second matrix operand */ - private static function resizeMatricesExtend(&$matrix1, &$matrix2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns) + private static function resizeMatricesExtend(&$matrix1, &$matrix2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns): void { if (($matrix2Columns < $matrix1Columns) || ($matrix2Rows < $matrix1Rows)) { if ($matrix2Columns < $matrix1Columns) { @@ -3188,10 +3956,12 @@ class Calculation } return '{ ' . implode($rpad, $returnMatrix) . ' }'; - } elseif (is_string($value) && (trim($value, '"') == $value)) { - return '"' . $value . '"'; + } elseif (is_string($value) && (trim($value, self::FORMULA_STRING_QUOTE) == $value)) { + return self::FORMULA_STRING_QUOTE . $value . self::FORMULA_STRING_QUOTE; } elseif (is_bool($value)) { return ($value) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE']; + } elseif ($value === null) { + return self::$localeBoolean['NULL']; } } @@ -3234,43 +4004,46 @@ class Calculation return $typeString . ' with a value of ' . $this->showValue($value); } + + return null; } /** * @param string $formula * - * @return string + * @return false|string False indicates an error */ private function convertMatrixReferences($formula) { - static $matrixReplaceFrom = ['{', ';', '}']; + static $matrixReplaceFrom = [self::FORMULA_OPEN_MATRIX_BRACE, ';', self::FORMULA_CLOSE_MATRIX_BRACE]; static $matrixReplaceTo = ['MKMATRIX(MKMATRIX(', '),MKMATRIX(', '))']; // Convert any Excel matrix references to the MKMATRIX() function - if (strpos($formula, '{') !== false) { + if (strpos($formula, self::FORMULA_OPEN_MATRIX_BRACE) !== false) { // If there is the possibility of braces within a quoted string, then we don't treat those as matrix indicators - if (strpos($formula, '"') !== false) { + if (strpos($formula, self::FORMULA_STRING_QUOTE) !== false) { // So instead we skip replacing in any quoted strings by only replacing in every other array element after we've exploded // the formula - $temp = explode('"', $formula); + $temp = explode(self::FORMULA_STRING_QUOTE, $formula); // Open and Closed counts used for trapping mismatched braces in the formula $openCount = $closeCount = 0; - $i = false; + $notWithinQuotes = false; foreach ($temp as &$value) { // Only count/replace in alternating array entries - if ($i = !$i) { - $openCount += substr_count($value, '{'); - $closeCount += substr_count($value, '}'); + $notWithinQuotes = $notWithinQuotes === false; + if ($notWithinQuotes === true) { + $openCount += substr_count($value, self::FORMULA_OPEN_MATRIX_BRACE); + $closeCount += substr_count($value, self::FORMULA_CLOSE_MATRIX_BRACE); $value = str_replace($matrixReplaceFrom, $matrixReplaceTo, $value); } } unset($value); // Then rebuild the formula string - $formula = implode('"', $temp); + $formula = implode(self::FORMULA_STRING_QUOTE, $temp); } else { // If there's no quoted strings, then we do a simple count/replace - $openCount = substr_count($formula, '{'); - $closeCount = substr_count($formula, '}'); + $openCount = substr_count($formula, self::FORMULA_OPEN_MATRIX_BRACE); + $closeCount = substr_count($formula, self::FORMULA_CLOSE_MATRIX_BRACE); $formula = str_replace($matrixReplaceFrom, $matrixReplaceTo, $formula); } // Trap for mismatched braces and trigger an appropriate error @@ -3292,33 +4065,41 @@ class Calculation return $formula; } - private static function mkMatrix(...$args) - { - return $args; - } - - // Binary Operators - // These operators always work on two values - // Array key is the operator, the value indicates whether this is a left or right associative operator + /** + * Binary Operators. + * These operators always work on two values. + * Array key is the operator, the value indicates whether this is a left or right associative operator. + * + * @var array + */ private static $operatorAssociativity = [ '^' => 0, // Exponentiation '*' => 0, '/' => 0, // Multiplication and Division '+' => 0, '-' => 0, // Addition and Subtraction '&' => 0, // Concatenation - '|' => 0, ':' => 0, // Intersect and Range + '∪' => 0, '∩' => 0, ':' => 0, // Union, Intersect and Range '>' => 0, '<' => 0, '=' => 0, '>=' => 0, '<=' => 0, '<>' => 0, // Comparison ]; - // Comparison (Boolean) Operators - // These operators work on two values, but always return a boolean result + /** + * Comparison (Boolean) Operators. + * These operators work on two values, but always return a boolean result. + * + * @var array + */ private static $comparisonOperators = ['>' => true, '<' => true, '=' => true, '>=' => true, '<=' => true, '<>' => true]; - // Operator Precedence - // This list includes all valid operators, whether binary (including boolean) or unary (such as %) - // Array key is the operator, the value is its precedence + /** + * Operator Precedence. + * This list includes all valid operators, whether binary (including boolean) or unary (such as %). + * Array key is the operator, the value is its precedence. + * + * @var array + */ private static $operatorPrecedence = [ - ':' => 8, // Range - '|' => 7, // Intersect + ':' => 9, // Range + '∩' => 8, // Intersect + '∪' => 7, // Union '~' => 6, // Negation '%' => 5, // Percentage '^' => 4, // Exponentiation @@ -3332,11 +4113,10 @@ class Calculation /** * @param string $formula - * @param null|\PhpOffice\PhpSpreadsheet\Cell\Cell $pCell * - * @return bool + * @return array|false */ - private function _parseFormula($formula, Cell $pCell = null) + private function internalParseFormula($formula, ?Cell $cell = null) { if (($formula = $this->convertMatrixReferences(trim($formula))) === false) { return false; @@ -3344,153 +4124,115 @@ class Calculation // If we're using cell caching, then $pCell may well be flushed back to the cache (which detaches the parent worksheet), // so we store the parent worksheet so that we can re-attach it when necessary - $pCellParent = ($pCell !== null) ? $pCell->getWorksheet() : null; + $pCellParent = ($cell !== null) ? $cell->getWorksheet() : null; - $regexpMatchString = '/^(' . self::CALCULATION_REGEXP_FUNCTION . - '|' . self::CALCULATION_REGEXP_CELLREF . - '|' . self::CALCULATION_REGEXP_NUMBER . - '|' . self::CALCULATION_REGEXP_STRING . - '|' . self::CALCULATION_REGEXP_OPENBRACE . - '|' . self::CALCULATION_REGEXP_NAMEDRANGE . - '|' . self::CALCULATION_REGEXP_ERROR . - ')/si'; + $regexpMatchString = '/^((?' . self::CALCULATION_REGEXP_STRING . + ')|(?' . self::CALCULATION_REGEXP_FUNCTION . + ')|(?' . self::CALCULATION_REGEXP_CELLREF . + ')|(?' . self::CALCULATION_REGEXP_COLUMN_RANGE . + ')|(?' . self::CALCULATION_REGEXP_ROW_RANGE . + ')|(?' . self::CALCULATION_REGEXP_NUMBER . + ')|(?' . self::CALCULATION_REGEXP_OPENBRACE . + ')|(?' . self::CALCULATION_REGEXP_STRUCTURED_REFERENCE . + ')|(?' . self::CALCULATION_REGEXP_DEFINEDNAME . + ')|(?' . self::CALCULATION_REGEXP_ERROR . + '))/sui'; // Start with initialisation $index = 0; - $stack = new Stack(); + $stack = new Stack($this->branchPruner); $output = []; $expectingOperator = false; // We use this test in syntax-checking the expression to determine when a - // - is a negation or + is a positive operator rather than an operation + // - is a negation or + is a positive operator rather than an operation $expectingOperand = false; // We use this test in syntax-checking the expression to determine whether an operand - // should be null in a function call - - // IF branch pruning - // currently pending storeKey (last item of the storeKeysStack - $pendingStoreKey = null; - // stores a list of storeKeys (string[]) - $pendingStoreKeysStack = []; - $expectingConditionMap = []; // ['storeKey' => true, ...] - $expectingThenMap = []; // ['storeKey' => true, ...] - $expectingElseMap = []; // ['storeKey' => true, ...] - $parenthesisDepthMap = []; // ['storeKey' => 4, ...] + // should be null in a function call // The guts of the lexical parser // Loop through the formula extracting each operator and operand in turn while (true) { // Branch pruning: we adapt the output item to the context (it will // be used to limit its computation) - $currentCondition = null; - $currentOnlyIf = null; - $currentOnlyIfNot = null; - $previousStoreKey = null; - $pendingStoreKey = end($pendingStoreKeysStack); - - if ($this->branchPruningEnabled) { - // this is a condition ? - if (isset($expectingConditionMap[$pendingStoreKey]) && $expectingConditionMap[$pendingStoreKey]) { - $currentCondition = $pendingStoreKey; - $stackDepth = count($pendingStoreKeysStack); - if ($stackDepth > 1) { // nested if - $previousStoreKey = $pendingStoreKeysStack[$stackDepth - 2]; - } - } - if (isset($expectingThenMap[$pendingStoreKey]) && $expectingThenMap[$pendingStoreKey]) { - $currentOnlyIf = $pendingStoreKey; - } elseif (isset($previousStoreKey)) { - if (isset($expectingThenMap[$previousStoreKey]) && $expectingThenMap[$previousStoreKey]) { - $currentOnlyIf = $previousStoreKey; - } - } - if (isset($expectingElseMap[$pendingStoreKey]) && $expectingElseMap[$pendingStoreKey]) { - $currentOnlyIfNot = $pendingStoreKey; - } elseif (isset($previousStoreKey)) { - if (isset($expectingElseMap[$previousStoreKey]) && $expectingElseMap[$previousStoreKey]) { - $currentOnlyIfNot = $previousStoreKey; - } - } - } + $this->branchPruner->initialiseForLoop(); $opCharacter = $formula[$index]; // Get the first character of the value at the current index position + + // Check for two-character operators (e.g. >=, <=, <>) if ((isset(self::$comparisonOperators[$opCharacter])) && (strlen($formula) > $index) && (isset(self::$comparisonOperators[$formula[$index + 1]]))) { $opCharacter .= $formula[++$index]; } + // Find out if we're currently at the beginning of a number, variable, cell/row/column reference, + // function, defined name, structured reference, parenthesis, error or operand + $isOperandOrFunction = (bool) preg_match($regexpMatchString, substr($formula, $index), $match); - // Find out if we're currently at the beginning of a number, variable, cell reference, function, parenthesis or operand - $isOperandOrFunction = preg_match($regexpMatchString, substr($formula, $index), $match); - - if ($opCharacter == '-' && !$expectingOperator) { // Is it a negation instead of a minus? + $expectingOperatorCopy = $expectingOperator; + if ($opCharacter === '-' && !$expectingOperator) { // Is it a negation instead of a minus? // Put a negation on the stack - $stack->push('Unary Operator', '~', null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot); + $stack->push('Unary Operator', '~'); ++$index; // and drop the negation symbol - } elseif ($opCharacter == '%' && $expectingOperator) { + } elseif ($opCharacter === '%' && $expectingOperator) { // Put a percentage on the stack - $stack->push('Unary Operator', '%', null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot); + $stack->push('Unary Operator', '%'); ++$index; - } elseif ($opCharacter == '+' && !$expectingOperator) { // Positive (unary plus rather than binary operator plus) can be discarded? + } elseif ($opCharacter === '+' && !$expectingOperator) { // Positive (unary plus rather than binary operator plus) can be discarded? ++$index; // Drop the redundant plus symbol - } elseif ((($opCharacter == '~') || ($opCharacter == '|')) && (!$isOperandOrFunction)) { // We have to explicitly deny a tilde or pipe, because they are legal + } elseif ((($opCharacter === '~') || ($opCharacter === '∩') || ($opCharacter === '∪')) && (!$isOperandOrFunction)) { + // We have to explicitly deny a tilde, union or intersect because they are legal return $this->raiseFormulaError("Formula Error: Illegal character '~'"); // on the stack but not in the input expression - } elseif ((isset(self::$operators[$opCharacter]) or $isOperandOrFunction) && $expectingOperator) { // Are we putting an operator on the stack? - while ($stack->count() > 0 && + } elseif ((isset(self::CALCULATION_OPERATORS[$opCharacter]) || $isOperandOrFunction) && $expectingOperator) { // Are we putting an operator on the stack? + while ( + $stack->count() > 0 && ($o2 = $stack->last()) && - isset(self::$operators[$o2['value']]) && - @(self::$operatorAssociativity[$opCharacter] ? self::$operatorPrecedence[$opCharacter] < self::$operatorPrecedence[$o2['value']] : self::$operatorPrecedence[$opCharacter] <= self::$operatorPrecedence[$o2['value']])) { + isset(self::CALCULATION_OPERATORS[$o2['value']]) && + @(self::$operatorAssociativity[$opCharacter] ? self::$operatorPrecedence[$opCharacter] < self::$operatorPrecedence[$o2['value']] : self::$operatorPrecedence[$opCharacter] <= self::$operatorPrecedence[$o2['value']]) + ) { $output[] = $stack->pop(); // Swap operands and higher precedence operators from the stack to the output } // Finally put our current operator onto the stack - $stack->push('Binary Operator', $opCharacter, null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot); + $stack->push('Binary Operator', $opCharacter); ++$index; $expectingOperator = false; - } elseif ($opCharacter == ')' && $expectingOperator) { // Are we expecting to close a parenthesis? + } elseif ($opCharacter === ')' && $expectingOperator) { // Are we expecting to close a parenthesis? $expectingOperand = false; - while (($o2 = $stack->pop()) && $o2['value'] != '(') { // Pop off the stack back to the last ( - if ($o2 === null) { - return $this->raiseFormulaError('Formula Error: Unexpected closing brace ")"'); - } + while (($o2 = $stack->pop()) && $o2['value'] !== '(') { // Pop off the stack back to the last ( $output[] = $o2; } $d = $stack->last(2); // Branch pruning we decrease the depth whether is it a function // call or a parenthesis - if (!empty($pendingStoreKey)) { - $parenthesisDepthMap[$pendingStoreKey] -= 1; - } + $this->branchPruner->decrementDepth(); - if (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $d['value'], $matches)) { // Did this parenthesis just close a function? - if (!empty($pendingStoreKey) && $parenthesisDepthMap[$pendingStoreKey] == -1) { - // we are closing an IF( - if ($d['value'] != 'IF(') { - return $this->raiseFormulaError('Parser bug we should be in an "IF("'); - } - if ($expectingConditionMap[$pendingStoreKey]) { - return $this->raiseFormulaError('We should not be expecting a condition'); - } - $expectingThenMap[$pendingStoreKey] = false; - $expectingElseMap[$pendingStoreKey] = false; - $parenthesisDepthMap[$pendingStoreKey] -= 1; - array_pop($pendingStoreKeysStack); - unset($pendingStoreKey); + if (is_array($d) && preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $d['value'], $matches)) { + // Did this parenthesis just close a function? + try { + $this->branchPruner->closingBrace($d['value']); + } catch (Exception $e) { + return $this->raiseFormulaError($e->getMessage()); } $functionName = $matches[1]; // Get the function name $d = $stack->pop(); - $argumentCount = $d['value']; // See how many arguments there were (argument count is the next value stored on the stack) + $argumentCount = $d['value'] ?? 0; // See how many arguments there were (argument count is the next value stored on the stack) $output[] = $d; // Dump the argument count on the output $output[] = $stack->pop(); // Pop the function and push onto the output if (isset(self::$controlFunctions[$functionName])) { $expectedArgumentCount = self::$controlFunctions[$functionName]['argumentCount']; + // Scrutinizer says functionCall is unused after this assignment. + // It might be right, but I'm too lazy to confirm. $functionCall = self::$controlFunctions[$functionName]['functionCall']; + self::doNothing($functionCall); } elseif (isset(self::$phpSpreadsheetFunctions[$functionName])) { $expectedArgumentCount = self::$phpSpreadsheetFunctions[$functionName]['argumentCount']; $functionCall = self::$phpSpreadsheetFunctions[$functionName]['functionCall']; + self::doNothing($functionCall); } else { // did we somehow push a non-function on the stack? this should never happen return $this->raiseFormulaError('Formula Error: Internal error, non-function on stack'); } // Check the argument count $argumentCountError = false; + $expectedArgumentCountString = null; if (is_numeric($expectedArgumentCount)) { if ($expectedArgumentCount < 0) { if ($argumentCount > abs($expectedArgumentCount)) { @@ -3505,6 +4247,7 @@ class Calculation } } elseif ($expectedArgumentCount != '*') { $isOperandOrFunction = preg_match('/(\d*)([-+,])(\d*)/', $expectedArgumentCount, $argMatch); + self::doNothing($isOperandOrFunction); switch ($argMatch[2]) { case '+': if ($argumentCount < $argMatch[1]) { @@ -3534,217 +4277,324 @@ class Calculation } } ++$index; - } elseif ($opCharacter == ',') { // Is this the separator for function arguments? - if (!empty($pendingStoreKey) && - $parenthesisDepthMap[$pendingStoreKey] == 0 - ) { - // We must go to the IF next argument - if ($expectingConditionMap[$pendingStoreKey]) { - $expectingConditionMap[$pendingStoreKey] = false; - $expectingThenMap[$pendingStoreKey] = true; - } elseif ($expectingThenMap[$pendingStoreKey]) { - $expectingThenMap[$pendingStoreKey] = false; - $expectingElseMap[$pendingStoreKey] = true; - } elseif ($expectingElseMap[$pendingStoreKey]) { - return $this->raiseFormulaError('Reaching fourth argument of an IF'); - } + } elseif ($opCharacter === ',') { // Is this the separator for function arguments? + try { + $this->branchPruner->argumentSeparator(); + } catch (Exception $e) { + return $this->raiseFormulaError($e->getMessage()); } - while (($o2 = $stack->pop()) && $o2['value'] != '(') { // Pop off the stack back to the last ( - if ($o2 === null) { - return $this->raiseFormulaError('Formula Error: Unexpected ,'); - } + + while (($o2 = $stack->pop()) && $o2['value'] !== '(') { // Pop off the stack back to the last ( $output[] = $o2; // pop the argument expression stuff and push onto the output } // If we've a comma when we're expecting an operand, then what we actually have is a null operand; // so push a null onto the stack if (($expectingOperand) || (!$expectingOperator)) { - $output[] = ['type' => 'NULL Value', 'value' => self::$excelConstants['NULL'], 'reference' => null]; + $output[] = ['type' => 'Empty Argument', 'value' => self::$excelConstants['NULL'], 'reference' => 'NULL']; } // make sure there was a function $d = $stack->last(2); - if (!preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $d['value'], $matches)) { + if (!preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $d['value'] ?? '', $matches)) { + // Can we inject a dummy function at this point so that the braces at least have some context + // because at least the braces are paired up (at this stage in the formula) + // MS Excel allows this if the content is cell references; but doesn't allow actual values, + // but at this point, we can't differentiate (so allow both) return $this->raiseFormulaError('Formula Error: Unexpected ,'); } + + /** @var array $d */ $d = $stack->pop(); - $itemStoreKey = $d['storeKey'] ?? null; - $itemOnlyIf = $d['onlyIf'] ?? null; - $itemOnlyIfNot = $d['onlyIfNot'] ?? null; - $stack->push($d['type'], ++$d['value'], $d['reference'], $itemStoreKey, $itemOnlyIf, $itemOnlyIfNot); // increment the argument count - $stack->push('Brace', '(', null, $itemStoreKey, $itemOnlyIf, $itemOnlyIfNot); // put the ( back on, we'll need to pop back to it again + ++$d['value']; // increment the argument count + + $stack->pushStackItem($d); + $stack->push('Brace', '('); // put the ( back on, we'll need to pop back to it again + $expectingOperator = false; $expectingOperand = true; ++$index; - } elseif ($opCharacter == '(' && !$expectingOperator) { - if (!empty($pendingStoreKey)) { // Branch pruning: we go deeper - $parenthesisDepthMap[$pendingStoreKey] += 1; - } - $stack->push('Brace', '(', null, $currentCondition, $currentOnlyIf, $currentOnlyIf); + } elseif ($opCharacter === '(' && !$expectingOperator) { + // Branch pruning: we go deeper + $this->branchPruner->incrementDepth(); + $stack->push('Brace', '(', null); ++$index; - } elseif ($isOperandOrFunction && !$expectingOperator) { // do we now have a function/variable/number? + } elseif ($isOperandOrFunction && !$expectingOperatorCopy) { + // do we now have a function/variable/number? $expectingOperator = true; $expectingOperand = false; $val = $match[1]; $length = strlen($val); - if (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $val, $matches)) { - $val = preg_replace('/\s/u', '', $val); + if (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $val, $matches)) { + $val = (string) preg_replace('/\s/u', '', $val); if (isset(self::$phpSpreadsheetFunctions[strtoupper($matches[1])]) || isset(self::$controlFunctions[strtoupper($matches[1])])) { // it's a function $valToUpper = strtoupper($val); - // here $matches[1] will contain values like "IF" - // and $val "IF(" - if ($this->branchPruningEnabled && ($valToUpper == 'IF(')) { // we handle a new if - $pendingStoreKey = $this->getUnusedBranchStoreKey(); - $pendingStoreKeysStack[] = $pendingStoreKey; - $expectingConditionMap[$pendingStoreKey] = true; - $parenthesisDepthMap[$pendingStoreKey] = 0; - } else { // this is not a if but we good deeper - if (!empty($pendingStoreKey) && array_key_exists($pendingStoreKey, $parenthesisDepthMap)) { - $parenthesisDepthMap[$pendingStoreKey] += 1; - } - } - - $stack->push('Function', $valToUpper, null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot); - // tests if the function is closed right after opening - $ax = preg_match('/^\s*(\s*\))/ui', substr($formula, $index + $length), $amatch); - if ($ax) { - $stack->push('Operand Count for Function ' . $valToUpper . ')', 0, null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot); - $expectingOperator = true; - } else { - $stack->push('Operand Count for Function ' . $valToUpper . ')', 1, null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot); - $expectingOperator = false; - } - $stack->push('Brace', '('); - } else { // it's a var w/ implicit multiplication - $output[] = ['type' => 'Value', 'value' => $matches[1], 'reference' => null]; + } else { + $valToUpper = 'NAME.ERROR('; } - } elseif (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $val, $matches)) { + // here $matches[1] will contain values like "IF" + // and $val "IF(" + + $this->branchPruner->functionCall($valToUpper); + + $stack->push('Function', $valToUpper); + // tests if the function is closed right after opening + $ax = preg_match('/^\s*\)/u', substr($formula, $index + $length)); + if ($ax) { + $stack->push('Operand Count for Function ' . $valToUpper . ')', 0); + $expectingOperator = true; + } else { + $stack->push('Operand Count for Function ' . $valToUpper . ')', 1); + $expectingOperator = false; + } + $stack->push('Brace', '('); + } elseif (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/miu', $val, $matches)) { // Watch for this case-change when modifying to allow cell references in different worksheets... // Should only be applied to the actual cell column, not the worksheet name - // If the last entry on the stack was a : operator, then we have a cell range reference $testPrevOp = $stack->last(1); - if ($testPrevOp !== null && $testPrevOp['value'] == ':') { + if ($testPrevOp !== null && $testPrevOp['value'] === ':') { // If we have a worksheet reference, then we're playing with a 3D reference - if ($matches[2] == '') { + if ($matches[2] === '') { // Otherwise, we 'inherit' the worksheet reference from the start cell reference // The start of the cell range reference should be the last entry in $output - $startCellRef = $output[count($output) - 1]['value']; - preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $startCellRef, $startMatches); - if ($startMatches[2] > '') { - $val = $startMatches[2] . '!' . $val; + $rangeStartCellRef = $output[count($output) - 1]['value'] ?? ''; + if ($rangeStartCellRef === ':') { + // Do we have chained range operators? + $rangeStartCellRef = $output[count($output) - 2]['value'] ?? ''; + } + preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/miu', $rangeStartCellRef, $rangeStartMatches); + if ($rangeStartMatches[2] > '') { + $val = $rangeStartMatches[2] . '!' . $val; } } else { - return $this->raiseFormulaError('3D Range references are not yet supported'); + $rangeStartCellRef = $output[count($output) - 1]['value'] ?? ''; + if ($rangeStartCellRef === ':') { + // Do we have chained range operators? + $rangeStartCellRef = $output[count($output) - 2]['value'] ?? ''; + } + preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/miu', $rangeStartCellRef, $rangeStartMatches); + if ($rangeStartMatches[2] !== $matches[2]) { + return $this->raiseFormulaError('3D Range references are not yet supported'); + } } + } elseif (strpos($val, '!') === false && $pCellParent !== null) { + $worksheet = $pCellParent->getTitle(); + $val = "'{$worksheet}'!{$val}"; } - - $outputItem = $stack->getStackItem('Cell Reference', $val, $val, $currentCondition, $currentOnlyIf, $currentOnlyIfNot); + // unescape any apostrophes or double quotes in worksheet name + $val = str_replace(["''", '""'], ["'", '"'], $val); + $outputItem = $stack->getStackItem('Cell Reference', $val, $val); $output[] = $outputItem; - } else { // it's a variable, constant, string, number or boolean - // If the last entry on the stack was a : operator, then we may have a row or column range reference - $testPrevOp = $stack->last(1); - if ($testPrevOp !== null && $testPrevOp['value'] == ':') { - $startRowColRef = $output[count($output) - 1]['value']; - [$rangeWS1, $startRowColRef] = Worksheet::extractSheetTitle($startRowColRef, true); - if ($rangeWS1 != '') { - $rangeWS1 .= '!'; - } - [$rangeWS2, $val] = Worksheet::extractSheetTitle($val, true); - if ($rangeWS2 != '') { - $rangeWS2 .= '!'; - } else { - $rangeWS2 = $rangeWS1; - } - if ((is_int($startRowColRef)) && (ctype_digit($val)) && - ($startRowColRef <= 1048576) && ($val <= 1048576)) { - // Row range - $endRowColRef = ($pCellParent !== null) ? $pCellParent->getHighestColumn() : 'XFD'; // Max 16,384 columns for Excel2007 - $output[count($output) - 1]['value'] = $rangeWS1 . 'A' . $startRowColRef; - $val = $rangeWS2 . $endRowColRef . $val; - } elseif ((ctype_alpha($startRowColRef)) && (ctype_alpha($val)) && - (strlen($startRowColRef) <= 3) && (strlen($val) <= 3)) { - // Column range - $endRowColRef = ($pCellParent !== null) ? $pCellParent->getHighestRow() : 1048576; // Max 1,048,576 rows for Excel2007 - $output[count($output) - 1]['value'] = $rangeWS1 . strtoupper($startRowColRef) . '1'; - $val = $rangeWS2 . $val . $endRowColRef; - } + } elseif (preg_match('/^' . self::CALCULATION_REGEXP_STRUCTURED_REFERENCE . '$/miu', $val, $matches)) { + try { + $structuredReference = Operands\StructuredReference::fromParser($formula, $index, $matches); + } catch (Exception $e) { + return $this->raiseFormulaError($e->getMessage()); } + $val = $structuredReference->value(); + $length = strlen($val); + $outputItem = $stack->getStackItem(Operands\StructuredReference::NAME, $structuredReference, null); + + $output[] = $outputItem; + $expectingOperator = true; + } else { + // it's a variable, constant, string, number or boolean $localeConstant = false; - if ($opCharacter == '"') { + $stackItemType = 'Value'; + $stackItemReference = null; + + // If the last entry on the stack was a : operator, then we may have a row or column range reference + $testPrevOp = $stack->last(1); + if ($testPrevOp !== null && $testPrevOp['value'] === ':') { + $stackItemType = 'Cell Reference'; + + if ( + !is_numeric($val) && + ((ctype_alpha($val) === false || strlen($val) > 3)) && + (preg_match('/^' . self::CALCULATION_REGEXP_DEFINEDNAME . '$/mui', $val) !== false) && + ($this->spreadsheet === null || $this->spreadsheet->getNamedRange($val) !== null) + ) { + $namedRange = ($this->spreadsheet === null) ? null : $this->spreadsheet->getNamedRange($val); + if ($namedRange !== null) { + $stackItemType = 'Defined Name'; + $address = str_replace('$', '', $namedRange->getValue()); + $stackItemReference = $val; + if (strpos($address, ':') !== false) { + // We'll need to manipulate the stack for an actual named range rather than a named cell + $fromTo = explode(':', $address); + $to = array_pop($fromTo); + foreach ($fromTo as $from) { + $output[] = $stack->getStackItem($stackItemType, $from, $stackItemReference); + $output[] = $stack->getStackItem('Binary Operator', ':'); + } + $address = $to; + } + $val = $address; + } + } else { + $startRowColRef = $output[count($output) - 1]['value'] ?? ''; + [$rangeWS1, $startRowColRef] = Worksheet::extractSheetTitle($startRowColRef, true); + $rangeSheetRef = $rangeWS1; + if ($rangeWS1 !== '') { + $rangeWS1 .= '!'; + } + $rangeSheetRef = trim($rangeSheetRef, "'"); + [$rangeWS2, $val] = Worksheet::extractSheetTitle($val, true); + if ($rangeWS2 !== '') { + $rangeWS2 .= '!'; + } else { + $rangeWS2 = $rangeWS1; + } + + $refSheet = $pCellParent; + if ($pCellParent !== null && $rangeSheetRef !== '' && $rangeSheetRef !== $pCellParent->getTitle()) { + $refSheet = $pCellParent->getParent()->getSheetByName($rangeSheetRef); + } + + if (ctype_digit($val) && $val <= 1048576) { + // Row range + $stackItemType = 'Row Reference'; + /** @var int $valx */ + $valx = $val; + $endRowColRef = ($refSheet !== null) ? $refSheet->getHighestDataColumn($valx) : AddressRange::MAX_COLUMN; // Max 16,384 columns for Excel2007 + $val = "{$rangeWS2}{$endRowColRef}{$val}"; + } elseif (ctype_alpha($val) && strlen($val) <= 3) { + // Column range + $stackItemType = 'Column Reference'; + $endRowColRef = ($refSheet !== null) ? $refSheet->getHighestDataRow($val) : AddressRange::MAX_ROW; // Max 1,048,576 rows for Excel2007 + $val = "{$rangeWS2}{$val}{$endRowColRef}"; + } + $stackItemReference = $val; + } + } elseif ($opCharacter === self::FORMULA_STRING_QUOTE) { // UnEscape any quotes within the string - $val = self::wrapResult(str_replace('""', '"', self::unwrapResult($val))); + $val = self::wrapResult(str_replace('""', self::FORMULA_STRING_QUOTE, self::unwrapResult($val))); + } elseif (isset(self::$excelConstants[trim(strtoupper($val))])) { + $stackItemType = 'Constant'; + $excelConstant = trim(strtoupper($val)); + $val = self::$excelConstants[$excelConstant]; + $stackItemReference = $excelConstant; + } elseif (($localeConstant = array_search(trim(strtoupper($val)), self::$localeBoolean)) !== false) { + $stackItemType = 'Constant'; + $val = self::$excelConstants[$localeConstant]; + $stackItemReference = $localeConstant; + } elseif ( + preg_match('/^' . self::CALCULATION_REGEXP_ROW_RANGE . '/miu', substr($formula, $index), $rowRangeReference) + ) { + $val = $rowRangeReference[1]; + $length = strlen($rowRangeReference[1]); + $stackItemType = 'Row Reference'; + // unescape any apostrophes or double quotes in worksheet name + $val = str_replace(["''", '""'], ["'", '"'], $val); + $column = 'A'; + if (($testPrevOp !== null && $testPrevOp['value'] === ':') && $pCellParent !== null) { + $column = $pCellParent->getHighestDataColumn($val); + } + $val = "{$rowRangeReference[2]}{$column}{$rowRangeReference[7]}"; + $stackItemReference = $val; + } elseif ( + preg_match('/^' . self::CALCULATION_REGEXP_COLUMN_RANGE . '/miu', substr($formula, $index), $columnRangeReference) + ) { + $val = $columnRangeReference[1]; + $length = strlen($val); + $stackItemType = 'Column Reference'; + // unescape any apostrophes or double quotes in worksheet name + $val = str_replace(["''", '""'], ["'", '"'], $val); + $row = '1'; + if (($testPrevOp !== null && $testPrevOp['value'] === ':') && $pCellParent !== null) { + $row = $pCellParent->getHighestDataRow($val); + } + $val = "{$val}{$row}"; + $stackItemReference = $val; + } elseif (preg_match('/^' . self::CALCULATION_REGEXP_DEFINEDNAME . '.*/miu', $val, $match)) { + $stackItemType = 'Defined Name'; + $stackItemReference = $val; } elseif (is_numeric($val)) { - if ((strpos($val, '.') !== false) || (stripos($val, 'e') !== false) || ($val > PHP_INT_MAX) || ($val < -PHP_INT_MAX)) { + if ((strpos((string) $val, '.') !== false) || (stripos((string) $val, 'e') !== false) || ($val > PHP_INT_MAX) || ($val < -PHP_INT_MAX)) { $val = (float) $val; } else { $val = (int) $val; } - } elseif (isset(self::$excelConstants[trim(strtoupper($val))])) { - $excelConstant = trim(strtoupper($val)); - $val = self::$excelConstants[$excelConstant]; - } elseif (($localeConstant = array_search(trim(strtoupper($val)), self::$localeBoolean)) !== false) { - $val = self::$excelConstants[$localeConstant]; } - $details = $stack->getStackItem('Value', $val, null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot); + + $details = $stack->getStackItem($stackItemType, $val, $stackItemReference); if ($localeConstant) { $details['localeValue'] = $localeConstant; } $output[] = $details; } $index += $length; - } elseif ($opCharacter == '$') { // absolute row or column range + } elseif ($opCharacter === '$') { // absolute row or column range ++$index; - } elseif ($opCharacter == ')') { // miscellaneous error checking + } elseif ($opCharacter === ')') { // miscellaneous error checking if ($expectingOperand) { - $output[] = ['type' => 'NULL Value', 'value' => self::$excelConstants['NULL'], 'reference' => null]; + $output[] = ['type' => 'Empty Argument', 'value' => self::$excelConstants['NULL'], 'reference' => 'NULL']; $expectingOperand = false; $expectingOperator = true; } else { return $this->raiseFormulaError("Formula Error: Unexpected ')'"); } - } elseif (isset(self::$operators[$opCharacter]) && !$expectingOperator) { + } elseif (isset(self::CALCULATION_OPERATORS[$opCharacter]) && !$expectingOperator) { return $this->raiseFormulaError("Formula Error: Unexpected operator '$opCharacter'"); } else { // I don't even want to know what you did to get here - return $this->raiseFormulaError('Formula Error: An unexpected error occured'); + return $this->raiseFormulaError('Formula Error: An unexpected error occurred'); } // Test for end of formula string if ($index == strlen($formula)) { // Did we end with an operator?. // Only valid for the % unary operator - if ((isset(self::$operators[$opCharacter])) && ($opCharacter != '%')) { + if ((isset(self::CALCULATION_OPERATORS[$opCharacter])) && ($opCharacter != '%')) { return $this->raiseFormulaError("Formula Error: Operator '$opCharacter' has no operands"); } break; } // Ignore white space - while (($formula[$index] == "\n") || ($formula[$index] == "\r")) { + while (($formula[$index] === "\n") || ($formula[$index] === "\r")) { ++$index; } - if ($formula[$index] == ' ') { - while ($formula[$index] == ' ') { + + if ($formula[$index] === ' ') { + while ($formula[$index] === ' ') { ++$index; } + // If we're expecting an operator, but only have a space between the previous and next operands (and both are - // Cell References) then we have an INTERSECTION operator - if (($expectingOperator) && (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '.*/Ui', substr($formula, $index), $match)) && - ($output[count($output) - 1]['type'] == 'Cell Reference')) { - while ($stack->count() > 0 && + // Cell References, Defined Names or Structured References) then we have an INTERSECTION operator + $countOutputMinus1 = count($output) - 1; + if ( + ($expectingOperator) && + array_key_exists($countOutputMinus1, $output) && + is_array($output[$countOutputMinus1]) && + array_key_exists('type', $output[$countOutputMinus1]) && + ( + (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '.*/miu', substr($formula, $index), $match)) && + ($output[$countOutputMinus1]['type'] === 'Cell Reference') || + (preg_match('/^' . self::CALCULATION_REGEXP_DEFINEDNAME . '.*/miu', substr($formula, $index), $match)) && + ($output[$countOutputMinus1]['type'] === 'Defined Name' || $output[$countOutputMinus1]['type'] === 'Value') || + (preg_match('/^' . self::CALCULATION_REGEXP_STRUCTURED_REFERENCE . '.*/miu', substr($formula, $index), $match)) && + ($output[$countOutputMinus1]['type'] === Operands\StructuredReference::NAME || $output[$countOutputMinus1]['type'] === 'Value') + ) + ) { + while ( + $stack->count() > 0 && ($o2 = $stack->last()) && - isset(self::$operators[$o2['value']]) && - @(self::$operatorAssociativity[$opCharacter] ? self::$operatorPrecedence[$opCharacter] < self::$operatorPrecedence[$o2['value']] : self::$operatorPrecedence[$opCharacter] <= self::$operatorPrecedence[$o2['value']])) { + isset(self::CALCULATION_OPERATORS[$o2['value']]) && + @(self::$operatorAssociativity[$opCharacter] ? self::$operatorPrecedence[$opCharacter] < self::$operatorPrecedence[$o2['value']] : self::$operatorPrecedence[$opCharacter] <= self::$operatorPrecedence[$o2['value']]) + ) { $output[] = $stack->pop(); // Swap operands and higher precedence operators from the stack to the output } - $stack->push('Binary Operator', '|'); // Put an Intersect Operator on the stack + $stack->push('Binary Operator', '∩'); // Put an Intersect Operator on the stack $expectingOperator = false; } } } - while (($op = $stack->pop()) !== null) { // pop everything off the stack and push onto output - if ((is_array($op) && $op['value'] == '(') || ($op === '(')) { + while (($op = $stack->pop()) !== null) { + // pop everything off the stack and push onto output + if ((is_array($op) && $op['value'] == '(')) { return $this->raiseFormulaError("Formula Error: Expecting ')'"); // if there are any opening braces on the stack, then braces were unbalanced } $output[] = $op; @@ -3753,15 +4603,26 @@ class Calculation return $output; } + /** + * @param array $operandData + * + * @return mixed + */ private static function dataTestReference(&$operandData) { $operand = $operandData['value']; if (($operandData['reference'] === null) && (is_array($operand))) { $rKeys = array_keys($operand); $rowKey = array_shift($rKeys); + if (is_array($operand[$rowKey]) === false) { + $operandData['value'] = $operand[$rowKey]; + + return $operand[$rowKey]; + } + $cKeys = array_keys(array_keys($operand[$rowKey])); $colKey = array_shift($cKeys); - if (ctype_upper($colKey)) { + if (ctype_upper("$colKey")) { $operandData['reference'] = $colKey . $rowKey; } } @@ -3769,48 +4630,46 @@ class Calculation return $operand; } - // evaluate postfix notation - /** * @param mixed $tokens * @param null|string $cellID - * @param null|Cell $pCell * - * @return bool + * @return array|false */ - private function processTokenStack($tokens, $cellID = null, Cell $pCell = null) + private function processTokenStack($tokens, $cellID = null, ?Cell $cell = null) { - if ($tokens == false) { + if ($tokens === false) { return false; } // If we're using cell caching, then $pCell may well be flushed back to the cache (which detaches the parent cell collection), // so we store the parent cell collection so that we can re-attach it when necessary - $pCellWorksheet = ($pCell !== null) ? $pCell->getWorksheet() : null; - $pCellParent = ($pCell !== null) ? $pCell->getParent() : null; - $stack = new Stack(); + $pCellWorksheet = ($cell !== null) ? $cell->getWorksheet() : null; + $pCellParent = ($cell !== null) ? $cell->getParent() : null; + $stack = new Stack($this->branchPruner); // Stores branches that have been pruned $fakedForBranchPruning = []; // help us to know when pruning ['branchTestId' => true/false] $branchStore = []; - // Loop through each token in turn foreach ($tokens as $tokenData) { $token = $tokenData['value']; - // Branch pruning: skip useless resolutions $storeKey = $tokenData['storeKey'] ?? null; if ($this->branchPruningEnabled && isset($tokenData['onlyIf'])) { $onlyIfStoreKey = $tokenData['onlyIf']; $storeValue = $branchStore[$onlyIfStoreKey] ?? null; + $storeValueAsBool = ($storeValue === null) ? + true : (bool) Functions::flattenSingleValue($storeValue); if (is_array($storeValue)) { $wrappedItem = end($storeValue); - $storeValue = end($wrappedItem); + $storeValue = is_array($wrappedItem) ? end($wrappedItem) : $wrappedItem; } - if (isset($storeValue) && (($storeValue !== true) - || ($storeValue === 'Pruned branch')) + if ( + (isset($storeValue) || $tokenData['reference'] === 'NULL') + && (!$storeValueAsBool || Information\ErrorValue::isError($storeValue) || ($storeValue === 'Pruned branch')) ) { // If branching value is not true, we don't need to compute if (!isset($fakedForBranchPruning['onlyIf-' . $onlyIfStoreKey])) { @@ -3833,12 +4692,16 @@ class Calculation if ($this->branchPruningEnabled && isset($tokenData['onlyIfNot'])) { $onlyIfNotStoreKey = $tokenData['onlyIfNot']; $storeValue = $branchStore[$onlyIfNotStoreKey] ?? null; + $storeValueAsBool = ($storeValue === null) ? + true : (bool) Functions::flattenSingleValue($storeValue); if (is_array($storeValue)) { $wrappedItem = end($storeValue); - $storeValue = end($wrappedItem); + $storeValue = is_array($wrappedItem) ? end($wrappedItem) : $wrappedItem; } - if (isset($storeValue) && ($storeValue - || ($storeValue === 'Pruned branch')) + + if ( + (isset($storeValue) || $tokenData['reference'] === 'NULL') + && ($storeValueAsBool || Information\ErrorValue::isError($storeValue) || ($storeValue === 'Pruned branch')) ) { // If branching value is true, we don't need to compute if (!isset($fakedForBranchPruning['onlyIfNot-' . $onlyIfNotStoreKey])) { @@ -3858,10 +4721,34 @@ class Calculation } } - $tIndex = \is_float($token) ? (int) $token : $token; + if ($token instanceof Operands\StructuredReference) { + if ($cell === null) { + return $this->raiseFormulaError('Structured References must exist in a Cell context'); + } - // if the token is a binary operator, pop the top two values off the stack, do the operation, and push the result back on the stack - if (isset(self::$binaryOperators[$tIndex])) { + try { + $cellRange = $token->parse($cell); + if (strpos($cellRange, ':') !== false) { + $this->debugLog->writeDebugLog('Evaluating Structured Reference %s as Cell Range %s', $token->value(), $cellRange); + $rangeValue = self::getInstance($cell->getWorksheet()->getParent())->_calculateFormulaValue("={$cellRange}", $token->value(), $cell); + $stack->push('Value', $rangeValue); + $this->debugLog->writeDebugLog('Evaluated Structured Reference %s as value %s', $token->value(), $this->showValue($rangeValue)); + } else { + $this->debugLog->writeDebugLog('Evaluating Structured Reference %s as Cell %s', $token->value(), $cellRange); + $cellValue = $cell->getWorksheet()->getCell($cellRange)->getCalculatedValue(false); + $stack->push('Cell Reference', $cellValue, $cellRange); + $this->debugLog->writeDebugLog('Evaluated Structured Reference %s as value %s', $token->value(), $this->showValue($cellValue)); + } + } catch (Exception $e) { + if ($e->getCode() === Exception::CALCULATION_ENGINE_PUSH_TO_STACK) { + $stack->push('Error', Information\ExcelError::REF(), null); + $this->debugLog->writeDebugLog('Evaluated Structured Reference %s as error value %s', $token->value(), Information\ExcelError::REF()); + } else { + return $this->raiseFormulaError($e->getMessage()); + } + } + } elseif (!is_numeric($token) && !is_object($token) && isset(self::BINARY_OPERATORS[$token])) { + // if the token is a binary operator, pop the top two values off the stack, do the operation, and push the result back on the stack // We must have two operands, error if we don't if (($operand2Data = $stack->pop()) === null) { return $this->raiseFormulaError('Internal error - Operand value missing from stack'); @@ -3875,32 +4762,40 @@ class Calculation // Log what we're doing if ($token == ':') { - $this->debugLog->writeDebugLog('Evaluating Range ', $this->showValue($operand1Data['reference']), ' ', $token, ' ', $this->showValue($operand2Data['reference'])); + $this->debugLog->writeDebugLog('Evaluating Range %s %s %s', $this->showValue($operand1Data['reference']), $token, $this->showValue($operand2Data['reference'])); } else { - $this->debugLog->writeDebugLog('Evaluating ', $this->showValue($operand1), ' ', $token, ' ', $this->showValue($operand2)); + $this->debugLog->writeDebugLog('Evaluating %s %s %s', $this->showValue($operand1), $token, $this->showValue($operand2)); } // Process the operation in the appropriate manner switch ($token) { - // Comparison (Boolean) Operators - case '>': // Greater than - case '<': // Less than - case '>=': // Greater than or Equal to - case '<=': // Less than or Equal to - case '=': // Equality - case '<>': // Inequality - $result = $this->executeBinaryComparisonOperation($cellID, $operand1, $operand2, $token, $stack); + // Comparison (Boolean) Operators + case '>': // Greater than + case '<': // Less than + case '>=': // Greater than or Equal to + case '<=': // Less than or Equal to + case '=': // Equality + case '<>': // Inequality + $result = $this->executeBinaryComparisonOperation($operand1, $operand2, (string) $token, $stack); if (isset($storeKey)) { $branchStore[$storeKey] = $result; } break; - // Binary Operators - case ':': // Range + // Binary Operators + case ':': // Range + if ($operand1Data['type'] === 'Defined Name') { + if (preg_match('/$' . self::CALCULATION_REGEXP_DEFINEDNAME . '^/mui', $operand1Data['reference']) !== false && $this->spreadsheet !== null) { + $definedName = $this->spreadsheet->getNamedRange($operand1Data['reference']); + if ($definedName !== null) { + $operand1Data['reference'] = $operand1Data['value'] = str_replace('$', '', $definedName->getValue()); + } + } + } if (strpos($operand1Data['reference'], '!') !== false) { [$sheet1, $operand1Data['reference']] = Worksheet::extractSheetTitle($operand1Data['reference'], true); } else { - $sheet1 = ($pCellParent !== null) ? $pCellWorksheet->getTitle() : ''; + $sheet1 = ($pCellWorksheet !== null) ? $pCellWorksheet->getTitle() : ''; } [$sheet2, $operand2Data['reference']] = Worksheet::extractSheetTitle($operand2Data['reference'], true); @@ -3908,23 +4803,27 @@ class Calculation $sheet2 = $sheet1; } - if ($sheet1 == $sheet2) { - if ($operand1Data['reference'] === null) { - if ((trim($operand1Data['value']) != '') && (is_numeric($operand1Data['value']))) { - $operand1Data['reference'] = $pCell->getColumn() . $operand1Data['value']; - } elseif (trim($operand1Data['reference']) == '') { - $operand1Data['reference'] = $pCell->getCoordinate(); + if (trim($sheet1, "'") === trim($sheet2, "'")) { + if ($operand1Data['reference'] === null && $cell !== null) { + if (is_array($operand1Data['value'])) { + $operand1Data['reference'] = $cell->getCoordinate(); + } elseif ((trim($operand1Data['value']) != '') && (is_numeric($operand1Data['value']))) { + $operand1Data['reference'] = $cell->getColumn() . $operand1Data['value']; + } elseif (trim($operand1Data['value']) == '') { + $operand1Data['reference'] = $cell->getCoordinate(); } else { - $operand1Data['reference'] = $operand1Data['value'] . $pCell->getRow(); + $operand1Data['reference'] = $operand1Data['value'] . $cell->getRow(); } } - if ($operand2Data['reference'] === null) { - if ((trim($operand2Data['value']) != '') && (is_numeric($operand2Data['value']))) { - $operand2Data['reference'] = $pCell->getColumn() . $operand2Data['value']; - } elseif (trim($operand2Data['reference']) == '') { - $operand2Data['reference'] = $pCell->getCoordinate(); + if ($operand2Data['reference'] === null && $cell !== null) { + if (is_array($operand2Data['value'])) { + $operand2Data['reference'] = $cell->getCoordinate(); + } elseif ((trim($operand2Data['value']) != '') && (is_numeric($operand2Data['value']))) { + $operand2Data['reference'] = $cell->getColumn() . $operand2Data['value']; + } elseif (trim($operand2Data['value']) == '') { + $operand2Data['reference'] = $cell->getCoordinate(); } else { - $operand2Data['reference'] = $operand2Data['value'] . $pCell->getRow(); + $operand2Data['reference'] = $operand2Data['value'] . $cell->getRow(); } } @@ -3936,47 +4835,24 @@ class Calculation $oRow[] = $oCR[1]; } $cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':' . Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow); - if ($pCellParent !== null) { + if ($pCellParent !== null && $this->spreadsheet !== null) { $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($sheet1), false); } else { return $this->raiseFormulaError('Unable to access Cell Reference'); } + $stack->push('Cell Reference', $cellValue, $cellRef); } else { - $stack->push('Error', Functions::REF(), null); + $stack->push('Error', Information\ExcelError::REF(), null); } break; case '+': // Addition - $result = $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'plusEquals', $stack); - if (isset($storeKey)) { - $branchStore[$storeKey] = $result; - } - - break; case '-': // Subtraction - $result = $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'minusEquals', $stack); - if (isset($storeKey)) { - $branchStore[$storeKey] = $result; - } - - break; case '*': // Multiplication - $result = $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'arrayTimesEquals', $stack); - if (isset($storeKey)) { - $branchStore[$storeKey] = $result; - } - - break; case '/': // Division - $result = $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'arrayRightDivide', $stack); - if (isset($storeKey)) { - $branchStore[$storeKey] = $result; - } - - break; case '^': // Exponential - $result = $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'power', $stack); + $result = $this->executeNumericBinaryOperation($operand1, $operand2, $token, $stack); if (isset($storeKey)) { $branchStore[$storeKey] = $result; } @@ -3986,30 +4862,39 @@ class Calculation // If either of the operands is a matrix, we need to treat them both as matrices // (converting the other operand to a matrix if need be); then perform the required // matrix operation - if (is_bool($operand1)) { - $operand1 = ($operand1) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE']; - } - if (is_bool($operand2)) { - $operand2 = ($operand2) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE']; - } - if ((is_array($operand1)) || (is_array($operand2))) { - // Ensure that both operands are arrays/matrices - self::checkMatrixOperands($operand1, $operand2, 2); - - try { - // Convert operand 1 from a PHP array to a matrix - $matrix = new Shared\JAMA\Matrix($operand1); - // Perform the required operation against the operand 1 matrix, passing in operand 2 - $matrixResult = $matrix->concat($operand2); - $result = $matrixResult->getArray(); - } catch (\Exception $ex) { - $this->debugLog->writeDebugLog('JAMA Matrix Exception: ', $ex->getMessage()); - $result = '#VALUE!'; + $operand1 = self::boolToString($operand1); + $operand2 = self::boolToString($operand2); + if (is_array($operand1) || is_array($operand2)) { + if (is_string($operand1)) { + $operand1 = self::unwrapResult($operand1); } + if (is_string($operand2)) { + $operand2 = self::unwrapResult($operand2); + } + // Ensure that both operands are arrays/matrices + [$rows, $columns] = self::checkMatrixOperands($operand1, $operand2, 2); + + for ($row = 0; $row < $rows; ++$row) { + for ($column = 0; $column < $columns; ++$column) { + $operand1[$row][$column] = + Shared\StringHelper::substring( + self::boolToString($operand1[$row][$column]) + . self::boolToString($operand2[$row][$column]), + 0, + DataType::MAX_STRING_LENGTH + ); + } + } + $result = $operand1; } else { - $result = '"' . str_replace('""', '"', self::unwrapResult($operand1) . self::unwrapResult($operand2)) . '"'; + // In theory, we should truncate here. + // But I can't figure out a formula + // using the concatenation operator + // with literals that fits in 32K, + // so I don't think we can overflow here. + $result = self::FORMULA_STRING_QUOTE . str_replace('""', self::FORMULA_STRING_QUOTE, self::unwrapResult($operand1) . self::unwrapResult($operand2)) . self::FORMULA_STRING_QUOTE; } - $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result)); + $this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($result)); $stack->push('Value', $result); if (isset($storeKey)) { @@ -4017,7 +4902,7 @@ class Calculation } break; - case '|': // Intersect + case '∩': // Intersect $rowIntersect = array_intersect_key($operand1, $operand2); $cellIntersect = $oCol = $oRow = []; foreach (array_keys($rowIntersect) as $row) { @@ -4027,51 +4912,60 @@ class Calculation $cellIntersect[$row] = array_intersect_key($operand1[$row], $operand2[$row]); } } - $cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':' . Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow); - $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($cellIntersect)); - $stack->push('Value', $cellIntersect, $cellRef); + if (count(Functions::flattenArray($cellIntersect)) === 0) { + $this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($cellIntersect)); + $stack->push('Error', Information\ExcelError::null(), null); + } else { + $cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':' . + Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow); + $this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($cellIntersect)); + $stack->push('Value', $cellIntersect, $cellRef); + } break; } - - // if the token is a unary operator, pop one value off the stack, do the operation, and push it back on } elseif (($token === '~') || ($token === '%')) { + // if the token is a unary operator, pop one value off the stack, do the operation, and push it back on if (($arg = $stack->pop()) === null) { return $this->raiseFormulaError('Internal error - Operand value missing from stack'); } $arg = $arg['value']; if ($token === '~') { - $this->debugLog->writeDebugLog('Evaluating Negation of ', $this->showValue($arg)); + $this->debugLog->writeDebugLog('Evaluating Negation of %s', $this->showValue($arg)); $multiplier = -1; } else { - $this->debugLog->writeDebugLog('Evaluating Percentile of ', $this->showValue($arg)); + $this->debugLog->writeDebugLog('Evaluating Percentile of %s', $this->showValue($arg)); $multiplier = 0.01; } if (is_array($arg)) { - self::checkMatrixOperands($arg, $multiplier, 2); - - try { - $matrix1 = new Shared\JAMA\Matrix($arg); - $matrixResult = $matrix1->arrayTimesEquals($multiplier); - $result = $matrixResult->getArray(); - } catch (\Exception $ex) { - $this->debugLog->writeDebugLog('JAMA Matrix Exception: ', $ex->getMessage()); - $result = '#VALUE!'; + $operand2 = $multiplier; + $result = $arg; + [$rows, $columns] = self::checkMatrixOperands($result, $operand2, 0); + for ($row = 0; $row < $rows; ++$row) { + for ($column = 0; $column < $columns; ++$column) { + if (is_numeric($result[$row][$column])) { + $result[$row][$column] *= $multiplier; + } else { + $result[$row][$column] = Information\ExcelError::VALUE(); + } + } } - $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result)); + + $this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($result)); $stack->push('Value', $result); if (isset($storeKey)) { $branchStore[$storeKey] = $result; } } else { - $this->executeNumericBinaryOperation($multiplier, $arg, '*', 'arrayTimesEquals', $stack); + $this->executeNumericBinaryOperation($multiplier, $arg, '*', $stack); } - } elseif (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $token, $matches)) { + } elseif (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $token ?? '', $matches)) { $cellRef = null; + if (isset($matches[8])) { - if ($pCell === null) { - // We can't access the range, so return a REF error - $cellValue = Functions::REF(); + if ($cell === null) { + // We can't access the range, so return a REF error + $cellValue = Information\ExcelError::REF(); } else { $cellRef = $matches[6] . $matches[7] . ':' . $matches[9] . $matches[10]; if ($matches[2] > '') { @@ -4081,27 +4975,27 @@ class Calculation return $this->raiseFormulaError('Unable to access External Workbook'); } $matches[2] = trim($matches[2], "\"'"); - $this->debugLog->writeDebugLog('Evaluating Cell Range ', $cellRef, ' in worksheet ', $matches[2]); - if ($pCellParent !== null) { + $this->debugLog->writeDebugLog('Evaluating Cell Range %s in worksheet %s', $cellRef, $matches[2]); + if ($pCellParent !== null && $this->spreadsheet !== null) { $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]), false); } else { return $this->raiseFormulaError('Unable to access Cell Reference'); } - $this->debugLog->writeDebugLog('Evaluation Result for cells ', $cellRef, ' in worksheet ', $matches[2], ' is ', $this->showTypeDetails($cellValue)); + $this->debugLog->writeDebugLog('Evaluation Result for cells %s in worksheet %s is %s', $cellRef, $matches[2], $this->showTypeDetails($cellValue)); } else { - $this->debugLog->writeDebugLog('Evaluating Cell Range ', $cellRef, ' in current worksheet'); + $this->debugLog->writeDebugLog('Evaluating Cell Range %s in current worksheet', $cellRef); if ($pCellParent !== null) { $cellValue = $this->extractCellRange($cellRef, $pCellWorksheet, false); } else { return $this->raiseFormulaError('Unable to access Cell Reference'); } - $this->debugLog->writeDebugLog('Evaluation Result for cells ', $cellRef, ' is ', $this->showTypeDetails($cellValue)); + $this->debugLog->writeDebugLog('Evaluation Result for cells %s is %s', $cellRef, $this->showTypeDetails($cellValue)); } } } else { - if ($pCell === null) { - // We can't access the cell, so return a REF error - $cellValue = Functions::REF(); + if ($cell === null) { + // We can't access the cell, so return a REF error + $cellValue = Information\ExcelError::REF(); } else { $cellRef = $matches[6] . $matches[7]; if ($matches[2] > '') { @@ -4110,56 +5004,53 @@ class Calculation // It's a Reference to an external spreadsheet (not currently supported) return $this->raiseFormulaError('Unable to access External Workbook'); } - $this->debugLog->writeDebugLog('Evaluating Cell ', $cellRef, ' in worksheet ', $matches[2]); - if ($pCellParent !== null) { + $this->debugLog->writeDebugLog('Evaluating Cell %s in worksheet %s', $cellRef, $matches[2]); + if ($pCellParent !== null && $this->spreadsheet !== null) { $cellSheet = $this->spreadsheet->getSheetByName($matches[2]); if ($cellSheet && $cellSheet->cellExists($cellRef)) { $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]), false); - $pCell->attach($pCellParent); + $cell->attach($pCellParent); } else { - $cellValue = null; + $cellRef = ($cellSheet !== null) ? "'{$matches[2]}'!{$cellRef}" : $cellRef; + $cellValue = ($cellSheet !== null) ? null : Information\ExcelError::REF(); } } else { return $this->raiseFormulaError('Unable to access Cell Reference'); } - $this->debugLog->writeDebugLog('Evaluation Result for cell ', $cellRef, ' in worksheet ', $matches[2], ' is ', $this->showTypeDetails($cellValue)); + $this->debugLog->writeDebugLog('Evaluation Result for cell %s in worksheet %s is %s', $cellRef, $matches[2], $this->showTypeDetails($cellValue)); } else { - $this->debugLog->writeDebugLog('Evaluating Cell ', $cellRef, ' in current worksheet'); - if ($pCellParent->has($cellRef)) { + $this->debugLog->writeDebugLog('Evaluating Cell %s in current worksheet', $cellRef); + if ($pCellParent !== null && $pCellParent->has($cellRef)) { $cellValue = $this->extractCellRange($cellRef, $pCellWorksheet, false); - $pCell->attach($pCellParent); + $cell->attach($pCellParent); } else { $cellValue = null; } - $this->debugLog->writeDebugLog('Evaluation Result for cell ', $cellRef, ' is ', $this->showTypeDetails($cellValue)); + $this->debugLog->writeDebugLog('Evaluation Result for cell %s is %s', $cellRef, $this->showTypeDetails($cellValue)); } } } - $stack->push('Value', $cellValue, $cellRef); + + $stack->push('Cell Value', $cellValue, $cellRef); if (isset($storeKey)) { $branchStore[$storeKey] = $cellValue; } - + } elseif (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $token ?? '', $matches)) { // if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on - } elseif (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $token, $matches)) { - if ($pCellParent) { - $pCell->attach($pCellParent); - } - if (($cellID == 'AC99') || (isset($pCell) && $pCell->getCoordinate() == 'AC99')) { - if (defined('RESOLVING')) { - define('RESOLVING2', true); - } else { - define('RESOLVING', true); - } + if ($cell !== null && $pCellParent !== null) { + $cell->attach($pCellParent); } $functionName = $matches[1]; $argCount = $stack->pop(); $argCount = $argCount['value']; - if ($functionName != 'MKMATRIX') { - $this->debugLog->writeDebugLog('Evaluating Function ', self::localeFunc($functionName), '() with ', (($argCount == 0) ? 'no' : $argCount), ' argument', (($argCount == 1) ? '' : 's')); + if ($functionName !== 'MKMATRIX') { + $this->debugLog->writeDebugLog('Evaluating Function %s() with %s argument%s', self::localeFunc($functionName), (($argCount == 0) ? 'no' : $argCount), (($argCount == 1) ? '' : 's')); } if ((isset(self::$phpSpreadsheetFunctions[$functionName])) || (isset(self::$controlFunctions[$functionName]))) { // function + $passByReference = false; + $passCellReference = false; + $functionCall = null; if (isset(self::$phpSpreadsheetFunctions[$functionName])) { $functionCall = self::$phpSpreadsheetFunctions[$functionName]['functionCall']; $passByReference = isset(self::$phpSpreadsheetFunctions[$functionName]['passByReference']); @@ -4169,28 +5060,33 @@ class Calculation $passByReference = isset(self::$controlFunctions[$functionName]['passByReference']); $passCellReference = isset(self::$controlFunctions[$functionName]['passCellReference']); } + // get the arguments for this function $args = $argArrayVals = []; + $emptyArguments = []; for ($i = 0; $i < $argCount; ++$i) { $arg = $stack->pop(); $a = $argCount - $i - 1; - if (($passByReference) && + if ( + ($passByReference) && (isset(self::$phpSpreadsheetFunctions[$functionName]['passByReference'][$a])) && - (self::$phpSpreadsheetFunctions[$functionName]['passByReference'][$a])) { + (self::$phpSpreadsheetFunctions[$functionName]['passByReference'][$a]) + ) { if ($arg['reference'] === null) { $args[] = $cellID; - if ($functionName != 'MKMATRIX') { + if ($functionName !== 'MKMATRIX') { $argArrayVals[] = $this->showValue($cellID); } } else { $args[] = $arg['reference']; - if ($functionName != 'MKMATRIX') { + if ($functionName !== 'MKMATRIX') { $argArrayVals[] = $this->showValue($arg['reference']); } } } else { + $emptyArguments[] = ($arg['type'] === 'Empty Argument'); $args[] = self::unwrapResult($arg['value']); - if ($functionName != 'MKMATRIX') { + if ($functionName !== 'MKMATRIX') { $argArrayVals[] = $this->showValue($arg['value']); } } @@ -4198,21 +5094,26 @@ class Calculation // Reverse the order of the arguments krsort($args); + krsort($emptyArguments); + + if ($argCount > 0) { + $args = $this->addDefaultArgumentValues($functionCall, $args, $emptyArguments); + } if (($passByReference) && ($argCount == 0)) { $args[] = $cellID; $argArrayVals[] = $this->showValue($cellID); } - if ($functionName != 'MKMATRIX') { + if ($functionName !== 'MKMATRIX') { if ($this->debugLog->getWriteDebugLog()) { krsort($argArrayVals); - $this->debugLog->writeDebugLog('Evaluating ', self::localeFunc($functionName), '( ', implode(self::$localeArgumentSeparator . ' ', Functions::flattenArray($argArrayVals)), ' )'); + $this->debugLog->writeDebugLog('Evaluating %s ( %s )', self::localeFunc($functionName), implode(self::$localeArgumentSeparator . ' ', Functions::flattenArray($argArrayVals))); } } // Process the argument with the appropriate function call - $args = $this->addCellReference($args, $passCellReference, $functionCall, $pCell); + $args = $this->addCellReference($args, $passCellReference, $functionCall, $cell); if (!is_array($functionCall)) { foreach ($args as &$arg) { @@ -4223,8 +5124,8 @@ class Calculation $result = call_user_func_array($functionCall, $args); - if ($functionName != 'MKMATRIX') { - $this->debugLog->writeDebugLog('Evaluation Result for ', self::localeFunc($functionName), '() function call is ', $this->showTypeDetails($result)); + if ($functionName !== 'MKMATRIX') { + $this->debugLog->writeDebugLog('Evaluation Result for %s() function call is %s', self::localeFunc($functionName), $this->showTypeDetails($result)); } $stack->push('Value', self::wrapResult($result)); if (isset($storeKey)) { @@ -4233,32 +5134,37 @@ class Calculation } } else { // if the token is a number, boolean, string or an Excel error, push it onto the stack - if (isset(self::$excelConstants[strtoupper($token)])) { + if (isset(self::$excelConstants[strtoupper($token ?? '')])) { $excelConstant = strtoupper($token); $stack->push('Constant Value', self::$excelConstants[$excelConstant]); if (isset($storeKey)) { $branchStore[$storeKey] = self::$excelConstants[$excelConstant]; } - $this->debugLog->writeDebugLog('Evaluating Constant ', $excelConstant, ' as ', $this->showTypeDetails(self::$excelConstants[$excelConstant])); - } elseif ((is_numeric($token)) || ($token === null) || (is_bool($token)) || ($token == '') || ($token[0] == '"') || ($token[0] == '#')) { - $stack->push('Value', $token); + $this->debugLog->writeDebugLog('Evaluating Constant %s as %s', $excelConstant, $this->showTypeDetails(self::$excelConstants[$excelConstant])); + } elseif ((is_numeric($token)) || ($token === null) || (is_bool($token)) || ($token == '') || ($token[0] == self::FORMULA_STRING_QUOTE) || ($token[0] == '#')) { + $stack->push($tokenData['type'], $token, $tokenData['reference']); if (isset($storeKey)) { $branchStore[$storeKey] = $token; } - // if the token is a named range, push the named range name onto the stack - } elseif (preg_match('/^' . self::CALCULATION_REGEXP_NAMEDRANGE . '$/i', $token, $matches)) { - $namedRange = $matches[6]; - $this->debugLog->writeDebugLog('Evaluating Named Range ', $namedRange); + } elseif (preg_match('/^' . self::CALCULATION_REGEXP_DEFINEDNAME . '$/miu', $token, $matches)) { + // if the token is a named range or formula, evaluate it and push the result onto the stack + $definedName = $matches[6]; + if ($cell === null || $pCellWorksheet === null) { + return $this->raiseFormulaError("undefined name '$token'"); + } - $cellValue = $this->extractNamedRange($namedRange, ((null !== $pCell) ? $pCellWorksheet : null), false); - $pCell->attach($pCellParent); - $this->debugLog->writeDebugLog('Evaluation Result for named range ', $namedRange, ' is ', $this->showTypeDetails($cellValue)); - $stack->push('Named Range', $cellValue, $namedRange); + $this->debugLog->writeDebugLog('Evaluating Defined Name %s', $definedName); + $namedRange = DefinedName::resolveName($definedName, $pCellWorksheet); + if ($namedRange === null) { + return $this->raiseFormulaError("undefined name '$definedName'"); + } + + $result = $this->evaluateDefinedName($cell, $namedRange, $pCellWorksheet, $stack); if (isset($storeKey)) { - $branchStore[$storeKey] = $cellValue; + $branchStore[$storeKey] = $result; } } else { - return $this->raiseFormulaError("undefined variable '$token'"); + return $this->raiseFormulaError("undefined name '$token'"); } } } @@ -4272,6 +5178,12 @@ class Calculation return $output; } + /** + * @param mixed $operand + * @param mixed $stack + * + * @return bool + */ private function validateBinaryOperand(&$operand, &$stack) { if (is_array($operand)) { @@ -4285,7 +5197,7 @@ class Calculation if (is_string($operand)) { // We only need special validations for the operand if it is a string // Start by stripping off the quotation marks we use to identify true excel string values internally - if ($operand > '' && $operand[0] == '"') { + if ($operand > '' && $operand[0] == self::FORMULA_STRING_QUOTE) { $operand = self::unwrapResult($operand); } // If the string is a numeric value, we treat it as a numeric, so no further testing @@ -4293,274 +5205,242 @@ class Calculation // If not a numeric, test to see if the value is an Excel error, and so can't be used in normal binary operations if ($operand > '' && $operand[0] == '#') { $stack->push('Value', $operand); - $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($operand)); + $this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($operand)); return false; - } elseif (!Shared\StringHelper::convertToNumberIfFraction($operand)) { - // If not a numeric or a fraction, then it's a text string, and so can't be used in mathematical binary operations - $stack->push('Value', '#VALUE!'); - $this->debugLog->writeDebugLog('Evaluation Result is a ', $this->showTypeDetails('#VALUE!')); + } elseif (Engine\FormattedNumber::convertToNumberIfFormatted($operand) === false) { + // If not a numeric, a fraction or a percentage, then it's a text string, and so can't be used in mathematical binary operations + $stack->push('Error', '#VALUE!'); + $this->debugLog->writeDebugLog('Evaluation Result is a %s', $this->showTypeDetails('#VALUE!')); return false; } } } - // return a true if the value of the operand is one that we can use in normal binary operations + // return a true if the value of the operand is one that we can use in normal binary mathematical operations return true; } /** - * @param null|string $cellID * @param mixed $operand1 * @param mixed $operand2 * @param string $operation - * @param Stack $stack + * + * @return array + */ + private function executeArrayComparison($operand1, $operand2, $operation, Stack &$stack, bool $recursingArrays) + { + $result = []; + if (!is_array($operand2)) { + // Operand 1 is an array, Operand 2 is a scalar + foreach ($operand1 as $x => $operandData) { + $this->debugLog->writeDebugLog('Evaluating Comparison %s %s %s', $this->showValue($operandData), $operation, $this->showValue($operand2)); + $this->executeBinaryComparisonOperation($operandData, $operand2, $operation, $stack); + $r = $stack->pop(); + $result[$x] = $r['value']; + } + } elseif (!is_array($operand1)) { + // Operand 1 is a scalar, Operand 2 is an array + foreach ($operand2 as $x => $operandData) { + $this->debugLog->writeDebugLog('Evaluating Comparison %s %s %s', $this->showValue($operand1), $operation, $this->showValue($operandData)); + $this->executeBinaryComparisonOperation($operand1, $operandData, $operation, $stack); + $r = $stack->pop(); + $result[$x] = $r['value']; + } + } else { + // Operand 1 and Operand 2 are both arrays + if (!$recursingArrays) { + self::checkMatrixOperands($operand1, $operand2, 2); + } + foreach ($operand1 as $x => $operandData) { + $this->debugLog->writeDebugLog('Evaluating Comparison %s %s %s', $this->showValue($operandData), $operation, $this->showValue($operand2[$x])); + $this->executeBinaryComparisonOperation($operandData, $operand2[$x], $operation, $stack, true); + $r = $stack->pop(); + $result[$x] = $r['value']; + } + } + // Log the result details + $this->debugLog->writeDebugLog('Comparison Evaluation Result is %s', $this->showTypeDetails($result)); + // And push the result onto the stack + $stack->push('Array', $result); + + return $result; + } + + /** + * @param mixed $operand1 + * @param mixed $operand2 + * @param string $operation * @param bool $recursingArrays * * @return mixed */ - private function executeBinaryComparisonOperation($cellID, $operand1, $operand2, $operation, Stack &$stack, $recursingArrays = false) + private function executeBinaryComparisonOperation($operand1, $operand2, $operation, Stack &$stack, $recursingArrays = false) { // If we're dealing with matrix operations, we want a matrix result if ((is_array($operand1)) || (is_array($operand2))) { - $result = []; - if ((is_array($operand1)) && (!is_array($operand2))) { - foreach ($operand1 as $x => $operandData) { - $this->debugLog->writeDebugLog('Evaluating Comparison ', $this->showValue($operandData), ' ', $operation, ' ', $this->showValue($operand2)); - $this->executeBinaryComparisonOperation($cellID, $operandData, $operand2, $operation, $stack); - $r = $stack->pop(); - $result[$x] = $r['value']; - } - } elseif ((!is_array($operand1)) && (is_array($operand2))) { - foreach ($operand2 as $x => $operandData) { - $this->debugLog->writeDebugLog('Evaluating Comparison ', $this->showValue($operand1), ' ', $operation, ' ', $this->showValue($operandData)); - $this->executeBinaryComparisonOperation($cellID, $operand1, $operandData, $operation, $stack); - $r = $stack->pop(); - $result[$x] = $r['value']; - } - } else { - if (!$recursingArrays) { - self::checkMatrixOperands($operand1, $operand2, 2); - } - foreach ($operand1 as $x => $operandData) { - $this->debugLog->writeDebugLog('Evaluating Comparison ', $this->showValue($operandData), ' ', $operation, ' ', $this->showValue($operand2[$x])); - $this->executeBinaryComparisonOperation($cellID, $operandData, $operand2[$x], $operation, $stack, true); - $r = $stack->pop(); - $result[$x] = $r['value']; - } - } - // Log the result details - $this->debugLog->writeDebugLog('Comparison Evaluation Result is ', $this->showTypeDetails($result)); - // And push the result onto the stack - $stack->push('Array', $result); - - return $result; + return $this->executeArrayComparison($operand1, $operand2, $operation, $stack, $recursingArrays); } - // Simple validate the two operands if they are string values - if (is_string($operand1) && $operand1 > '' && $operand1[0] == '"') { - $operand1 = self::unwrapResult($operand1); - } - if (is_string($operand2) && $operand2 > '' && $operand2[0] == '"') { - $operand2 = self::unwrapResult($operand2); - } - - // Use case insensitive comparaison if not OpenOffice mode - if (Functions::getCompatibilityMode() != Functions::COMPATIBILITY_OPENOFFICE) { - if (is_string($operand1)) { - $operand1 = strtoupper($operand1); - } - if (is_string($operand2)) { - $operand2 = strtoupper($operand2); - } - } - - $useLowercaseFirstComparison = is_string($operand1) && is_string($operand2) && Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE; - - // execute the necessary operation - switch ($operation) { - // Greater than - case '>': - if ($useLowercaseFirstComparison) { - $result = $this->strcmpLowercaseFirst($operand1, $operand2) > 0; - } else { - $result = ($operand1 > $operand2); - } - - break; - // Less than - case '<': - if ($useLowercaseFirstComparison) { - $result = $this->strcmpLowercaseFirst($operand1, $operand2) < 0; - } else { - $result = ($operand1 < $operand2); - } - - break; - // Equality - case '=': - if (is_numeric($operand1) && is_numeric($operand2)) { - $result = (abs($operand1 - $operand2) < $this->delta); - } else { - $result = strcmp($operand1, $operand2) == 0; - } - - break; - // Greater than or equal - case '>=': - if (is_numeric($operand1) && is_numeric($operand2)) { - $result = ((abs($operand1 - $operand2) < $this->delta) || ($operand1 > $operand2)); - } elseif ($useLowercaseFirstComparison) { - $result = $this->strcmpLowercaseFirst($operand1, $operand2) >= 0; - } else { - $result = strcmp($operand1, $operand2) >= 0; - } - - break; - // Less than or equal - case '<=': - if (is_numeric($operand1) && is_numeric($operand2)) { - $result = ((abs($operand1 - $operand2) < $this->delta) || ($operand1 < $operand2)); - } elseif ($useLowercaseFirstComparison) { - $result = $this->strcmpLowercaseFirst($operand1, $operand2) <= 0; - } else { - $result = strcmp($operand1, $operand2) <= 0; - } - - break; - // Inequality - case '<>': - if (is_numeric($operand1) && is_numeric($operand2)) { - $result = (abs($operand1 - $operand2) > 1E-14); - } else { - $result = strcmp($operand1, $operand2) != 0; - } - - break; - } + $result = BinaryComparison::compare($operand1, $operand2, $operation); // Log the result details - $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result)); + $this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($result)); // And push the result onto the stack $stack->push('Value', $result); return $result; } - /** - * Compare two strings in the same way as strcmp() except that lowercase come before uppercase letters. - * - * @param string $str1 First string value for the comparison - * @param string $str2 Second string value for the comparison - * - * @return int - */ - private function strcmpLowercaseFirst($str1, $str2) - { - $inversedStr1 = Shared\StringHelper::strCaseReverse($str1); - $inversedStr2 = Shared\StringHelper::strCaseReverse($str2); - - return strcmp($inversedStr1, $inversedStr2); - } - /** * @param mixed $operand1 * @param mixed $operand2 - * @param mixed $operation - * @param string $matrixFunction - * @param mixed $stack + * @param string $operation + * @param Stack $stack * * @return bool|mixed */ - private function executeNumericBinaryOperation($operand1, $operand2, $operation, $matrixFunction, &$stack) + private function executeNumericBinaryOperation($operand1, $operand2, $operation, &$stack) { // Validate the two operands - if (!$this->validateBinaryOperand($operand1, $stack)) { - return false; - } - if (!$this->validateBinaryOperand($operand2, $stack)) { + if ( + ($this->validateBinaryOperand($operand1, $stack) === false) || + ($this->validateBinaryOperand($operand2, $stack) === false) + ) { return false; } - // If either of the operands is a matrix, we need to treat them both as matrices - // (converting the other operand to a matrix if need be); then perform the required - // matrix operation - if ((is_array($operand1)) || (is_array($operand2))) { - // Ensure that both operands are arrays/matrices of the same size - self::checkMatrixOperands($operand1, $operand2, 2); - - try { - // Convert operand 1 from a PHP array to a matrix - $matrix = new Shared\JAMA\Matrix($operand1); - // Perform the required operation against the operand 1 matrix, passing in operand 2 - $matrixResult = $matrix->$matrixFunction($operand2); - $result = $matrixResult->getArray(); - } catch (\Exception $ex) { - $this->debugLog->writeDebugLog('JAMA Matrix Exception: ', $ex->getMessage()); - $result = '#VALUE!'; - } - } else { - if ((Functions::getCompatibilityMode() != Functions::COMPATIBILITY_OPENOFFICE) && - ((is_string($operand1) && !is_numeric($operand1) && strlen($operand1) > 0) || - (is_string($operand2) && !is_numeric($operand2) && strlen($operand2) > 0))) { - $result = Functions::VALUE(); - } else { - // If we're dealing with non-matrix operations, execute the necessary operation - switch ($operation) { - // Addition - case '+': - $result = $operand1 + $operand2; - - break; - // Subtraction - case '-': - $result = $operand1 - $operand2; - - break; - // Multiplication - case '*': - $result = $operand1 * $operand2; - - break; - // Division - case '/': - if ($operand2 == 0) { - // Trap for Divide by Zero error - $stack->push('Value', '#DIV/0!'); - $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails('#DIV/0!')); - - return false; - } - $result = $operand1 / $operand2; - - break; - // Power - case '^': - $result = pow($operand1, $operand2); - - break; + if ( + (Functions::getCompatibilityMode() != Functions::COMPATIBILITY_OPENOFFICE) && + ((is_string($operand1) && !is_numeric($operand1) && strlen($operand1) > 0) || + (is_string($operand2) && !is_numeric($operand2) && strlen($operand2) > 0)) + ) { + $result = Information\ExcelError::VALUE(); + } elseif (is_array($operand1) || is_array($operand2)) { + // Ensure that both operands are arrays/matrices + if (is_array($operand1)) { + foreach ($operand1 as $key => $value) { + $operand1[$key] = Functions::flattenArray($value); } } + if (is_array($operand2)) { + foreach ($operand2 as $key => $value) { + $operand2[$key] = Functions::flattenArray($value); + } + } + [$rows, $columns] = self::checkMatrixOperands($operand1, $operand2, 2); + + for ($row = 0; $row < $rows; ++$row) { + for ($column = 0; $column < $columns; ++$column) { + if ($operand1[$row][$column] === null) { + $operand1[$row][$column] = 0; + } elseif (!is_numeric($operand1[$row][$column])) { + $operand1[$row][$column] = Information\ExcelError::VALUE(); + + continue; + } + if ($operand2[$row][$column] === null) { + $operand2[$row][$column] = 0; + } elseif (!is_numeric($operand2[$row][$column])) { + $operand1[$row][$column] = Information\ExcelError::VALUE(); + + continue; + } + switch ($operation) { + case '+': + $operand1[$row][$column] += $operand2[$row][$column]; + + break; + case '-': + $operand1[$row][$column] -= $operand2[$row][$column]; + + break; + case '*': + $operand1[$row][$column] *= $operand2[$row][$column]; + + break; + case '/': + if ($operand2[$row][$column] == 0) { + $operand1[$row][$column] = Information\ExcelError::DIV0(); + } else { + $operand1[$row][$column] /= $operand2[$row][$column]; + } + + break; + case '^': + $operand1[$row][$column] = $operand1[$row][$column] ** $operand2[$row][$column]; + + break; + + default: + throw new Exception('Unsupported numeric binary operation'); + } + } + } + $result = $operand1; + } else { + // If we're dealing with non-matrix operations, execute the necessary operation + switch ($operation) { + // Addition + case '+': + $result = $operand1 + $operand2; + + break; + // Subtraction + case '-': + $result = $operand1 - $operand2; + + break; + // Multiplication + case '*': + $result = $operand1 * $operand2; + + break; + // Division + case '/': + if ($operand2 == 0) { + // Trap for Divide by Zero error + $stack->push('Error', Information\ExcelError::DIV0()); + $this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails(Information\ExcelError::DIV0())); + + return false; + } + $result = $operand1 / $operand2; + + break; + // Power + case '^': + $result = $operand1 ** $operand2; + + break; + + default: + throw new Exception('Unsupported numeric binary operation'); + } } // Log the result details - $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result)); + $this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($result)); // And push the result onto the stack $stack->push('Value', $result); return $result; } - // trigger an error, but nicely, if need be - protected function raiseFormulaError($errorMessage) + /** + * Trigger an error, but nicely, if need be. + * + * @return false + */ + protected function raiseFormulaError(string $errorMessage) { $this->formulaError = $errorMessage; $this->cyclicReferenceStack->clear(); - if (!$this->suppressFormulaErrors) { + $suppress = /** @scrutinizer ignore-deprecated */ $this->suppressFormulaErrors ?? $this->suppressFormulaErrorsNew; + if (!$suppress) { throw new Exception($errorMessage); } - trigger_error($errorMessage, E_USER_ERROR); return false; } @@ -4568,46 +5448,45 @@ class Calculation /** * Extract range values. * - * @param string &$pRange String based range representation - * @param Worksheet $pSheet Worksheet + * @param string $range String based range representation + * @param Worksheet $worksheet Worksheet * @param bool $resetLog Flag indicating whether calculation log should be reset or not * * @return mixed Array of values in range if range contains more than one element. Otherwise, a single value is returned. */ - public function extractCellRange(&$pRange = 'A1', Worksheet $pSheet = null, $resetLog = true) + public function extractCellRange(&$range = 'A1', ?Worksheet $worksheet = null, $resetLog = true) { // Return value $returnValue = []; - if ($pSheet !== null) { - $pSheetName = $pSheet->getTitle(); - if (strpos($pRange, '!') !== false) { - [$pSheetName, $pRange] = Worksheet::extractSheetTitle($pRange, true); - $pSheet = $this->spreadsheet->getSheetByName($pSheetName); + if ($worksheet !== null) { + $worksheetName = $worksheet->getTitle(); + + if (strpos($range, '!') !== false) { + [$worksheetName, $range] = Worksheet::extractSheetTitle($range, true); + $worksheet = ($this->spreadsheet === null) ? null : $this->spreadsheet->getSheetByName($worksheetName); } // Extract range - $aReferences = Coordinate::extractAllCellReferencesInRange($pRange); - $pRange = $pSheetName . '!' . $pRange; + $aReferences = Coordinate::extractAllCellReferencesInRange($range); + $range = "'" . $worksheetName . "'" . '!' . $range; + $currentCol = ''; + $currentRow = 0; if (!isset($aReferences[1])) { - $currentCol = ''; - $currentRow = 0; // Single cell in range sscanf($aReferences[0], '%[A-Z]%d', $currentCol, $currentRow); - if ($pSheet->cellExists($aReferences[0])) { - $returnValue[$currentRow][$currentCol] = $pSheet->getCell($aReferences[0])->getCalculatedValue($resetLog); + if ($worksheet !== null && $worksheet->cellExists($aReferences[0])) { + $returnValue[$currentRow][$currentCol] = $worksheet->getCell($aReferences[0])->getCalculatedValue($resetLog); } else { $returnValue[$currentRow][$currentCol] = null; } } else { // Extract cell data for all cells in the range foreach ($aReferences as $reference) { - $currentCol = ''; - $currentRow = 0; // Extract range sscanf($reference, '%[A-Z]%d', $currentCol, $currentRow); - if ($pSheet->cellExists($reference)) { - $returnValue[$currentRow][$currentCol] = $pSheet->getCell($reference)->getCalculatedValue($resetLog); + if ($worksheet !== null && $worksheet->cellExists($reference)) { + $returnValue[$currentRow][$currentCol] = $worksheet->getCell($reference)->getCalculatedValue($resetLog); } else { $returnValue[$currentRow][$currentCol] = null; } @@ -4621,47 +5500,46 @@ class Calculation /** * Extract range values. * - * @param string &$pRange String based range representation - * @param Worksheet $pSheet Worksheet + * @param string $range String based range representation + * @param null|Worksheet $worksheet Worksheet * @param bool $resetLog Flag indicating whether calculation log should be reset or not * * @return mixed Array of values in range if range contains more than one element. Otherwise, a single value is returned. */ - public function extractNamedRange(&$pRange = 'A1', Worksheet $pSheet = null, $resetLog = true) + public function extractNamedRange(string &$range = 'A1', ?Worksheet $worksheet = null, $resetLog = true) { // Return value $returnValue = []; - if ($pSheet !== null) { - $pSheetName = $pSheet->getTitle(); - if (strpos($pRange, '!') !== false) { - [$pSheetName, $pRange] = Worksheet::extractSheetTitle($pRange, true); - $pSheet = $this->spreadsheet->getSheetByName($pSheetName); + if ($worksheet !== null) { + if (strpos($range, '!') !== false) { + [$worksheetName, $range] = Worksheet::extractSheetTitle($range, true); + $worksheet = ($this->spreadsheet === null) ? null : $this->spreadsheet->getSheetByName($worksheetName); } // Named range? - $namedRange = NamedRange::resolveRange($pRange, $pSheet); - if ($namedRange !== null) { - $pSheet = $namedRange->getWorksheet(); - $pRange = $namedRange->getRange(); - $splitRange = Coordinate::splitRange($pRange); - // Convert row and column references - if (ctype_alpha($splitRange[0][0])) { - $pRange = $splitRange[0][0] . '1:' . $splitRange[0][1] . $namedRange->getWorksheet()->getHighestRow(); - } elseif (ctype_digit($splitRange[0][0])) { - $pRange = 'A' . $splitRange[0][0] . ':' . $namedRange->getWorksheet()->getHighestColumn() . $splitRange[0][1]; - } - } else { - return Functions::REF(); + $namedRange = ($worksheet === null) ? null : DefinedName::resolveName($range, $worksheet); + if ($namedRange === null) { + return Information\ExcelError::REF(); + } + + $worksheet = $namedRange->getWorksheet(); + $range = $namedRange->getValue(); + $splitRange = Coordinate::splitRange($range); + // Convert row and column references + if ($worksheet !== null && ctype_alpha($splitRange[0][0])) { + $range = $splitRange[0][0] . '1:' . $splitRange[0][1] . $worksheet->getHighestRow(); + } elseif ($worksheet !== null && ctype_digit($splitRange[0][0])) { + $range = 'A' . $splitRange[0][0] . ':' . $worksheet->getHighestColumn() . $splitRange[0][1]; } // Extract range - $aReferences = Coordinate::extractAllCellReferencesInRange($pRange); + $aReferences = Coordinate::extractAllCellReferencesInRange($range); if (!isset($aReferences[1])) { // Single cell (or single column or row) in range [$currentCol, $currentRow] = Coordinate::coordinateFromString($aReferences[0]); - if ($pSheet->cellExists($aReferences[0])) { - $returnValue[$currentRow][$currentCol] = $pSheet->getCell($aReferences[0])->getCalculatedValue($resetLog); + if ($worksheet !== null && $worksheet->cellExists($aReferences[0])) { + $returnValue[$currentRow][$currentCol] = $worksheet->getCell($aReferences[0])->getCalculatedValue($resetLog); } else { $returnValue[$currentRow][$currentCol] = null; } @@ -4670,8 +5548,8 @@ class Calculation foreach ($aReferences as $reference) { // Extract range [$currentCol, $currentRow] = Coordinate::coordinateFromString($reference); - if ($pSheet->cellExists($reference)) { - $returnValue[$currentRow][$currentCol] = $pSheet->getCell($reference)->getCalculatedValue($resetLog); + if ($worksheet !== null && $worksheet->cellExists($reference)) { + $returnValue[$currentRow][$currentCol] = $worksheet->getCell($reference)->getCalculatedValue($resetLog); } else { $returnValue[$currentRow][$currentCol] = null; } @@ -4685,24 +5563,22 @@ class Calculation /** * Is a specific function implemented? * - * @param string $pFunction Function Name + * @param string $function Function Name * * @return bool */ - public function isImplemented($pFunction) + public function isImplemented($function) { - $pFunction = strtoupper($pFunction); - $notImplemented = !isset(self::$phpSpreadsheetFunctions[$pFunction]) || (is_array(self::$phpSpreadsheetFunctions[$pFunction]['functionCall']) && self::$phpSpreadsheetFunctions[$pFunction]['functionCall'][1] === 'DUMMY'); + $function = strtoupper($function); + $notImplemented = !isset(self::$phpSpreadsheetFunctions[$function]) || (is_array(self::$phpSpreadsheetFunctions[$function]['functionCall']) && self::$phpSpreadsheetFunctions[$function]['functionCall'][1] === 'DUMMY'); return !$notImplemented; } /** * Get a list of all implemented functions as an array of function objects. - * - * @return array of Category */ - public function getFunctions() + public static function getFunctions(): array { return self::$phpSpreadsheetFunctions; } @@ -4724,56 +5600,167 @@ class Calculation return $returnValue; } + private function addDefaultArgumentValues(array $functionCall, array $args, array $emptyArguments): array + { + $reflector = new ReflectionMethod(implode('::', $functionCall)); + $methodArguments = $reflector->getParameters(); + + if (count($methodArguments) > 0) { + // Apply any defaults for empty argument values + foreach ($emptyArguments as $argumentId => $isArgumentEmpty) { + if ($isArgumentEmpty === true) { + $reflectedArgumentId = count($args) - (int) $argumentId - 1; + if ( + !array_key_exists($reflectedArgumentId, $methodArguments) || + $methodArguments[$reflectedArgumentId]->isVariadic() + ) { + break; + } + + $args[$argumentId] = $this->getArgumentDefaultValue($methodArguments[$reflectedArgumentId]); + } + } + } + + return $args; + } + + /** + * @return null|mixed + */ + private function getArgumentDefaultValue(ReflectionParameter $methodArgument) + { + $defaultValue = null; + + if ($methodArgument->isDefaultValueAvailable()) { + $defaultValue = $methodArgument->getDefaultValue(); + if ($methodArgument->isDefaultValueConstant()) { + $constantName = $methodArgument->getDefaultValueConstantName() ?? ''; + // read constant value + if (strpos($constantName, '::') !== false) { + [$className, $constantName] = explode('::', $constantName); + $constantReflector = new ReflectionClassConstant($className, $constantName); + + return $constantReflector->getValue(); + } + + return constant($constantName); + } + } + + return $defaultValue; + } + /** * Add cell reference if needed while making sure that it is the last argument. * - * @param array $args * @param bool $passCellReference * @param array|string $functionCall - * @param null|Cell $pCell * * @return array */ - private function addCellReference(array $args, $passCellReference, $functionCall, Cell $pCell = null) + private function addCellReference(array $args, $passCellReference, $functionCall, ?Cell $cell = null) { if ($passCellReference) { if (is_array($functionCall)) { $className = $functionCall[0]; $methodName = $functionCall[1]; - $reflectionMethod = new \ReflectionMethod($className, $methodName); + $reflectionMethod = new ReflectionMethod($className, $methodName); $argumentCount = count($reflectionMethod->getParameters()); while (count($args) < $argumentCount - 1) { $args[] = null; } } - $args[] = $pCell; + $args[] = $cell; } return $args; } - private function getUnusedBranchStoreKey() + /** + * @return mixed|string + */ + private function evaluateDefinedName(Cell $cell, DefinedName $namedRange, Worksheet $cellWorksheet, Stack $stack) { - $storeKeyValue = 'storeKey-' . $this->branchStoreKeyCounter; - ++$this->branchStoreKeyCounter; + $definedNameScope = $namedRange->getScope(); + if ($definedNameScope !== null && $definedNameScope !== $cellWorksheet) { + // The defined name isn't in our current scope, so #REF + $result = Information\ExcelError::REF(); + $stack->push('Error', $result, $namedRange->getName()); - return $storeKeyValue; + return $result; + } + + $definedNameValue = $namedRange->getValue(); + $definedNameType = $namedRange->isFormula() ? 'Formula' : 'Range'; + $definedNameWorksheet = $namedRange->getWorksheet(); + + if ($definedNameValue[0] !== '=') { + $definedNameValue = '=' . $definedNameValue; + } + + $this->debugLog->writeDebugLog('Defined Name is a %s with a value of %s', $definedNameType, $definedNameValue); + + $recursiveCalculationCell = ($definedNameWorksheet !== null && $definedNameWorksheet !== $cellWorksheet) + ? $definedNameWorksheet->getCell('A1') + : $cell; + $recursiveCalculationCellAddress = $recursiveCalculationCell->getCoordinate(); + + // Adjust relative references in ranges and formulae so that we execute the calculation for the correct rows and columns + $definedNameValue = self::$referenceHelper->updateFormulaReferencesAnyWorksheet( + $definedNameValue, + Coordinate::columnIndexFromString($cell->getColumn()) - 1, + $cell->getRow() - 1 + ); + + $this->debugLog->writeDebugLog('Value adjusted for relative references is %s', $definedNameValue); + + $recursiveCalculator = new self($this->spreadsheet); + $recursiveCalculator->getDebugLog()->setWriteDebugLog($this->getDebugLog()->getWriteDebugLog()); + $recursiveCalculator->getDebugLog()->setEchoDebugLog($this->getDebugLog()->getEchoDebugLog()); + $result = $recursiveCalculator->_calculateFormulaValue($definedNameValue, $recursiveCalculationCellAddress, $recursiveCalculationCell); + + if ($this->getDebugLog()->getWriteDebugLog()) { + $this->debugLog->mergeDebugLog(array_slice($recursiveCalculator->getDebugLog()->getLog(), 3)); + $this->debugLog->writeDebugLog('Evaluation Result for Named %s %s is %s', $definedNameType, $namedRange->getName(), $this->showTypeDetails($result)); + } + + $stack->push('Defined Name', $result, $namedRange->getName()); + + return $result; } - private function getTokensAsString($tokens) + public function setSuppressFormulaErrors(bool $suppressFormulaErrors): void { - $tokensStr = array_map(function ($token) { - $value = $token['value'] ?? 'no value'; - while (is_array($value)) { - $value = array_pop($value); - } + $this->suppressFormulaErrorsNew = $suppressFormulaErrors; + } - return $value; - }, $tokens); - $str = '[ ' . implode(' | ', $tokensStr) . ' ]'; + public function getSuppressFormulaErrors(): bool + { + return $this->suppressFormulaErrorsNew; + } - return $str; + /** @param mixed $arg */ + private static function doNothing($arg): bool + { + return (bool) $arg; + } + + /** + * @param mixed $operand1 + * + * @return mixed + */ + private static function boolToString($operand1) + { + if (is_bool($operand1)) { + $operand1 = ($operand1) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE']; + } elseif ($operand1 === null) { + $operand1 = ''; + } + + return $operand1; } } diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Category.php b/PhpOffice/PhpSpreadsheet/Calculation/Category.php old mode 100755 new mode 100644 index 7574cb4..b661faf --- a/PhpOffice/PhpSpreadsheet/Calculation/Category.php +++ b/PhpOffice/PhpSpreadsheet/Calculation/Category.php @@ -16,4 +16,6 @@ abstract class Category const CATEGORY_MATH_AND_TRIG = 'Math and Trig'; const CATEGORY_STATISTICAL = 'Statistical'; const CATEGORY_TEXT_AND_DATA = 'Text and Data'; + const CATEGORY_WEB = 'Web'; + const CATEGORY_UNCATEGORISED = 'Uncategorised'; } diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Database.php b/PhpOffice/PhpSpreadsheet/Calculation/Database.php old mode 100755 new mode 100644 index d31b00d..017c571 --- a/PhpOffice/PhpSpreadsheet/Calculation/Database.php +++ b/PhpOffice/PhpSpreadsheet/Calculation/Database.php @@ -2,126 +2,13 @@ namespace PhpOffice\PhpSpreadsheet\Calculation; +/** + * @deprecated 1.17.0 + * + * @codeCoverageIgnore + */ class Database { - /** - * fieldExtract. - * - * Extracts the column ID to use for the data field. - * - * @param mixed[] $database The range of cells that makes up the list or database. - * A database is a list of related data in which rows of related - * information are records, and columns of data are fields. The - * first row of the list contains labels for each column. - * @param mixed $field Indicates which column is used in the function. Enter the - * column label enclosed between double quotation marks, such as - * "Age" or "Yield," or a number (without quotation marks) that - * represents the position of the column within the list: 1 for - * the first column, 2 for the second column, and so on. - * - * @return null|string - */ - private static function fieldExtract($database, $field) - { - $field = strtoupper(Functions::flattenSingleValue($field)); - $fieldNames = array_map('strtoupper', array_shift($database)); - - if (is_numeric($field)) { - $keys = array_keys($fieldNames); - - return $keys[$field - 1]; - } - $key = array_search($field, $fieldNames); - - return ($key) ? $key : null; - } - - /** - * filter. - * - * Parses the selection criteria, extracts the database rows that match those criteria, and - * returns that subset of rows. - * - * @param mixed[] $database The range of cells that makes up the list or database. - * A database is a list of related data in which rows of related - * information are records, and columns of data are fields. The - * first row of the list contains labels for each column. - * @param mixed[] $criteria The range of cells that contains the conditions you specify. - * You can use any range for the criteria argument, as long as it - * includes at least one column label and at least one cell below - * the column label in which you specify a condition for the - * column. - * - * @return array of mixed - */ - private static function filter($database, $criteria) - { - $fieldNames = array_shift($database); - $criteriaNames = array_shift($criteria); - - // Convert the criteria into a set of AND/OR conditions with [:placeholders] - $testConditions = $testValues = []; - $testConditionsCount = 0; - foreach ($criteriaNames as $key => $criteriaName) { - $testCondition = []; - $testConditionCount = 0; - foreach ($criteria as $row => $criterion) { - if ($criterion[$key] > '') { - $testCondition[] = '[:' . $criteriaName . ']' . Functions::ifCondition($criterion[$key]); - ++$testConditionCount; - } - } - if ($testConditionCount > 1) { - $testConditions[] = 'OR(' . implode(',', $testCondition) . ')'; - ++$testConditionsCount; - } elseif ($testConditionCount == 1) { - $testConditions[] = $testCondition[0]; - ++$testConditionsCount; - } - } - - if ($testConditionsCount > 1) { - $testConditionSet = 'AND(' . implode(',', $testConditions) . ')'; - } elseif ($testConditionsCount == 1) { - $testConditionSet = $testConditions[0]; - } - - // Loop through each row of the database - foreach ($database as $dataRow => $dataValues) { - // Substitute actual values from the database row for our [:placeholders] - $testConditionList = $testConditionSet; - foreach ($criteriaNames as $key => $criteriaName) { - $k = array_search($criteriaName, $fieldNames); - if (isset($dataValues[$k])) { - $dataValue = $dataValues[$k]; - $dataValue = (is_string($dataValue)) ? Calculation::wrapResult(strtoupper($dataValue)) : $dataValue; - $testConditionList = str_replace('[:' . $criteriaName . ']', $dataValue, $testConditionList); - } - } - // evaluate the criteria against the row data - $result = Calculation::getInstance()->_calculateFormulaValue('=' . $testConditionList); - // If the row failed to meet the criteria, remove it from the database - if (!$result) { - unset($database[$dataRow]); - } - } - - return $database; - } - - private static function getFilteredColumn($database, $field, $criteria) - { - // reduce the database to a set of rows that match all the criteria - $database = self::filter($database, $criteria); - // extract an array of values for the requested column - $colData = []; - foreach ($database as $row) { - $colData[] = $row[$field]; - } - - return $colData; - } - /** * DAVERAGE. * @@ -130,7 +17,9 @@ class Database * Excel Function: * DAVERAGE(database,field,criteria) * - * @category Database Functions + * @deprecated 1.17.0 + * Use the evaluate() method in the Database\DAverage class instead + * @see Database\DAverage::evaluate() * * @param mixed[] $database The range of cells that makes up the list or database. * A database is a list of related data in which rows of related @@ -151,15 +40,7 @@ class Database */ public static function DAVERAGE($database, $field, $criteria) { - $field = self::fieldExtract($database, $field); - if ($field === null) { - return null; - } - - // Return - return Statistical::AVERAGE( - self::getFilteredColumn($database, $field, $criteria) - ); + return Database\DAverage::evaluate($database, $field, $criteria); } /** @@ -171,16 +52,15 @@ class Database * Excel Function: * DCOUNT(database,[field],criteria) * - * Excel Function: - * DAVERAGE(database,field,criteria) - * - * @category Database Functions + * @deprecated 1.17.0 + * Use the evaluate() method in the Database\DCount class instead + * @see Database\DCount::evaluate() * * @param mixed[] $database The range of cells that makes up the list or database. * A database is a list of related data in which rows of related * information are records, and columns of data are fields. The * first row of the list contains labels for each column. - * @param int|string $field Indicates which column is used in the function. Enter the + * @param null|int|string $field Indicates which column is used in the function. Enter the * column label enclosed between double quotation marks, such as * "Age" or "Yield," or a number (without quotation marks) that * represents the position of the column within the list: 1 for @@ -191,22 +71,14 @@ class Database * the column label in which you specify a condition for the * column. * - * @return int + * @return int|string * * @TODO The field argument is optional. If field is omitted, DCOUNT counts all records in the * database that match the criteria. */ public static function DCOUNT($database, $field, $criteria) { - $field = self::fieldExtract($database, $field); - if ($field === null) { - return null; - } - - // Return - return Statistical::COUNT( - self::getFilteredColumn($database, $field, $criteria) - ); + return Database\DCount::evaluate($database, $field, $criteria); } /** @@ -217,7 +89,9 @@ class Database * Excel Function: * DCOUNTA(database,[field],criteria) * - * @category Database Functions + * @deprecated 1.17.0 + * Use the evaluate() method in the Database\DCountA class instead + * @see Database\DCountA::evaluate() * * @param mixed[] $database The range of cells that makes up the list or database. * A database is a list of related data in which rows of related @@ -234,30 +108,11 @@ class Database * the column label in which you specify a condition for the * column. * - * @return int - * - * @TODO The field argument is optional. If field is omitted, DCOUNTA counts all records in the - * database that match the criteria. + * @return int|string */ public static function DCOUNTA($database, $field, $criteria) { - $field = self::fieldExtract($database, $field); - if ($field === null) { - return null; - } - - // reduce the database to a set of rows that match all the criteria - $database = self::filter($database, $criteria); - // extract an array of values for the requested column - $colData = []; - foreach ($database as $row) { - $colData[] = $row[$field]; - } - - // Return - return Statistical::COUNTA( - self::getFilteredColumn($database, $field, $criteria) - ); + return Database\DCountA::evaluate($database, $field, $criteria); } /** @@ -269,7 +124,9 @@ class Database * Excel Function: * DGET(database,field,criteria) * - * @category Database Functions + * @deprecated 1.17.0 + * Use the evaluate() method in the Database\DGet class instead + * @see Database\DGet::evaluate() * * @param mixed[] $database The range of cells that makes up the list or database. * A database is a list of related data in which rows of related @@ -290,18 +147,7 @@ class Database */ public static function DGET($database, $field, $criteria) { - $field = self::fieldExtract($database, $field); - if ($field === null) { - return null; - } - - // Return - $colData = self::getFilteredColumn($database, $field, $criteria); - if (count($colData) > 1) { - return Functions::NAN(); - } - - return $colData[0]; + return Database\DGet::evaluate($database, $field, $criteria); } /** @@ -313,7 +159,9 @@ class Database * Excel Function: * DMAX(database,field,criteria) * - * @category Database Functions + * @deprecated 1.17.0 + * Use the evaluate() method in the Database\DMax class instead + * @see Database\DMax::evaluate() * * @param mixed[] $database The range of cells that makes up the list or database. * A database is a list of related data in which rows of related @@ -330,19 +178,11 @@ class Database * the column label in which you specify a condition for the * column. * - * @return float + * @return null|float|string */ public static function DMAX($database, $field, $criteria) { - $field = self::fieldExtract($database, $field); - if ($field === null) { - return null; - } - - // Return - return Statistical::MAX( - self::getFilteredColumn($database, $field, $criteria) - ); + return Database\DMax::evaluate($database, $field, $criteria); } /** @@ -354,7 +194,9 @@ class Database * Excel Function: * DMIN(database,field,criteria) * - * @category Database Functions + * @deprecated 1.17.0 + * Use the evaluate() method in the Database\DMin class instead + * @see Database\DMin::evaluate() * * @param mixed[] $database The range of cells that makes up the list or database. * A database is a list of related data in which rows of related @@ -371,19 +213,11 @@ class Database * the column label in which you specify a condition for the * column. * - * @return float + * @return null|float|string */ public static function DMIN($database, $field, $criteria) { - $field = self::fieldExtract($database, $field); - if ($field === null) { - return null; - } - - // Return - return Statistical::MIN( - self::getFilteredColumn($database, $field, $criteria) - ); + return Database\DMin::evaluate($database, $field, $criteria); } /** @@ -394,7 +228,9 @@ class Database * Excel Function: * DPRODUCT(database,field,criteria) * - * @category Database Functions + * @deprecated 1.17.0 + * Use the evaluate() method in the Database\DProduct class instead + * @see Database\DProduct::evaluate() * * @param mixed[] $database The range of cells that makes up the list or database. * A database is a list of related data in which rows of related @@ -411,19 +247,11 @@ class Database * the column label in which you specify a condition for the * column. * - * @return float + * @return float|string */ public static function DPRODUCT($database, $field, $criteria) { - $field = self::fieldExtract($database, $field); - if ($field === null) { - return null; - } - - // Return - return MathTrig::PRODUCT( - self::getFilteredColumn($database, $field, $criteria) - ); + return Database\DProduct::evaluate($database, $field, $criteria); } /** @@ -435,7 +263,9 @@ class Database * Excel Function: * DSTDEV(database,field,criteria) * - * @category Database Functions + * @deprecated 1.17.0 + * Use the evaluate() method in the Database\DStDev class instead + * @see Database\DStDev::evaluate() * * @param mixed[] $database The range of cells that makes up the list or database. * A database is a list of related data in which rows of related @@ -456,15 +286,7 @@ class Database */ public static function DSTDEV($database, $field, $criteria) { - $field = self::fieldExtract($database, $field); - if ($field === null) { - return null; - } - - // Return - return Statistical::STDEV( - self::getFilteredColumn($database, $field, $criteria) - ); + return Database\DStDev::evaluate($database, $field, $criteria); } /** @@ -476,7 +298,9 @@ class Database * Excel Function: * DSTDEVP(database,field,criteria) * - * @category Database Functions + * @deprecated 1.17.0 + * Use the evaluate() method in the Database\DStDevP class instead + * @see Database\DStDevP::evaluate() * * @param mixed[] $database The range of cells that makes up the list or database. * A database is a list of related data in which rows of related @@ -497,15 +321,7 @@ class Database */ public static function DSTDEVP($database, $field, $criteria) { - $field = self::fieldExtract($database, $field); - if ($field === null) { - return null; - } - - // Return - return Statistical::STDEVP( - self::getFilteredColumn($database, $field, $criteria) - ); + return Database\DStDevP::evaluate($database, $field, $criteria); } /** @@ -516,7 +332,9 @@ class Database * Excel Function: * DSUM(database,field,criteria) * - * @category Database Functions + * @deprecated 1.17.0 + * Use the evaluate() method in the Database\DSum class instead + * @see Database\DSum::evaluate() * * @param mixed[] $database The range of cells that makes up the list or database. * A database is a list of related data in which rows of related @@ -533,19 +351,11 @@ class Database * the column label in which you specify a condition for the * column. * - * @return float + * @return null|float|string */ public static function DSUM($database, $field, $criteria) { - $field = self::fieldExtract($database, $field); - if ($field === null) { - return null; - } - - // Return - return MathTrig::SUM( - self::getFilteredColumn($database, $field, $criteria) - ); + return Database\DSum::evaluate($database, $field, $criteria); } /** @@ -557,7 +367,9 @@ class Database * Excel Function: * DVAR(database,field,criteria) * - * @category Database Functions + * @deprecated 1.17.0 + * Use the evaluate() method in the Database\DVar class instead + * @see Database\DVar::evaluate() * * @param mixed[] $database The range of cells that makes up the list or database. * A database is a list of related data in which rows of related @@ -574,19 +386,11 @@ class Database * the column label in which you specify a condition for the * column. * - * @return float + * @return float|string (string if result is an error) */ public static function DVAR($database, $field, $criteria) { - $field = self::fieldExtract($database, $field); - if ($field === null) { - return null; - } - - // Return - return Statistical::VARFunc( - self::getFilteredColumn($database, $field, $criteria) - ); + return Database\DVar::evaluate($database, $field, $criteria); } /** @@ -598,7 +402,9 @@ class Database * Excel Function: * DVARP(database,field,criteria) * - * @category Database Functions + * @deprecated 1.17.0 + * Use the evaluate() method in the Database\DVarP class instead + * @see Database\DVarP::evaluate() * * @param mixed[] $database The range of cells that makes up the list or database. * A database is a list of related data in which rows of related @@ -615,18 +421,10 @@ class Database * the column label in which you specify a condition for the * column. * - * @return float + * @return float|string (string if result is an error) */ public static function DVARP($database, $field, $criteria) { - $field = self::fieldExtract($database, $field); - if ($field === null) { - return null; - } - - // Return - return Statistical::VARP( - self::getFilteredColumn($database, $field, $criteria) - ); + return Database\DVarP::evaluate($database, $field, $criteria); } } diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Database/DAverage.php b/PhpOffice/PhpSpreadsheet/Calculation/Database/DAverage.php new file mode 100644 index 0000000..245e970 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Database/DAverage.php @@ -0,0 +1,46 @@ + 1) { + return ExcelError::NAN(); + } + + $row = array_pop($columnData); + + return array_pop($row); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Database/DMax.php b/PhpOffice/PhpSpreadsheet/Calculation/Database/DMax.php new file mode 100644 index 0000000..748fd2f --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Database/DMax.php @@ -0,0 +1,47 @@ += count($fieldNames)) { + return null; + } + + return $field; + } + $key = array_search($field, array_values($fieldNames), true); + + return ($key !== false) ? (int) $key : null; + } + + /** + * filter. + * + * Parses the selection criteria, extracts the database rows that match those criteria, and + * returns that subset of rows. + * + * @param mixed[] $database The range of cells that makes up the list or database. + * A database is a list of related data in which rows of related + * information are records, and columns of data are fields. The + * first row of the list contains labels for each column. + * @param mixed[] $criteria The range of cells that contains the conditions you specify. + * You can use any range for the criteria argument, as long as it + * includes at least one column label and at least one cell below + * the column label in which you specify a condition for the + * column. + * + * @return mixed[] + */ + protected static function filter(array $database, array $criteria): array + { + $fieldNames = array_shift($database); + $criteriaNames = array_shift($criteria); + + // Convert the criteria into a set of AND/OR conditions with [:placeholders] + $query = self::buildQuery($criteriaNames, $criteria); + + // Loop through each row of the database + return self::executeQuery($database, $query, $criteriaNames, $fieldNames); + } + + protected static function getFilteredColumn(array $database, ?int $field, array $criteria): array + { + // reduce the database to a set of rows that match all the criteria + $database = self::filter($database, $criteria); + $defaultReturnColumnValue = ($field === null) ? 1 : null; + + // extract an array of values for the requested column + $columnData = []; + foreach ($database as $rowKey => $row) { + $keys = array_keys($row); + $key = $keys[$field] ?? null; + $columnKey = $key ?? 'A'; + $columnData[$rowKey][$columnKey] = $row[$key] ?? $defaultReturnColumnValue; + } + + return $columnData; + } + + private static function buildQuery(array $criteriaNames, array $criteria): string + { + $baseQuery = []; + foreach ($criteria as $key => $criterion) { + foreach ($criterion as $field => $value) { + $criterionName = $criteriaNames[$field]; + if ($value !== null) { + $condition = self::buildCondition($value, $criterionName); + $baseQuery[$key][] = $condition; + } + } + } + + $rowQuery = array_map( + function ($rowValue) { + return (count($rowValue) > 1) ? 'AND(' . implode(',', $rowValue) . ')' : ($rowValue[0] ?? ''); + }, + $baseQuery + ); + + return (count($rowQuery) > 1) ? 'OR(' . implode(',', $rowQuery) . ')' : ($rowQuery[0] ?? ''); + } + + /** + * @param mixed $criterion + */ + private static function buildCondition($criterion, string $criterionName): string + { + $ifCondition = Functions::ifCondition($criterion); + + // Check for wildcard characters used in the condition + $result = preg_match('/(?[^"]*)(?".*[*?].*")/ui', $ifCondition, $matches); + if ($result !== 1) { + return "[:{$criterionName}]{$ifCondition}"; + } + + $trueFalse = ($matches['operator'] !== '<>'); + $wildcard = WildcardMatch::wildcard($matches['operand']); + $condition = "WILDCARDMATCH([:{$criterionName}],{$wildcard})"; + if ($trueFalse === false) { + $condition = "NOT({$condition})"; + } + + return $condition; + } + + private static function executeQuery(array $database, string $query, array $criteria, array $fields): array + { + foreach ($database as $dataRow => $dataValues) { + // Substitute actual values from the database row for our [:placeholders] + $conditions = $query; + foreach ($criteria as $criterion) { + $conditions = self::processCondition($criterion, $fields, $dataValues, $conditions); + } + + // evaluate the criteria against the row data + $result = Calculation::getInstance()->_calculateFormulaValue('=' . $conditions); + + // If the row failed to meet the criteria, remove it from the database + if ($result !== true) { + unset($database[$dataRow]); + } + } + + return $database; + } + + /** + * @return mixed + */ + private static function processCondition(string $criterion, array $fields, array $dataValues, string $conditions) + { + $key = array_search($criterion, $fields, true); + + $dataValue = 'NULL'; + if (is_bool($dataValues[$key])) { + $dataValue = ($dataValues[$key]) ? 'TRUE' : 'FALSE'; + } elseif ($dataValues[$key] !== null) { + $dataValue = $dataValues[$key]; + // escape quotes if we have a string containing quotes + if (is_string($dataValue) && strpos($dataValue, '"') !== false) { + $dataValue = str_replace('"', '""', $dataValue); + } + $dataValue = (is_string($dataValue)) ? Calculation::wrapResult(strtoupper($dataValue)) : $dataValue; + } + + return str_replace('[:' . $criterion . ']', $dataValue, $conditions); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/DateTime.php b/PhpOffice/PhpSpreadsheet/Calculation/DateTime.php old mode 100755 new mode 100644 index 796e795..26b667c --- a/PhpOffice/PhpSpreadsheet/Calculation/DateTime.php +++ b/PhpOffice/PhpSpreadsheet/Calculation/DateTime.php @@ -2,129 +2,47 @@ namespace PhpOffice\PhpSpreadsheet\Calculation; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PhpOffice\PhpSpreadsheet\Shared\StringHelper; +use DateTimeInterface; +/** + * @deprecated 1.18.0 + */ class DateTime { /** * Identify if a year is a leap year or not. * + * @deprecated 1.18.0 + * Use the isLeapYear method in the DateTimeExcel\Helpers class instead + * @see DateTimeExcel\Helpers::isLeapYear() + * * @param int|string $year The year to test * * @return bool TRUE if the year is a leap year, otherwise FALSE */ public static function isLeapYear($year) { - return (($year % 4) === 0) && (($year % 100) !== 0) || (($year % 400) === 0); - } - - /** - * Return the number of days between two dates based on a 360 day calendar. - * - * @param int $startDay Day of month of the start date - * @param int $startMonth Month of the start date - * @param int $startYear Year of the start date - * @param int $endDay Day of month of the start date - * @param int $endMonth Month of the start date - * @param int $endYear Year of the start date - * @param bool $methodUS Whether to use the US method or the European method of calculation - * - * @return int Number of days between the start date and the end date - */ - private static function dateDiff360($startDay, $startMonth, $startYear, $endDay, $endMonth, $endYear, $methodUS) - { - if ($startDay == 31) { - --$startDay; - } elseif ($methodUS && ($startMonth == 2 && ($startDay == 29 || ($startDay == 28 && !self::isLeapYear($startYear))))) { - $startDay = 30; - } - if ($endDay == 31) { - if ($methodUS && $startDay != 30) { - $endDay = 1; - if ($endMonth == 12) { - ++$endYear; - $endMonth = 1; - } else { - ++$endMonth; - } - } else { - $endDay = 30; - } - } - - return $endDay + $endMonth * 30 + $endYear * 360 - $startDay - $startMonth * 30 - $startYear * 360; + return DateTimeExcel\Helpers::isLeapYear($year); } /** * getDateValue. * - * @param string $dateValue + * @deprecated 1.18.0 + * Use the getDateValue method in the DateTimeExcel\Helpers class instead + * @see DateTimeExcel\Helpers::getDateValue() + * + * @param mixed $dateValue * * @return mixed Excel date/time serial value, or string if error */ public static function getDateValue($dateValue) { - if (!is_numeric($dateValue)) { - if ((is_string($dateValue)) && - (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC)) { - return Functions::VALUE(); - } - if ((is_object($dateValue)) && ($dateValue instanceof \DateTimeInterface)) { - $dateValue = Date::PHPToExcel($dateValue); - } else { - $saveReturnDateType = Functions::getReturnDateType(); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - $dateValue = self::DATEVALUE($dateValue); - Functions::setReturnDateType($saveReturnDateType); - } + try { + return DateTimeExcel\Helpers::getDateValue($dateValue); + } catch (Exception $e) { + return $e->getMessage(); } - - return $dateValue; - } - - /** - * getTimeValue. - * - * @param string $timeValue - * - * @return mixed Excel date/time serial value, or string if error - */ - private static function getTimeValue($timeValue) - { - $saveReturnDateType = Functions::getReturnDateType(); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - $timeValue = self::TIMEVALUE($timeValue); - Functions::setReturnDateType($saveReturnDateType); - - return $timeValue; - } - - private static function adjustDateByMonths($dateValue = 0, $adjustmentMonths = 0) - { - // Execute function - $PHPDateObject = Date::excelToDateTimeObject($dateValue); - $oMonth = (int) $PHPDateObject->format('m'); - $oYear = (int) $PHPDateObject->format('Y'); - - $adjustmentMonthsString = (string) $adjustmentMonths; - if ($adjustmentMonths > 0) { - $adjustmentMonthsString = '+' . $adjustmentMonths; - } - if ($adjustmentMonths != 0) { - $PHPDateObject->modify($adjustmentMonthsString . ' months'); - } - $nMonth = (int) $PHPDateObject->format('m'); - $nYear = (int) $PHPDateObject->format('Y'); - - $monthDiff = ($nMonth - $oMonth) + (($nYear - $oYear) * 12); - if ($monthDiff != $adjustmentMonths) { - $adjustDays = (int) $PHPDateObject->format('d'); - $adjustDaysString = '-' . $adjustDays . ' days'; - $PHPDateObject->modify($adjustDaysString); - } - - return $PHPDateObject; } /** @@ -141,33 +59,16 @@ class DateTime * Excel Function: * NOW() * - * @category Date/Time Functions + * @deprecated 1.18.0 + * Use the now method in the DateTimeExcel\Current class instead + * @see DateTimeExcel\Current::now() * * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, * depending on the value of the ReturnDateType flag */ public static function DATETIMENOW() { - $saveTimeZone = date_default_timezone_get(); - date_default_timezone_set('UTC'); - $retValue = false; - switch (Functions::getReturnDateType()) { - case Functions::RETURNDATE_EXCEL: - $retValue = (float) Date::PHPToExcel(time()); - - break; - case Functions::RETURNDATE_UNIX_TIMESTAMP: - $retValue = (int) time(); - - break; - case Functions::RETURNDATE_PHP_DATETIME_OBJECT: - $retValue = new \DateTime(); - - break; - } - date_default_timezone_set($saveTimeZone); - - return $retValue; + return DateTimeExcel\Current::now(); } /** @@ -184,34 +85,16 @@ class DateTime * Excel Function: * TODAY() * - * @category Date/Time Functions + * @deprecated 1.18.0 + * Use the today method in the DateTimeExcel\Current class instead + * @see DateTimeExcel\Current::today() * * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, * depending on the value of the ReturnDateType flag */ public static function DATENOW() { - $saveTimeZone = date_default_timezone_get(); - date_default_timezone_set('UTC'); - $retValue = false; - $excelDateTime = floor(Date::PHPToExcel(time())); - switch (Functions::getReturnDateType()) { - case Functions::RETURNDATE_EXCEL: - $retValue = (float) $excelDateTime; - - break; - case Functions::RETURNDATE_UNIX_TIMESTAMP: - $retValue = (int) Date::excelToTimestamp($excelDateTime); - - break; - case Functions::RETURNDATE_PHP_DATETIME_OBJECT: - $retValue = Date::excelToDateTimeObject($excelDateTime); - - break; - } - date_default_timezone_set($saveTimeZone); - - return $retValue; + return DateTimeExcel\Current::today(); } /** @@ -222,15 +105,18 @@ class DateTime * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date * format of your regional settings. PhpSpreadsheet does not change cell formatting in this way. * + * * Excel Function: * DATE(year,month,day) * + * @deprecated 1.18.0 + * Use the fromYMD method in the DateTimeExcel\Date class instead + * @see DateTimeExcel\Date::fromYMD() + * * PhpSpreadsheet is a lot more forgiving than MS Excel when passing non numeric values to this function. * A Month name or abbreviation (English only at this point) such as 'January' or 'Jan' will still be accepted, * as will a day value with a suffix (e.g. '21st' rather than simply 21); again only English language. * - * @category Date/Time Functions - * * @param int $year The value of the year argument can include one to four digits. * Excel interprets the year argument according to the configured * date system: 1900 or 1904. @@ -267,69 +153,7 @@ class DateTime */ public static function DATE($year = 0, $month = 1, $day = 1) { - $year = Functions::flattenSingleValue($year); - $month = Functions::flattenSingleValue($month); - $day = Functions::flattenSingleValue($day); - - if (($month !== null) && (!is_numeric($month))) { - $month = Date::monthStringToNumber($month); - } - - if (($day !== null) && (!is_numeric($day))) { - $day = Date::dayStringToNumber($day); - } - - $year = ($year !== null) ? StringHelper::testStringAsNumeric($year) : 0; - $month = ($month !== null) ? StringHelper::testStringAsNumeric($month) : 0; - $day = ($day !== null) ? StringHelper::testStringAsNumeric($day) : 0; - if ((!is_numeric($year)) || - (!is_numeric($month)) || - (!is_numeric($day))) { - return Functions::VALUE(); - } - $year = (int) $year; - $month = (int) $month; - $day = (int) $day; - - $baseYear = Date::getExcelCalendar(); - // Validate parameters - if ($year < ($baseYear - 1900)) { - return Functions::NAN(); - } - if ((($baseYear - 1900) != 0) && ($year < $baseYear) && ($year >= 1900)) { - return Functions::NAN(); - } - - if (($year < $baseYear) && ($year >= ($baseYear - 1900))) { - $year += 1900; - } - - if ($month < 1) { - // Handle year/month adjustment if month < 1 - --$month; - $year += ceil($month / 12) - 1; - $month = 13 - abs($month % 12); - } elseif ($month > 12) { - // Handle year/month adjustment if month > 12 - $year += floor($month / 12); - $month = ($month % 12); - } - - // Re-validate the year parameter after adjustments - if (($year < $baseYear) || ($year >= 10000)) { - return Functions::NAN(); - } - - // Execute function - $excelDateValue = Date::formattedPHPToExcel($year, $month, $day); - switch (Functions::getReturnDateType()) { - case Functions::RETURNDATE_EXCEL: - return (float) $excelDateValue; - case Functions::RETURNDATE_UNIX_TIMESTAMP: - return (int) Date::excelToTimestamp($excelDateValue); - case Functions::RETURNDATE_PHP_DATETIME_OBJECT: - return Date::excelToDateTimeObject($excelDateValue); - } + return DateTimeExcel\Date::fromYMD($year, $month, $day); } /** @@ -343,7 +167,9 @@ class DateTime * Excel Function: * TIME(hour,minute,second) * - * @category Date/Time Functions + * @deprecated 1.18.0 + * Use the fromHMS method in the DateTimeExcel\Time class instead + * @see DateTimeExcel\Time::fromHMS() * * @param int $hour A number from 0 (zero) to 32767 representing the hour. * Any value greater than 23 will be divided by 24 and the remainder @@ -362,85 +188,7 @@ class DateTime */ public static function TIME($hour = 0, $minute = 0, $second = 0) { - $hour = Functions::flattenSingleValue($hour); - $minute = Functions::flattenSingleValue($minute); - $second = Functions::flattenSingleValue($second); - - if ($hour == '') { - $hour = 0; - } - if ($minute == '') { - $minute = 0; - } - if ($second == '') { - $second = 0; - } - - if ((!is_numeric($hour)) || (!is_numeric($minute)) || (!is_numeric($second))) { - return Functions::VALUE(); - } - $hour = (int) $hour; - $minute = (int) $minute; - $second = (int) $second; - - if ($second < 0) { - $minute += floor($second / 60); - $second = 60 - abs($second % 60); - if ($second == 60) { - $second = 0; - } - } elseif ($second >= 60) { - $minute += floor($second / 60); - $second = $second % 60; - } - if ($minute < 0) { - $hour += floor($minute / 60); - $minute = 60 - abs($minute % 60); - if ($minute == 60) { - $minute = 0; - } - } elseif ($minute >= 60) { - $hour += floor($minute / 60); - $minute = $minute % 60; - } - - if ($hour > 23) { - $hour = $hour % 24; - } elseif ($hour < 0) { - return Functions::NAN(); - } - - // Execute function - switch (Functions::getReturnDateType()) { - case Functions::RETURNDATE_EXCEL: - $date = 0; - $calendar = Date::getExcelCalendar(); - if ($calendar != Date::CALENDAR_WINDOWS_1900) { - $date = 1; - } - - return (float) Date::formattedPHPToExcel($calendar, 1, $date, $hour, $minute, $second); - case Functions::RETURNDATE_UNIX_TIMESTAMP: - return (int) Date::excelToTimestamp(Date::formattedPHPToExcel(1970, 1, 1, $hour, $minute, $second)); // -2147468400; // -2147472000 + 3600 - case Functions::RETURNDATE_PHP_DATETIME_OBJECT: - $dayAdjust = 0; - if ($hour < 0) { - $dayAdjust = floor($hour / 24); - $hour = 24 - abs($hour % 24); - if ($hour == 24) { - $hour = 0; - } - } elseif ($hour >= 24) { - $dayAdjust = floor($hour / 24); - $hour = $hour % 24; - } - $phpDateObject = new \DateTime('1900-01-01 ' . $hour . ':' . $minute . ':' . $second); - if ($dayAdjust != 0) { - $phpDateObject->modify($dayAdjust . ' days'); - } - - return $phpDateObject; - } + return DateTimeExcel\Time::fromHMS($hour, $minute, $second); } /** @@ -456,7 +204,9 @@ class DateTime * Excel Function: * DATEVALUE(dateValue) * - * @category Date/Time Functions + * @deprecated 1.18.0 + * Use the fromString method in the DateTimeExcel\DateValue class instead + * @see DateTimeExcel\DateValue::fromString() * * @param string $dateValue Text that represents a date in a Microsoft Excel date format. * For example, "1/30/2008" or "30-Jan-2008" are text strings within @@ -470,112 +220,9 @@ class DateTime * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, * depending on the value of the ReturnDateType flag */ - public static function DATEVALUE($dateValue = 1) + public static function DATEVALUE($dateValue) { - $dateValue = trim(Functions::flattenSingleValue($dateValue), '"'); - // Strip any ordinals because they're allowed in Excel (English only) - $dateValue = preg_replace('/(\d)(st|nd|rd|th)([ -\/])/Ui', '$1$3', $dateValue); - // Convert separators (/ . or space) to hyphens (should also handle dot used for ordinals in some countries, e.g. Denmark, Germany) - $dateValue = str_replace(['/', '.', '-', ' '], ' ', $dateValue); - - $yearFound = false; - $t1 = explode(' ', $dateValue); - foreach ($t1 as &$t) { - if ((is_numeric($t)) && ($t > 31)) { - if ($yearFound) { - return Functions::VALUE(); - } - if ($t < 100) { - $t += 1900; - } - $yearFound = true; - } - } - if ((count($t1) == 1) && (strpos($t, ':') != false)) { - // We've been fed a time value without any date - return 0.0; - } elseif (count($t1) == 2) { - // We only have two parts of the date: either day/month or month/year - if ($yearFound) { - array_unshift($t1, 1); - } else { - if ($t1[1] > 29) { - $t1[1] += 1900; - array_unshift($t1, 1); - } else { - $t1[] = date('Y'); - } - } - } - unset($t); - $dateValue = implode(' ', $t1); - - $PHPDateArray = date_parse($dateValue); - if (($PHPDateArray === false) || ($PHPDateArray['error_count'] > 0)) { - $testVal1 = strtok($dateValue, '- '); - if ($testVal1 !== false) { - $testVal2 = strtok('- '); - if ($testVal2 !== false) { - $testVal3 = strtok('- '); - if ($testVal3 === false) { - $testVal3 = strftime('%Y'); - } - } else { - return Functions::VALUE(); - } - } else { - return Functions::VALUE(); - } - if ($testVal1 < 31 && $testVal2 < 12 && $testVal3 < 12 && strlen($testVal3) == 2) { - $testVal3 += 2000; - } - $PHPDateArray = date_parse($testVal1 . '-' . $testVal2 . '-' . $testVal3); - if (($PHPDateArray === false) || ($PHPDateArray['error_count'] > 0)) { - $PHPDateArray = date_parse($testVal2 . '-' . $testVal1 . '-' . $testVal3); - if (($PHPDateArray === false) || ($PHPDateArray['error_count'] > 0)) { - return Functions::VALUE(); - } - } - } - - if (($PHPDateArray !== false) && ($PHPDateArray['error_count'] == 0)) { - // Execute function - if ($PHPDateArray['year'] == '') { - $PHPDateArray['year'] = strftime('%Y'); - } - if ($PHPDateArray['year'] < 1900) { - return Functions::VALUE(); - } - if ($PHPDateArray['month'] == '') { - $PHPDateArray['month'] = strftime('%m'); - } - if ($PHPDateArray['day'] == '') { - $PHPDateArray['day'] = strftime('%d'); - } - if (!checkdate($PHPDateArray['month'], $PHPDateArray['day'], $PHPDateArray['year'])) { - return Functions::VALUE(); - } - $excelDateValue = floor( - Date::formattedPHPToExcel( - $PHPDateArray['year'], - $PHPDateArray['month'], - $PHPDateArray['day'], - $PHPDateArray['hour'], - $PHPDateArray['minute'], - $PHPDateArray['second'] - ) - ); - switch (Functions::getReturnDateType()) { - case Functions::RETURNDATE_EXCEL: - return (float) $excelDateValue; - case Functions::RETURNDATE_UNIX_TIMESTAMP: - return (int) Date::excelToTimestamp($excelDateValue); - case Functions::RETURNDATE_PHP_DATETIME_OBJECT: - return new \DateTime($PHPDateArray['year'] . '-' . $PHPDateArray['month'] . '-' . $PHPDateArray['day'] . ' 00:00:00'); - } - } - - return Functions::VALUE(); + return DateTimeExcel\DateValue::fromString($dateValue); } /** @@ -591,7 +238,9 @@ class DateTime * Excel Function: * TIMEVALUE(timeValue) * - * @category Date/Time Functions + * @deprecated 1.18.0 + * Use the fromString method in the DateTimeExcel\TimeValue class instead + * @see DateTimeExcel\TimeValue::fromString() * * @param string $timeValue A text string that represents a time in any one of the Microsoft * Excel time formats; for example, "6:45 PM" and "18:45" text strings @@ -603,163 +252,30 @@ class DateTime */ public static function TIMEVALUE($timeValue) { - $timeValue = trim(Functions::flattenSingleValue($timeValue), '"'); - $timeValue = str_replace(['/', '.'], '-', $timeValue); - - $arraySplit = preg_split('/[\/:\-\s]/', $timeValue); - if ((count($arraySplit) == 2 || count($arraySplit) == 3) && $arraySplit[0] > 24) { - $arraySplit[0] = ($arraySplit[0] % 24); - $timeValue = implode(':', $arraySplit); - } - - $PHPDateArray = date_parse($timeValue); - if (($PHPDateArray !== false) && ($PHPDateArray['error_count'] == 0)) { - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) { - $excelDateValue = Date::formattedPHPToExcel( - $PHPDateArray['year'], - $PHPDateArray['month'], - $PHPDateArray['day'], - $PHPDateArray['hour'], - $PHPDateArray['minute'], - $PHPDateArray['second'] - ); - } else { - $excelDateValue = Date::formattedPHPToExcel(1900, 1, 1, $PHPDateArray['hour'], $PHPDateArray['minute'], $PHPDateArray['second']) - 1; - } - - switch (Functions::getReturnDateType()) { - case Functions::RETURNDATE_EXCEL: - return (float) $excelDateValue; - case Functions::RETURNDATE_UNIX_TIMESTAMP: - return (int) $phpDateValue = Date::excelToTimestamp($excelDateValue + 25569) - 3600; - case Functions::RETURNDATE_PHP_DATETIME_OBJECT: - return new \DateTime('1900-01-01 ' . $PHPDateArray['hour'] . ':' . $PHPDateArray['minute'] . ':' . $PHPDateArray['second']); - } - } - - return Functions::VALUE(); + return DateTimeExcel\TimeValue::fromString($timeValue); } /** * DATEDIF. * + * Excel Function: + * DATEDIF(startdate, enddate, unit) + * + * @deprecated 1.18.0 + * Use the interval method in the DateTimeExcel\Difference class instead + * @see DateTimeExcel\Difference::interval() + * * @param mixed $startDate Excel date serial value, PHP date/time stamp, PHP DateTime object * or a standard date string * @param mixed $endDate Excel date serial value, PHP date/time stamp, PHP DateTime object * or a standard date string - * @param string $unit + * @param array|string $unit * - * @return int|string Interval between the dates + * @return array|int|string Interval between the dates */ public static function DATEDIF($startDate = 0, $endDate = 0, $unit = 'D') { - $startDate = Functions::flattenSingleValue($startDate); - $endDate = Functions::flattenSingleValue($endDate); - $unit = strtoupper(Functions::flattenSingleValue($unit)); - - if (is_string($startDate = self::getDateValue($startDate))) { - return Functions::VALUE(); - } - if (is_string($endDate = self::getDateValue($endDate))) { - return Functions::VALUE(); - } - - // Validate parameters - if ($startDate > $endDate) { - return Functions::NAN(); - } - - // Execute function - $difference = $endDate - $startDate; - - $PHPStartDateObject = Date::excelToDateTimeObject($startDate); - $startDays = $PHPStartDateObject->format('j'); - $startMonths = $PHPStartDateObject->format('n'); - $startYears = $PHPStartDateObject->format('Y'); - - $PHPEndDateObject = Date::excelToDateTimeObject($endDate); - $endDays = $PHPEndDateObject->format('j'); - $endMonths = $PHPEndDateObject->format('n'); - $endYears = $PHPEndDateObject->format('Y'); - - switch ($unit) { - case 'D': - $retVal = (int) $difference; - - break; - case 'M': - $retVal = (int) ($endMonths - $startMonths) + ((int) ($endYears - $startYears) * 12); - // We're only interested in full months - if ($endDays < $startDays) { - --$retVal; - } - - break; - case 'Y': - $retVal = (int) ($endYears - $startYears); - // We're only interested in full months - if ($endMonths < $startMonths) { - --$retVal; - } elseif (($endMonths == $startMonths) && ($endDays < $startDays)) { - // Remove start month - --$retVal; - // Remove end month - --$retVal; - } - - break; - case 'MD': - if ($endDays < $startDays) { - $retVal = $endDays; - $PHPEndDateObject->modify('-' . $endDays . ' days'); - $adjustDays = $PHPEndDateObject->format('j'); - $retVal += ($adjustDays - $startDays); - } else { - $retVal = $endDays - $startDays; - } - - break; - case 'YM': - $retVal = (int) ($endMonths - $startMonths); - if ($retVal < 0) { - $retVal += 12; - } - // We're only interested in full months - if ($endDays < $startDays) { - --$retVal; - } - - break; - case 'YD': - $retVal = (int) $difference; - if ($endYears > $startYears) { - $isLeapStartYear = $PHPStartDateObject->format('L'); - $wasLeapEndYear = $PHPEndDateObject->format('L'); - - // Adjust end year to be as close as possible as start year - while ($PHPEndDateObject >= $PHPStartDateObject) { - $PHPEndDateObject->modify('-1 year'); - $endYears = $PHPEndDateObject->format('Y'); - } - $PHPEndDateObject->modify('+1 year'); - - // Get the result - $retVal = $PHPEndDateObject->diff($PHPStartDateObject)->days; - - // Adjust for leap years cases - $isLeapEndYear = $PHPEndDateObject->format('L'); - $limit = new \DateTime($PHPEndDateObject->format('Y-02-29')); - if (!$isLeapStartYear && !$wasLeapEndYear && $isLeapEndYear && $PHPEndDateObject >= $limit) { - --$retVal; - } - } - - break; - default: - $retVal = Functions::VALUE(); - } - - return $retVal; + return DateTimeExcel\Difference::interval($startDate, $endDate, $unit); } /** @@ -770,42 +286,20 @@ class DateTime * Excel Function: * DAYS(endDate, startDate) * - * @category Date/Time Functions + * @deprecated 1.18.0 + * Use the between method in the DateTimeExcel\Days class instead + * @see DateTimeExcel\Days::between() * - * @param \DateTimeImmutable|float|int|string $endDate Excel date serial value (float), + * @param array|DateTimeInterface|float|int|string $endDate Excel date serial value (float), * PHP date timestamp (integer), PHP DateTime object, or a standard date string - * @param \DateTimeImmutable|float|int|string $startDate Excel date serial value (float), + * @param array|DateTimeInterface|float|int|string $startDate Excel date serial value (float), * PHP date timestamp (integer), PHP DateTime object, or a standard date string * - * @return int|string Number of days between start date and end date or an error + * @return array|int|string Number of days between start date and end date or an error */ public static function DAYS($endDate = 0, $startDate = 0) { - $startDate = Functions::flattenSingleValue($startDate); - $endDate = Functions::flattenSingleValue($endDate); - - $startDate = self::getDateValue($startDate); - if (is_string($startDate)) { - return Functions::VALUE(); - } - - $endDate = self::getDateValue($endDate); - if (is_string($endDate)) { - return Functions::VALUE(); - } - - // Execute function - $PHPStartDateObject = Date::excelToDateTimeObject($startDate); - $PHPEndDateObject = Date::excelToDateTimeObject($endDate); - - $diff = $PHPStartDateObject->diff($PHPEndDateObject); - $days = $diff->days; - - if ($diff->invert) { - $days = -$days; - } - - return $days; + return DateTimeExcel\Days::between($endDate, $startDate); } /** @@ -818,13 +312,15 @@ class DateTime * Excel Function: * DAYS360(startDate,endDate[,method]) * - * @category Date/Time Functions + * @deprecated 1.18.0 + * Use the between method in the DateTimeExcel\Days360 class instead + * @see DateTimeExcel\Days360::between() * * @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string - * @param bool $method US or European Method + * @param array|bool $method US or European Method * FALSE or omitted: U.S. (NASD) method. If the starting date is * the last day of a month, it becomes equal to the 30th of the * same month. If the ending date is the last day of a month and @@ -836,36 +332,11 @@ class DateTime * occur on the 31st of a month become equal to the 30th of the * same month. * - * @return int|string Number of days between start date and end date + * @return array|int|string Number of days between start date and end date */ public static function DAYS360($startDate = 0, $endDate = 0, $method = false) { - $startDate = Functions::flattenSingleValue($startDate); - $endDate = Functions::flattenSingleValue($endDate); - - if (is_string($startDate = self::getDateValue($startDate))) { - return Functions::VALUE(); - } - if (is_string($endDate = self::getDateValue($endDate))) { - return Functions::VALUE(); - } - - if (!is_bool($method)) { - return Functions::VALUE(); - } - - // Execute function - $PHPStartDateObject = Date::excelToDateTimeObject($startDate); - $startDay = $PHPStartDateObject->format('j'); - $startMonth = $PHPStartDateObject->format('n'); - $startYear = $PHPStartDateObject->format('Y'); - - $PHPEndDateObject = Date::excelToDateTimeObject($endDate); - $endDay = $PHPEndDateObject->format('j'); - $endMonth = $PHPEndDateObject->format('n'); - $endYear = $PHPEndDateObject->format('Y'); - - return self::dateDiff360($startDay, $startMonth, $startYear, $endDay, $endMonth, $endYear, !$method); + return DateTimeExcel\Days360::between($startDate, $endDate, $method); } /** @@ -879,93 +350,29 @@ class DateTime * Excel Function: * YEARFRAC(startDate,endDate[,method]) * - * @category Date/Time Functions + * @deprecated 1.18.0 + * Use the fraction method in the DateTimeExcel\YearFrac class instead + * @see DateTimeExcel\YearFrac::fraction() + * + * See https://lists.oasis-open.org/archives/office-formula/200806/msg00039.html + * for description of algorithm used in Excel * * @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string - * @param int $method Method used for the calculation + * @param array|int $method Method used for the calculation * 0 or omitted US (NASD) 30/360 * 1 Actual/actual * 2 Actual/360 * 3 Actual/365 * 4 European 30/360 * - * @return float fraction of the year + * @return array|float|string fraction of the year, or a string containing an error */ public static function YEARFRAC($startDate = 0, $endDate = 0, $method = 0) { - $startDate = Functions::flattenSingleValue($startDate); - $endDate = Functions::flattenSingleValue($endDate); - $method = Functions::flattenSingleValue($method); - - if (is_string($startDate = self::getDateValue($startDate))) { - return Functions::VALUE(); - } - if (is_string($endDate = self::getDateValue($endDate))) { - return Functions::VALUE(); - } - - if (((is_numeric($method)) && (!is_string($method))) || ($method == '')) { - switch ($method) { - case 0: - return self::DAYS360($startDate, $endDate) / 360; - case 1: - $days = self::DATEDIF($startDate, $endDate); - $startYear = self::YEAR($startDate); - $endYear = self::YEAR($endDate); - $years = $endYear - $startYear + 1; - $leapDays = 0; - if ($years == 1) { - if (self::isLeapYear($endYear)) { - $startMonth = self::MONTHOFYEAR($startDate); - $endMonth = self::MONTHOFYEAR($endDate); - $endDay = self::DAYOFMONTH($endDate); - if (($startMonth < 3) || - (($endMonth * 100 + $endDay) >= (2 * 100 + 29))) { - $leapDays += 1; - } - } - } else { - for ($year = $startYear; $year <= $endYear; ++$year) { - if ($year == $startYear) { - $startMonth = self::MONTHOFYEAR($startDate); - $startDay = self::DAYOFMONTH($startDate); - if ($startMonth < 3) { - $leapDays += (self::isLeapYear($year)) ? 1 : 0; - } - } elseif ($year == $endYear) { - $endMonth = self::MONTHOFYEAR($endDate); - $endDay = self::DAYOFMONTH($endDate); - if (($endMonth * 100 + $endDay) >= (2 * 100 + 29)) { - $leapDays += (self::isLeapYear($year)) ? 1 : 0; - } - } else { - $leapDays += (self::isLeapYear($year)) ? 1 : 0; - } - } - if ($years == 2) { - if (($leapDays == 0) && (self::isLeapYear($startYear)) && ($days > 365)) { - $leapDays = 1; - } elseif ($days < 366) { - $years = 1; - } - } - $leapDays /= $years; - } - - return $days / (365 + $leapDays); - case 2: - return self::DATEDIF($startDate, $endDate) / 360; - case 3: - return self::DATEDIF($startDate, $endDate) / 365; - case 4: - return self::DAYS360($startDate, $endDate, true) / 360; - } - } - - return Functions::VALUE(); + return DateTimeExcel\YearFrac::fraction($startDate, $endDate, $method); } /** @@ -979,73 +386,21 @@ class DateTime * Excel Function: * NETWORKDAYS(startDate,endDate[,holidays[,holiday[,...]]]) * - * @category Date/Time Functions + * @deprecated 1.18.0 + * Use the count method in the DateTimeExcel\NetworkDays class instead + * @see DateTimeExcel\NetworkDays::count() * * @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * @param mixed $endDate Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string + * @param mixed $dateArgs * - * @return int|string Interval between the dates + * @return array|int|string Interval between the dates */ public static function NETWORKDAYS($startDate, $endDate, ...$dateArgs) { - // Retrieve the mandatory start and end date that are referenced in the function definition - $startDate = Functions::flattenSingleValue($startDate); - $endDate = Functions::flattenSingleValue($endDate); - // Get the optional days - $dateArgs = Functions::flattenArray($dateArgs); - - // Validate the start and end dates - if (is_string($startDate = $sDate = self::getDateValue($startDate))) { - return Functions::VALUE(); - } - $startDate = (float) floor($startDate); - if (is_string($endDate = $eDate = self::getDateValue($endDate))) { - return Functions::VALUE(); - } - $endDate = (float) floor($endDate); - - if ($sDate > $eDate) { - $startDate = $eDate; - $endDate = $sDate; - } - - // Execute function - $startDoW = 6 - self::WEEKDAY($startDate, 2); - if ($startDoW < 0) { - $startDoW = 0; - } - $endDoW = self::WEEKDAY($endDate, 2); - if ($endDoW >= 6) { - $endDoW = 0; - } - - $wholeWeekDays = floor(($endDate - $startDate) / 7) * 5; - $partWeekDays = $endDoW + $startDoW; - if ($partWeekDays > 5) { - $partWeekDays -= 5; - } - - // Test any extra holiday parameters - $holidayCountedArray = []; - foreach ($dateArgs as $holidayDate) { - if (is_string($holidayDate = self::getDateValue($holidayDate))) { - return Functions::VALUE(); - } - if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) { - if ((self::WEEKDAY($holidayDate, 2) < 6) && (!in_array($holidayDate, $holidayCountedArray))) { - --$partWeekDays; - $holidayCountedArray[] = $holidayDate; - } - } - } - - if ($sDate > $eDate) { - return 0 - ($wholeWeekDays + $partWeekDays); - } - - return $wholeWeekDays + $partWeekDays; + return DateTimeExcel\NetworkDays::count($startDate, $endDate, ...$dateArgs); } /** @@ -1059,104 +414,23 @@ class DateTime * Excel Function: * WORKDAY(startDate,endDays[,holidays[,holiday[,...]]]) * - * @category Date/Time Functions + * @deprecated 1.18.0 + * Use the date method in the DateTimeExcel\WorkDay class instead + * @see DateTimeExcel\WorkDay::date() * * @param mixed $startDate Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * @param int $endDays The number of nonweekend and nonholiday days before or after * startDate. A positive value for days yields a future date; a * negative value yields a past date. + * @param mixed $dateArgs * * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, * depending on the value of the ReturnDateType flag */ public static function WORKDAY($startDate, $endDays, ...$dateArgs) { - // Retrieve the mandatory start date and days that are referenced in the function definition - $startDate = Functions::flattenSingleValue($startDate); - $endDays = Functions::flattenSingleValue($endDays); - // Get the optional days - $dateArgs = Functions::flattenArray($dateArgs); - - if ((is_string($startDate = self::getDateValue($startDate))) || (!is_numeric($endDays))) { - return Functions::VALUE(); - } - $startDate = (float) floor($startDate); - $endDays = (int) floor($endDays); - // If endDays is 0, we always return startDate - if ($endDays == 0) { - return $startDate; - } - - $decrementing = $endDays < 0; - - // Adjust the start date if it falls over a weekend - - $startDoW = self::WEEKDAY($startDate, 3); - if (self::WEEKDAY($startDate, 3) >= 5) { - $startDate += ($decrementing) ? -$startDoW + 4 : 7 - $startDoW; - ($decrementing) ? $endDays++ : $endDays--; - } - - // Add endDays - $endDate = (float) $startDate + ((int) ($endDays / 5) * 7) + ($endDays % 5); - - // Adjust the calculated end date if it falls over a weekend - $endDoW = self::WEEKDAY($endDate, 3); - if ($endDoW >= 5) { - $endDate += ($decrementing) ? -$endDoW + 4 : 7 - $endDoW; - } - - // Test any extra holiday parameters - if (!empty($dateArgs)) { - $holidayCountedArray = $holidayDates = []; - foreach ($dateArgs as $holidayDate) { - if (($holidayDate !== null) && (trim($holidayDate) > '')) { - if (is_string($holidayDate = self::getDateValue($holidayDate))) { - return Functions::VALUE(); - } - if (self::WEEKDAY($holidayDate, 3) < 5) { - $holidayDates[] = $holidayDate; - } - } - } - if ($decrementing) { - rsort($holidayDates, SORT_NUMERIC); - } else { - sort($holidayDates, SORT_NUMERIC); - } - foreach ($holidayDates as $holidayDate) { - if ($decrementing) { - if (($holidayDate <= $startDate) && ($holidayDate >= $endDate)) { - if (!in_array($holidayDate, $holidayCountedArray)) { - --$endDate; - $holidayCountedArray[] = $holidayDate; - } - } - } else { - if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) { - if (!in_array($holidayDate, $holidayCountedArray)) { - ++$endDate; - $holidayCountedArray[] = $holidayDate; - } - } - } - // Adjust the calculated end date if it falls over a weekend - $endDoW = self::WEEKDAY($endDate, 3); - if ($endDoW >= 5) { - $endDate += ($decrementing) ? -$endDoW + 4 : 7 - $endDoW; - } - } - } - - switch (Functions::getReturnDateType()) { - case Functions::RETURNDATE_EXCEL: - return (float) $endDate; - case Functions::RETURNDATE_UNIX_TIMESTAMP: - return (int) Date::excelToTimestamp($endDate); - case Functions::RETURNDATE_PHP_DATETIME_OBJECT: - return Date::excelToDateTimeObject($endDate); - } + return DateTimeExcel\WorkDay::date($startDate, $endDays, ...$dateArgs); } /** @@ -1168,33 +442,18 @@ class DateTime * Excel Function: * DAY(dateValue) * + * @deprecated 1.18.0 + * Use the day method in the DateTimeExcel\DateParts class instead + * @see DateTimeExcel\DateParts::day() + * * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * - * @return int|string Day of the month + * @return array|int|string Day of the month */ public static function DAYOFMONTH($dateValue = 1) { - $dateValue = Functions::flattenSingleValue($dateValue); - - if ($dateValue === null) { - $dateValue = 1; - } elseif (is_string($dateValue = self::getDateValue($dateValue))) { - return Functions::VALUE(); - } - - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_EXCEL) { - if ($dateValue < 0.0) { - return Functions::NAN(); - } elseif ($dateValue < 1.0) { - return 0; - } - } - - // Execute function - $PHPDateObject = Date::excelToDateTimeObject($dateValue); - - return (int) $PHPDateObject->format('j'); + return DateTimeExcel\DateParts::day($dateValue); } /** @@ -1206,73 +465,197 @@ class DateTime * Excel Function: * WEEKDAY(dateValue[,style]) * - * @param int $dateValue Excel date serial value (float), PHP date timestamp (integer), + * @deprecated 1.18.0 + * Use the day method in the DateTimeExcel\Week class instead + * @see DateTimeExcel\Week::day() + * + * @param float|int|string $dateValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * @param int $style A number that determines the type of return value * 1 or omitted Numbers 1 (Sunday) through 7 (Saturday). * 2 Numbers 1 (Monday) through 7 (Sunday). * 3 Numbers 0 (Monday) through 6 (Sunday). * - * @return int|string Day of the week value + * @return array|int|string Day of the week value */ public static function WEEKDAY($dateValue = 1, $style = 1) { - $dateValue = Functions::flattenSingleValue($dateValue); - $style = Functions::flattenSingleValue($style); - - if (!is_numeric($style)) { - return Functions::VALUE(); - } elseif (($style < 1) || ($style > 3)) { - return Functions::NAN(); - } - $style = floor($style); - - if ($dateValue === null) { - $dateValue = 1; - } elseif (is_string($dateValue = self::getDateValue($dateValue))) { - return Functions::VALUE(); - } elseif ($dateValue < 0.0) { - return Functions::NAN(); - } - - // Execute function - $PHPDateObject = Date::excelToDateTimeObject($dateValue); - $DoW = (int) $PHPDateObject->format('w'); - - $firstDay = 1; - switch ($style) { - case 1: - ++$DoW; - - break; - case 2: - if ($DoW === 0) { - $DoW = 7; - } - - break; - case 3: - if ($DoW === 0) { - $DoW = 7; - } - $firstDay = 0; - --$DoW; - - break; - } - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_EXCEL) { - // Test for Excel's 1900 leap year, and introduce the error as required - if (($PHPDateObject->format('Y') == 1900) && ($PHPDateObject->format('n') <= 2)) { - --$DoW; - if ($DoW < $firstDay) { - $DoW += 7; - } - } - } - - return $DoW; + return DateTimeExcel\Week::day($dateValue, $style); } + /** + * STARTWEEK_SUNDAY. + * + * @deprecated 1.18.0 + * Use DateTimeExcel\Constants::STARTWEEK_SUNDAY + * @see DateTimeExcel\Constants::STARTWEEK_SUNDAY + */ + const STARTWEEK_SUNDAY = 1; + + /** + * STARTWEEK_MONDAY. + * + * @deprecated 1.18.0 + * Use DateTimeExcel\Constants::STARTWEEK_MONDAY + * @see DateTimeExcel\Constants::STARTWEEK_MONDAY + */ + const STARTWEEK_MONDAY = 2; + + /** + * STARTWEEK_MONDAY_ALT. + * + * @deprecated 1.18.0 + * Use DateTimeExcel\Constants::STARTWEEK_MONDAY_ALT + * @see DateTimeExcel\Constants::STARTWEEK_MONDAY_ALT + */ + const STARTWEEK_MONDAY_ALT = 11; + + /** + * STARTWEEK_TUESDAY. + * + * @deprecated 1.18.0 + * Use DateTimeExcel\Constants::STARTWEEK_TUESDAY + * @see DateTimeExcel\Constants::STARTWEEK_TUESDAY + */ + const STARTWEEK_TUESDAY = 12; + + /** + * STARTWEEK_WEDNESDAY. + * + * @deprecated 1.18.0 + * Use DateTimeExcel\Constants::STARTWEEK_WEDNESDAY + * @see DateTimeExcel\Constants::STARTWEEK_WEDNESDAY + */ + const STARTWEEK_WEDNESDAY = 13; + + /** + * STARTWEEK_THURSDAY. + * + * @deprecated 1.18.0 + * Use DateTimeExcel\Constants::STARTWEEK_THURSDAY + * @see DateTimeExcel\Constants::STARTWEEK_THURSDAY + */ + const STARTWEEK_THURSDAY = 14; + + /** + * STARTWEEK_FRIDAY. + * + * @deprecated 1.18.0 + * Use DateTimeExcel\Constants::STARTWEEK_FRIDAY + * @see DateTimeExcel\Constants::STARTWEEK_FRIDAY + */ + const STARTWEEK_FRIDAY = 15; + + /** + * STARTWEEK_SATURDAY. + * + * @deprecated 1.18.0 + * Use DateTimeExcel\Constants::STARTWEEK_SATURDAY + * @see DateTimeExcel\Constants::STARTWEEK_SATURDAY + */ + const STARTWEEK_SATURDAY = 16; + + /** + * STARTWEEK_SUNDAY_ALT. + * + * @deprecated 1.18.0 + * Use DateTimeExcel\Constants::STARTWEEK_SUNDAY_ALT + * @see DateTimeExcel\Constants::STARTWEEK_SUNDAY_ALT + */ + const STARTWEEK_SUNDAY_ALT = 17; + + /** + * DOW_SUNDAY. + * + * @deprecated 1.18.0 + * Use DateTimeExcel\Constants::DOW_SUNDAY + * @see DateTimeExcel\Constants::DOW_SUNDAY + */ + const DOW_SUNDAY = 1; + + /** + * DOW_MONDAY. + * + * @deprecated 1.18.0 + * Use DateTimeExcel\Constants::DOW_MONDAY + * @see DateTimeExcel\Constants::DOW_MONDAY + */ + const DOW_MONDAY = 2; + + /** + * DOW_TUESDAY. + * + * @deprecated 1.18.0 + * Use DateTimeExcel\Constants::DOW_TUESDAY + * @see DateTimeExcel\Constants::DOW_TUESDAY + */ + const DOW_TUESDAY = 3; + + /** + * DOW_WEDNESDAY. + * + * @deprecated 1.18.0 + * Use DateTimeExcel\Constants::DOW_WEDNESDAY + * @see DateTimeExcel\Constants::DOW_WEDNESDAY + */ + const DOW_WEDNESDAY = 4; + + /** + * DOW_THURSDAY. + * + * @deprecated 1.18.0 + * Use DateTimeExcel\Constants::DOW_THURSDAY + * @see DateTimeExcel\Constants::DOW_THURSDAY + */ + const DOW_THURSDAY = 5; + + /** + * DOW_FRIDAY. + * + * @deprecated 1.18.0 + * Use DateTimeExcel\Constants::DOW_FRIDAY + * @see DateTimeExcel\Constants::DOW_FRIDAY + */ + const DOW_FRIDAY = 6; + + /** + * DOW_SATURDAY. + * + * @deprecated 1.18.0 + * Use DateTimeExcel\Constants::DOW_SATURDAY + * @see DateTimeExcel\Constants::DOW_SATURDAY + */ + const DOW_SATURDAY = 7; + + /** + * STARTWEEK_MONDAY_ISO. + * + * @deprecated 1.18.0 + * Use DateTimeExcel\Constants::STARTWEEK_MONDAY_ISO + * @see DateTimeExcel\Constants::STARTWEEK_MONDAY_ISO + */ + const STARTWEEK_MONDAY_ISO = 21; + + /** + * METHODARR. + * + * @deprecated 1.18.0 + * Use DateTimeExcel\Constants::METHODARR + * @see DateTimeExcel\Constants::METHODARR + */ + const METHODARR = [ + self::STARTWEEK_SUNDAY => self::DOW_SUNDAY, + self::DOW_MONDAY, + self::STARTWEEK_MONDAY_ALT => self::DOW_MONDAY, + self::DOW_TUESDAY, + self::DOW_WEDNESDAY, + self::DOW_THURSDAY, + self::DOW_FRIDAY, + self::DOW_SATURDAY, + self::DOW_SUNDAY, + self::STARTWEEK_MONDAY_ISO => self::STARTWEEK_MONDAY_ISO, + ]; + /** * WEEKNUM. * @@ -1286,48 +669,29 @@ class DateTime * Excel Function: * WEEKNUM(dateValue[,style]) * + * @deprecated 1.18.0 + * Use the number method in the DateTimeExcel\Week class instead + * @see DateTimeExcel\Week::number() + * * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * @param int $method Week begins on Sunday or Monday * 1 or omitted Week begins on Sunday. * 2 Week begins on Monday. + * 11 Week begins on Monday. + * 12 Week begins on Tuesday. + * 13 Week begins on Wednesday. + * 14 Week begins on Thursday. + * 15 Week begins on Friday. + * 16 Week begins on Saturday. + * 17 Week begins on Sunday. + * 21 ISO (Jan. 4 is week 1, begins on Monday). * - * @return int|string Week Number + * @return array|int|string Week Number */ - public static function WEEKNUM($dateValue = 1, $method = 1) + public static function WEEKNUM($dateValue = 1, $method = /** @scrutinizer ignore-deprecated */ self::STARTWEEK_SUNDAY) { - $dateValue = Functions::flattenSingleValue($dateValue); - $method = Functions::flattenSingleValue($method); - - if (!is_numeric($method)) { - return Functions::VALUE(); - } elseif (($method < 1) || ($method > 2)) { - return Functions::NAN(); - } - $method = floor($method); - - if ($dateValue === null) { - $dateValue = 1; - } elseif (is_string($dateValue = self::getDateValue($dateValue))) { - return Functions::VALUE(); - } elseif ($dateValue < 0.0) { - return Functions::NAN(); - } - - // Execute function - $PHPDateObject = Date::excelToDateTimeObject($dateValue); - $dayOfYear = $PHPDateObject->format('z'); - $PHPDateObject->modify('-' . $dayOfYear . ' days'); - $firstDayOfFirstWeek = $PHPDateObject->format('w'); - $daysInFirstWeek = (6 - $firstDayOfFirstWeek + $method) % 7; - $interval = $dayOfYear - $daysInFirstWeek; - $weekOfYear = floor($interval / 7) + 1; - - if ($daysInFirstWeek) { - ++$weekOfYear; - } - - return (int) $weekOfYear; + return DateTimeExcel\Week::number($dateValue, $method); } /** @@ -1338,27 +702,18 @@ class DateTime * Excel Function: * ISOWEEKNUM(dateValue) * + * @deprecated 1.18.0 + * Use the isoWeekNumber method in the DateTimeExcel\Week class instead + * @see DateTimeExcel\Week::isoWeekNumber() + * * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * - * @return int|string Week Number + * @return array|int|string Week Number */ public static function ISOWEEKNUM($dateValue = 1) { - $dateValue = Functions::flattenSingleValue($dateValue); - - if ($dateValue === null) { - $dateValue = 1; - } elseif (is_string($dateValue = self::getDateValue($dateValue))) { - return Functions::VALUE(); - } elseif ($dateValue < 0.0) { - return Functions::NAN(); - } - - // Execute function - $PHPDateObject = Date::excelToDateTimeObject($dateValue); - - return (int) $PHPDateObject->format('W'); + return DateTimeExcel\Week::isoWeekNumber($dateValue); } /** @@ -1370,28 +725,18 @@ class DateTime * Excel Function: * MONTH(dateValue) * + * @deprecated 1.18.0 + * Use the month method in the DateTimeExcel\DateParts class instead + * @see DateTimeExcel\DateParts::month() + * * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * - * @return int|string Month of the year + * @return array|int|string Month of the year */ public static function MONTHOFYEAR($dateValue = 1) { - $dateValue = Functions::flattenSingleValue($dateValue); - - if (empty($dateValue)) { - $dateValue = 1; - } - if (is_string($dateValue = self::getDateValue($dateValue))) { - return Functions::VALUE(); - } elseif ($dateValue < 0.0) { - return Functions::NAN(); - } - - // Execute function - $PHPDateObject = Date::excelToDateTimeObject($dateValue); - - return (int) $PHPDateObject->format('n'); + return DateTimeExcel\DateParts::month($dateValue); } /** @@ -1403,27 +748,18 @@ class DateTime * Excel Function: * YEAR(dateValue) * + * @deprecated 1.18.0 + * Use the ear method in the DateTimeExcel\DateParts class instead + * @see DateTimeExcel\DateParts::year() + * * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * - * @return int|string Year + * @return array|int|string Year */ public static function YEAR($dateValue = 1) { - $dateValue = Functions::flattenSingleValue($dateValue); - - if ($dateValue === null) { - $dateValue = 1; - } elseif (is_string($dateValue = self::getDateValue($dateValue))) { - return Functions::VALUE(); - } elseif ($dateValue < 0.0) { - return Functions::NAN(); - } - - // Execute function - $PHPDateObject = Date::excelToDateTimeObject($dateValue); - - return (int) $PHPDateObject->format('Y'); + return DateTimeExcel\DateParts::year($dateValue); } /** @@ -1435,36 +771,18 @@ class DateTime * Excel Function: * HOUR(timeValue) * + * @deprecated 1.18.0 + * Use the hour method in the DateTimeExcel\TimeParts class instead + * @see DateTimeExcel\TimeParts::hour() + * * @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard time string * - * @return int|string Hour + * @return array|int|string Hour */ public static function HOUROFDAY($timeValue = 0) { - $timeValue = Functions::flattenSingleValue($timeValue); - - if (!is_numeric($timeValue)) { - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { - $testVal = strtok($timeValue, '/-: '); - if (strlen($testVal) < strlen($timeValue)) { - return Functions::VALUE(); - } - } - $timeValue = self::getTimeValue($timeValue); - if (is_string($timeValue)) { - return Functions::VALUE(); - } - } - // Execute function - if ($timeValue >= 1) { - $timeValue = fmod($timeValue, 1); - } elseif ($timeValue < 0.0) { - return Functions::NAN(); - } - $timeValue = Date::excelToTimestamp($timeValue); - - return (int) gmdate('G', $timeValue); + return DateTimeExcel\TimeParts::hour($timeValue); } /** @@ -1476,36 +794,18 @@ class DateTime * Excel Function: * MINUTE(timeValue) * + * @deprecated 1.18.0 + * Use the minute method in the DateTimeExcel\TimeParts class instead + * @see DateTimeExcel\TimeParts::minute() + * * @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard time string * - * @return int|string Minute + * @return array|int|string Minute */ public static function MINUTE($timeValue = 0) { - $timeValue = $timeTester = Functions::flattenSingleValue($timeValue); - - if (!is_numeric($timeValue)) { - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { - $testVal = strtok($timeValue, '/-: '); - if (strlen($testVal) < strlen($timeValue)) { - return Functions::VALUE(); - } - } - $timeValue = self::getTimeValue($timeValue); - if (is_string($timeValue)) { - return Functions::VALUE(); - } - } - // Execute function - if ($timeValue >= 1) { - $timeValue = fmod($timeValue, 1); - } elseif ($timeValue < 0.0) { - return Functions::NAN(); - } - $timeValue = Date::excelToTimestamp($timeValue); - - return (int) gmdate('i', $timeValue); + return DateTimeExcel\TimeParts::minute($timeValue); } /** @@ -1517,36 +817,18 @@ class DateTime * Excel Function: * SECOND(timeValue) * + * @deprecated 1.18.0 + * Use the second method in the DateTimeExcel\TimeParts class instead + * @see DateTimeExcel\TimeParts::second() + * * @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard time string * - * @return int|string Second + * @return array|int|string Second */ public static function SECOND($timeValue = 0) { - $timeValue = Functions::flattenSingleValue($timeValue); - - if (!is_numeric($timeValue)) { - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { - $testVal = strtok($timeValue, '/-: '); - if (strlen($testVal) < strlen($timeValue)) { - return Functions::VALUE(); - } - } - $timeValue = self::getTimeValue($timeValue); - if (is_string($timeValue)) { - return Functions::VALUE(); - } - } - // Execute function - if ($timeValue >= 1) { - $timeValue = fmod($timeValue, 1); - } elseif ($timeValue < 0.0) { - return Functions::NAN(); - } - $timeValue = Date::excelToTimestamp($timeValue); - - return (int) gmdate('s', $timeValue); + return DateTimeExcel\TimeParts::second($timeValue); } /** @@ -1560,6 +842,10 @@ class DateTime * Excel Function: * EDATE(dateValue,adjustmentMonths) * + * @deprecated 1.18.0 + * Use the adjust method in the DateTimeExcel\Edate class instead + * @see DateTimeExcel\Month::adjust() + * * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * @param int $adjustmentMonths The number of months before or after start_date. @@ -1571,29 +857,7 @@ class DateTime */ public static function EDATE($dateValue = 1, $adjustmentMonths = 0) { - $dateValue = Functions::flattenSingleValue($dateValue); - $adjustmentMonths = Functions::flattenSingleValue($adjustmentMonths); - - if (!is_numeric($adjustmentMonths)) { - return Functions::VALUE(); - } - $adjustmentMonths = floor($adjustmentMonths); - - if (is_string($dateValue = self::getDateValue($dateValue))) { - return Functions::VALUE(); - } - - // Execute function - $PHPDateObject = self::adjustDateByMonths($dateValue, $adjustmentMonths); - - switch (Functions::getReturnDateType()) { - case Functions::RETURNDATE_EXCEL: - return (float) Date::PHPToExcel($PHPDateObject); - case Functions::RETURNDATE_UNIX_TIMESTAMP: - return (int) Date::excelToTimestamp(Date::PHPToExcel($PHPDateObject)); - case Functions::RETURNDATE_PHP_DATETIME_OBJECT: - return $PHPDateObject; - } + return DateTimeExcel\Month::adjust($dateValue, $adjustmentMonths); } /** @@ -1606,6 +870,10 @@ class DateTime * Excel Function: * EOMONTH(dateValue,adjustmentMonths) * + * @deprecated 1.18.0 + * Use the lastDay method in the DateTimeExcel\EoMonth class instead + * @see DateTimeExcel\Month::lastDay() + * * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), * PHP DateTime object, or a standard date string * @param int $adjustmentMonths The number of months before or after start_date. @@ -1617,31 +885,6 @@ class DateTime */ public static function EOMONTH($dateValue = 1, $adjustmentMonths = 0) { - $dateValue = Functions::flattenSingleValue($dateValue); - $adjustmentMonths = Functions::flattenSingleValue($adjustmentMonths); - - if (!is_numeric($adjustmentMonths)) { - return Functions::VALUE(); - } - $adjustmentMonths = floor($adjustmentMonths); - - if (is_string($dateValue = self::getDateValue($dateValue))) { - return Functions::VALUE(); - } - - // Execute function - $PHPDateObject = self::adjustDateByMonths($dateValue, $adjustmentMonths + 1); - $adjustDays = (int) $PHPDateObject->format('d'); - $adjustDaysString = '-' . $adjustDays . ' days'; - $PHPDateObject->modify($adjustDaysString); - - switch (Functions::getReturnDateType()) { - case Functions::RETURNDATE_EXCEL: - return (float) Date::PHPToExcel($PHPDateObject); - case Functions::RETURNDATE_UNIX_TIMESTAMP: - return (int) Date::excelToTimestamp(Date::PHPToExcel($PHPDateObject)); - case Functions::RETURNDATE_PHP_DATETIME_OBJECT: - return $PHPDateObject; - } + return DateTimeExcel\Month::lastDay($dateValue, $adjustmentMonths); } } diff --git a/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Constants.php b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Constants.php new file mode 100644 index 0000000..1165eb1 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Constants.php @@ -0,0 +1,38 @@ + self::DOW_SUNDAY, + self::DOW_MONDAY, + self::STARTWEEK_MONDAY_ALT => self::DOW_MONDAY, + self::DOW_TUESDAY, + self::DOW_WEDNESDAY, + self::DOW_THURSDAY, + self::DOW_FRIDAY, + self::DOW_SATURDAY, + self::DOW_SUNDAY, + self::STARTWEEK_MONDAY_ISO => self::STARTWEEK_MONDAY_ISO, + ]; +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Current.php b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Current.php new file mode 100644 index 0000000..5de671d --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Current.php @@ -0,0 +1,59 @@ +format('c')); + + return Helpers::dateParseSucceeded($dateArray) ? Helpers::returnIn3FormatsArray($dateArray, true) : ExcelError::VALUE(); + } + + /** + * DATETIMENOW. + * + * Returns the current date and time. + * The NOW function is useful when you need to display the current date and time on a worksheet or + * calculate a value based on the current date and time, and have that value updated each time you + * open the worksheet. + * + * NOTE: When used in a Cell Formula, MS Excel changes the cell format so that it matches the date + * and time format of your regional settings. PhpSpreadsheet does not change cell formatting in this way. + * + * Excel Function: + * NOW() + * + * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, + * depending on the value of the ReturnDateType flag + */ + public static function now() + { + $dti = new DateTimeImmutable(); + $dateArray = Helpers::dateParse($dti->format('c')); + + return Helpers::dateParseSucceeded($dateArray) ? Helpers::returnIn3FormatsArray($dateArray) : ExcelError::VALUE(); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Date.php b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Date.php new file mode 100644 index 0000000..6b55e79 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Date.php @@ -0,0 +1,172 @@ +getMessage(); + } + + // Execute function + $excelDateValue = SharedDateHelper::formattedPHPToExcel(/** @scrutinizer ignore-type */ $year, $month, $day); + + return Helpers::returnIn3FormatsFloat($excelDateValue); + } + + /** + * Convert year from multiple formats to int. + * + * @param mixed $year + */ + private static function getYear($year, int $baseYear): int + { + $year = ($year !== null) ? StringHelper::testStringAsNumeric((string) $year) : 0; + if (!is_numeric($year)) { + throw new Exception(ExcelError::VALUE()); + } + $year = (int) $year; + + if ($year < ($baseYear - 1900)) { + throw new Exception(ExcelError::NAN()); + } + if ((($baseYear - 1900) !== 0) && ($year < $baseYear) && ($year >= 1900)) { + throw new Exception(ExcelError::NAN()); + } + + if (($year < $baseYear) && ($year >= ($baseYear - 1900))) { + $year += 1900; + } + + return (int) $year; + } + + /** + * Convert month from multiple formats to int. + * + * @param mixed $month + */ + private static function getMonth($month): int + { + if (($month !== null) && (!is_numeric($month))) { + $month = SharedDateHelper::monthStringToNumber($month); + } + + $month = ($month !== null) ? StringHelper::testStringAsNumeric((string) $month) : 0; + if (!is_numeric($month)) { + throw new Exception(ExcelError::VALUE()); + } + + return (int) $month; + } + + /** + * Convert day from multiple formats to int. + * + * @param mixed $day + */ + private static function getDay($day): int + { + if (($day !== null) && (!is_numeric($day))) { + $day = SharedDateHelper::dayStringToNumber($day); + } + + $day = ($day !== null) ? StringHelper::testStringAsNumeric((string) $day) : 0; + if (!is_numeric($day)) { + throw new Exception(ExcelError::VALUE()); + } + + return (int) $day; + } + + private static function adjustYearMonth(int &$year, int &$month, int $baseYear): void + { + if ($month < 1) { + // Handle year/month adjustment if month < 1 + --$month; + $year += ceil($month / 12) - 1; + $month = 13 - abs($month % 12); + } elseif ($month > 12) { + // Handle year/month adjustment if month > 12 + $year += floor($month / 12); + $month = ($month % 12); + } + + // Re-validate the year parameter after adjustments + if (($year < $baseYear) || ($year >= 10000)) { + throw new Exception(ExcelError::NAN()); + } + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/DateParts.php b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/DateParts.php new file mode 100644 index 0000000..b669eb0 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/DateParts.php @@ -0,0 +1,151 @@ += 0) { + return $weirdResult; + } + + try { + $dateValue = Helpers::getDateValue($dateValue); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Execute function + $PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue); + + return (int) $PHPDateObject->format('j'); + } + + /** + * MONTHOFYEAR. + * + * 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). + * + * Excel Function: + * MONTH(dateValue) + * + * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard date string + * Or can be an array of date values + * + * @return array|int|string Month of the year + * If an array of numbers is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function month($dateValue) + { + if (is_array($dateValue)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $dateValue); + } + + try { + $dateValue = Helpers::getDateValue($dateValue); + } catch (Exception $e) { + return $e->getMessage(); + } + if ($dateValue < 1 && SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_WINDOWS_1900) { + return 1; + } + + // Execute function + $PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue); + + return (int) $PHPDateObject->format('n'); + } + + /** + * YEAR. + * + * Returns the year corresponding to a date. + * The year is returned as an integer in the range 1900-9999. + * + * Excel Function: + * YEAR(dateValue) + * + * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard date string + * Or can be an array of date values + * + * @return array|int|string Year + * If an array of numbers is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function year($dateValue) + { + if (is_array($dateValue)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $dateValue); + } + + try { + $dateValue = Helpers::getDateValue($dateValue); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($dateValue < 1 && SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_WINDOWS_1900) { + return 1900; + } + // Execute function + $PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue); + + return (int) $PHPDateObject->format('Y'); + } + + /** + * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard date string + */ + private static function weirdCondition($dateValue): int + { + // Excel does not treat 0 consistently for DAY vs. (MONTH or YEAR) + if (SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_WINDOWS_1900 && Functions::getCompatibilityMode() == Functions::COMPATIBILITY_EXCEL) { + if (is_bool($dateValue)) { + return (int) $dateValue; + } + if ($dateValue === null) { + return 0; + } + if (is_numeric($dateValue) && $dateValue < 1 && $dateValue >= 0) { + return 0; + } + } + + return -1; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php new file mode 100644 index 0000000..52543a7 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/DateValue.php @@ -0,0 +1,157 @@ + 31)) { + if ($yearFound) { + return ExcelError::VALUE(); + } + if ($t < 100) { + $t += 1900; + } + $yearFound = true; + } + } + if (count($t1) === 1) { + // We've been fed a time value without any date + return ((strpos((string) $t, ':') === false)) ? ExcelError::Value() : 0.0; + } + unset($t); + + $dateValue = self::t1ToString($t1, $dti, $yearFound); + + $PHPDateArray = self::setUpArray($dateValue, $dti); + + return self::finalResults($PHPDateArray, $dti, $baseYear); + } + + private static function t1ToString(array $t1, DateTimeImmutable $dti, bool $yearFound): string + { + if (count($t1) == 2) { + // We only have two parts of the date: either day/month or month/year + if ($yearFound) { + array_unshift($t1, 1); + } else { + if (is_numeric($t1[1]) && $t1[1] > 29) { + $t1[1] += 1900; + array_unshift($t1, 1); + } else { + $t1[] = $dti->format('Y'); + } + } + } + $dateValue = implode(' ', $t1); + + return $dateValue; + } + + /** + * Parse date. + */ + private static function setUpArray(string $dateValue, DateTimeImmutable $dti): array + { + $PHPDateArray = Helpers::dateParse($dateValue); + if (!Helpers::dateParseSucceeded($PHPDateArray)) { + // If original count was 1, we've already returned. + // If it was 2, we added another. + // Therefore, neither of the first 2 stroks below can fail. + $testVal1 = strtok($dateValue, '- '); + $testVal2 = strtok('- '); + $testVal3 = strtok('- ') ?: $dti->format('Y'); + Helpers::adjustYear((string) $testVal1, (string) $testVal2, $testVal3); + $PHPDateArray = Helpers::dateParse($testVal1 . '-' . $testVal2 . '-' . $testVal3); + if (!Helpers::dateParseSucceeded($PHPDateArray)) { + $PHPDateArray = Helpers::dateParse($testVal2 . '-' . $testVal1 . '-' . $testVal3); + } + } + + return $PHPDateArray; + } + + /** + * Final results. + * + * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, + * depending on the value of the ReturnDateType flag + */ + private static function finalResults(array $PHPDateArray, DateTimeImmutable $dti, int $baseYear) + { + $retValue = ExcelError::Value(); + if (Helpers::dateParseSucceeded($PHPDateArray)) { + // Execute function + Helpers::replaceIfEmpty($PHPDateArray['year'], $dti->format('Y')); + if ($PHPDateArray['year'] < $baseYear) { + return ExcelError::VALUE(); + } + Helpers::replaceIfEmpty($PHPDateArray['month'], $dti->format('m')); + Helpers::replaceIfEmpty($PHPDateArray['day'], $dti->format('d')); + $PHPDateArray['hour'] = 0; + $PHPDateArray['minute'] = 0; + $PHPDateArray['second'] = 0; + $month = (int) $PHPDateArray['month']; + $day = (int) $PHPDateArray['day']; + $year = (int) $PHPDateArray['year']; + if (!checkdate($month, $day, $year)) { + return ($year === 1900 && $month === 2 && $day === 29) ? Helpers::returnIn3FormatsFloat(60.0) : ExcelError::VALUE(); + } + $retValue = Helpers::returnIn3FormatsArray($PHPDateArray, true); + } + + return $retValue; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Days.php b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Days.php new file mode 100644 index 0000000..a3b9745 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Days.php @@ -0,0 +1,62 @@ +getMessage(); + } + + // Execute function + $PHPStartDateObject = SharedDateHelper::excelToDateTimeObject($startDate); + $PHPEndDateObject = SharedDateHelper::excelToDateTimeObject($endDate); + + $days = ExcelError::VALUE(); + $diff = $PHPStartDateObject->diff($PHPEndDateObject); + if ($diff !== false && !is_bool($diff->days)) { + $days = $diff->days; + if ($diff->invert) { + $days = -$days; + } + } + + return $days; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Days360.php b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Days360.php new file mode 100644 index 0000000..6f71621 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Days360.php @@ -0,0 +1,118 @@ +getMessage(); + } + + if (!is_bool($method)) { + return ExcelError::VALUE(); + } + + // Execute function + $PHPStartDateObject = SharedDateHelper::excelToDateTimeObject($startDate); + $startDay = $PHPStartDateObject->format('j'); + $startMonth = $PHPStartDateObject->format('n'); + $startYear = $PHPStartDateObject->format('Y'); + + $PHPEndDateObject = SharedDateHelper::excelToDateTimeObject($endDate); + $endDay = $PHPEndDateObject->format('j'); + $endMonth = $PHPEndDateObject->format('n'); + $endYear = $PHPEndDateObject->format('Y'); + + return self::dateDiff360((int) $startDay, (int) $startMonth, (int) $startYear, (int) $endDay, (int) $endMonth, (int) $endYear, !$method); + } + + /** + * Return the number of days between two dates based on a 360 day calendar. + */ + private static function dateDiff360(int $startDay, int $startMonth, int $startYear, int $endDay, int $endMonth, int $endYear, bool $methodUS): int + { + $startDay = self::getStartDay($startDay, $startMonth, $startYear, $methodUS); + $endDay = self::getEndDay($endDay, $endMonth, $endYear, $startDay, $methodUS); + + return $endDay + $endMonth * 30 + $endYear * 360 - $startDay - $startMonth * 30 - $startYear * 360; + } + + private static function getStartDay(int $startDay, int $startMonth, int $startYear, bool $methodUS): int + { + if ($startDay == 31) { + --$startDay; + } elseif ($methodUS && ($startMonth == 2 && ($startDay == 29 || ($startDay == 28 && !Helpers::isLeapYear($startYear))))) { + $startDay = 30; + } + + return $startDay; + } + + private static function getEndDay(int $endDay, int &$endMonth, int &$endYear, int $startDay, bool $methodUS): int + { + if ($endDay == 31) { + if ($methodUS && $startDay != 30) { + $endDay = 1; + if ($endMonth == 12) { + ++$endYear; + $endMonth = 1; + } else { + ++$endMonth; + } + } else { + $endDay = 30; + } + } + + return $endDay; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Difference.php b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Difference.php new file mode 100644 index 0000000..fd71c9b --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Difference.php @@ -0,0 +1,158 @@ +getMessage(); + } + + // Execute function + $PHPStartDateObject = SharedDateHelper::excelToDateTimeObject($startDate); + $startDays = (int) $PHPStartDateObject->format('j'); + //$startMonths = (int) $PHPStartDateObject->format('n'); + $startYears = (int) $PHPStartDateObject->format('Y'); + + $PHPEndDateObject = SharedDateHelper::excelToDateTimeObject($endDate); + $endDays = (int) $PHPEndDateObject->format('j'); + //$endMonths = (int) $PHPEndDateObject->format('n'); + $endYears = (int) $PHPEndDateObject->format('Y'); + + $PHPDiffDateObject = $PHPEndDateObject->diff($PHPStartDateObject); + + $retVal = false; + $retVal = self::replaceRetValue($retVal, $unit, 'D') ?? self::datedifD($difference); + $retVal = self::replaceRetValue($retVal, $unit, 'M') ?? self::datedifM($PHPDiffDateObject); + $retVal = self::replaceRetValue($retVal, $unit, 'MD') ?? self::datedifMD($startDays, $endDays, $PHPEndDateObject, $PHPDiffDateObject); + $retVal = self::replaceRetValue($retVal, $unit, 'Y') ?? self::datedifY($PHPDiffDateObject); + $retVal = self::replaceRetValue($retVal, $unit, 'YD') ?? self::datedifYD($difference, $startYears, $endYears, $PHPStartDateObject, $PHPEndDateObject); + $retVal = self::replaceRetValue($retVal, $unit, 'YM') ?? self::datedifYM($PHPDiffDateObject); + + return is_bool($retVal) ? ExcelError::VALUE() : $retVal; + } + + private static function initialDiff(float $startDate, float $endDate): float + { + // Validate parameters + if ($startDate > $endDate) { + throw new Exception(ExcelError::NAN()); + } + + return $endDate - $startDate; + } + + /** + * Decide whether it's time to set retVal. + * + * @param bool|int $retVal + * + * @return null|bool|int + */ + private static function replaceRetValue($retVal, string $unit, string $compare) + { + if ($retVal !== false || $unit !== $compare) { + return $retVal; + } + + return null; + } + + private static function datedifD(float $difference): int + { + return (int) $difference; + } + + private static function datedifM(DateInterval $PHPDiffDateObject): int + { + return 12 * (int) $PHPDiffDateObject->format('%y') + (int) $PHPDiffDateObject->format('%m'); + } + + private static function datedifMD(int $startDays, int $endDays, DateTime $PHPEndDateObject, DateInterval $PHPDiffDateObject): int + { + if ($endDays < $startDays) { + $retVal = $endDays; + $PHPEndDateObject->modify('-' . $endDays . ' days'); + $adjustDays = (int) $PHPEndDateObject->format('j'); + $retVal += ($adjustDays - $startDays); + } else { + $retVal = (int) $PHPDiffDateObject->format('%d'); + } + + return $retVal; + } + + private static function datedifY(DateInterval $PHPDiffDateObject): int + { + return (int) $PHPDiffDateObject->format('%y'); + } + + private static function datedifYD(float $difference, int $startYears, int $endYears, DateTime $PHPStartDateObject, DateTime $PHPEndDateObject): int + { + $retVal = (int) $difference; + if ($endYears > $startYears) { + $isLeapStartYear = $PHPStartDateObject->format('L'); + $wasLeapEndYear = $PHPEndDateObject->format('L'); + + // Adjust end year to be as close as possible as start year + while ($PHPEndDateObject >= $PHPStartDateObject) { + $PHPEndDateObject->modify('-1 year'); + //$endYears = $PHPEndDateObject->format('Y'); + } + $PHPEndDateObject->modify('+1 year'); + + // Get the result + $retVal = (int) $PHPEndDateObject->diff($PHPStartDateObject)->days; + + // Adjust for leap years cases + $isLeapEndYear = $PHPEndDateObject->format('L'); + $limit = new DateTime($PHPEndDateObject->format('Y-02-29')); + if (!$isLeapStartYear && !$wasLeapEndYear && $isLeapEndYear && $PHPEndDateObject >= $limit) { + --$retVal; + } + } + + return (int) $retVal; + } + + private static function datedifYM(DateInterval $PHPDiffDateObject): int + { + return (int) $PHPDiffDateObject->format('%m'); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php new file mode 100644 index 0000000..2384515 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php @@ -0,0 +1,307 @@ +format('m'); + $oYear = (int) $PHPDateObject->format('Y'); + + $adjustmentMonthsString = (string) $adjustmentMonths; + if ($adjustmentMonths > 0) { + $adjustmentMonthsString = '+' . $adjustmentMonths; + } + if ($adjustmentMonths != 0) { + $PHPDateObject->modify($adjustmentMonthsString . ' months'); + } + $nMonth = (int) $PHPDateObject->format('m'); + $nYear = (int) $PHPDateObject->format('Y'); + + $monthDiff = ($nMonth - $oMonth) + (($nYear - $oYear) * 12); + if ($monthDiff != $adjustmentMonths) { + $adjustDays = (int) $PHPDateObject->format('d'); + $adjustDaysString = '-' . $adjustDays . ' days'; + $PHPDateObject->modify($adjustDaysString); + } + + return $PHPDateObject; + } + + /** + * Help reduce perceived complexity of some tests. + * + * @param mixed $value + * @param mixed $altValue + */ + public static function replaceIfEmpty(&$value, $altValue): void + { + $value = $value ?: $altValue; + } + + /** + * Adjust year in ambiguous situations. + */ + public static function adjustYear(string $testVal1, string $testVal2, string &$testVal3): void + { + if (!is_numeric($testVal1) || $testVal1 < 31) { + if (!is_numeric($testVal2) || $testVal2 < 12) { + if (is_numeric($testVal3) && $testVal3 < 12) { + $testVal3 += 2000; + } + } + } + } + + /** + * Return result in one of three formats. + * + * @return mixed + */ + public static function returnIn3FormatsArray(array $dateArray, bool $noFrac = false) + { + $retType = Functions::getReturnDateType(); + if ($retType === Functions::RETURNDATE_PHP_DATETIME_OBJECT) { + return new DateTime( + $dateArray['year'] + . '-' . $dateArray['month'] + . '-' . $dateArray['day'] + . ' ' . $dateArray['hour'] + . ':' . $dateArray['minute'] + . ':' . $dateArray['second'] + ); + } + $excelDateValue = + SharedDateHelper::formattedPHPToExcel( + $dateArray['year'], + $dateArray['month'], + $dateArray['day'], + $dateArray['hour'], + $dateArray['minute'], + $dateArray['second'] + ); + if ($retType === Functions::RETURNDATE_EXCEL) { + return $noFrac ? floor($excelDateValue) : (float) $excelDateValue; + } + // RETURNDATE_UNIX_TIMESTAMP) + + return (int) SharedDateHelper::excelToTimestamp($excelDateValue); + } + + /** + * Return result in one of three formats. + * + * @return mixed + */ + public static function returnIn3FormatsFloat(float $excelDateValue) + { + $retType = Functions::getReturnDateType(); + if ($retType === Functions::RETURNDATE_EXCEL) { + return $excelDateValue; + } + if ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) { + return (int) SharedDateHelper::excelToTimestamp($excelDateValue); + } + // RETURNDATE_PHP_DATETIME_OBJECT + + return SharedDateHelper::excelToDateTimeObject($excelDateValue); + } + + /** + * Return result in one of three formats. + * + * @return mixed + */ + public static function returnIn3FormatsObject(DateTime $PHPDateObject) + { + $retType = Functions::getReturnDateType(); + if ($retType === Functions::RETURNDATE_PHP_DATETIME_OBJECT) { + return $PHPDateObject; + } + if ($retType === Functions::RETURNDATE_EXCEL) { + return (float) SharedDateHelper::PHPToExcel($PHPDateObject); + } + // RETURNDATE_UNIX_TIMESTAMP + $stamp = SharedDateHelper::PHPToExcel($PHPDateObject); + $stamp = is_bool($stamp) ? ((int) $stamp) : $stamp; + + return (int) SharedDateHelper::excelToTimestamp($stamp); + } + + private static function baseDate(): int + { + if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) { + return 0; + } + if (SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_MAC_1904) { + return 0; + } + + return 1; + } + + /** + * Many functions accept null/false/true argument treated as 0/0/1. + * + * @param mixed $number + */ + public static function nullFalseTrueToNumber(&$number, bool $allowBool = true): void + { + $number = Functions::flattenSingleValue($number); + $nullVal = self::baseDate(); + if ($number === null) { + $number = $nullVal; + } elseif ($allowBool && is_bool($number)) { + $number = $nullVal + (int) $number; + } + } + + /** + * Many functions accept null argument treated as 0. + * + * @param mixed $number + * + * @return float|int + */ + public static function validateNumericNull($number) + { + $number = Functions::flattenSingleValue($number); + if ($number === null) { + return 0; + } + if (is_int($number)) { + return $number; + } + if (is_numeric($number)) { + return (float) $number; + } + + throw new Exception(ExcelError::VALUE()); + } + + /** + * Many functions accept null/false/true argument treated as 0/0/1. + * + * @param mixed $number + * + * @return float + */ + public static function validateNotNegative($number) + { + if (!is_numeric($number)) { + throw new Exception(ExcelError::VALUE()); + } + if ($number >= 0) { + return (float) $number; + } + + throw new Exception(ExcelError::NAN()); + } + + public static function silly1900(DateTime $PHPDateObject, string $mod = '-1 day'): void + { + $isoDate = $PHPDateObject->format('c'); + if ($isoDate < '1900-03-01') { + $PHPDateObject->modify($mod); + } + } + + public static function dateParse(string $string): array + { + return self::forceArray(date_parse($string)); + } + + public static function dateParseSucceeded(array $dateArray): bool + { + return $dateArray['error_count'] === 0; + } + + /** + * Despite documentation, date_parse probably never returns false. + * Just in case, this routine helps guarantee it. + * + * @param array|false $dateArray + */ + private static function forceArray($dateArray): array + { + return is_array($dateArray) ? $dateArray : ['error_count' => 1]; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Month.php b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Month.php new file mode 100644 index 0000000..c72d006 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Month.php @@ -0,0 +1,101 @@ +getMessage(); + } + $adjustmentMonths = floor($adjustmentMonths); + + // Execute function + $PHPDateObject = Helpers::adjustDateByMonths($dateValue, $adjustmentMonths); + + return Helpers::returnIn3FormatsObject($PHPDateObject); + } + + /** + * EOMONTH. + * + * Returns the date value for the last day of the month that is the indicated number of months + * before or after start_date. + * Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month. + * + * Excel Function: + * EOMONTH(dateValue,adjustmentMonths) + * + * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard date string + * Or can be an array of date values + * @param array|int $adjustmentMonths The number of months before or after start_date. + * A positive value for months yields a future date; + * a negative value yields a past date. + * Or can be an array of adjustment values + * + * @return array|mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, + * depending on the value of the ReturnDateType flag + * If an array of values is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function lastDay($dateValue, $adjustmentMonths) + { + if (is_array($dateValue) || is_array($adjustmentMonths)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $dateValue, $adjustmentMonths); + } + + try { + $dateValue = Helpers::getDateValue($dateValue, false); + $adjustmentMonths = Helpers::validateNumericNull($adjustmentMonths); + } catch (Exception $e) { + return $e->getMessage(); + } + $adjustmentMonths = floor($adjustmentMonths); + + // Execute function + $PHPDateObject = Helpers::adjustDateByMonths($dateValue, $adjustmentMonths + 1); + $adjustDays = (int) $PHPDateObject->format('d'); + $adjustDaysString = '-' . $adjustDays . ' days'; + $PHPDateObject->modify($adjustDaysString); + + return Helpers::returnIn3FormatsObject($PHPDateObject); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.php b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.php new file mode 100644 index 0000000..3b8942b --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.php @@ -0,0 +1,119 @@ +getMessage(); + } + + // Execute function + $startDow = self::calcStartDow($startDate); + $endDow = self::calcEndDow($endDate); + $wholeWeekDays = (int) floor(($endDate - $startDate) / 7) * 5; + $partWeekDays = self::calcPartWeekDays($startDow, $endDow); + + // Test any extra holiday parameters + $holidayCountedArray = []; + foreach ($holidayArray as $holidayDate) { + if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) { + if ((Week::day($holidayDate, 2) < 6) && (!in_array($holidayDate, $holidayCountedArray))) { + --$partWeekDays; + $holidayCountedArray[] = $holidayDate; + } + } + } + + return self::applySign($wholeWeekDays + $partWeekDays, $sDate, $eDate); + } + + private static function calcStartDow(float $startDate): int + { + $startDow = 6 - (int) Week::day($startDate, 2); + if ($startDow < 0) { + $startDow = 5; + } + + return $startDow; + } + + private static function calcEndDow(float $endDate): int + { + $endDow = (int) Week::day($endDate, 2); + if ($endDow >= 6) { + $endDow = 0; + } + + return $endDow; + } + + private static function calcPartWeekDays(int $startDow, int $endDow): int + { + $partWeekDays = $endDow + $startDow; + if ($partWeekDays > 5) { + $partWeekDays -= 5; + } + + return $partWeekDays; + } + + private static function applySign(int $result, float $sDate, float $eDate): int + { + return ($sDate > $eDate) ? -$result : $result; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Time.php b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Time.php new file mode 100644 index 0000000..4ff7198 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Time.php @@ -0,0 +1,130 @@ +getMessage(); + } + + self::adjustSecond($second, $minute); + self::adjustMinute($minute, $hour); + + if ($hour > 23) { + $hour = $hour % 24; + } elseif ($hour < 0) { + return ExcelError::NAN(); + } + + // Execute function + $retType = Functions::getReturnDateType(); + if ($retType === Functions::RETURNDATE_EXCEL) { + $calendar = SharedDateHelper::getExcelCalendar(); + $date = (int) ($calendar !== SharedDateHelper::CALENDAR_WINDOWS_1900); + + return (float) SharedDateHelper::formattedPHPToExcel($calendar, 1, $date, $hour, $minute, $second); + } + if ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) { + return (int) SharedDateHelper::excelToTimestamp(SharedDateHelper::formattedPHPToExcel(1970, 1, 1, $hour, $minute, $second)); // -2147468400; // -2147472000 + 3600 + } + // RETURNDATE_PHP_DATETIME_OBJECT + // Hour has already been normalized (0-23) above + $phpDateObject = new DateTime('1900-01-01 ' . $hour . ':' . $minute . ':' . $second); + + return $phpDateObject; + } + + private static function adjustSecond(int &$second, int &$minute): void + { + if ($second < 0) { + $minute += floor($second / 60); + $second = 60 - abs($second % 60); + if ($second == 60) { + $second = 0; + } + } elseif ($second >= 60) { + $minute += floor($second / 60); + $second = $second % 60; + } + } + + private static function adjustMinute(int &$minute, int &$hour): void + { + if ($minute < 0) { + $hour += floor($minute / 60); + $minute = 60 - abs($minute % 60); + if ($minute == 60) { + $minute = 0; + } + } elseif ($minute >= 60) { + $hour += floor($minute / 60); + $minute = $minute % 60; + } + } + + /** + * @param mixed $value expect int + */ + private static function toIntWithNullBool($value): int + { + $value = $value ?? 0; + if (is_bool($value)) { + $value = (int) $value; + } + if (!is_numeric($value)) { + throw new Exception(ExcelError::VALUE()); + } + + return (int) $value; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/TimeParts.php b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/TimeParts.php new file mode 100644 index 0000000..d9b99f3 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/TimeParts.php @@ -0,0 +1,132 @@ +getMessage(); + } + + // Execute function + $timeValue = fmod($timeValue, 1); + $timeValue = SharedDateHelper::excelToDateTimeObject($timeValue); + + return (int) $timeValue->format('H'); + } + + /** + * MINUTE. + * + * Returns the minutes of a time value. + * The minute is given as an integer, ranging from 0 to 59. + * + * Excel Function: + * MINUTE(timeValue) + * + * @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard time string + * Or can be an array of date/time values + * + * @return array|int|string Minute + * If an array of numbers is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function minute($timeValue) + { + if (is_array($timeValue)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $timeValue); + } + + try { + Helpers::nullFalseTrueToNumber($timeValue); + if (!is_numeric($timeValue)) { + $timeValue = Helpers::getTimeValue($timeValue); + } + Helpers::validateNotNegative($timeValue); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Execute function + $timeValue = fmod($timeValue, 1); + $timeValue = SharedDateHelper::excelToDateTimeObject($timeValue); + + return (int) $timeValue->format('i'); + } + + /** + * SECOND. + * + * Returns the seconds of a time value. + * The minute is given as an integer, ranging from 0 to 59. + * + * Excel Function: + * SECOND(timeValue) + * + * @param mixed $timeValue Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard time string + * Or can be an array of date/time values + * + * @return array|int|string Second + * If an array of numbers is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function second($timeValue) + { + if (is_array($timeValue)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $timeValue); + } + + try { + Helpers::nullFalseTrueToNumber($timeValue); + if (!is_numeric($timeValue)) { + $timeValue = Helpers::getTimeValue($timeValue); + } + Helpers::validateNotNegative($timeValue); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Execute function + $timeValue = fmod($timeValue, 1); + $timeValue = SharedDateHelper::excelToDateTimeObject($timeValue); + + return (int) $timeValue->format('s'); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php new file mode 100644 index 0000000..4abdd75 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/TimeValue.php @@ -0,0 +1,78 @@ + 24) { + $arraySplit[0] = ($arraySplit[0] % 24); + $timeValue = implode(':', $arraySplit); + } + + $PHPDateArray = Helpers::dateParse($timeValue); + $retValue = ExcelError::VALUE(); + if (Helpers::dateParseSucceeded($PHPDateArray)) { + /** @var int */ + $hour = $PHPDateArray['hour']; + /** @var int */ + $minute = $PHPDateArray['minute']; + /** @var int */ + $second = $PHPDateArray['second']; + // OpenOffice-specific code removed - it works just like Excel + $excelDateValue = SharedDateHelper::formattedPHPToExcel(1900, 1, 1, $hour, $minute, $second) - 1; + + $retType = Functions::getReturnDateType(); + if ($retType === Functions::RETURNDATE_EXCEL) { + $retValue = (float) $excelDateValue; + } elseif ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) { + $retValue = (int) SharedDateHelper::excelToTimestamp($excelDateValue + 25569) - 3600; + } else { + $retValue = new DateTime('1900-01-01 ' . $PHPDateArray['hour'] . ':' . $PHPDateArray['minute'] . ':' . $PHPDateArray['second']); + } + } + + return $retValue; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Week.php b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Week.php new file mode 100644 index 0000000..2f69007 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/Week.php @@ -0,0 +1,278 @@ +getMessage(); + } + + // Execute function + $PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue); + if ($method == Constants::STARTWEEK_MONDAY_ISO) { + Helpers::silly1900($PHPDateObject); + + return (int) $PHPDateObject->format('W'); + } + if (self::buggyWeekNum1904($method, $origDateValueNull, $PHPDateObject)) { + return 0; + } + Helpers::silly1900($PHPDateObject, '+ 5 years'); // 1905 calendar matches + $dayOfYear = (int) $PHPDateObject->format('z'); + $PHPDateObject->modify('-' . $dayOfYear . ' days'); + $firstDayOfFirstWeek = (int) $PHPDateObject->format('w'); + $daysInFirstWeek = (6 - $firstDayOfFirstWeek + $method) % 7; + $daysInFirstWeek += 7 * !$daysInFirstWeek; + $endFirstWeek = $daysInFirstWeek - 1; + $weekOfYear = floor(($dayOfYear - $endFirstWeek + 13) / 7); + + return (int) $weekOfYear; + } + + /** + * ISOWEEKNUM. + * + * Returns the ISO 8601 week number of the year for a specified date. + * + * Excel Function: + * ISOWEEKNUM(dateValue) + * + * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard date string + * Or can be an array of date values + * + * @return array|int|string Week Number + * If an array of numbers is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function isoWeekNumber($dateValue) + { + if (is_array($dateValue)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $dateValue); + } + + if (self::apparentBug($dateValue)) { + return 52; + } + + try { + $dateValue = Helpers::getDateValue($dateValue); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Execute function + $PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue); + Helpers::silly1900($PHPDateObject); + + return (int) $PHPDateObject->format('W'); + } + + /** + * WEEKDAY. + * + * Returns the day of the week for a specified date. The day is given as an integer + * ranging from 0 to 7 (dependent on the requested style). + * + * Excel Function: + * WEEKDAY(dateValue[,style]) + * + * @param null|array|float|int|string $dateValue Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard date string + * Or can be an array of date values + * @param mixed $style A number that determines the type of return value + * 1 or omitted Numbers 1 (Sunday) through 7 (Saturday). + * 2 Numbers 1 (Monday) through 7 (Sunday). + * 3 Numbers 0 (Monday) through 6 (Sunday). + * Or can be an array of styles + * + * @return array|int|string Day of the week value + * If an array of values is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function day($dateValue, $style = 1) + { + if (is_array($dateValue) || is_array($style)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $dateValue, $style); + } + + try { + $dateValue = Helpers::getDateValue($dateValue); + $style = self::validateStyle($style); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Execute function + $PHPDateObject = SharedDateHelper::excelToDateTimeObject($dateValue); + Helpers::silly1900($PHPDateObject); + $DoW = (int) $PHPDateObject->format('w'); + + switch ($style) { + case 1: + ++$DoW; + + break; + case 2: + $DoW = self::dow0Becomes7($DoW); + + break; + case 3: + $DoW = self::dow0Becomes7($DoW) - 1; + + break; + } + + return $DoW; + } + + /** + * @param mixed $style expect int + */ + private static function validateStyle($style): int + { + if (!is_numeric($style)) { + throw new Exception(ExcelError::VALUE()); + } + $style = (int) $style; + if (($style < 1) || ($style > 3)) { + throw new Exception(ExcelError::NAN()); + } + + return $style; + } + + private static function dow0Becomes7(int $DoW): int + { + return ($DoW === 0) ? 7 : $DoW; + } + + /** + * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer), + * PHP DateTime object, or a standard date string + */ + private static function apparentBug($dateValue): bool + { + if (SharedDateHelper::getExcelCalendar() !== SharedDateHelper::CALENDAR_MAC_1904) { + if (is_bool($dateValue)) { + return true; + } + if (is_numeric($dateValue) && !((int) $dateValue)) { + return true; + } + } + + return false; + } + + /** + * Validate dateValue parameter. + * + * @param mixed $dateValue + */ + private static function validateDateValue($dateValue): float + { + if (is_bool($dateValue)) { + throw new Exception(ExcelError::VALUE()); + } + + return Helpers::getDateValue($dateValue); + } + + /** + * Validate method parameter. + * + * @param mixed $method + */ + private static function validateMethod($method): int + { + if ($method === null) { + $method = Constants::STARTWEEK_SUNDAY; + } + + if (!is_numeric($method)) { + throw new Exception(ExcelError::VALUE()); + } + + $method = (int) $method; + if (!array_key_exists($method, Constants::METHODARR)) { + throw new Exception(ExcelError::NAN()); + } + $method = Constants::METHODARR[$method]; + + return $method; + } + + private static function buggyWeekNum1900(int $method): bool + { + return $method === Constants::DOW_SUNDAY && SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_WINDOWS_1900; + } + + private static function buggyWeekNum1904(int $method, bool $origNull, DateTime $dateObject): bool + { + // This appears to be another Excel bug. + + return $method === Constants::DOW_SUNDAY && SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_MAC_1904 && + !$origNull && $dateObject->format('Y-m-d') === '1904-01-01'; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.php b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.php new file mode 100644 index 0000000..1f5735e --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/WorkDay.php @@ -0,0 +1,201 @@ +getMessage(); + } + + $startDate = (float) floor($startDate); + $endDays = (int) floor($endDays); + // If endDays is 0, we always return startDate + if ($endDays == 0) { + return $startDate; + } + if ($endDays < 0) { + return self::decrementing($startDate, $endDays, $holidayArray); + } + + return self::incrementing($startDate, $endDays, $holidayArray); + } + + /** + * Use incrementing logic to determine Workday. + * + * @return mixed + */ + private static function incrementing(float $startDate, int $endDays, array $holidayArray) + { + // Adjust the start date if it falls over a weekend + $startDoW = self::getWeekDay($startDate, 3); + if ($startDoW >= 5) { + $startDate += 7 - $startDoW; + --$endDays; + } + + // Add endDays + $endDate = (float) $startDate + ((int) ($endDays / 5) * 7); + $endDays = $endDays % 5; + while ($endDays > 0) { + ++$endDate; + // Adjust the calculated end date if it falls over a weekend + $endDow = self::getWeekDay($endDate, 3); + if ($endDow >= 5) { + $endDate += 7 - $endDow; + } + --$endDays; + } + + // Test any extra holiday parameters + if (!empty($holidayArray)) { + $endDate = self::incrementingArray($startDate, $endDate, $holidayArray); + } + + return Helpers::returnIn3FormatsFloat($endDate); + } + + private static function incrementingArray(float $startDate, float $endDate, array $holidayArray): float + { + $holidayCountedArray = $holidayDates = []; + foreach ($holidayArray as $holidayDate) { + if (self::getWeekDay($holidayDate, 3) < 5) { + $holidayDates[] = $holidayDate; + } + } + sort($holidayDates, SORT_NUMERIC); + foreach ($holidayDates as $holidayDate) { + if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) { + if (!in_array($holidayDate, $holidayCountedArray)) { + ++$endDate; + $holidayCountedArray[] = $holidayDate; + } + } + // Adjust the calculated end date if it falls over a weekend + $endDoW = self::getWeekDay($endDate, 3); + if ($endDoW >= 5) { + $endDate += 7 - $endDoW; + } + } + + return $endDate; + } + + /** + * Use decrementing logic to determine Workday. + * + * @return mixed + */ + private static function decrementing(float $startDate, int $endDays, array $holidayArray) + { + // Adjust the start date if it falls over a weekend + $startDoW = self::getWeekDay($startDate, 3); + if ($startDoW >= 5) { + $startDate += -$startDoW + 4; + ++$endDays; + } + + // Add endDays + $endDate = (float) $startDate + ((int) ($endDays / 5) * 7); + $endDays = $endDays % 5; + while ($endDays < 0) { + --$endDate; + // Adjust the calculated end date if it falls over a weekend + $endDow = self::getWeekDay($endDate, 3); + if ($endDow >= 5) { + $endDate += 4 - $endDow; + } + ++$endDays; + } + + // Test any extra holiday parameters + if (!empty($holidayArray)) { + $endDate = self::decrementingArray($startDate, $endDate, $holidayArray); + } + + return Helpers::returnIn3FormatsFloat($endDate); + } + + private static function decrementingArray(float $startDate, float $endDate, array $holidayArray): float + { + $holidayCountedArray = $holidayDates = []; + foreach ($holidayArray as $holidayDate) { + if (self::getWeekDay($holidayDate, 3) < 5) { + $holidayDates[] = $holidayDate; + } + } + rsort($holidayDates, SORT_NUMERIC); + foreach ($holidayDates as $holidayDate) { + if (($holidayDate <= $startDate) && ($holidayDate >= $endDate)) { + if (!in_array($holidayDate, $holidayCountedArray)) { + --$endDate; + $holidayCountedArray[] = $holidayDate; + } + } + // Adjust the calculated end date if it falls over a weekend + $endDoW = self::getWeekDay($endDate, 3); + /** int $endDoW */ + if ($endDoW >= 5) { + $endDate += -$endDoW + 4; + } + } + + return $endDate; + } + + private static function getWeekDay(float $date, int $wd): int + { + $result = Functions::scalar(Week::day($date, $wd)); + + return is_int($result) ? $result : -1; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php new file mode 100644 index 0000000..394c6b7 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/DateTimeExcel/YearFrac.php @@ -0,0 +1,133 @@ +getMessage(); + } + + switch ($method) { + case 0: + return Functions::scalar(Days360::between($startDate, $endDate)) / 360; + case 1: + return self::method1($startDate, $endDate); + case 2: + return Functions::scalar(Difference::interval($startDate, $endDate)) / 360; + case 3: + return Functions::scalar(Difference::interval($startDate, $endDate)) / 365; + case 4: + return Functions::scalar(Days360::between($startDate, $endDate, true)) / 360; + } + + return ExcelError::NAN(); + } + + /** + * Excel 1900 calendar treats date argument of null as 1900-01-00. Really. + * + * @param mixed $startDate + * @param mixed $endDate + */ + private static function excelBug(float $sDate, $startDate, $endDate, int $method): float + { + if (Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_OPENOFFICE && SharedDateHelper::getExcelCalendar() !== SharedDateHelper::CALENDAR_MAC_1904) { + if ($endDate === null && $startDate !== null) { + if (DateParts::month($sDate) == 12 && DateParts::day($sDate) === 31 && $method === 0) { + $sDate += 2; + } else { + ++$sDate; + } + } + } + + return $sDate; + } + + private static function method1(float $startDate, float $endDate): float + { + $days = Functions::scalar(Difference::interval($startDate, $endDate)); + $startYear = (int) DateParts::year($startDate); + $endYear = (int) DateParts::year($endDate); + $years = $endYear - $startYear + 1; + $startMonth = (int) DateParts::month($startDate); + $startDay = (int) DateParts::day($startDate); + $endMonth = (int) DateParts::month($endDate); + $endDay = (int) DateParts::day($endDate); + $startMonthDay = 100 * $startMonth + $startDay; + $endMonthDay = 100 * $endMonth + $endDay; + if ($years == 1) { + $tmpCalcAnnualBasis = 365 + (int) Helpers::isLeapYear($endYear); + } elseif ($years == 2 && $startMonthDay >= $endMonthDay) { + if (Helpers::isLeapYear($startYear)) { + $tmpCalcAnnualBasis = 365 + (int) ($startMonthDay <= 229); + } elseif (Helpers::isLeapYear($endYear)) { + $tmpCalcAnnualBasis = 365 + (int) ($endMonthDay >= 229); + } else { + $tmpCalcAnnualBasis = 365; + } + } else { + $tmpCalcAnnualBasis = 0; + for ($year = $startYear; $year <= $endYear; ++$year) { + $tmpCalcAnnualBasis += 365 + (int) Helpers::isLeapYear($year); + } + $tmpCalcAnnualBasis /= $years; + } + + return $days / $tmpCalcAnnualBasis; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Engine/ArrayArgumentHelper.php b/PhpOffice/PhpSpreadsheet/Calculation/Engine/ArrayArgumentHelper.php new file mode 100644 index 0000000..fae9d90 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Engine/ArrayArgumentHelper.php @@ -0,0 +1,209 @@ +indexStart = (int) array_shift($keys); + $this->rows = $this->rows($arguments); + $this->columns = $this->columns($arguments); + + $this->argumentCount = count($arguments); + $this->arguments = $this->flattenSingleCellArrays($arguments, $this->rows, $this->columns); + + $this->rows = $this->rows($arguments); + $this->columns = $this->columns($arguments); + + if ($this->arrayArguments() > 2) { + throw new Exception('Formulae with more than two array arguments are not supported'); + } + } + + public function arguments(): array + { + return $this->arguments; + } + + public function hasArrayArgument(): bool + { + return $this->arrayArguments() > 0; + } + + public function getFirstArrayArgumentNumber(): int + { + $rowArrays = $this->filterArray($this->rows); + $columnArrays = $this->filterArray($this->columns); + + for ($index = $this->indexStart; $index < $this->argumentCount; ++$index) { + if (isset($rowArrays[$index]) || isset($columnArrays[$index])) { + return ++$index; + } + } + + return 0; + } + + public function getSingleRowVector(): ?int + { + $rowVectors = $this->getRowVectors(); + + return count($rowVectors) === 1 ? array_pop($rowVectors) : null; + } + + private function getRowVectors(): array + { + $rowVectors = []; + for ($index = $this->indexStart; $index < ($this->indexStart + $this->argumentCount); ++$index) { + if ($this->rows[$index] === 1 && $this->columns[$index] > 1) { + $rowVectors[] = $index; + } + } + + return $rowVectors; + } + + public function getSingleColumnVector(): ?int + { + $columnVectors = $this->getColumnVectors(); + + return count($columnVectors) === 1 ? array_pop($columnVectors) : null; + } + + private function getColumnVectors(): array + { + $columnVectors = []; + for ($index = $this->indexStart; $index < ($this->indexStart + $this->argumentCount); ++$index) { + if ($this->rows[$index] > 1 && $this->columns[$index] === 1) { + $columnVectors[] = $index; + } + } + + return $columnVectors; + } + + public function getMatrixPair(): array + { + for ($i = $this->indexStart; $i < ($this->indexStart + $this->argumentCount - 1); ++$i) { + for ($j = $i + 1; $j < $this->argumentCount; ++$j) { + if (isset($this->rows[$i], $this->rows[$j])) { + return [$i, $j]; + } + } + } + + return []; + } + + public function isVector(int $argument): bool + { + return $this->rows[$argument] === 1 || $this->columns[$argument] === 1; + } + + public function isRowVector(int $argument): bool + { + return $this->rows[$argument] === 1; + } + + public function isColumnVector(int $argument): bool + { + return $this->columns[$argument] === 1; + } + + public function rowCount(int $argument): int + { + return $this->rows[$argument]; + } + + public function columnCount(int $argument): int + { + return $this->columns[$argument]; + } + + private function rows(array $arguments): array + { + return array_map( + function ($argument) { + return is_countable($argument) ? count($argument) : 1; + }, + $arguments + ); + } + + private function columns(array $arguments): array + { + return array_map( + function ($argument) { + return is_array($argument) && is_array($argument[array_keys($argument)[0]]) + ? count($argument[array_keys($argument)[0]]) + : 1; + }, + $arguments + ); + } + + public function arrayArguments(): int + { + $count = 0; + foreach (array_keys($this->arguments) as $argument) { + if ($this->rows[$argument] > 1 || $this->columns[$argument] > 1) { + ++$count; + } + } + + return $count; + } + + private function flattenSingleCellArrays(array $arguments, array $rows, array $columns): array + { + foreach ($arguments as $index => $argument) { + if ($rows[$index] === 1 && $columns[$index] === 1) { + while (is_array($argument)) { + $argument = array_pop($argument); + } + $arguments[$index] = $argument; + } + } + + return $arguments; + } + + private function filterArray(array $array): array + { + return array_filter( + $array, + function ($value) { + return $value > 1; + } + ); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Engine/ArrayArgumentProcessor.php b/PhpOffice/PhpSpreadsheet/Calculation/Engine/ArrayArgumentProcessor.php new file mode 100644 index 0000000..3e69d77 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Engine/ArrayArgumentProcessor.php @@ -0,0 +1,175 @@ +hasArrayArgument() === false) { + return [$method(...$arguments)]; + } + + if (self::$arrayArgumentHelper->arrayArguments() === 1) { + $nthArgument = self::$arrayArgumentHelper->getFirstArrayArgumentNumber(); + + return self::evaluateNthArgumentAsArray($method, $nthArgument, ...$arguments); + } + + $singleRowVectorIndex = self::$arrayArgumentHelper->getSingleRowVector(); + $singleColumnVectorIndex = self::$arrayArgumentHelper->getSingleColumnVector(); + + if ($singleRowVectorIndex !== null && $singleColumnVectorIndex !== null) { + // Basic logic for a single row vector and a single column vector + return self::evaluateVectorPair($method, $singleRowVectorIndex, $singleColumnVectorIndex, ...$arguments); + } + + $matrixPair = self::$arrayArgumentHelper->getMatrixPair(); + if ($matrixPair !== []) { + if ( + (self::$arrayArgumentHelper->isVector($matrixPair[0]) === true && + self::$arrayArgumentHelper->isVector($matrixPair[1]) === false) || + (self::$arrayArgumentHelper->isVector($matrixPair[0]) === false && + self::$arrayArgumentHelper->isVector($matrixPair[1]) === true) + ) { + // Logic for a matrix and a vector (row or column) + return self::evaluateVectorMatrixPair($method, $matrixPair, ...$arguments); + } + // Logic for matrix/matrix, column vector/column vector or row vector/row vector + return self::evaluateMatrixPair($method, $matrixPair, ...$arguments); + } + + // Still need to work out the logic for more than two array arguments, + // For the moment, we're throwing an Exception when we initialise the ArrayArgumentHelper + return ['#VALUE!']; + } + + /** + * @param mixed ...$arguments + */ + private static function evaluateVectorMatrixPair(callable $method, array $matrixIndexes, ...$arguments): array + { + $matrix2 = array_pop($matrixIndexes); + /** @var array $matrixValues2 */ + $matrixValues2 = $arguments[$matrix2]; + $matrix1 = array_pop($matrixIndexes); + /** @var array $matrixValues1 */ + $matrixValues1 = $arguments[$matrix1]; + + $rows = min(array_map([self::$arrayArgumentHelper, 'rowCount'], [$matrix1, $matrix2])); + $columns = min(array_map([self::$arrayArgumentHelper, 'columnCount'], [$matrix1, $matrix2])); + + if ($rows === 1) { + $rows = max(array_map([self::$arrayArgumentHelper, 'rowCount'], [$matrix1, $matrix2])); + } + if ($columns === 1) { + $columns = max(array_map([self::$arrayArgumentHelper, 'columnCount'], [$matrix1, $matrix2])); + } + + $result = []; + for ($rowIndex = 0; $rowIndex < $rows; ++$rowIndex) { + for ($columnIndex = 0; $columnIndex < $columns; ++$columnIndex) { + $rowIndex1 = self::$arrayArgumentHelper->isRowVector($matrix1) ? 0 : $rowIndex; + $columnIndex1 = self::$arrayArgumentHelper->isColumnVector($matrix1) ? 0 : $columnIndex; + $value1 = $matrixValues1[$rowIndex1][$columnIndex1]; + $rowIndex2 = self::$arrayArgumentHelper->isRowVector($matrix2) ? 0 : $rowIndex; + $columnIndex2 = self::$arrayArgumentHelper->isColumnVector($matrix2) ? 0 : $columnIndex; + $value2 = $matrixValues2[$rowIndex2][$columnIndex2]; + $arguments[$matrix1] = $value1; + $arguments[$matrix2] = $value2; + + $result[$rowIndex][$columnIndex] = $method(...$arguments); + } + } + + return $result; + } + + /** + * @param mixed ...$arguments + */ + private static function evaluateMatrixPair(callable $method, array $matrixIndexes, ...$arguments): array + { + $matrix2 = array_pop($matrixIndexes); + /** @var array $matrixValues2 */ + $matrixValues2 = $arguments[$matrix2]; + $matrix1 = array_pop($matrixIndexes); + /** @var array $matrixValues1 */ + $matrixValues1 = $arguments[$matrix1]; + + $result = []; + foreach ($matrixValues1 as $rowIndex => $row) { + foreach ($row as $columnIndex => $value1) { + if (isset($matrixValues2[$rowIndex][$columnIndex]) === false) { + continue; + } + + $value2 = $matrixValues2[$rowIndex][$columnIndex]; + $arguments[$matrix1] = $value1; + $arguments[$matrix2] = $value2; + + $result[$rowIndex][$columnIndex] = $method(...$arguments); + } + } + + return $result; + } + + /** + * @param mixed ...$arguments + */ + private static function evaluateVectorPair(callable $method, int $rowIndex, int $columnIndex, ...$arguments): array + { + $rowVector = Functions::flattenArray($arguments[$rowIndex]); + $columnVector = Functions::flattenArray($arguments[$columnIndex]); + + $result = []; + foreach ($columnVector as $column) { + $rowResults = []; + foreach ($rowVector as $row) { + $arguments[$rowIndex] = $row; + $arguments[$columnIndex] = $column; + + $rowResults[] = $method(...$arguments); + } + $result[] = $rowResults; + } + + return $result; + } + + /** + * Note, offset is from 1 (for the first argument) rather than from 0. + * + * @param mixed ...$arguments + */ + private static function evaluateNthArgumentAsArray(callable $method, int $nthArgument, ...$arguments): array + { + $values = array_slice($arguments, $nthArgument - 1, 1); + /** @var array $values */ + $values = array_pop($values); + + $result = []; + foreach ($values as $value) { + $arguments[$nthArgument - 1] = $value; + $result[] = $method(...$arguments); + } + + return $result; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Engine/BranchPruner.php b/PhpOffice/PhpSpreadsheet/Calculation/Engine/BranchPruner.php new file mode 100644 index 0000000..9cd767e --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Engine/BranchPruner.php @@ -0,0 +1,223 @@ +branchPruningEnabled = $branchPruningEnabled; + } + + public function clearBranchStore(): void + { + $this->branchStoreKeyCounter = 0; + } + + public function initialiseForLoop(): void + { + $this->currentCondition = null; + $this->currentOnlyIf = null; + $this->currentOnlyIfNot = null; + $this->previousStoreKey = null; + $this->pendingStoreKey = empty($this->storeKeysStack) ? null : end($this->storeKeysStack); + + if ($this->branchPruningEnabled) { + $this->initialiseCondition(); + $this->initialiseThen(); + $this->initialiseElse(); + } + } + + private function initialiseCondition(): void + { + if (isset($this->conditionMap[$this->pendingStoreKey]) && $this->conditionMap[$this->pendingStoreKey]) { + $this->currentCondition = $this->pendingStoreKey; + $stackDepth = count($this->storeKeysStack); + if ($stackDepth > 1) { + // nested if + $this->previousStoreKey = $this->storeKeysStack[$stackDepth - 2]; + } + } + } + + private function initialiseThen(): void + { + if (isset($this->thenMap[$this->pendingStoreKey]) && $this->thenMap[$this->pendingStoreKey]) { + $this->currentOnlyIf = $this->pendingStoreKey; + } elseif ( + isset($this->previousStoreKey, $this->thenMap[$this->previousStoreKey]) + && $this->thenMap[$this->previousStoreKey] + ) { + $this->currentOnlyIf = $this->previousStoreKey; + } + } + + private function initialiseElse(): void + { + if (isset($this->elseMap[$this->pendingStoreKey]) && $this->elseMap[$this->pendingStoreKey]) { + $this->currentOnlyIfNot = $this->pendingStoreKey; + } elseif ( + isset($this->previousStoreKey, $this->elseMap[$this->previousStoreKey]) + && $this->elseMap[$this->previousStoreKey] + ) { + $this->currentOnlyIfNot = $this->previousStoreKey; + } + } + + public function decrementDepth(): void + { + if (!empty($this->pendingStoreKey)) { + --$this->braceDepthMap[$this->pendingStoreKey]; + } + } + + public function incrementDepth(): void + { + if (!empty($this->pendingStoreKey)) { + ++$this->braceDepthMap[$this->pendingStoreKey]; + } + } + + public function functionCall(string $functionName): void + { + if ($this->branchPruningEnabled && ($functionName === 'IF(')) { + // we handle a new if + $this->pendingStoreKey = $this->getUnusedBranchStoreKey(); + $this->storeKeysStack[] = $this->pendingStoreKey; + $this->conditionMap[$this->pendingStoreKey] = true; + $this->braceDepthMap[$this->pendingStoreKey] = 0; + } elseif (!empty($this->pendingStoreKey) && array_key_exists($this->pendingStoreKey, $this->braceDepthMap)) { + // this is not an if but we go deeper + ++$this->braceDepthMap[$this->pendingStoreKey]; + } + } + + public function argumentSeparator(): void + { + if (!empty($this->pendingStoreKey) && $this->braceDepthMap[$this->pendingStoreKey] === 0) { + // We must go to the IF next argument + if ($this->conditionMap[$this->pendingStoreKey]) { + $this->conditionMap[$this->pendingStoreKey] = false; + $this->thenMap[$this->pendingStoreKey] = true; + } elseif ($this->thenMap[$this->pendingStoreKey]) { + $this->thenMap[$this->pendingStoreKey] = false; + $this->elseMap[$this->pendingStoreKey] = true; + } elseif ($this->elseMap[$this->pendingStoreKey]) { + throw new Exception('Reaching fourth argument of an IF'); + } + } + } + + /** + * @param mixed $value + */ + public function closingBrace($value): void + { + if (!empty($this->pendingStoreKey) && $this->braceDepthMap[$this->pendingStoreKey] === -1) { + // we are closing an IF( + if ($value !== 'IF(') { + throw new Exception('Parser bug we should be in an "IF("'); + } + + if ($this->conditionMap[$this->pendingStoreKey]) { + throw new Exception('We should not be expecting a condition'); + } + + $this->thenMap[$this->pendingStoreKey] = false; + $this->elseMap[$this->pendingStoreKey] = false; + --$this->braceDepthMap[$this->pendingStoreKey]; + array_pop($this->storeKeysStack); + $this->pendingStoreKey = null; + } + } + + public function currentCondition(): ?string + { + return $this->currentCondition; + } + + public function currentOnlyIf(): ?string + { + return $this->currentOnlyIf; + } + + public function currentOnlyIfNot(): ?string + { + return $this->currentOnlyIfNot; + } + + private function getUnusedBranchStoreKey(): string + { + $storeKeyValue = 'storeKey-' . $this->branchStoreKeyCounter; + ++$this->branchStoreKeyCounter; + + return $storeKeyValue; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Engine/CyclicReferenceStack.php b/PhpOffice/PhpSpreadsheet/Calculation/Engine/CyclicReferenceStack.php old mode 100755 new mode 100644 index 5a54d83..b688e05 --- a/PhpOffice/PhpSpreadsheet/Calculation/Engine/CyclicReferenceStack.php +++ b/PhpOffice/PhpSpreadsheet/Calculation/Engine/CyclicReferenceStack.php @@ -26,7 +26,7 @@ class CyclicReferenceStack * * @param mixed $value */ - public function push($value) + public function push($value): void { $this->stack[$value] = $value; } @@ -56,7 +56,7 @@ class CyclicReferenceStack /** * Clear the stack. */ - public function clear() + public function clear(): void { $this->stack = []; } diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Engine/FormattedNumber.php b/PhpOffice/PhpSpreadsheet/Calculation/Engine/FormattedNumber.php new file mode 100644 index 0000000..8fe7aa4 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Engine/FormattedNumber.php @@ -0,0 +1,126 @@ +[-+])? *\% *(?[-+])? *(?[0-9]+\.?[0-9*]*(?:E[-+]?[0-9]*)?) *)|(?: *(?[-+])? *(?[0-9]+\.?[0-9]*(?:E[-+]?[0-9]*)?) *\% *))$~i'; + + private const STRING_CONVERSION_LIST = [ + [self::class, 'convertToNumberIfNumeric'], + [self::class, 'convertToNumberIfFraction'], + [self::class, 'convertToNumberIfPercent'], + [self::class, 'convertToNumberIfCurrency'], + ]; + + /** + * Identify whether a string contains a formatted numeric value, + * and convert it to a numeric if it is. + * + * @param string $operand string value to test + */ + public static function convertToNumberIfFormatted(string &$operand): bool + { + foreach (self::STRING_CONVERSION_LIST as $conversionMethod) { + if ($conversionMethod($operand) === true) { + return true; + } + } + + return false; + } + + /** + * Identify whether a string contains a numeric value, + * and convert it to a numeric if it is. + * + * @param string $operand string value to test + */ + public static function convertToNumberIfNumeric(string &$operand): bool + { + $value = preg_replace(['/(\d),(\d)/u', '/([+-])\s+(\d)/u'], ['$1$2', '$1$2'], trim($operand)); + + if (is_numeric($value)) { + $operand = (float) $value; + + return true; + } + + return false; + } + + /** + * Identify whether a string contains a fractional numeric value, + * and convert it to a numeric if it is. + * + * @param string $operand string value to test + */ + public static function convertToNumberIfFraction(string &$operand): bool + { + if (preg_match(self::STRING_REGEXP_FRACTION, $operand, $match)) { + $sign = ($match[1] === '-') ? '-' : '+'; + $wholePart = ($match[3] === '') ? '' : ($sign . $match[3]); + $fractionFormula = '=' . $wholePart . $sign . $match[4]; + $operand = Calculation::getInstance()->_calculateFormulaValue($fractionFormula); + + return true; + } + + return false; + } + + /** + * Identify whether a string contains a percentage, and if so, + * convert it to a numeric. + * + * @param string $operand string value to test + */ + public static function convertToNumberIfPercent(string &$operand): bool + { + $value = preg_replace('/(\d),(\d)/u', '$1$2', $operand); + + $match = []; + if ($value !== null && preg_match(self::STRING_REGEXP_PERCENT, $value, $match, PREG_UNMATCHED_AS_NULL)) { + //Calculate the percentage + $sign = ($match['PrefixedSign'] ?? $match['PrefixedSign2'] ?? $match['PostfixedSign']) ?? ''; + $operand = (float) ($sign . ($match['PostfixedValue'] ?? $match['PrefixedValue'])) / 100; + + return true; + } + + return false; + } + + /** + * Identify whether a string contains a currency value, and if so, + * convert it to a numeric. + * + * @param string $operand string value to test + */ + public static function convertToNumberIfCurrency(string &$operand): bool + { + $quotedCurrencyCode = preg_quote(StringHelper::getCurrencyCode()); + + $value = preg_replace('/(\d),(\d)/u', '$1$2', $operand); + $regExp = '~^(?:(?: *(?[-+])? *' . $quotedCurrencyCode . ' *(?[-+])? *(?[0-9]+\.?[0-9*]*(?:E[-+]?[0-9]*)?) *)|(?: *(?[-+])? *(?[0-9]+\.?[0-9]*(?:E[-+]?[0-9]*)?) *' . $quotedCurrencyCode . ' *))$~ui'; + + $match = []; + if ($value !== null && preg_match($regExp, $value, $match, PREG_UNMATCHED_AS_NULL)) { + //Determine the sign + $sign = ($match['PrefixedSign'] ?? $match['PrefixedSign2'] ?? $match['PostfixedSign']) ?? ''; + //Cast to a float + $operand = (float) ($sign . ($match['PostfixedValue'] ?? $match['PrefixedValue'])); + + return true; + } + + return false; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Engine/Logger.php b/PhpOffice/PhpSpreadsheet/Calculation/Engine/Logger.php old mode 100755 new mode 100644 index 6793dad..256c3ef --- a/PhpOffice/PhpSpreadsheet/Calculation/Engine/Logger.php +++ b/PhpOffice/PhpSpreadsheet/Calculation/Engine/Logger.php @@ -39,8 +39,6 @@ class Logger /** * Instantiate a Calculation engine logger. - * - * @param CyclicReferenceStack $stack */ public function __construct(CyclicReferenceStack $stack) { @@ -50,11 +48,11 @@ class Logger /** * Enable/Disable Calculation engine logging. * - * @param bool $pValue + * @param bool $writeDebugLog */ - public function setWriteDebugLog($pValue) + public function setWriteDebugLog($writeDebugLog): void { - $this->writeDebugLog = $pValue; + $this->writeDebugLog = $writeDebugLog; } /** @@ -70,11 +68,11 @@ class Logger /** * Enable/Disable echoing of debug log information. * - * @param bool $pValue + * @param bool $echoDebugLog */ - public function setEchoDebugLog($pValue) + public function setEchoDebugLog($echoDebugLog): void { - $this->echoDebugLog = $pValue; + $this->echoDebugLog = $echoDebugLog; } /** @@ -89,18 +87,20 @@ class Logger /** * Write an entry to the calculation engine debug log. + * + * @param mixed $args */ - public function writeDebugLog(...$args) + public function writeDebugLog(string $message, ...$args): void { // Only write the debug log if logging is enabled if ($this->writeDebugLog) { - $message = implode($args); + $message = sprintf($message, ...$args); $cellReference = implode(' -> ', $this->cellStack->showStack()); if ($this->echoDebugLog) { echo $cellReference, - ($this->cellStack->count() > 0 ? ' => ' : ''), - $message, - PHP_EOL; + ($this->cellStack->count() > 0 ? ' => ' : ''), + $message, + PHP_EOL; } $this->debugLog[] = $cellReference . ($this->cellStack->count() > 0 ? ' => ' : '') . @@ -108,10 +108,24 @@ class Logger } } + /** + * Write a series of entries to the calculation engine debug log. + * + * @param string[] $args + */ + public function mergeDebugLog(array $args): void + { + if ($this->writeDebugLog) { + foreach ($args as $entry) { + $this->writeDebugLog($entry); + } + } + } + /** * Clear the calculation engine debug log. */ - public function clearLog() + public function clearLog(): void { $this->debugLog = []; } diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Engine/Operands/Operand.php b/PhpOffice/PhpSpreadsheet/Calculation/Engine/Operands/Operand.php new file mode 100644 index 0000000..05264c3 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Engine/Operands/Operand.php @@ -0,0 +1,10 @@ +value = $structuredReference; + } + + public static function fromParser(string $formula, int $index, array $matches): self + { + $val = $matches[0]; + + $srCount = substr_count($val, self::OPEN_BRACE) + - substr_count($val, self::CLOSE_BRACE); + while ($srCount > 0) { + $srIndex = strlen($val); + $srStringRemainder = substr($formula, $index + $srIndex); + $closingPos = strpos($srStringRemainder, self::CLOSE_BRACE); + if ($closingPos === false) { + throw new Exception("Formula Error: No closing ']' to match opening '['"); + } + $srStringRemainder = substr($srStringRemainder, 0, $closingPos + 1); + --$srCount; + if (strpos($srStringRemainder, self::OPEN_BRACE) !== false) { + ++$srCount; + } + $val .= $srStringRemainder; + } + + return new self($val); + } + + /** + * @throws Exception + * @throws \PhpOffice\PhpSpreadsheet\Exception + */ + public function parse(Cell $cell): string + { + $this->getTableStructure($cell); + $cellRange = ($this->isRowReference()) ? $this->getRowReference($cell) : $this->getColumnReference(); + + return $cellRange; + } + + private function isRowReference(): bool + { + return strpos($this->value, '[@') !== false + || strpos($this->value, '[' . self::ITEM_SPECIFIER_THIS_ROW . ']') !== false; + } + + /** + * @throws Exception + * @throws \PhpOffice\PhpSpreadsheet\Exception + */ + private function getTableStructure(Cell $cell): void + { + preg_match(self::TABLE_REFERENCE, $this->value, $matches); + + $this->tableName = $matches[1]; + $this->table = ($this->tableName === '') + ? $this->getTableForCell($cell) + : $this->getTableByName($cell); + $this->reference = $matches[2]; + $tableRange = Coordinate::getRangeBoundaries($this->table->getRange()); + + $this->headersRow = ($this->table->getShowHeaderRow()) ? (int) $tableRange[0][1] : null; + $this->firstDataRow = ($this->table->getShowHeaderRow()) ? (int) $tableRange[0][1] + 1 : $tableRange[0][1]; + $this->totalsRow = ($this->table->getShowTotalsRow()) ? (int) $tableRange[1][1] : null; + $this->lastDataRow = ($this->table->getShowTotalsRow()) ? (int) $tableRange[1][1] - 1 : $tableRange[1][1]; + + $this->columns = $this->getColumns($cell, $tableRange); + } + + /** + * @throws Exception + * @throws \PhpOffice\PhpSpreadsheet\Exception + */ + private function getTableForCell(Cell $cell): Table + { + $tables = $cell->getWorksheet()->getTableCollection(); + foreach ($tables as $table) { + /** @var Table $table */ + $range = $table->getRange(); + if ($cell->isInRange($range) === true) { + $this->tableName = $table->getName(); + + return $table; + } + } + + throw new Exception('Table for Structured Reference cannot be identified'); + } + + /** + * @throws Exception + * @throws \PhpOffice\PhpSpreadsheet\Exception + */ + private function getTableByName(Cell $cell): Table + { + $table = $cell->getWorksheet()->getTableByName($this->tableName); + + if ($table === null) { + throw new Exception("Table {$this->tableName} for Structured Reference cannot be located"); + } + + return $table; + } + + private function getColumns(Cell $cell, array $tableRange): array + { + $worksheet = $cell->getWorksheet(); + $cellReference = $cell->getCoordinate(); + + $columns = []; + $lastColumn = ++$tableRange[1][0]; + for ($column = $tableRange[0][0]; $column !== $lastColumn; ++$column) { + $columns[$column] = $worksheet + ->getCell($column . ($this->headersRow ?? ($this->firstDataRow - 1))) + ->getCalculatedValue(); + } + + $cell = $worksheet->getCell($cellReference); + + return $columns; + } + + private function getRowReference(Cell $cell): string + { + $reference = str_replace("\u{a0}", ' ', $this->reference); + /** @var string $reference */ + $reference = str_replace('[' . self::ITEM_SPECIFIER_THIS_ROW . '],', '', $reference); + + foreach ($this->columns as $columnId => $columnName) { + $columnName = str_replace("\u{a0}", ' ', $columnName); + $cellReference = $columnId . $cell->getRow(); + $pattern1 = '/\[' . preg_quote($columnName) . '\]/miu'; + $pattern2 = '/@' . preg_quote($columnName) . '/miu'; + /** @var string $reference */ + if (preg_match($pattern1, $reference) === 1) { + $reference = preg_replace($pattern1, $cellReference, $reference); + } elseif (preg_match($pattern2, $reference) === 1) { + $reference = preg_replace($pattern2, $cellReference, $reference); + } + } + + /** @var string $reference */ + return $this->validateParsedReference(trim($reference, '[]@, ')); + } + + /** + * @throws Exception + * @throws \PhpOffice\PhpSpreadsheet\Exception + */ + private function getColumnReference(): string + { + $reference = str_replace("\u{a0}", ' ', $this->reference); + $startRow = ($this->totalsRow === null) ? $this->lastDataRow : $this->totalsRow; + $endRow = ($this->headersRow === null) ? $this->firstDataRow : $this->headersRow; + + [$startRow, $endRow] = $this->getRowsForColumnReference($reference, $startRow, $endRow); + $reference = $this->getColumnsForColumnReference($reference, $startRow, $endRow); + + $reference = trim($reference, '[]@, '); + if (substr_count($reference, ':') > 1) { + $cells = explode(':', $reference); + $firstCell = array_shift($cells); + $lastCell = array_pop($cells); + $reference = "{$firstCell}:{$lastCell}"; + } + + return $this->validateParsedReference($reference); + } + + /** + * @throws Exception + * @throws \PhpOffice\PhpSpreadsheet\Exception + */ + private function validateParsedReference(string $reference): string + { + if (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . ':' . Calculation::CALCULATION_REGEXP_CELLREF . '$/miu', $reference) !== 1) { + if (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/miu', $reference) !== 1) { + throw new Exception("Invalid Structured Reference {$this->reference} {$reference}"); + } + } + + return $reference; + } + + private function fullData(int $startRow, int $endRow): string + { + $columns = array_keys($this->columns); + $firstColumn = array_shift($columns); + $lastColumn = (empty($columns)) ? $firstColumn : array_pop($columns); + + return "{$firstColumn}{$startRow}:{$lastColumn}{$endRow}"; + } + + private function getMinimumRow(string $reference): int + { + switch ($reference) { + case self::ITEM_SPECIFIER_ALL: + case self::ITEM_SPECIFIER_HEADERS: + return $this->headersRow ?? $this->firstDataRow; + case self::ITEM_SPECIFIER_DATA: + return $this->firstDataRow; + case self::ITEM_SPECIFIER_TOTALS: + return $this->totalsRow ?? $this->lastDataRow; + } + + return $this->headersRow ?? $this->firstDataRow; + } + + private function getMaximumRow(string $reference): int + { + switch ($reference) { + case self::ITEM_SPECIFIER_HEADERS: + return $this->headersRow ?? $this->firstDataRow; + case self::ITEM_SPECIFIER_DATA: + return $this->lastDataRow; + case self::ITEM_SPECIFIER_ALL: + case self::ITEM_SPECIFIER_TOTALS: + return $this->totalsRow ?? $this->lastDataRow; + } + + return $this->totalsRow ?? $this->lastDataRow; + } + + public function value(): string + { + return $this->value; + } + + /** + * @return array + */ + private function getRowsForColumnReference(string &$reference, int $startRow, int $endRow): array + { + $rowsSelected = false; + foreach (self::ITEM_SPECIFIER_ROWS_SET as $rowReference) { + $pattern = '/\[' . $rowReference . '\]/mui'; + /** @var string $reference */ + if (preg_match($pattern, $reference) === 1) { + if (($rowReference === self::ITEM_SPECIFIER_HEADERS) && ($this->table->getShowHeaderRow() === false)) { + throw new Exception( + 'Table Headers are Hidden, and should not be Referenced', + Exception::CALCULATION_ENGINE_PUSH_TO_STACK + ); + } + $rowsSelected = true; + $startRow = min($startRow, $this->getMinimumRow($rowReference)); + $endRow = max($endRow, $this->getMaximumRow($rowReference)); + $reference = preg_replace($pattern, '', $reference); + } + } + if ($rowsSelected === false) { + // If there isn't any Special Item Identifier specified, then the selection defaults to data rows only. + $startRow = $this->firstDataRow; + $endRow = $this->lastDataRow; + } + + return [$startRow, $endRow]; + } + + private function getColumnsForColumnReference(string $reference, int $startRow, int $endRow): string + { + $columnsSelected = false; + foreach ($this->columns as $columnId => $columnName) { + $columnName = str_replace("\u{a0}", ' ', $columnName); + $cellFrom = "{$columnId}{$startRow}"; + $cellTo = "{$columnId}{$endRow}"; + $cellReference = ($cellFrom === $cellTo) ? $cellFrom : "{$cellFrom}:{$cellTo}"; + $pattern = '/\[' . preg_quote($columnName) . '\]/mui'; + if (preg_match($pattern, $reference) === 1) { + $columnsSelected = true; + $reference = preg_replace($pattern, $cellReference, $reference); + } + /** @var string $reference */ + } + if ($columnsSelected === false) { + return $this->fullData($startRow, $endRow); + } + + return $reference; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Engineering.php b/PhpOffice/PhpSpreadsheet/Calculation/Engineering.php old mode 100755 new mode 100644 index 458d0e8..e3a2bd6 --- a/PhpOffice/PhpSpreadsheet/Calculation/Engineering.php +++ b/PhpOffice/PhpSpreadsheet/Calculation/Engineering.php @@ -3,725 +3,29 @@ namespace PhpOffice\PhpSpreadsheet\Calculation; use Complex\Complex; -use Complex\Exception as ComplexException; +use PhpOffice\PhpSpreadsheet\Calculation\Engineering\ComplexFunctions; +use PhpOffice\PhpSpreadsheet\Calculation\Engineering\ComplexOperations; +/** + * @deprecated 1.18.0 + */ class Engineering { /** * EULER. - */ - const EULER = 2.71828182845904523536; - - /** - * Details of the Units of measure that can be used in CONVERTUOM(). * - * @var mixed[] + * @deprecated 1.18.0 + * Use Engineering\Constants::EULER instead + * @see Engineering\Constants::EULER */ - private static $conversionUnits = [ - 'g' => ['Group' => 'Mass', 'Unit Name' => 'Gram', 'AllowPrefix' => true], - 'sg' => ['Group' => 'Mass', 'Unit Name' => 'Slug', 'AllowPrefix' => false], - 'lbm' => ['Group' => 'Mass', 'Unit Name' => 'Pound mass (avoirdupois)', 'AllowPrefix' => false], - 'u' => ['Group' => 'Mass', 'Unit Name' => 'U (atomic mass unit)', 'AllowPrefix' => true], - 'ozm' => ['Group' => 'Mass', 'Unit Name' => 'Ounce mass (avoirdupois)', 'AllowPrefix' => false], - 'm' => ['Group' => 'Distance', 'Unit Name' => 'Meter', 'AllowPrefix' => true], - 'mi' => ['Group' => 'Distance', 'Unit Name' => 'Statute mile', 'AllowPrefix' => false], - 'Nmi' => ['Group' => 'Distance', 'Unit Name' => 'Nautical mile', 'AllowPrefix' => false], - 'in' => ['Group' => 'Distance', 'Unit Name' => 'Inch', 'AllowPrefix' => false], - 'ft' => ['Group' => 'Distance', 'Unit Name' => 'Foot', 'AllowPrefix' => false], - 'yd' => ['Group' => 'Distance', 'Unit Name' => 'Yard', 'AllowPrefix' => false], - 'ang' => ['Group' => 'Distance', 'Unit Name' => 'Angstrom', 'AllowPrefix' => true], - 'Pica' => ['Group' => 'Distance', 'Unit Name' => 'Pica (1/72 in)', 'AllowPrefix' => false], - 'yr' => ['Group' => 'Time', 'Unit Name' => 'Year', 'AllowPrefix' => false], - 'day' => ['Group' => 'Time', 'Unit Name' => 'Day', 'AllowPrefix' => false], - 'hr' => ['Group' => 'Time', 'Unit Name' => 'Hour', 'AllowPrefix' => false], - 'mn' => ['Group' => 'Time', 'Unit Name' => 'Minute', 'AllowPrefix' => false], - 'sec' => ['Group' => 'Time', 'Unit Name' => 'Second', 'AllowPrefix' => true], - 'Pa' => ['Group' => 'Pressure', 'Unit Name' => 'Pascal', 'AllowPrefix' => true], - 'p' => ['Group' => 'Pressure', 'Unit Name' => 'Pascal', 'AllowPrefix' => true], - 'atm' => ['Group' => 'Pressure', 'Unit Name' => 'Atmosphere', 'AllowPrefix' => true], - 'at' => ['Group' => 'Pressure', 'Unit Name' => 'Atmosphere', 'AllowPrefix' => true], - 'mmHg' => ['Group' => 'Pressure', 'Unit Name' => 'mm of Mercury', 'AllowPrefix' => true], - 'N' => ['Group' => 'Force', 'Unit Name' => 'Newton', 'AllowPrefix' => true], - 'dyn' => ['Group' => 'Force', 'Unit Name' => 'Dyne', 'AllowPrefix' => true], - 'dy' => ['Group' => 'Force', 'Unit Name' => 'Dyne', 'AllowPrefix' => true], - 'lbf' => ['Group' => 'Force', 'Unit Name' => 'Pound force', 'AllowPrefix' => false], - 'J' => ['Group' => 'Energy', 'Unit Name' => 'Joule', 'AllowPrefix' => true], - 'e' => ['Group' => 'Energy', 'Unit Name' => 'Erg', 'AllowPrefix' => true], - 'c' => ['Group' => 'Energy', 'Unit Name' => 'Thermodynamic calorie', 'AllowPrefix' => true], - 'cal' => ['Group' => 'Energy', 'Unit Name' => 'IT calorie', 'AllowPrefix' => true], - 'eV' => ['Group' => 'Energy', 'Unit Name' => 'Electron volt', 'AllowPrefix' => true], - 'ev' => ['Group' => 'Energy', 'Unit Name' => 'Electron volt', 'AllowPrefix' => true], - 'HPh' => ['Group' => 'Energy', 'Unit Name' => 'Horsepower-hour', 'AllowPrefix' => false], - 'hh' => ['Group' => 'Energy', 'Unit Name' => 'Horsepower-hour', 'AllowPrefix' => false], - 'Wh' => ['Group' => 'Energy', 'Unit Name' => 'Watt-hour', 'AllowPrefix' => true], - 'wh' => ['Group' => 'Energy', 'Unit Name' => 'Watt-hour', 'AllowPrefix' => true], - 'flb' => ['Group' => 'Energy', 'Unit Name' => 'Foot-pound', 'AllowPrefix' => false], - 'BTU' => ['Group' => 'Energy', 'Unit Name' => 'BTU', 'AllowPrefix' => false], - 'btu' => ['Group' => 'Energy', 'Unit Name' => 'BTU', 'AllowPrefix' => false], - 'HP' => ['Group' => 'Power', 'Unit Name' => 'Horsepower', 'AllowPrefix' => false], - 'h' => ['Group' => 'Power', 'Unit Name' => 'Horsepower', 'AllowPrefix' => false], - 'W' => ['Group' => 'Power', 'Unit Name' => 'Watt', 'AllowPrefix' => true], - 'w' => ['Group' => 'Power', 'Unit Name' => 'Watt', 'AllowPrefix' => true], - 'T' => ['Group' => 'Magnetism', 'Unit Name' => 'Tesla', 'AllowPrefix' => true], - 'ga' => ['Group' => 'Magnetism', 'Unit Name' => 'Gauss', 'AllowPrefix' => true], - 'C' => ['Group' => 'Temperature', 'Unit Name' => 'Celsius', 'AllowPrefix' => false], - 'cel' => ['Group' => 'Temperature', 'Unit Name' => 'Celsius', 'AllowPrefix' => false], - 'F' => ['Group' => 'Temperature', 'Unit Name' => 'Fahrenheit', 'AllowPrefix' => false], - 'fah' => ['Group' => 'Temperature', 'Unit Name' => 'Fahrenheit', 'AllowPrefix' => false], - 'K' => ['Group' => 'Temperature', 'Unit Name' => 'Kelvin', 'AllowPrefix' => false], - 'kel' => ['Group' => 'Temperature', 'Unit Name' => 'Kelvin', 'AllowPrefix' => false], - 'tsp' => ['Group' => 'Liquid', 'Unit Name' => 'Teaspoon', 'AllowPrefix' => false], - 'tbs' => ['Group' => 'Liquid', 'Unit Name' => 'Tablespoon', 'AllowPrefix' => false], - 'oz' => ['Group' => 'Liquid', 'Unit Name' => 'Fluid Ounce', 'AllowPrefix' => false], - 'cup' => ['Group' => 'Liquid', 'Unit Name' => 'Cup', 'AllowPrefix' => false], - 'pt' => ['Group' => 'Liquid', 'Unit Name' => 'U.S. Pint', 'AllowPrefix' => false], - 'us_pt' => ['Group' => 'Liquid', 'Unit Name' => 'U.S. Pint', 'AllowPrefix' => false], - 'uk_pt' => ['Group' => 'Liquid', 'Unit Name' => 'U.K. Pint', 'AllowPrefix' => false], - 'qt' => ['Group' => 'Liquid', 'Unit Name' => 'Quart', 'AllowPrefix' => false], - 'gal' => ['Group' => 'Liquid', 'Unit Name' => 'Gallon', 'AllowPrefix' => false], - 'l' => ['Group' => 'Liquid', 'Unit Name' => 'Litre', 'AllowPrefix' => true], - 'lt' => ['Group' => 'Liquid', 'Unit Name' => 'Litre', 'AllowPrefix' => true], - ]; - - /** - * Details of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM(). - * - * @var mixed[] - */ - private static $conversionMultipliers = [ - 'Y' => ['multiplier' => 1E24, 'name' => 'yotta'], - 'Z' => ['multiplier' => 1E21, 'name' => 'zetta'], - 'E' => ['multiplier' => 1E18, 'name' => 'exa'], - 'P' => ['multiplier' => 1E15, 'name' => 'peta'], - 'T' => ['multiplier' => 1E12, 'name' => 'tera'], - 'G' => ['multiplier' => 1E9, 'name' => 'giga'], - 'M' => ['multiplier' => 1E6, 'name' => 'mega'], - 'k' => ['multiplier' => 1E3, 'name' => 'kilo'], - 'h' => ['multiplier' => 1E2, 'name' => 'hecto'], - 'e' => ['multiplier' => 1E1, 'name' => 'deka'], - 'd' => ['multiplier' => 1E-1, 'name' => 'deci'], - 'c' => ['multiplier' => 1E-2, 'name' => 'centi'], - 'm' => ['multiplier' => 1E-3, 'name' => 'milli'], - 'u' => ['multiplier' => 1E-6, 'name' => 'micro'], - 'n' => ['multiplier' => 1E-9, 'name' => 'nano'], - 'p' => ['multiplier' => 1E-12, 'name' => 'pico'], - 'f' => ['multiplier' => 1E-15, 'name' => 'femto'], - 'a' => ['multiplier' => 1E-18, 'name' => 'atto'], - 'z' => ['multiplier' => 1E-21, 'name' => 'zepto'], - 'y' => ['multiplier' => 1E-24, 'name' => 'yocto'], - ]; - - /** - * Details of the Units of measure conversion factors, organised by group. - * - * @var mixed[] - */ - private static $unitConversions = [ - 'Mass' => [ - 'g' => [ - 'g' => 1.0, - 'sg' => 6.85220500053478E-05, - 'lbm' => 2.20462291469134E-03, - 'u' => 6.02217000000000E+23, - 'ozm' => 3.52739718003627E-02, - ], - 'sg' => [ - 'g' => 1.45938424189287E+04, - 'sg' => 1.0, - 'lbm' => 3.21739194101647E+01, - 'u' => 8.78866000000000E+27, - 'ozm' => 5.14782785944229E+02, - ], - 'lbm' => [ - 'g' => 4.5359230974881148E+02, - 'sg' => 3.10810749306493E-02, - 'lbm' => 1.0, - 'u' => 2.73161000000000E+26, - 'ozm' => 1.60000023429410E+01, - ], - 'u' => [ - 'g' => 1.66053100460465E-24, - 'sg' => 1.13782988532950E-28, - 'lbm' => 3.66084470330684E-27, - 'u' => 1.0, - 'ozm' => 5.85735238300524E-26, - ], - 'ozm' => [ - 'g' => 2.83495152079732E+01, - 'sg' => 1.94256689870811E-03, - 'lbm' => 6.24999908478882E-02, - 'u' => 1.70725600000000E+25, - 'ozm' => 1.0, - ], - ], - 'Distance' => [ - 'm' => [ - 'm' => 1.0, - 'mi' => 6.21371192237334E-04, - 'Nmi' => 5.39956803455724E-04, - 'in' => 3.93700787401575E+01, - 'ft' => 3.28083989501312E+00, - 'yd' => 1.09361329797891E+00, - 'ang' => 1.00000000000000E+10, - 'Pica' => 2.83464566929116E+03, - ], - 'mi' => [ - 'm' => 1.60934400000000E+03, - 'mi' => 1.0, - 'Nmi' => 8.68976241900648E-01, - 'in' => 6.33600000000000E+04, - 'ft' => 5.28000000000000E+03, - 'yd' => 1.76000000000000E+03, - 'ang' => 1.60934400000000E+13, - 'Pica' => 4.56191999999971E+06, - ], - 'Nmi' => [ - 'm' => 1.85200000000000E+03, - 'mi' => 1.15077944802354E+00, - 'Nmi' => 1.0, - 'in' => 7.29133858267717E+04, - 'ft' => 6.07611548556430E+03, - 'yd' => 2.02537182785694E+03, - 'ang' => 1.85200000000000E+13, - 'Pica' => 5.24976377952723E+06, - ], - 'in' => [ - 'm' => 2.54000000000000E-02, - 'mi' => 1.57828282828283E-05, - 'Nmi' => 1.37149028077754E-05, - 'in' => 1.0, - 'ft' => 8.33333333333333E-02, - 'yd' => 2.77777777686643E-02, - 'ang' => 2.54000000000000E+08, - 'Pica' => 7.19999999999955E+01, - ], - 'ft' => [ - 'm' => 3.04800000000000E-01, - 'mi' => 1.89393939393939E-04, - 'Nmi' => 1.64578833693305E-04, - 'in' => 1.20000000000000E+01, - 'ft' => 1.0, - 'yd' => 3.33333333223972E-01, - 'ang' => 3.04800000000000E+09, - 'Pica' => 8.63999999999946E+02, - ], - 'yd' => [ - 'm' => 9.14400000300000E-01, - 'mi' => 5.68181818368230E-04, - 'Nmi' => 4.93736501241901E-04, - 'in' => 3.60000000118110E+01, - 'ft' => 3.00000000000000E+00, - 'yd' => 1.0, - 'ang' => 9.14400000300000E+09, - 'Pica' => 2.59200000085023E+03, - ], - 'ang' => [ - 'm' => 1.00000000000000E-10, - 'mi' => 6.21371192237334E-14, - 'Nmi' => 5.39956803455724E-14, - 'in' => 3.93700787401575E-09, - 'ft' => 3.28083989501312E-10, - 'yd' => 1.09361329797891E-10, - 'ang' => 1.0, - 'Pica' => 2.83464566929116E-07, - ], - 'Pica' => [ - 'm' => 3.52777777777800E-04, - 'mi' => 2.19205948372629E-07, - 'Nmi' => 1.90484761219114E-07, - 'in' => 1.38888888888898E-02, - 'ft' => 1.15740740740748E-03, - 'yd' => 3.85802469009251E-04, - 'ang' => 3.52777777777800E+06, - 'Pica' => 1.0, - ], - ], - 'Time' => [ - 'yr' => [ - 'yr' => 1.0, - 'day' => 365.25, - 'hr' => 8766.0, - 'mn' => 525960.0, - 'sec' => 31557600.0, - ], - 'day' => [ - 'yr' => 2.73785078713210E-03, - 'day' => 1.0, - 'hr' => 24.0, - 'mn' => 1440.0, - 'sec' => 86400.0, - ], - 'hr' => [ - 'yr' => 1.14077116130504E-04, - 'day' => 4.16666666666667E-02, - 'hr' => 1.0, - 'mn' => 60.0, - 'sec' => 3600.0, - ], - 'mn' => [ - 'yr' => 1.90128526884174E-06, - 'day' => 6.94444444444444E-04, - 'hr' => 1.66666666666667E-02, - 'mn' => 1.0, - 'sec' => 60.0, - ], - 'sec' => [ - 'yr' => 3.16880878140289E-08, - 'day' => 1.15740740740741E-05, - 'hr' => 2.77777777777778E-04, - 'mn' => 1.66666666666667E-02, - 'sec' => 1.0, - ], - ], - 'Pressure' => [ - 'Pa' => [ - 'Pa' => 1.0, - 'p' => 1.0, - 'atm' => 9.86923299998193E-06, - 'at' => 9.86923299998193E-06, - 'mmHg' => 7.50061707998627E-03, - ], - 'p' => [ - 'Pa' => 1.0, - 'p' => 1.0, - 'atm' => 9.86923299998193E-06, - 'at' => 9.86923299998193E-06, - 'mmHg' => 7.50061707998627E-03, - ], - 'atm' => [ - 'Pa' => 1.01324996583000E+05, - 'p' => 1.01324996583000E+05, - 'atm' => 1.0, - 'at' => 1.0, - 'mmHg' => 760.0, - ], - 'at' => [ - 'Pa' => 1.01324996583000E+05, - 'p' => 1.01324996583000E+05, - 'atm' => 1.0, - 'at' => 1.0, - 'mmHg' => 760.0, - ], - 'mmHg' => [ - 'Pa' => 1.33322363925000E+02, - 'p' => 1.33322363925000E+02, - 'atm' => 1.31578947368421E-03, - 'at' => 1.31578947368421E-03, - 'mmHg' => 1.0, - ], - ], - 'Force' => [ - 'N' => [ - 'N' => 1.0, - 'dyn' => 1.0E+5, - 'dy' => 1.0E+5, - 'lbf' => 2.24808923655339E-01, - ], - 'dyn' => [ - 'N' => 1.0E-5, - 'dyn' => 1.0, - 'dy' => 1.0, - 'lbf' => 2.24808923655339E-06, - ], - 'dy' => [ - 'N' => 1.0E-5, - 'dyn' => 1.0, - 'dy' => 1.0, - 'lbf' => 2.24808923655339E-06, - ], - 'lbf' => [ - 'N' => 4.448222, - 'dyn' => 4.448222E+5, - 'dy' => 4.448222E+5, - 'lbf' => 1.0, - ], - ], - 'Energy' => [ - 'J' => [ - 'J' => 1.0, - '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, - ], - 'e' => [ - 'J' => 1.00000048065700E-07, - 'e' => 1.0, - 'c' => 2.39006364353494E-08, - 'cal' => 2.38846305445111E-08, - 'eV' => 6.24146000000000E+11, - 'ev' => 6.24146000000000E+11, - 'HPh' => 3.72506609848824E-14, - 'hh' => 3.72506609848824E-14, - 'Wh' => 2.77778049754611E-11, - 'wh' => 2.77778049754611E-11, - 'flb' => 2.37304336254586E-06, - 'BTU' => 9.47815522922962E-11, - 'btu' => 9.47815522922962E-11, - ], - 'c' => [ - 'J' => 4.18399101363672E+00, - 'e' => 4.18398900257312E+07, - 'c' => 1.0, - 'cal' => 9.99330315287563E-01, - 'eV' => 2.61142000000000E+19, - 'ev' => 2.61142000000000E+19, - 'HPh' => 1.55856355899327E-06, - 'hh' => 1.55856355899327E-06, - 'Wh' => 1.16222030532950E-03, - 'wh' => 1.16222030532950E-03, - 'flb' => 9.92878733152102E+01, - 'BTU' => 3.96564972437776E-03, - 'btu' => 3.96564972437776E-03, - ], - 'cal' => [ - 'J' => 4.18679484613929E+00, - 'e' => 4.18679283372801E+07, - 'c' => 1.00067013349059E+00, - 'cal' => 1.0, - 'eV' => 2.61317000000000E+19, - 'ev' => 2.61317000000000E+19, - 'HPh' => 1.55960800463137E-06, - 'hh' => 1.55960800463137E-06, - 'Wh' => 1.16299914807955E-03, - 'wh' => 1.16299914807955E-03, - 'flb' => 9.93544094443283E+01, - 'BTU' => 3.96830723907002E-03, - 'btu' => 3.96830723907002E-03, - ], - 'eV' => [ - 'J' => 1.60219000146921E-19, - 'e' => 1.60218923136574E-12, - 'c' => 3.82933423195043E-20, - 'cal' => 3.82676978535648E-20, - 'eV' => 1.0, - 'ev' => 1.0, - 'HPh' => 5.96826078912344E-26, - 'hh' => 5.96826078912344E-26, - 'Wh' => 4.45053000026614E-23, - 'wh' => 4.45053000026614E-23, - 'flb' => 3.80206452103492E-18, - 'BTU' => 1.51857982414846E-22, - 'btu' => 1.51857982414846E-22, - ], - 'ev' => [ - 'J' => 1.60219000146921E-19, - 'e' => 1.60218923136574E-12, - 'c' => 3.82933423195043E-20, - 'cal' => 3.82676978535648E-20, - 'eV' => 1.0, - 'ev' => 1.0, - 'HPh' => 5.96826078912344E-26, - 'hh' => 5.96826078912344E-26, - 'Wh' => 4.45053000026614E-23, - 'wh' => 4.45053000026614E-23, - 'flb' => 3.80206452103492E-18, - 'BTU' => 1.51857982414846E-22, - 'btu' => 1.51857982414846E-22, - ], - 'HPh' => [ - 'J' => 2.68451741316170E+06, - 'e' => 2.68451612283024E+13, - 'c' => 6.41616438565991E+05, - 'cal' => 6.41186757845835E+05, - 'eV' => 1.67553000000000E+25, - 'ev' => 1.67553000000000E+25, - 'HPh' => 1.0, - 'hh' => 1.0, - 'Wh' => 7.45699653134593E+02, - 'wh' => 7.45699653134593E+02, - 'flb' => 6.37047316692964E+07, - 'BTU' => 2.54442605275546E+03, - 'btu' => 2.54442605275546E+03, - ], - 'hh' => [ - 'J' => 2.68451741316170E+06, - 'e' => 2.68451612283024E+13, - 'c' => 6.41616438565991E+05, - 'cal' => 6.41186757845835E+05, - 'eV' => 1.67553000000000E+25, - 'ev' => 1.67553000000000E+25, - 'HPh' => 1.0, - 'hh' => 1.0, - 'Wh' => 7.45699653134593E+02, - 'wh' => 7.45699653134593E+02, - 'flb' => 6.37047316692964E+07, - 'BTU' => 2.54442605275546E+03, - 'btu' => 2.54442605275546E+03, - ], - 'Wh' => [ - 'J' => 3.59999820554720E+03, - 'e' => 3.59999647518369E+10, - 'c' => 8.60422069219046E+02, - 'cal' => 8.59845857713046E+02, - 'eV' => 2.24692340000000E+22, - 'ev' => 2.24692340000000E+22, - 'HPh' => 1.34102248243839E-03, - 'hh' => 1.34102248243839E-03, - 'Wh' => 1.0, - 'wh' => 1.0, - 'flb' => 8.54294774062316E+04, - 'BTU' => 3.41213254164705E+00, - 'btu' => 3.41213254164705E+00, - ], - 'wh' => [ - 'J' => 3.59999820554720E+03, - 'e' => 3.59999647518369E+10, - 'c' => 8.60422069219046E+02, - 'cal' => 8.59845857713046E+02, - 'eV' => 2.24692340000000E+22, - 'ev' => 2.24692340000000E+22, - 'HPh' => 1.34102248243839E-03, - 'hh' => 1.34102248243839E-03, - 'Wh' => 1.0, - 'wh' => 1.0, - 'flb' => 8.54294774062316E+04, - 'BTU' => 3.41213254164705E+00, - 'btu' => 3.41213254164705E+00, - ], - 'flb' => [ - 'J' => 4.21400003236424E-02, - 'e' => 4.21399800687660E+05, - 'c' => 1.00717234301644E-02, - 'cal' => 1.00649785509554E-02, - 'eV' => 2.63015000000000E+17, - 'ev' => 2.63015000000000E+17, - 'HPh' => 1.56974211145130E-08, - 'hh' => 1.56974211145130E-08, - 'Wh' => 1.17055614802000E-05, - 'wh' => 1.17055614802000E-05, - 'flb' => 1.0, - 'BTU' => 3.99409272448406E-05, - 'btu' => 3.99409272448406E-05, - ], - 'BTU' => [ - 'J' => 1.05505813786749E+03, - 'e' => 1.05505763074665E+10, - 'c' => 2.52165488508168E+02, - 'cal' => 2.51996617135510E+02, - 'eV' => 6.58510000000000E+21, - 'ev' => 6.58510000000000E+21, - 'HPh' => 3.93015941224568E-04, - 'hh' => 3.93015941224568E-04, - 'Wh' => 2.93071851047526E-01, - 'wh' => 2.93071851047526E-01, - 'flb' => 2.50369750774671E+04, - 'BTU' => 1.0, - 'btu' => 1.0, - ], - 'btu' => [ - 'J' => 1.05505813786749E+03, - 'e' => 1.05505763074665E+10, - 'c' => 2.52165488508168E+02, - 'cal' => 2.51996617135510E+02, - 'eV' => 6.58510000000000E+21, - 'ev' => 6.58510000000000E+21, - 'HPh' => 3.93015941224568E-04, - 'hh' => 3.93015941224568E-04, - 'Wh' => 2.93071851047526E-01, - 'wh' => 2.93071851047526E-01, - 'flb' => 2.50369750774671E+04, - 'BTU' => 1.0, - 'btu' => 1.0, - ], - ], - 'Power' => [ - 'HP' => [ - 'HP' => 1.0, - 'h' => 1.0, - 'W' => 7.45701000000000E+02, - 'w' => 7.45701000000000E+02, - ], - 'h' => [ - 'HP' => 1.0, - 'h' => 1.0, - 'W' => 7.45701000000000E+02, - 'w' => 7.45701000000000E+02, - ], - 'W' => [ - 'HP' => 1.34102006031908E-03, - 'h' => 1.34102006031908E-03, - 'W' => 1.0, - 'w' => 1.0, - ], - 'w' => [ - 'HP' => 1.34102006031908E-03, - 'h' => 1.34102006031908E-03, - 'W' => 1.0, - 'w' => 1.0, - ], - ], - 'Magnetism' => [ - 'T' => [ - 'T' => 1.0, - 'ga' => 10000.0, - ], - 'ga' => [ - 'T' => 0.0001, - 'ga' => 1.0, - ], - ], - 'Liquid' => [ - 'tsp' => [ - 'tsp' => 1.0, - 'tbs' => 3.33333333333333E-01, - 'oz' => 1.66666666666667E-01, - 'cup' => 2.08333333333333E-02, - 'pt' => 1.04166666666667E-02, - 'us_pt' => 1.04166666666667E-02, - 'uk_pt' => 8.67558516821960E-03, - 'qt' => 5.20833333333333E-03, - 'gal' => 1.30208333333333E-03, - 'l' => 4.92999408400710E-03, - 'lt' => 4.92999408400710E-03, - ], - 'tbs' => [ - 'tsp' => 3.00000000000000E+00, - 'tbs' => 1.0, - 'oz' => 5.00000000000000E-01, - 'cup' => 6.25000000000000E-02, - 'pt' => 3.12500000000000E-02, - 'us_pt' => 3.12500000000000E-02, - 'uk_pt' => 2.60267555046588E-02, - 'qt' => 1.56250000000000E-02, - 'gal' => 3.90625000000000E-03, - 'l' => 1.47899822520213E-02, - 'lt' => 1.47899822520213E-02, - ], - 'oz' => [ - 'tsp' => 6.00000000000000E+00, - 'tbs' => 2.00000000000000E+00, - 'oz' => 1.0, - 'cup' => 1.25000000000000E-01, - 'pt' => 6.25000000000000E-02, - 'us_pt' => 6.25000000000000E-02, - 'uk_pt' => 5.20535110093176E-02, - 'qt' => 3.12500000000000E-02, - 'gal' => 7.81250000000000E-03, - 'l' => 2.95799645040426E-02, - 'lt' => 2.95799645040426E-02, - ], - 'cup' => [ - 'tsp' => 4.80000000000000E+01, - 'tbs' => 1.60000000000000E+01, - 'oz' => 8.00000000000000E+00, - 'cup' => 1.0, - 'pt' => 5.00000000000000E-01, - 'us_pt' => 5.00000000000000E-01, - 'uk_pt' => 4.16428088074541E-01, - 'qt' => 2.50000000000000E-01, - 'gal' => 6.25000000000000E-02, - 'l' => 2.36639716032341E-01, - 'lt' => 2.36639716032341E-01, - ], - 'pt' => [ - 'tsp' => 9.60000000000000E+01, - 'tbs' => 3.20000000000000E+01, - 'oz' => 1.60000000000000E+01, - 'cup' => 2.00000000000000E+00, - 'pt' => 1.0, - 'us_pt' => 1.0, - 'uk_pt' => 8.32856176149081E-01, - 'qt' => 5.00000000000000E-01, - 'gal' => 1.25000000000000E-01, - 'l' => 4.73279432064682E-01, - 'lt' => 4.73279432064682E-01, - ], - 'us_pt' => [ - 'tsp' => 9.60000000000000E+01, - 'tbs' => 3.20000000000000E+01, - 'oz' => 1.60000000000000E+01, - 'cup' => 2.00000000000000E+00, - 'pt' => 1.0, - 'us_pt' => 1.0, - 'uk_pt' => 8.32856176149081E-01, - 'qt' => 5.00000000000000E-01, - 'gal' => 1.25000000000000E-01, - 'l' => 4.73279432064682E-01, - 'lt' => 4.73279432064682E-01, - ], - 'uk_pt' => [ - 'tsp' => 1.15266000000000E+02, - 'tbs' => 3.84220000000000E+01, - 'oz' => 1.92110000000000E+01, - 'cup' => 2.40137500000000E+00, - 'pt' => 1.20068750000000E+00, - 'us_pt' => 1.20068750000000E+00, - 'uk_pt' => 1.0, - 'qt' => 6.00343750000000E-01, - 'gal' => 1.50085937500000E-01, - 'l' => 5.68260698087162E-01, - 'lt' => 5.68260698087162E-01, - ], - 'qt' => [ - 'tsp' => 1.92000000000000E+02, - 'tbs' => 6.40000000000000E+01, - 'oz' => 3.20000000000000E+01, - 'cup' => 4.00000000000000E+00, - 'pt' => 2.00000000000000E+00, - 'us_pt' => 2.00000000000000E+00, - 'uk_pt' => 1.66571235229816E+00, - 'qt' => 1.0, - 'gal' => 2.50000000000000E-01, - 'l' => 9.46558864129363E-01, - 'lt' => 9.46558864129363E-01, - ], - 'gal' => [ - 'tsp' => 7.68000000000000E+02, - 'tbs' => 2.56000000000000E+02, - 'oz' => 1.28000000000000E+02, - 'cup' => 1.60000000000000E+01, - 'pt' => 8.00000000000000E+00, - 'us_pt' => 8.00000000000000E+00, - 'uk_pt' => 6.66284940919265E+00, - 'qt' => 4.00000000000000E+00, - 'gal' => 1.0, - 'l' => 3.78623545651745E+00, - 'lt' => 3.78623545651745E+00, - ], - 'l' => [ - 'tsp' => 2.02840000000000E+02, - 'tbs' => 6.76133333333333E+01, - 'oz' => 3.38066666666667E+01, - 'cup' => 4.22583333333333E+00, - 'pt' => 2.11291666666667E+00, - 'us_pt' => 2.11291666666667E+00, - 'uk_pt' => 1.75975569552166E+00, - 'qt' => 1.05645833333333E+00, - 'gal' => 2.64114583333333E-01, - 'l' => 1.0, - 'lt' => 1.0, - ], - 'lt' => [ - 'tsp' => 2.02840000000000E+02, - 'tbs' => 6.76133333333333E+01, - 'oz' => 3.38066666666667E+01, - 'cup' => 4.22583333333333E+00, - 'pt' => 2.11291666666667E+00, - 'us_pt' => 2.11291666666667E+00, - 'uk_pt' => 1.75975569552166E+00, - 'qt' => 1.05645833333333E+00, - 'gal' => 2.64114583333333E-01, - 'l' => 1.0, - 'lt' => 1.0, - ], - ], - ]; + public const EULER = 2.71828182845904523536; /** * parseComplex. * * Parses a complex number into its real and imaginary parts, and an I or J suffix * - * @deprecated 2.0.0 No longer used by internal code. Please use the Complex\Complex class instead + * @deprecated 1.12.0 No longer used by internal code. Please use the \Complex\Complex class instead * * @param string $complexNumber The complex number * @@ -738,35 +42,6 @@ class Engineering ]; } - /** - * Formats a number base string value with leading zeroes. - * - * @param string $xVal The "number" to pad - * @param int $places The length that we want to pad this value - * - * @return string The padded "number" - */ - private static function nbrConversionFormat($xVal, $places) - { - if ($places !== null) { - if (is_numeric($places)) { - $places = (int) $places; - } else { - return Functions::VALUE(); - } - if ($places < 0) { - return Functions::NAN(); - } - if (strlen($xVal) <= $places) { - return substr(str_pad($xVal, $places, '0', STR_PAD_LEFT), -10); - } - - return Functions::NAN(); - } - - return substr($xVal, -10); - } - /** * BESSELI. * @@ -776,7 +51,9 @@ class Engineering * Excel Function: * BESSELI(x,ord) * - * @category Engineering Functions + * @deprecated 1.17.0 + * Use the BESSELI() method in the Engineering\BesselI class instead + * @see Engineering\BesselI::BESSELI() * * @param float $x The value at which to evaluate the function. * If x is nonnumeric, BESSELI returns the #VALUE! error value. @@ -785,42 +62,11 @@ class Engineering * If $ord is nonnumeric, BESSELI returns the #VALUE! error value. * If $ord < 0, BESSELI returns the #NUM! error value. * - * @return float + * @return array|float|string Result, or a string containing an error */ public static function BESSELI($x, $ord) { - $x = ($x === null) ? 0.0 : Functions::flattenSingleValue($x); - $ord = ($ord === null) ? 0.0 : Functions::flattenSingleValue($ord); - - if ((is_numeric($x)) && (is_numeric($ord))) { - $ord = floor($ord); - if ($ord < 0) { - return Functions::NAN(); - } - - if (abs($x) <= 30) { - $fResult = $fTerm = pow($x / 2, $ord) / MathTrig::FACT($ord); - $ordK = 1; - $fSqrX = ($x * $x) / 4; - do { - $fTerm *= $fSqrX; - $fTerm /= ($ordK * ($ordK + $ord)); - $fResult += $fTerm; - } while ((abs($fTerm) > 1e-12) && (++$ordK < 100)); - } else { - $f_2_PI = 2 * M_PI; - - $fXAbs = abs($x); - $fResult = exp($fXAbs) / sqrt($f_2_PI * $fXAbs); - if (($ord & 1) && ($x < 0)) { - $fResult = -$fResult; - } - } - - return (is_nan($fResult)) ? Functions::NAN() : $fResult; - } - - return Functions::VALUE(); + return Engineering\BesselI::BESSELI($x, $ord); } /** @@ -831,7 +77,9 @@ class Engineering * Excel Function: * BESSELJ(x,ord) * - * @category Engineering Functions + * @deprecated 1.17.0 + * Use the BESSELJ() method in the Engineering\BesselJ class instead + * @see Engineering\BesselJ::BESSELJ() * * @param float $x The value at which to evaluate the function. * If x is nonnumeric, BESSELJ returns the #VALUE! error value. @@ -839,80 +87,11 @@ class Engineering * If $ord is nonnumeric, BESSELJ returns the #VALUE! error value. * If $ord < 0, BESSELJ returns the #NUM! error value. * - * @return float + * @return array|float|string Result, or a string containing an error */ public static function BESSELJ($x, $ord) { - $x = ($x === null) ? 0.0 : Functions::flattenSingleValue($x); - $ord = ($ord === null) ? 0.0 : Functions::flattenSingleValue($ord); - - if ((is_numeric($x)) && (is_numeric($ord))) { - $ord = floor($ord); - if ($ord < 0) { - return Functions::NAN(); - } - - $fResult = 0; - if (abs($x) <= 30) { - $fResult = $fTerm = pow($x / 2, $ord) / MathTrig::FACT($ord); - $ordK = 1; - $fSqrX = ($x * $x) / -4; - do { - $fTerm *= $fSqrX; - $fTerm /= ($ordK * ($ordK + $ord)); - $fResult += $fTerm; - } while ((abs($fTerm) > 1e-12) && (++$ordK < 100)); - } else { - $f_PI_DIV_2 = M_PI / 2; - $f_PI_DIV_4 = M_PI / 4; - - $fXAbs = abs($x); - $fResult = sqrt(Functions::M_2DIVPI / $fXAbs) * cos($fXAbs - $ord * $f_PI_DIV_2 - $f_PI_DIV_4); - if (($ord & 1) && ($x < 0)) { - $fResult = -$fResult; - } - } - - return (is_nan($fResult)) ? Functions::NAN() : $fResult; - } - - return Functions::VALUE(); - } - - private static function besselK0($fNum) - { - if ($fNum <= 2) { - $fNum2 = $fNum * 0.5; - $y = ($fNum2 * $fNum2); - $fRet = -log($fNum2) * self::BESSELI($fNum, 0) + - (-0.57721566 + $y * (0.42278420 + $y * (0.23069756 + $y * (0.3488590e-1 + $y * (0.262698e-2 + $y * - (0.10750e-3 + $y * 0.74e-5)))))); - } else { - $y = 2 / $fNum; - $fRet = exp(-$fNum) / sqrt($fNum) * - (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)))))); - } - - return $fRet; - } - - private static function besselK1($fNum) - { - if ($fNum <= 2) { - $fNum2 = $fNum * 0.5; - $y = ($fNum2 * $fNum2); - $fRet = log($fNum2) * self::BESSELI($fNum, 1) + - (1 + $y * (0.15443144 + $y * (-0.67278579 + $y * (-0.18156897 + $y * (-0.1919402e-1 + $y * - (-0.110404e-2 + $y * (-0.4686e-4))))))) / $fNum; - } else { - $y = 2 / $fNum; - $fRet = exp(-$fNum) / sqrt($fNum) * - (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))))))); - } - - return $fRet; + return Engineering\BesselJ::BESSELJ($x, $ord); } /** @@ -924,7 +103,9 @@ class Engineering * Excel Function: * BESSELK(x,ord) * - * @category Engineering Functions + * @deprecated 1.17.0 + * Use the BESSELK() method in the Engineering\BesselK class instead + * @see Engineering\BesselK::BESSELK() * * @param float $x The value at which to evaluate the function. * If x is nonnumeric, BESSELK returns the #VALUE! error value. @@ -932,77 +113,11 @@ class Engineering * If $ord is nonnumeric, BESSELK returns the #VALUE! error value. * If $ord < 0, BESSELK returns the #NUM! error value. * - * @return float + * @return array|float|string Result, or a string containing an error */ public static function BESSELK($x, $ord) { - $x = ($x === null) ? 0.0 : Functions::flattenSingleValue($x); - $ord = ($ord === null) ? 0.0 : Functions::flattenSingleValue($ord); - - if ((is_numeric($x)) && (is_numeric($ord))) { - if (($ord < 0) || ($x == 0.0)) { - return Functions::NAN(); - } - - switch (floor($ord)) { - case 0: - $fBk = self::besselK0($x); - - break; - case 1: - $fBk = self::besselK1($x); - - break; - default: - $fTox = 2 / $x; - $fBkm = self::besselK0($x); - $fBk = self::besselK1($x); - for ($n = 1; $n < $ord; ++$n) { - $fBkp = $fBkm + $n * $fTox * $fBk; - $fBkm = $fBk; - $fBk = $fBkp; - } - } - - return (is_nan($fBk)) ? Functions::NAN() : $fBk; - } - - return Functions::VALUE(); - } - - private static function besselY0($fNum) - { - if ($fNum < 8.0) { - $y = ($fNum * $fNum); - $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)))); - $fRet = $f1 / $f2 + 0.636619772 * self::BESSELJ($fNum, 0) * log($fNum); - } else { - $z = 8.0 / $fNum; - $y = ($z * $z); - $xx = $fNum - 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)))); - $fRet = sqrt(0.636619772 / $fNum) * (sin($xx) * $f1 + $z * cos($xx) * $f2); - } - - return $fRet; - } - - private static function besselY1($fNum) - { - if ($fNum < 8.0) { - $y = ($fNum * $fNum); - $f1 = $fNum * (-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))))); - $fRet = $f1 / $f2 + 0.636619772 * (self::BESSELJ($fNum, 1) * log($fNum) - 1 / $fNum); - } else { - $fRet = sqrt(0.636619772 / $fNum) * sin($fNum - 2.356194491); - } - - return $fRet; + return Engineering\BesselK::BESSELK($x, $ord); } /** @@ -1013,50 +128,21 @@ class Engineering * Excel Function: * BESSELY(x,ord) * - * @category Engineering Functions + * @deprecated 1.17.0 + * Use the BESSELY() method in the Engineering\BesselY class instead + * @see Engineering\BesselY::BESSELY() * * @param float $x The value at which to evaluate the function. - * If x is nonnumeric, BESSELK returns the #VALUE! error value. + * If x is nonnumeric, BESSELY returns the #VALUE! error value. * @param int $ord The order of the Bessel function. If n is not an integer, it is truncated. - * If $ord is nonnumeric, BESSELK returns the #VALUE! error value. - * If $ord < 0, BESSELK returns the #NUM! error value. + * If $ord is nonnumeric, BESSELY returns the #VALUE! error value. + * If $ord < 0, BESSELY returns the #NUM! error value. * - * @return float + * @return array|float|string Result, or a string containing an error */ public static function BESSELY($x, $ord) { - $x = ($x === null) ? 0.0 : Functions::flattenSingleValue($x); - $ord = ($ord === null) ? 0.0 : Functions::flattenSingleValue($ord); - - if ((is_numeric($x)) && (is_numeric($ord))) { - if (($ord < 0) || ($x == 0.0)) { - return Functions::NAN(); - } - - switch (floor($ord)) { - case 0: - $fBy = self::besselY0($x); - - break; - case 1: - $fBy = self::besselY1($x); - - break; - default: - $fTox = 2 / $x; - $fBym = self::besselY0($x); - $fBy = self::besselY1($x); - for ($n = 1; $n < $ord; ++$n) { - $fByp = $n * $fTox * $fBy - $fBym; - $fBym = $fBy; - $fBy = $fByp; - } - } - - return (is_nan($fBy)) ? Functions::NAN() : $fBy; - } - - return Functions::VALUE(); + return Engineering\BesselY::BESSELY($x, $ord); } /** @@ -1067,45 +153,22 @@ class Engineering * Excel Function: * BIN2DEC(x) * - * @category Engineering Functions + * @deprecated 1.17.0 + * Use the toDecimal() method in the Engineering\ConvertBinary class instead + * @see Engineering\ConvertBinary::toDecimal() * - * @param string $x The binary number (as a string) that you want to convert. The number + * @param mixed $x The binary number (as a string) that you want to convert. The number * cannot contain more than 10 characters (10 bits). The most significant * bit of number is the sign bit. The remaining 9 bits are magnitude bits. * Negative numbers are represented using two's-complement notation. * If number is not a valid binary number, or if number contains more than * 10 characters (10 bits), BIN2DEC returns the #NUM! error value. * - * @return string + * @return array|string */ public static function BINTODEC($x) { - $x = Functions::flattenSingleValue($x); - - if (is_bool($x)) { - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) { - $x = (int) $x; - } else { - return Functions::VALUE(); - } - } - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { - $x = floor($x); - } - $x = (string) $x; - if (strlen($x) > preg_match_all('/[01]/', $x, $out)) { - return Functions::NAN(); - } - if (strlen($x) > 10) { - return Functions::NAN(); - } elseif (strlen($x) == 10) { - // Two's Complement - $x = substr($x, -9); - - return '-' . (512 - bindec($x)); - } - - return bindec($x); + return Engineering\ConvertBinary::toDecimal($x); } /** @@ -1116,52 +179,28 @@ class Engineering * Excel Function: * BIN2HEX(x[,places]) * - * @category Engineering Functions + * @deprecated 1.17.0 + * Use the toHex() method in the Engineering\ConvertBinary class instead + * @see Engineering\ConvertBinary::toHex() * - * @param string $x The binary number (as a string) that you want to convert. The number + * @param mixed $x The binary number (as a string) that you want to convert. The number * cannot contain more than 10 characters (10 bits). The most significant * bit of number is the sign bit. The remaining 9 bits are magnitude bits. * Negative numbers are represented using two's-complement notation. * If number is not a valid binary number, or if number contains more than * 10 characters (10 bits), BIN2HEX returns the #NUM! error value. - * @param int $places The number of characters to use. If places is omitted, BIN2HEX uses the + * @param mixed $places The number of characters to use. If places is omitted, BIN2HEX uses the * minimum number of characters necessary. Places is useful for padding the * return value with leading 0s (zeros). * If places is not an integer, it is truncated. * If places is nonnumeric, BIN2HEX returns the #VALUE! error value. * If places is negative, BIN2HEX returns the #NUM! error value. * - * @return string + * @return array|string */ public static function BINTOHEX($x, $places = null) { - $x = Functions::flattenSingleValue($x); - $places = Functions::flattenSingleValue($places); - - // Argument X - if (is_bool($x)) { - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) { - $x = (int) $x; - } else { - return Functions::VALUE(); - } - } - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { - $x = floor($x); - } - $x = (string) $x; - if (strlen($x) > preg_match_all('/[01]/', $x, $out)) { - return Functions::NAN(); - } - if (strlen($x) > 10) { - return Functions::NAN(); - } elseif (strlen($x) == 10) { - // Two's Complement - return str_repeat('F', 8) . substr(strtoupper(dechex(bindec(substr($x, -9)))), -2); - } - $hexVal = (string) strtoupper(dechex(bindec($x))); - - return self::nbrConversionFormat($hexVal, $places); + return Engineering\ConvertBinary::toHex($x, $places); } /** @@ -1172,51 +211,28 @@ class Engineering * Excel Function: * BIN2OCT(x[,places]) * - * @category Engineering Functions + * @deprecated 1.17.0 + * Use the toOctal() method in the Engineering\ConvertBinary class instead + * @see Engineering\ConvertBinary::toOctal() * - * @param string $x The binary number (as a string) that you want to convert. The number + * @param mixed $x The binary number (as a string) that you want to convert. The number * cannot contain more than 10 characters (10 bits). The most significant * bit of number is the sign bit. The remaining 9 bits are magnitude bits. * Negative numbers are represented using two's-complement notation. * If number is not a valid binary number, or if number contains more than * 10 characters (10 bits), BIN2OCT returns the #NUM! error value. - * @param int $places The number of characters to use. If places is omitted, BIN2OCT uses the + * @param mixed $places The number of characters to use. If places is omitted, BIN2OCT uses the * minimum number of characters necessary. Places is useful for padding the * return value with leading 0s (zeros). * If places is not an integer, it is truncated. * If places is nonnumeric, BIN2OCT returns the #VALUE! error value. * If places is negative, BIN2OCT returns the #NUM! error value. * - * @return string + * @return array|string */ public static function BINTOOCT($x, $places = null) { - $x = Functions::flattenSingleValue($x); - $places = Functions::flattenSingleValue($places); - - if (is_bool($x)) { - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) { - $x = (int) $x; - } else { - return Functions::VALUE(); - } - } - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { - $x = floor($x); - } - $x = (string) $x; - if (strlen($x) > preg_match_all('/[01]/', $x, $out)) { - return Functions::NAN(); - } - if (strlen($x) > 10) { - return Functions::NAN(); - } elseif (strlen($x) == 10) { - // Two's Complement - return str_repeat('7', 7) . substr(strtoupper(decoct(bindec(substr($x, -9)))), -3); - } - $octVal = (string) decoct(bindec($x)); - - return self::nbrConversionFormat($octVal, $places); + return Engineering\ConvertBinary::toOctal($x, $places); } /** @@ -1227,9 +243,11 @@ class Engineering * Excel Function: * DEC2BIN(x[,places]) * - * @category Engineering Functions + * @deprecated 1.17.0 + * Use the toBinary() method in the Engineering\ConvertDecimal class instead + * @see Engineering\ConvertDecimal::toBinary() * - * @param string $x The decimal integer you want to convert. If number is negative, + * @param mixed $x The decimal integer you want to convert. If number is negative, * valid place values are ignored and DEC2BIN returns a 10-character * (10-bit) binary number in which the most significant bit is the sign * bit. The remaining 9 bits are magnitude bits. Negative numbers are @@ -1239,45 +257,18 @@ class Engineering * If number is nonnumeric, DEC2BIN returns the #VALUE! error value. * If DEC2BIN requires more than places characters, it returns the #NUM! * error value. - * @param int $places The number of characters to use. If places is omitted, DEC2BIN uses + * @param mixed $places The number of characters to use. If places is omitted, DEC2BIN uses * the minimum number of characters necessary. Places is useful for * padding the return value with leading 0s (zeros). * If places is not an integer, it is truncated. * If places is nonnumeric, DEC2BIN returns the #VALUE! error value. * If places is zero or negative, DEC2BIN returns the #NUM! error value. * - * @return string + * @return array|string */ public static function DECTOBIN($x, $places = null) { - $x = Functions::flattenSingleValue($x); - $places = Functions::flattenSingleValue($places); - - if (is_bool($x)) { - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) { - $x = (int) $x; - } else { - return Functions::VALUE(); - } - } - $x = (string) $x; - if (strlen($x) > preg_match_all('/[-0123456789.]/', $x, $out)) { - return Functions::VALUE(); - } - - $x = (string) floor($x); - if ($x < -512 || $x > 511) { - return Functions::NAN(); - } - - $r = decbin($x); - // Two's Complement - $r = substr($r, -10); - if (strlen($r) >= 11) { - return Functions::NAN(); - } - - return self::nbrConversionFormat($r, $places); + return Engineering\ConvertDecimal::toBinary($x, $places); } /** @@ -1288,9 +279,11 @@ class Engineering * Excel Function: * DEC2HEX(x[,places]) * - * @category Engineering Functions + * @deprecated 1.17.0 + * Use the toHex() method in the Engineering\ConvertDecimal class instead + * @see Engineering\ConvertDecimal::toHex() * - * @param string $x The decimal integer you want to convert. If number is negative, + * @param mixed $x The decimal integer you want to convert. If number is negative, * places is ignored and DEC2HEX returns a 10-character (40-bit) * hexadecimal number in which the most significant bit is the sign * bit. The remaining 39 bits are magnitude bits. Negative numbers @@ -1300,39 +293,18 @@ class Engineering * If number is nonnumeric, DEC2HEX returns the #VALUE! error value. * If DEC2HEX requires more than places characters, it returns the * #NUM! error value. - * @param int $places The number of characters to use. If places is omitted, DEC2HEX uses + * @param mixed $places The number of characters to use. If places is omitted, DEC2HEX uses * the minimum number of characters necessary. Places is useful for * padding the return value with leading 0s (zeros). * If places is not an integer, it is truncated. * If places is nonnumeric, DEC2HEX returns the #VALUE! error value. * If places is zero or negative, DEC2HEX returns the #NUM! error value. * - * @return string + * @return array|string */ public static function DECTOHEX($x, $places = null) { - $x = Functions::flattenSingleValue($x); - $places = Functions::flattenSingleValue($places); - - if (is_bool($x)) { - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) { - $x = (int) $x; - } else { - return Functions::VALUE(); - } - } - $x = (string) $x; - if (strlen($x) > preg_match_all('/[-0123456789.]/', $x, $out)) { - return Functions::VALUE(); - } - $x = (string) floor($x); - $r = strtoupper(dechex($x)); - if (strlen($r) == 8) { - // Two's Complement - $r = 'FF' . $r; - } - - return self::nbrConversionFormat($r, $places); + return Engineering\ConvertDecimal::toHex($x, $places); } /** @@ -1343,9 +315,11 @@ class Engineering * Excel Function: * DEC2OCT(x[,places]) * - * @category Engineering Functions + * @deprecated 1.17.0 + * Use the toOctal() method in the Engineering\ConvertDecimal class instead + * @see Engineering\ConvertDecimal::toOctal() * - * @param string $x The decimal integer you want to convert. If number is negative, + * @param mixed $x The decimal integer you want to convert. If number is negative, * places is ignored and DEC2OCT returns a 10-character (30-bit) * octal number in which the most significant bit is the sign bit. * The remaining 29 bits are magnitude bits. Negative numbers are @@ -1355,40 +329,18 @@ class Engineering * If number is nonnumeric, DEC2OCT returns the #VALUE! error value. * If DEC2OCT requires more than places characters, it returns the * #NUM! error value. - * @param int $places The number of characters to use. If places is omitted, DEC2OCT uses + * @param mixed $places The number of characters to use. If places is omitted, DEC2OCT uses * the minimum number of characters necessary. Places is useful for * padding the return value with leading 0s (zeros). * If places is not an integer, it is truncated. * If places is nonnumeric, DEC2OCT returns the #VALUE! error value. * If places is zero or negative, DEC2OCT returns the #NUM! error value. * - * @return string + * @return array|string */ public static function DECTOOCT($x, $places = null) { - $xorig = $x; - $x = Functions::flattenSingleValue($x); - $places = Functions::flattenSingleValue($places); - - if (is_bool($x)) { - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) { - $x = (int) $x; - } else { - return Functions::VALUE(); - } - } - $x = (string) $x; - if (strlen($x) > preg_match_all('/[-0123456789.]/', $x, $out)) { - return Functions::VALUE(); - } - $x = (string) floor($x); - $r = decoct($x); - if (strlen($r) == 11) { - // Two's Complement - $r = substr($r, -10); - } - - return self::nbrConversionFormat($r, $places); + return Engineering\ConvertDecimal::toOctal($x, $places); } /** @@ -1399,9 +351,11 @@ class Engineering * Excel Function: * HEX2BIN(x[,places]) * - * @category Engineering Functions + * @deprecated 1.17.0 + * Use the toBinary() method in the Engineering\ConvertHex class instead + * @see Engineering\ConvertHex::toBinary() * - * @param string $x the hexadecimal number you want to convert. + * @param mixed $x the hexadecimal number (as a string) that you want to convert. * Number cannot contain more than 10 characters. * The most significant bit of number is the sign bit (40th bit from the right). * The remaining 9 bits are magnitude bits. @@ -1411,29 +365,18 @@ class Engineering * and if number is positive, it cannot be greater than 1FF. * If number is not a valid hexadecimal number, HEX2BIN returns the #NUM! error value. * If HEX2BIN requires more than places characters, it returns the #NUM! error value. - * @param int $places The number of characters to use. If places is omitted, + * @param mixed $places The number of characters to use. If places is omitted, * HEX2BIN uses the minimum number of characters necessary. Places * is useful for padding the return value with leading 0s (zeros). * If places is not an integer, it is truncated. * If places is nonnumeric, HEX2BIN returns the #VALUE! error value. * If places is negative, HEX2BIN returns the #NUM! error value. * - * @return string + * @return array|string */ public static function HEXTOBIN($x, $places = null) { - $x = Functions::flattenSingleValue($x); - $places = Functions::flattenSingleValue($places); - - if (is_bool($x)) { - return Functions::VALUE(); - } - $x = (string) $x; - if (strlen($x) > preg_match_all('/[0123456789ABCDEF]/', strtoupper($x), $out)) { - return Functions::NAN(); - } - - return self::DECTOBIN(self::HEXTODEC($x), $places); + return Engineering\ConvertHex::toBinary($x, $places); } /** @@ -1444,9 +387,11 @@ class Engineering * Excel Function: * HEX2DEC(x) * - * @category Engineering Functions + * @deprecated 1.17.0 + * Use the toDecimal() method in the Engineering\ConvertHex class instead + * @see Engineering\ConvertHex::toDecimal() * - * @param string $x The hexadecimal number you want to convert. This number cannot + * @param mixed $x The hexadecimal number (as a string) that you want to convert. This number cannot * contain more than 10 characters (40 bits). The most significant * bit of number is the sign bit. The remaining 39 bits are magnitude * bits. Negative numbers are represented using two's-complement @@ -1454,37 +399,11 @@ class Engineering * If number is not a valid hexadecimal number, HEX2DEC returns the * #NUM! error value. * - * @return string + * @return array|string */ public static function HEXTODEC($x) { - $x = Functions::flattenSingleValue($x); - - if (is_bool($x)) { - return Functions::VALUE(); - } - $x = (string) $x; - if (strlen($x) > preg_match_all('/[0123456789ABCDEF]/', strtoupper($x), $out)) { - return Functions::NAN(); - } - - if (strlen($x) > 10) { - return Functions::NAN(); - } - - $binX = ''; - foreach (str_split($x) as $char) { - $binX .= str_pad(base_convert($char, 16, 2), 4, '0', STR_PAD_LEFT); - } - if (strlen($binX) == 40 && $binX[0] == '1') { - for ($i = 0; $i < 40; ++$i) { - $binX[$i] = ($binX[$i] == '1' ? '0' : '1'); - } - - return (bindec($binX) + 1) * -1; - } - - return bindec($binX); + return Engineering\ConvertHex::toDecimal($x); } /** @@ -1495,9 +414,11 @@ class Engineering * Excel Function: * HEX2OCT(x[,places]) * - * @category Engineering Functions + * @deprecated 1.17.0 + * Use the toOctal() method in the Engineering\ConvertHex class instead + * @see Engineering\ConvertHex::toOctal() * - * @param string $x The hexadecimal number you want to convert. Number cannot + * @param mixed $x The hexadecimal number (as a string) that you want to convert. Number cannot * contain more than 10 characters. The most significant bit of * number is the sign bit. The remaining 39 bits are magnitude * bits. Negative numbers are represented using two's-complement @@ -1510,7 +431,7 @@ class Engineering * the #NUM! error value. * If HEX2OCT requires more than places characters, it returns * the #NUM! error value. - * @param int $places The number of characters to use. If places is omitted, HEX2OCT + * @param mixed $places The number of characters to use. If places is omitted, HEX2OCT * uses the minimum number of characters necessary. Places is * useful for padding the return value with leading 0s (zeros). * If places is not an integer, it is truncated. @@ -1518,27 +439,11 @@ class Engineering * value. * If places is negative, HEX2OCT returns the #NUM! error value. * - * @return string + * @return array|string */ public static function HEXTOOCT($x, $places = null) { - $x = Functions::flattenSingleValue($x); - $places = Functions::flattenSingleValue($places); - - if (is_bool($x)) { - return Functions::VALUE(); - } - $x = (string) $x; - if (strlen($x) > preg_match_all('/[0123456789ABCDEF]/', strtoupper($x), $out)) { - return Functions::NAN(); - } - - $decimal = self::HEXTODEC($x); - if ($decimal < -536870912 || $decimal > 536870911) { - return Functions::NAN(); - } - - return self::DECTOOCT($decimal, $places); + return Engineering\ConvertHex::toOctal($x, $places); } /** @@ -1549,9 +454,11 @@ class Engineering * Excel Function: * OCT2BIN(x[,places]) * - * @category Engineering Functions + * @deprecated 1.17.0 + * Use the toBinary() method in the Engineering\ConvertOctal class instead + * @see Engineering\ConvertOctal::toBinary() * - * @param string $x The octal number you want to convert. Number may not + * @param mixed $x The octal number you want to convert. Number may not * contain more than 10 characters. The most significant * bit of number is the sign bit. The remaining 29 bits * are magnitude bits. Negative numbers are represented @@ -1564,7 +471,7 @@ class Engineering * the #NUM! error value. * If OCT2BIN requires more than places characters, it * returns the #NUM! error value. - * @param int $places The number of characters to use. If places is omitted, + * @param mixed $places The number of characters to use. If places is omitted, * OCT2BIN uses the minimum number of characters necessary. * Places is useful for padding the return value with * leading 0s (zeros). @@ -1574,22 +481,11 @@ class Engineering * If places is negative, OCT2BIN returns the #NUM! error * value. * - * @return string + * @return array|string */ public static function OCTTOBIN($x, $places = null) { - $x = Functions::flattenSingleValue($x); - $places = Functions::flattenSingleValue($places); - - if (is_bool($x)) { - return Functions::VALUE(); - } - $x = (string) $x; - if (preg_match_all('/[01234567]/', $x, $out) != strlen($x)) { - return Functions::NAN(); - } - - return self::DECTOBIN(self::OCTTODEC($x), $places); + return Engineering\ConvertOctal::toBinary($x, $places); } /** @@ -1600,9 +496,11 @@ class Engineering * Excel Function: * OCT2DEC(x) * - * @category Engineering Functions + * @deprecated 1.17.0 + * Use the toDecimal() method in the Engineering\ConvertOctal class instead + * @see Engineering\ConvertOctal::toDecimal() * - * @param string $x The octal number you want to convert. Number may not contain + * @param mixed $x The octal number you want to convert. Number may not contain * more than 10 octal characters (30 bits). The most significant * bit of number is the sign bit. The remaining 29 bits are * magnitude bits. Negative numbers are represented using @@ -1610,32 +508,11 @@ class Engineering * If number is not a valid octal number, OCT2DEC returns the * #NUM! error value. * - * @return string + * @return array|string */ public static function OCTTODEC($x) { - $x = Functions::flattenSingleValue($x); - - if (is_bool($x)) { - return Functions::VALUE(); - } - $x = (string) $x; - if (preg_match_all('/[01234567]/', $x, $out) != strlen($x)) { - return Functions::NAN(); - } - $binX = ''; - foreach (str_split($x) as $char) { - $binX .= str_pad(decbin((int) $char), 3, '0', STR_PAD_LEFT); - } - if (strlen($binX) == 30 && $binX[0] == '1') { - for ($i = 0; $i < 30; ++$i) { - $binX[$i] = ($binX[$i] == '1' ? '0' : '1'); - } - - return (bindec($binX) + 1) * -1; - } - - return bindec($binX); + return Engineering\ConvertOctal::toDecimal($x); } /** @@ -1646,9 +523,11 @@ class Engineering * Excel Function: * OCT2HEX(x[,places]) * - * @category Engineering Functions + * @deprecated 1.17.0 + * Use the toHex() method in the Engineering\ConvertOctal class instead + * @see Engineering\ConvertOctal::toHex() * - * @param string $x The octal number you want to convert. Number may not contain + * @param mixed $x The octal number you want to convert. Number may not contain * more than 10 octal characters (30 bits). The most significant * bit of number is the sign bit. The remaining 29 bits are * magnitude bits. Negative numbers are represented using @@ -1659,30 +538,18 @@ class Engineering * #NUM! error value. * If OCT2HEX requires more than places characters, it returns * the #NUM! error value. - * @param int $places The number of characters to use. If places is omitted, OCT2HEX + * @param mixed $places The number of characters to use. If places is omitted, OCT2HEX * uses the minimum number of characters necessary. Places is useful * for padding the return value with leading 0s (zeros). * If places is not an integer, it is truncated. * If places is nonnumeric, OCT2HEX returns the #VALUE! error value. * If places is negative, OCT2HEX returns the #NUM! error value. * - * @return string + * @return array|string */ public static function OCTTOHEX($x, $places = null) { - $x = Functions::flattenSingleValue($x); - $places = Functions::flattenSingleValue($places); - - if (is_bool($x)) { - return Functions::VALUE(); - } - $x = (string) $x; - if (preg_match_all('/[01234567]/', $x, $out) != strlen($x)) { - return Functions::NAN(); - } - $hexVal = strtoupper(dechex(self::OCTTODEC($x))); - - return self::nbrConversionFormat($hexVal, $places); + return Engineering\ConvertOctal::toHex($x, $places); } /** @@ -1693,30 +560,20 @@ class Engineering * Excel Function: * COMPLEX(realNumber,imaginary[,suffix]) * - * @category Engineering Functions + * @deprecated 1.18.0 + * Use the COMPLEX() method in the Engineering\Complex class instead + * @see Engineering\Complex::COMPLEX() * - * @param float $realNumber the real coefficient of the complex number - * @param float $imaginary the imaginary coefficient of the complex number - * @param string $suffix The suffix for the imaginary component of the complex number. + * @param array|float $realNumber the real coefficient of the complex number + * @param array|float $imaginary the imaginary coefficient of the complex number + * @param array|string $suffix The suffix for the imaginary component of the complex number. * If omitted, the suffix is assumed to be "i". * - * @return string + * @return array|string */ public static function COMPLEX($realNumber = 0.0, $imaginary = 0.0, $suffix = 'i') { - $realNumber = ($realNumber === null) ? 0.0 : Functions::flattenSingleValue($realNumber); - $imaginary = ($imaginary === null) ? 0.0 : Functions::flattenSingleValue($imaginary); - $suffix = ($suffix === null) ? 'i' : Functions::flattenSingleValue($suffix); - - if (((is_numeric($realNumber)) && (is_numeric($imaginary))) && - (($suffix == 'i') || ($suffix == 'j') || ($suffix == '')) - ) { - $complex = new Complex($realNumber, $imaginary, $suffix); - - return (string) $complex; - } - - return Functions::VALUE(); + return Engineering\Complex::COMPLEX($realNumber, $imaginary, $suffix); } /** @@ -1727,18 +584,18 @@ class Engineering * Excel Function: * IMAGINARY(complexNumber) * - * @category Engineering Functions + * @deprecated 1.18.0 + * Use the IMAGINARY() method in the Engineering\Complex class instead + * @see Engineering\Complex::IMAGINARY() * * @param string $complexNumber the complex number for which you want the imaginary * coefficient * - * @return float + * @return array|float|string */ public static function IMAGINARY($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - return (new Complex($complexNumber))->getImaginary(); + return Engineering\Complex::IMAGINARY($complexNumber); } /** @@ -1749,17 +606,17 @@ class Engineering * Excel Function: * IMREAL(complexNumber) * - * @category Engineering Functions + * @deprecated 1.18.0 + * Use the IMREAL() method in the Engineering\Complex class instead + * @see Engineering\Complex::IMREAL() * * @param string $complexNumber the complex number for which you want the real coefficient * - * @return float + * @return array|float|string */ public static function IMREAL($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - return (new Complex($complexNumber))->getReal(); + return Engineering\Complex::IMREAL($complexNumber); } /** @@ -1770,15 +627,17 @@ class Engineering * Excel Function: * IMABS(complexNumber) * + * @deprecated 1.18.0 + * Use the IMABS() method in the Engineering\ComplexFunctions class instead + * @see ComplexFunctions::IMABS() + * * @param string $complexNumber the complex number for which you want the absolute value * - * @return float + * @return array|float|string */ public static function IMABS($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - return (new Complex($complexNumber))->abs(); + return ComplexFunctions::IMABS($complexNumber); } /** @@ -1790,20 +649,17 @@ class Engineering * Excel Function: * IMARGUMENT(complexNumber) * - * @param string $complexNumber the complex number for which you want the argument theta + * @deprecated 1.18.0 + * Use the IMARGUMENT() method in the Engineering\ComplexFunctions class instead + * @see ComplexFunctions::IMARGUMENT() * - * @return float|string + * @param array|string $complexNumber the complex number for which you want the argument theta + * + * @return array|float|string */ public static function IMARGUMENT($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - $complex = new Complex($complexNumber); - if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { - return Functions::DIV0(); - } - - return $complex->argument(); + return ComplexFunctions::IMARGUMENT($complexNumber); } /** @@ -1814,15 +670,17 @@ class Engineering * Excel Function: * IMCONJUGATE(complexNumber) * - * @param string $complexNumber the complex number for which you want the conjugate + * @deprecated 1.18.0 + * Use the IMCONJUGATE() method in the Engineering\ComplexFunctions class instead + * @see ComplexFunctions::IMCONJUGATE() * - * @return string + * @param array|string $complexNumber the complex number for which you want the conjugate + * + * @return array|string */ public static function IMCONJUGATE($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - return (string) (new Complex($complexNumber))->conjugate(); + return ComplexFunctions::IMCONJUGATE($complexNumber); } /** @@ -1833,15 +691,17 @@ class Engineering * Excel Function: * IMCOS(complexNumber) * - * @param string $complexNumber the complex number for which you want the cosine + * @deprecated 1.18.0 + * Use the IMCOS() method in the Engineering\ComplexFunctions class instead + * @see ComplexFunctions::IMCOS() * - * @return float|string + * @param array|string $complexNumber the complex number for which you want the cosine + * + * @return array|float|string */ public static function IMCOS($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - return (string) (new Complex($complexNumber))->cos(); + return ComplexFunctions::IMCOS($complexNumber); } /** @@ -1852,15 +712,17 @@ class Engineering * Excel Function: * IMCOSH(complexNumber) * - * @param string $complexNumber the complex number for which you want the hyperbolic cosine + * @deprecated 1.18.0 + * Use the IMCOSH() method in the Engineering\ComplexFunctions class instead + * @see ComplexFunctions::IMCOSH() * - * @return float|string + * @param array|string $complexNumber the complex number for which you want the hyperbolic cosine + * + * @return array|float|string */ public static function IMCOSH($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - return (string) (new Complex($complexNumber))->cosh(); + return ComplexFunctions::IMCOSH($complexNumber); } /** @@ -1871,15 +733,17 @@ class Engineering * Excel Function: * IMCOT(complexNumber) * - * @param string $complexNumber the complex number for which you want the cotangent + * @deprecated 1.18.0 + * Use the IMCOT() method in the Engineering\ComplexFunctions class instead + * @see ComplexFunctions::IMCOT() * - * @return float|string + * @param array|string $complexNumber the complex number for which you want the cotangent + * + * @return array|float|string */ public static function IMCOT($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - return (string) (new Complex($complexNumber))->cot(); + return ComplexFunctions::IMCOT($complexNumber); } /** @@ -1890,15 +754,17 @@ class Engineering * Excel Function: * IMCSC(complexNumber) * - * @param string $complexNumber the complex number for which you want the cosecant + * @deprecated 1.18.0 + * Use the IMCSC() method in the Engineering\ComplexFunctions class instead + * @see ComplexFunctions::IMCSC() * - * @return float|string + * @param array|string $complexNumber the complex number for which you want the cosecant + * + * @return array|float|string */ public static function IMCSC($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - return (string) (new Complex($complexNumber))->csc(); + return ComplexFunctions::IMCSC($complexNumber); } /** @@ -1909,15 +775,17 @@ class Engineering * Excel Function: * IMCSCH(complexNumber) * - * @param string $complexNumber the complex number for which you want the hyperbolic cosecant + * @deprecated 1.18.0 + * Use the IMCSCH() method in the Engineering\ComplexFunctions class instead + * @see ComplexFunctions::IMCSCH() * - * @return float|string + * @param array|string $complexNumber the complex number for which you want the hyperbolic cosecant + * + * @return array|float|string */ public static function IMCSCH($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - return (string) (new Complex($complexNumber))->csch(); + return ComplexFunctions::IMCSCH($complexNumber); } /** @@ -1928,15 +796,17 @@ class Engineering * Excel Function: * IMSIN(complexNumber) * + * @deprecated 1.18.0 + * Use the IMSIN() method in the Engineering\ComplexFunctions class instead + * @see ComplexFunctions::IMSIN() + * * @param string $complexNumber the complex number for which you want the sine * - * @return float|string + * @return array|float|string */ public static function IMSIN($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - return (string) (new Complex($complexNumber))->sin(); + return ComplexFunctions::IMSIN($complexNumber); } /** @@ -1947,15 +817,17 @@ class Engineering * Excel Function: * IMSINH(complexNumber) * + * @deprecated 1.18.0 + * Use the IMSINH() method in the Engineering\ComplexFunctions class instead + * @see ComplexFunctions::IMSINH() + * * @param string $complexNumber the complex number for which you want the hyperbolic sine * - * @return float|string + * @return array|float|string */ public static function IMSINH($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - return (string) (new Complex($complexNumber))->sinh(); + return ComplexFunctions::IMSINH($complexNumber); } /** @@ -1966,15 +838,17 @@ class Engineering * Excel Function: * IMSEC(complexNumber) * + * @deprecated 1.18.0 + * Use the IMSEC() method in the Engineering\ComplexFunctions class instead + * @see ComplexFunctions::IMSEC() + * * @param string $complexNumber the complex number for which you want the secant * - * @return float|string + * @return array|float|string */ public static function IMSEC($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - return (string) (new Complex($complexNumber))->sec(); + return ComplexFunctions::IMSEC($complexNumber); } /** @@ -1985,15 +859,17 @@ class Engineering * Excel Function: * IMSECH(complexNumber) * + * @deprecated 1.18.0 + * Use the IMSECH() method in the Engineering\ComplexFunctions class instead + * @see ComplexFunctions::IMSECH() + * * @param string $complexNumber the complex number for which you want the hyperbolic secant * - * @return float|string + * @return array|float|string */ public static function IMSECH($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - return (string) (new Complex($complexNumber))->sech(); + return ComplexFunctions::IMSECH($complexNumber); } /** @@ -2004,15 +880,17 @@ class Engineering * Excel Function: * IMTAN(complexNumber) * + * @deprecated 1.18.0 + * Use the IMTAN() method in the Engineering\ComplexFunctions class instead + * @see ComplexFunctions::IMTAN() + * * @param string $complexNumber the complex number for which you want the tangent * - * @return float|string + * @return array|float|string */ public static function IMTAN($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - return (string) (new Complex($complexNumber))->tan(); + return ComplexFunctions::IMTAN($complexNumber); } /** @@ -2023,20 +901,17 @@ class Engineering * Excel Function: * IMSQRT(complexNumber) * + * @deprecated 1.18.0 + * Use the IMSQRT() method in the Engineering\ComplexFunctions class instead + * @see ComplexFunctions::IMSQRT() + * * @param string $complexNumber the complex number for which you want the square root * - * @return string + * @return array|string */ public static function IMSQRT($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - $theta = self::IMARGUMENT($complexNumber); - if ($theta === Functions::DIV0()) { - return '0'; - } - - return (string) (new Complex($complexNumber))->sqrt(); + return ComplexFunctions::IMSQRT($complexNumber); } /** @@ -2047,20 +922,17 @@ class Engineering * Excel Function: * IMLN(complexNumber) * + * @deprecated 1.18.0 + * Use the IMLN() method in the Engineering\ComplexFunctions class instead + * @see ComplexFunctions::IMLN() + * * @param string $complexNumber the complex number for which you want the natural logarithm * - * @return string + * @return array|string */ public static function IMLN($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - $complex = new Complex($complexNumber); - if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { - return Functions::NAN(); - } - - return (string) (new Complex($complexNumber))->ln(); + return ComplexFunctions::IMLN($complexNumber); } /** @@ -2071,20 +943,17 @@ class Engineering * Excel Function: * IMLOG10(complexNumber) * + * @deprecated 1.18.0 + * Use the IMLOG10() method in the Engineering\ComplexFunctions class instead + * @see ComplexFunctions::IMLOG10() + * * @param string $complexNumber the complex number for which you want the common logarithm * - * @return string + * @return array|string */ public static function IMLOG10($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - $complex = new Complex($complexNumber); - if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { - return Functions::NAN(); - } - - return (string) (new Complex($complexNumber))->log10(); + return ComplexFunctions::IMLOG10($complexNumber); } /** @@ -2095,20 +964,17 @@ class Engineering * Excel Function: * IMLOG2(complexNumber) * + * @deprecated 1.18.0 + * Use the IMLOG2() method in the Engineering\ComplexFunctions class instead + * @see ComplexFunctions::IMLOG2() + * * @param string $complexNumber the complex number for which you want the base-2 logarithm * - * @return string + * @return array|string */ public static function IMLOG2($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - $complex = new Complex($complexNumber); - if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { - return Functions::NAN(); - } - - return (string) (new Complex($complexNumber))->log2(); + return ComplexFunctions::IMLOG2($complexNumber); } /** @@ -2119,15 +985,17 @@ class Engineering * Excel Function: * IMEXP(complexNumber) * + * @deprecated 1.18.0 + * Use the IMEXP() method in the Engineering\ComplexFunctions class instead + * @see ComplexFunctions::IMEXP() + * * @param string $complexNumber the complex number for which you want the exponential * - * @return string + * @return array|string */ public static function IMEXP($complexNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - - return (string) (new Complex($complexNumber))->exp(); + return ComplexFunctions::IMEXP($complexNumber); } /** @@ -2138,21 +1006,18 @@ class Engineering * Excel Function: * IMPOWER(complexNumber,realNumber) * + * @deprecated 1.18.0 + * Use the IMPOWER() method in the Engineering\ComplexFunctions class instead + * @see ComplexFunctions::IMPOWER() + * * @param string $complexNumber the complex number you want to raise to a power * @param float $realNumber the power to which you want to raise the complex number * - * @return string + * @return array|string */ public static function IMPOWER($complexNumber, $realNumber) { - $complexNumber = Functions::flattenSingleValue($complexNumber); - $realNumber = Functions::flattenSingleValue($realNumber); - - if (!is_numeric($realNumber)) { - return Functions::VALUE(); - } - - return (string) (new Complex($complexNumber))->pow($realNumber); + return ComplexFunctions::IMPOWER($complexNumber, $realNumber); } /** @@ -2163,21 +1028,18 @@ class Engineering * Excel Function: * IMDIV(complexDividend,complexDivisor) * + * @deprecated 1.18.0 + * Use the IMDIV() method in the Engineering\ComplexOperations class instead + * @see ComplexOperations::IMDIV() + * * @param string $complexDividend the complex numerator or dividend * @param string $complexDivisor the complex denominator or divisor * - * @return string + * @return array|string */ public static function IMDIV($complexDividend, $complexDivisor) { - $complexDividend = Functions::flattenSingleValue($complexDividend); - $complexDivisor = Functions::flattenSingleValue($complexDivisor); - - try { - return (string) (new Complex($complexDividend))->divideby(new Complex($complexDivisor)); - } catch (ComplexException $e) { - return Functions::NAN(); - } + return ComplexOperations::IMDIV($complexDividend, $complexDivisor); } /** @@ -2188,21 +1050,18 @@ class Engineering * Excel Function: * IMSUB(complexNumber1,complexNumber2) * + * @deprecated 1.18.0 + * Use the IMSUB() method in the Engineering\ComplexOperations class instead + * @see ComplexOperations::IMSUB() + * * @param string $complexNumber1 the complex number from which to subtract complexNumber2 * @param string $complexNumber2 the complex number to subtract from complexNumber1 * - * @return string + * @return array|string */ public static function IMSUB($complexNumber1, $complexNumber2) { - $complexNumber1 = Functions::flattenSingleValue($complexNumber1); - $complexNumber2 = Functions::flattenSingleValue($complexNumber2); - - try { - return (string) (new Complex($complexNumber1))->subtract(new Complex($complexNumber2)); - } catch (ComplexException $e) { - return Functions::NAN(); - } + return ComplexOperations::IMSUB($complexNumber1, $complexNumber2); } /** @@ -2213,26 +1072,17 @@ class Engineering * Excel Function: * IMSUM(complexNumber[,complexNumber[,...]]) * + * @deprecated 1.18.0 + * Use the IMSUM() method in the Engineering\ComplexOperations class instead + * @see ComplexOperations::IMSUM() + * * @param string ...$complexNumbers Series of complex numbers to add * * @return string */ public static function IMSUM(...$complexNumbers) { - // Return value - $returnValue = new Complex(0.0); - $aArgs = Functions::flattenArray($complexNumbers); - - try { - // Loop through the arguments - foreach ($aArgs as $complex) { - $returnValue = $returnValue->add(new Complex($complex)); - } - } catch (ComplexException $e) { - return Functions::NAN(); - } - - return (string) $returnValue; + return ComplexOperations::IMSUM(...$complexNumbers); } /** @@ -2243,50 +1093,42 @@ class Engineering * Excel Function: * IMPRODUCT(complexNumber[,complexNumber[,...]]) * + * @deprecated 1.18.0 + * Use the IMPRODUCT() method in the Engineering\ComplexOperations class instead + * @see ComplexOperations::IMPRODUCT() + * * @param string ...$complexNumbers Series of complex numbers to multiply * * @return string */ public static function IMPRODUCT(...$complexNumbers) { - // Return value - $returnValue = new Complex(1.0); - $aArgs = Functions::flattenArray($complexNumbers); - - try { - // Loop through the arguments - foreach ($aArgs as $complex) { - $returnValue = $returnValue->multiply(new Complex($complex)); - } - } catch (ComplexException $e) { - return Functions::NAN(); - } - - return (string) $returnValue; + return ComplexOperations::IMPRODUCT(...$complexNumbers); } /** * DELTA. * * Tests whether two values are equal. Returns 1 if number1 = number2; returns 0 otherwise. - * Use this function to filter a set of values. For example, by summing several DELTA - * functions you calculate the count of equal pairs. This function is also known as the - * Kronecker Delta function. + * Use this function to filter a set of values. For example, by summing several DELTA + * functions you calculate the count of equal pairs. This function is also known as the + * Kronecker Delta function. * * Excel Function: * DELTA(a[,b]) * + * @deprecated 1.17.0 + * Use the DELTA() method in the Engineering\Compare class instead + * @see Engineering\Compare::DELTA() + * * @param float $a the first number * @param float $b The second number. If omitted, b is assumed to be zero. * - * @return int + * @return array|int|string (string in the event of an error) */ public static function DELTA($a, $b = 0) { - $a = Functions::flattenSingleValue($a); - $b = Functions::flattenSingleValue($b); - - return (int) ($a == $b); + return Engineering\Compare::DELTA($a, $b); } /** @@ -2297,79 +1139,20 @@ class Engineering * * Returns 1 if number >= step; returns 0 (zero) otherwise * Use this function to filter a set of values. For example, by summing several GESTEP - * functions you calculate the count of values that exceed a threshold. + * functions you calculate the count of values that exceed a threshold. + * + * @deprecated 1.17.0 + * Use the GESTEP() method in the Engineering\Compare class instead + * @see Engineering\Compare::GESTEP() * * @param float $number the value to test against step - * @param float $step The threshold value. - * If you omit a value for step, GESTEP uses zero. + * @param float $step The threshold value. If you omit a value for step, GESTEP uses zero. * - * @return int + * @return array|int|string (string in the event of an error) */ public static function GESTEP($number, $step = 0) { - $number = Functions::flattenSingleValue($number); - $step = Functions::flattenSingleValue($step); - - return (int) ($number >= $step); - } - - // - // Private method to calculate the erf value - // - private static $twoSqrtPi = 1.128379167095512574; - - public static function erfVal($x) - { - if (abs($x) > 2.2) { - return 1 - self::erfcVal($x); - } - $sum = $term = $x; - $xsqr = ($x * $x); - $j = 1; - do { - $term *= $xsqr / $j; - $sum -= $term / (2 * $j + 1); - ++$j; - $term *= $xsqr / $j; - $sum += $term / (2 * $j + 1); - ++$j; - if ($sum == 0.0) { - break; - } - } while (abs($term / $sum) > Functions::PRECISION); - - return self::$twoSqrtPi * $sum; - } - - /** - * Validate arguments passed to the bitwise functions. - * - * @param mixed $value - * - * @throws Exception - * - * @return int - */ - private static function validateBitwiseArgument($value) - { - $value = Functions::flattenSingleValue($value); - - if (is_int($value)) { - return $value; - } elseif (is_numeric($value)) { - if ($value == (int) ($value)) { - $value = (int) ($value); - if (($value > pow(2, 48) - 1) || ($value < 0)) { - throw new Exception(Functions::NAN()); - } - - return $value; - } - - throw new Exception(Functions::NAN()); - } - - throw new Exception(Functions::VALUE()); + return Engineering\Compare::GESTEP($number, $step); } /** @@ -2380,23 +1163,18 @@ class Engineering * Excel Function: * BITAND(number1, number2) * - * @category Engineering Functions + * @deprecated 1.17.0 + * Use the BITAND() method in the Engineering\BitWise class instead + * @see Engineering\BitWise::BITAND() * * @param int $number1 * @param int $number2 * - * @return int|string + * @return array|int|string */ public static function BITAND($number1, $number2) { - try { - $number1 = self::validateBitwiseArgument($number1); - $number2 = self::validateBitwiseArgument($number2); - } catch (Exception $e) { - return $e->getMessage(); - } - - return $number1 & $number2; + return Engineering\BitWise::BITAND($number1, $number2); } /** @@ -2407,23 +1185,18 @@ class Engineering * Excel Function: * BITOR(number1, number2) * - * @category Engineering Functions + * @deprecated 1.17.0 + * Use the BITOR() method in the Engineering\BitWise class instead + * @see Engineering\BitWise::BITOR() * * @param int $number1 * @param int $number2 * - * @return int|string + * @return array|int|string */ public static function BITOR($number1, $number2) { - try { - $number1 = self::validateBitwiseArgument($number1); - $number2 = self::validateBitwiseArgument($number2); - } catch (Exception $e) { - return $e->getMessage(); - } - - return $number1 | $number2; + return Engineering\BitWise::BITOR($number1, $number2); } /** @@ -2434,23 +1207,18 @@ class Engineering * Excel Function: * BITXOR(number1, number2) * - * @category Engineering Functions + * @deprecated 1.17.0 + * Use the BITXOR() method in the Engineering\BitWise class instead + * @see Engineering\BitWise::BITXOR() * * @param int $number1 * @param int $number2 * - * @return int|string + * @return array|int|string */ public static function BITXOR($number1, $number2) { - try { - $number1 = self::validateBitwiseArgument($number1); - $number2 = self::validateBitwiseArgument($number2); - } catch (Exception $e) { - return $e->getMessage(); - } - - return $number1 ^ $number2; + return Engineering\BitWise::BITXOR($number1, $number2); } /** @@ -2461,29 +1229,18 @@ class Engineering * Excel Function: * BITLSHIFT(number, shift_amount) * - * @category Engineering Functions + * @deprecated 1.17.0 + * Use the BITLSHIFT() method in the Engineering\BitWise class instead + * @see Engineering\BitWise::BITLSHIFT() * * @param int $number * @param int $shiftAmount * - * @return int|string + * @return array|float|int|string */ public static function BITLSHIFT($number, $shiftAmount) { - try { - $number = self::validateBitwiseArgument($number); - } catch (Exception $e) { - return $e->getMessage(); - } - - $shiftAmount = Functions::flattenSingleValue($shiftAmount); - - $result = $number << $shiftAmount; - if ($result > pow(2, 48) - 1) { - return Functions::NAN(); - } - - return $result; + return Engineering\BitWise::BITLSHIFT($number, $shiftAmount); } /** @@ -2494,24 +1251,18 @@ class Engineering * Excel Function: * BITRSHIFT(number, shift_amount) * - * @category Engineering Functions + * @deprecated 1.17.0 + * Use the BITRSHIFT() method in the Engineering\BitWise class instead + * @see Engineering\BitWise::BITRSHIFT() * * @param int $number * @param int $shiftAmount * - * @return int|string + * @return array|float|int|string */ public static function BITRSHIFT($number, $shiftAmount) { - try { - $number = self::validateBitwiseArgument($number); - } catch (Exception $e) { - return $e->getMessage(); - } - - $shiftAmount = Functions::flattenSingleValue($shiftAmount); - - return $number >> $shiftAmount; + return Engineering\BitWise::BITRSHIFT($number, $shiftAmount); } /** @@ -2527,27 +1278,19 @@ class Engineering * Excel Function: * ERF(lower[,upper]) * + * @deprecated 1.17.0 + * Use the ERF() method in the Engineering\Erf class instead + * @see Engineering\Erf::ERF() + * * @param float $lower lower bound for integrating ERF * @param float $upper upper bound for integrating ERF. * If omitted, ERF integrates between zero and lower_limit * - * @return float|string + * @return array|float|string */ public static function ERF($lower, $upper = null) { - $lower = Functions::flattenSingleValue($lower); - $upper = Functions::flattenSingleValue($upper); - - if (is_numeric($lower)) { - if ($upper === null) { - return self::erfVal($lower); - } - if (is_numeric($upper)) { - return self::erfVal($upper) - self::erfVal($lower); - } - } - - return Functions::VALUE(); + return Engineering\Erf::ERF($lower, $upper); } /** @@ -2558,48 +1301,17 @@ class Engineering * Excel Function: * ERF.PRECISE(limit) * + * @deprecated 1.17.0 + * Use the ERFPRECISE() method in the Engineering\Erf class instead + * @see Engineering\Erf::ERFPRECISE() + * * @param float $limit bound for integrating ERF * - * @return float|string + * @return array|float|string */ public static function ERFPRECISE($limit) { - $limit = Functions::flattenSingleValue($limit); - - return self::ERF($limit); - } - - // - // Private method to calculate the erfc value - // - private static $oneSqrtPi = 0.564189583547756287; - - private static function erfcVal($x) - { - if (abs($x) < 2.2) { - return 1 - self::erfVal($x); - } - if ($x < 0) { - return 2 - self::ERFC(-$x); - } - $a = $n = 1; - $b = $c = $x; - $d = ($x * $x) + 0.5; - $q1 = $q2 = $b / $d; - $t = 0; - do { - $t = $a * $n + $b * $x; - $a = $b; - $b = $t; - $t = $c * $n + $d * $x; - $c = $d; - $d = $t; - $n += 0.5; - $q1 = $q2; - $q2 = $b / $d; - } while ((abs($q1 - $q2) / $q2) > Functions::PRECISION); - - return self::$oneSqrtPi * exp(-$x * $x) * $q2; + return Engineering\Erf::ERFPRECISE($limit); } /** @@ -2615,88 +1327,97 @@ class Engineering * Excel Function: * ERFC(x) * + * @deprecated 1.17.0 + * Use the ERFC() method in the Engineering\ErfC class instead + * @see Engineering\ErfC::ERFC() + * * @param float $x The lower bound for integrating ERFC * - * @return float|string + * @return array|float|string */ public static function ERFC($x) { - $x = Functions::flattenSingleValue($x); - - if (is_numeric($x)) { - return self::erfcVal($x); - } - - return Functions::VALUE(); + return Engineering\ErfC::ERFC($x); } /** * getConversionGroups * Returns a list of the different conversion groups for UOM conversions. * + * @deprecated 1.16.0 + * Use the getConversionCategories() method in the Engineering\ConvertUOM class instead + * @see Engineering\ConvertUOM::getConversionCategories() + * * @return array */ public static function getConversionGroups() { - $conversionGroups = []; - foreach (self::$conversionUnits as $conversionUnit) { - $conversionGroups[] = $conversionUnit['Group']; - } - - return array_merge(array_unique($conversionGroups)); + return Engineering\ConvertUOM::getConversionCategories(); } /** * getConversionGroupUnits * Returns an array of units of measure, for a specified conversion group, or for all groups. * - * @param string $group The group whose units of measure you want to retrieve + * @deprecated 1.16.0 + * Use the getConversionCategoryUnits() method in the ConvertUOM class instead + * @see Engineering\ConvertUOM::getConversionCategoryUnits() + * + * @param null|mixed $category * * @return array */ - public static function getConversionGroupUnits($group = null) + public static function getConversionGroupUnits($category = null) { - $conversionGroups = []; - foreach (self::$conversionUnits as $conversionUnit => $conversionGroup) { - if (($group === null) || ($conversionGroup['Group'] == $group)) { - $conversionGroups[$conversionGroup['Group']][] = $conversionUnit; - } - } - - return $conversionGroups; + return Engineering\ConvertUOM::getConversionCategoryUnits($category); } /** * getConversionGroupUnitDetails. * - * @param string $group The group whose units of measure you want to retrieve + * @deprecated 1.16.0 + * Use the getConversionCategoryUnitDetails() method in the ConvertUOM class instead + * @see Engineering\ConvertUOM::getConversionCategoryUnitDetails() + * + * @param null|mixed $category * * @return array */ - public static function getConversionGroupUnitDetails($group = null) + public static function getConversionGroupUnitDetails($category = null) { - $conversionGroups = []; - foreach (self::$conversionUnits as $conversionUnit => $conversionGroup) { - if (($group === null) || ($conversionGroup['Group'] == $group)) { - $conversionGroups[$conversionGroup['Group']][] = [ - 'unit' => $conversionUnit, - 'description' => $conversionGroup['Unit Name'], - ]; - } - } - - return $conversionGroups; + return Engineering\ConvertUOM::getConversionCategoryUnitDetails($category); } /** * getConversionMultipliers * Returns an array of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM(). * - * @return array of mixed + * @deprecated 1.16.0 + * Use the getConversionMultipliers() method in the ConvertUOM class instead + * @see Engineering\ConvertUOM::getConversionMultipliers() + * + * @return mixed[] */ public static function getConversionMultipliers() { - return self::$conversionMultipliers; + return Engineering\ConvertUOM::getConversionMultipliers(); + } + + /** + * getBinaryConversionMultipliers. + * + * Returns an array of the additional Multiplier prefixes that can be used with Information Units of Measure + * in CONVERTUOM(). + * + * @deprecated 1.16.0 + * Use the getBinaryConversionMultipliers() method in the ConvertUOM class instead + * @see Engineering\ConvertUOM::getBinaryConversionMultipliers() + * + * @return mixed[] + */ + public static function getBinaryConversionMultipliers() + { + return Engineering\ConvertUOM::getBinaryConversionMultipliers(); } /** @@ -2709,99 +1430,18 @@ class Engineering * Excel Function: * CONVERT(value,fromUOM,toUOM) * - * @param float $value the value in fromUOM to convert + * @deprecated 1.16.0 + * Use the CONVERT() method in the ConvertUOM class instead + * @see Engineering\ConvertUOM::CONVERT() + * + * @param float|int $value the value in fromUOM to convert * @param string $fromUOM the units for value * @param string $toUOM the units for the result * - * @return float|string + * @return array|float|string */ public static function CONVERTUOM($value, $fromUOM, $toUOM) { - $value = Functions::flattenSingleValue($value); - $fromUOM = Functions::flattenSingleValue($fromUOM); - $toUOM = Functions::flattenSingleValue($toUOM); - - if (!is_numeric($value)) { - return Functions::VALUE(); - } - $fromMultiplier = 1.0; - if (isset(self::$conversionUnits[$fromUOM])) { - $unitGroup1 = self::$conversionUnits[$fromUOM]['Group']; - } else { - $fromMultiplier = substr($fromUOM, 0, 1); - $fromUOM = substr($fromUOM, 1); - if (isset(self::$conversionMultipliers[$fromMultiplier])) { - $fromMultiplier = self::$conversionMultipliers[$fromMultiplier]['multiplier']; - } else { - return Functions::NA(); - } - if ((isset(self::$conversionUnits[$fromUOM])) && (self::$conversionUnits[$fromUOM]['AllowPrefix'])) { - $unitGroup1 = self::$conversionUnits[$fromUOM]['Group']; - } else { - return Functions::NA(); - } - } - $value *= $fromMultiplier; - - $toMultiplier = 1.0; - if (isset(self::$conversionUnits[$toUOM])) { - $unitGroup2 = self::$conversionUnits[$toUOM]['Group']; - } else { - $toMultiplier = substr($toUOM, 0, 1); - $toUOM = substr($toUOM, 1); - if (isset(self::$conversionMultipliers[$toMultiplier])) { - $toMultiplier = self::$conversionMultipliers[$toMultiplier]['multiplier']; - } else { - return Functions::NA(); - } - if ((isset(self::$conversionUnits[$toUOM])) && (self::$conversionUnits[$toUOM]['AllowPrefix'])) { - $unitGroup2 = self::$conversionUnits[$toUOM]['Group']; - } else { - return Functions::NA(); - } - } - if ($unitGroup1 != $unitGroup2) { - return Functions::NA(); - } - - if (($fromUOM == $toUOM) && ($fromMultiplier == $toMultiplier)) { - // We've already factored $fromMultiplier into the value, so we need - // to reverse it again - return $value / $fromMultiplier; - } elseif ($unitGroup1 == 'Temperature') { - if (($fromUOM == 'F') || ($fromUOM == 'fah')) { - if (($toUOM == 'F') || ($toUOM == 'fah')) { - return $value; - } - $value = (($value - 32) / 1.8); - if (($toUOM == 'K') || ($toUOM == 'kel')) { - $value += 273.15; - } - - return $value; - } elseif ((($fromUOM == 'K') || ($fromUOM == 'kel')) && - (($toUOM == 'K') || ($toUOM == 'kel')) - ) { - return $value; - } elseif ((($fromUOM == 'C') || ($fromUOM == 'cel')) && - (($toUOM == 'C') || ($toUOM == 'cel')) - ) { - return $value; - } - if (($toUOM == 'F') || ($toUOM == 'fah')) { - if (($fromUOM == 'K') || ($fromUOM == 'kel')) { - $value -= 273.15; - } - - return ($value * 1.8) + 32; - } - if (($toUOM == 'C') || ($toUOM == 'cel')) { - return $value - 273.15; - } - - return $value + 273.15; - } - - return ($value * self::$unitConversions[$unitGroup1][$fromUOM][$toUOM]) / $toMultiplier; + return Engineering\ConvertUOM::CONVERT($value, $fromUOM, $toUOM); } } diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Engineering/BesselI.php b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/BesselI.php new file mode 100644 index 0000000..1134574 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/BesselI.php @@ -0,0 +1,152 @@ +getMessage(); + } + + if ($ord < 0) { + return ExcelError::NAN(); + } + + $fResult = self::calculate($x, $ord); + + return (is_nan($fResult)) ? ExcelError::NAN() : $fResult; + } + + private static function calculate(float $x, int $ord): float + { + // special cases + switch ($ord) { + case 0: + return self::besselI0($x); + case 1: + return self::besselI1($x); + } + + return self::besselI2($x, $ord); + } + + private static function besselI0(float $x): float + { + $ax = abs($x); + + if ($ax < 3.75) { + $y = $x / 3.75; + $y = $y * $y; + + return 1.0 + $y * (3.5156229 + $y * (3.0899424 + $y * (1.2067492 + + $y * (0.2659732 + $y * (0.360768e-1 + $y * 0.45813e-2))))); + } + + $y = 3.75 / $ax; + + return (exp($ax) / sqrt($ax)) * (0.39894228 + $y * (0.1328592e-1 + $y * (0.225319e-2 + $y * (-0.157565e-2 + + $y * (0.916281e-2 + $y * (-0.2057706e-1 + $y * (0.2635537e-1 + + $y * (-0.1647633e-1 + $y * 0.392377e-2)))))))); + } + + private static function besselI1(float $x): float + { + $ax = abs($x); + + if ($ax < 3.75) { + $y = $x / 3.75; + $y = $y * $y; + $ans = $ax * (0.5 + $y * (0.87890594 + $y * (0.51498869 + $y * (0.15084934 + $y * (0.2658733e-1 + + $y * (0.301532e-2 + $y * 0.32411e-3)))))); + + return ($x < 0.0) ? -$ans : $ans; + } + + $y = 3.75 / $ax; + $ans = 0.2282967e-1 + $y * (-0.2895312e-1 + $y * (0.1787654e-1 - $y * 0.420059e-2)); + $ans = 0.39894228 + $y * (-0.3988024e-1 + $y * (-0.362018e-2 + $y * (0.163801e-2 + + $y * (-0.1031555e-1 + $y * $ans)))); + $ans *= exp($ax) / sqrt($ax); + + return ($x < 0.0) ? -$ans : $ans; + } + + /** + * Sop to Scrutinizer. + * + * @var float + */ + private static $zeroPointZero = 0.0; + + private static function besselI2(float $x, int $ord): float + { + if ($x === self::$zeroPointZero) { + return 0.0; + } + + $tox = 2.0 / abs($x); + $bip = 0; + $ans = 0.0; + $bi = 1.0; + + for ($j = 2 * ($ord + (int) sqrt(40.0 * $ord)); $j > 0; --$j) { + $bim = $bip + $j * $tox * $bi; + $bip = $bi; + $bi = $bim; + + if (abs($bi) > 1.0e+12) { + $ans *= 1.0e-12; + $bi *= 1.0e-12; + $bip *= 1.0e-12; + } + + if ($j === $ord) { + $ans = $bip; + } + } + + $ans *= self::besselI0($x) / $bi; + + return ($x < 0.0 && (($ord % 2) === 1)) ? -$ans : $ans; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Engineering/BesselJ.php b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/BesselJ.php new file mode 100644 index 0000000..800a8a1 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/BesselJ.php @@ -0,0 +1,180 @@ + 8. This code provides a more accurate calculation + * + * @param mixed $x A float value at which to evaluate the function. + * If x is nonnumeric, BESSELJ returns the #VALUE! error value. + * Or can be an array of values + * @param mixed $ord The integer order of the Bessel function. + * If ord is not an integer, it is truncated. + * If $ord is nonnumeric, BESSELJ returns the #VALUE! error value. + * If $ord < 0, BESSELJ returns the #NUM! error value. + * Or can be an array of values + * + * @return array|float|string Result, or a string containing an error + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function BESSELJ($x, $ord) + { + if (is_array($x) || is_array($ord)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $x, $ord); + } + + try { + $x = EngineeringValidations::validateFloat($x); + $ord = EngineeringValidations::validateInt($ord); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($ord < 0) { + return ExcelError::NAN(); + } + + $fResult = self::calculate($x, $ord); + + return (is_nan($fResult)) ? ExcelError::NAN() : $fResult; + } + + private static function calculate(float $x, int $ord): float + { + // special cases + switch ($ord) { + case 0: + return self::besselJ0($x); + case 1: + return self::besselJ1($x); + } + + return self::besselJ2($x, $ord); + } + + private static function besselJ0(float $x): float + { + $ax = abs($x); + + if ($ax < 8.0) { + $y = $x * $x; + $ans1 = 57568490574.0 + $y * (-13362590354.0 + $y * (651619640.7 + $y * (-11214424.18 + $y * + (77392.33017 + $y * (-184.9052456))))); + $ans2 = 57568490411.0 + $y * (1029532985.0 + $y * (9494680.718 + $y * (59272.64853 + $y * + (267.8532712 + $y * 1.0)))); + + return $ans1 / $ans2; + } + + $z = 8.0 / $ax; + $y = $z * $z; + $xx = $ax - 0.785398164; + $ans1 = 1.0 + $y * (-0.1098628627e-2 + $y * (0.2734510407e-4 + $y * (-0.2073370639e-5 + $y * 0.2093887211e-6))); + $ans2 = -0.1562499995e-1 + $y * (0.1430488765e-3 + $y * (-0.6911147651e-5 + $y * + (0.7621095161e-6 - $y * 0.934935152e-7))); + + return sqrt(0.636619772 / $ax) * (cos($xx) * $ans1 - $z * sin($xx) * $ans2); + } + + private static function besselJ1(float $x): float + { + $ax = abs($x); + + if ($ax < 8.0) { + $y = $x * $x; + $ans1 = $x * (72362614232.0 + $y * (-7895059235.0 + $y * (242396853.1 + $y * + (-2972611.439 + $y * (15704.48260 + $y * (-30.16036606)))))); + $ans2 = 144725228442.0 + $y * (2300535178.0 + $y * (18583304.74 + $y * (99447.43394 + $y * + (376.9991397 + $y * 1.0)))); + + return $ans1 / $ans2; + } + + $z = 8.0 / $ax; + $y = $z * $z; + $xx = $ax - 2.356194491; + + $ans1 = 1.0 + $y * (0.183105e-2 + $y * (-0.3516396496e-4 + $y * (0.2457520174e-5 + $y * (-0.240337019e-6)))); + $ans2 = 0.04687499995 + $y * (-0.2002690873e-3 + $y * (0.8449199096e-5 + $y * + (-0.88228987e-6 + $y * 0.105787412e-6))); + $ans = sqrt(0.636619772 / $ax) * (cos($xx) * $ans1 - $z * sin($xx) * $ans2); + + return ($x < 0.0) ? -$ans : $ans; + } + + private static function besselJ2(float $x, int $ord): float + { + $ax = abs($x); + if ($ax === 0.0) { + return 0.0; + } + + if ($ax > $ord) { + return self::besselj2a($ax, $ord, $x); + } + + return self::besselj2b($ax, $ord, $x); + } + + private static function besselj2a(float $ax, int $ord, float $x) + { + $tox = 2.0 / $ax; + $bjm = self::besselJ0($ax); + $bj = self::besselJ1($ax); + for ($j = 1; $j < $ord; ++$j) { + $bjp = $j * $tox * $bj - $bjm; + $bjm = $bj; + $bj = $bjp; + } + $ans = $bj; + + return ($x < 0.0 && ($ord % 2) == 1) ? -$ans : $ans; + } + + private static function besselj2b(float $ax, int $ord, float $x) + { + $tox = 2.0 / $ax; + $jsum = false; + $bjp = $ans = $sum = 0.0; + $bj = 1.0; + for ($j = 2 * ($ord + (int) sqrt(40.0 * $ord)); $j > 0; --$j) { + $bjm = $j * $tox * $bj - $bjp; + $bjp = $bj; + $bj = $bjm; + if (abs($bj) > 1.0e+10) { + $bj *= 1.0e-10; + $bjp *= 1.0e-10; + $ans *= 1.0e-10; + $sum *= 1.0e-10; + } + if ($jsum === true) { + $sum += $bj; + } + $jsum = $jsum === false; + if ($j === $ord) { + $ans = $bjp; + } + } + $sum = 2.0 * $sum - $bj; + $ans /= $sum; + + return ($x < 0.0 && ($ord % 2) === 1) ? -$ans : $ans; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Engineering/BesselK.php b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/BesselK.php new file mode 100644 index 0000000..2d21e75 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/BesselK.php @@ -0,0 +1,135 @@ +getMessage(); + } + + if (($ord < 0) || ($x <= 0.0)) { + return ExcelError::NAN(); + } + + $fBk = self::calculate($x, $ord); + + return (is_nan($fBk)) ? ExcelError::NAN() : $fBk; + } + + private static function calculate(float $x, int $ord): float + { + // special cases + switch ($ord) { + case 0: + return self::besselK0($x); + case 1: + return self::besselK1($x); + } + + return self::besselK2($x, $ord); + } + + /** + * Mollify Phpstan. + * + * @codeCoverageIgnore + */ + private static function callBesselI(float $x, int $ord): float + { + $rslt = BesselI::BESSELI($x, $ord); + if (!is_float($rslt)) { + throw new Exception('Unexpected array or string'); + } + + return $rslt; + } + + private static function besselK0(float $x): float + { + if ($x <= 2) { + $fNum2 = $x * 0.5; + $y = ($fNum2 * $fNum2); + + return -log($fNum2) * self::callBesselI($x, 0) + + (-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; + + return exp(-$x) / sqrt($x) * + (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)))))); + } + + private static function besselK1(float $x): float + { + if ($x <= 2) { + $fNum2 = $x * 0.5; + $y = ($fNum2 * $fNum2); + + return log($fNum2) * self::callBesselI($x, 1) + + (1 + $y * (0.15443144 + $y * (-0.67278579 + $y * (-0.18156897 + $y * (-0.1919402e-1 + $y * + (-0.110404e-2 + $y * (-0.4686e-4))))))) / $x; + } + + $y = 2 / $x; + + return exp(-$x) / sqrt($x) * + (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))))))); + } + + private static function besselK2(float $x, int $ord): float + { + $fTox = 2 / $x; + $fBkm = self::besselK0($x); + $fBk = self::besselK1($x); + for ($n = 1; $n < $ord; ++$n) { + $fBkp = $fBkm + $n * $fTox * $fBk; + $fBkm = $fBk; + $fBk = $fBkp; + } + + return $fBk; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Engineering/BesselY.php b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/BesselY.php new file mode 100644 index 0000000..31d9694 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/BesselY.php @@ -0,0 +1,141 @@ +getMessage(); + } + + if (($ord < 0) || ($x <= 0.0)) { + return ExcelError::NAN(); + } + + $fBy = self::calculate($x, $ord); + + return (is_nan($fBy)) ? ExcelError::NAN() : $fBy; + } + + private static function calculate(float $x, int $ord): float + { + // special cases + switch ($ord) { + case 0: + return self::besselY0($x); + case 1: + return self::besselY1($x); + } + + return self::besselY2($x, $ord); + } + + /** + * Mollify Phpstan. + * + * @codeCoverageIgnore + */ + private static function callBesselJ(float $x, int $ord): float + { + $rslt = BesselJ::BESSELJ($x, $ord); + if (!is_float($rslt)) { + throw new Exception('Unexpected array or string'); + } + + return $rslt; + } + + private static function besselY0(float $x): float + { + if ($x < 8.0) { + $y = ($x * $x); + $ans1 = -2957821389.0 + $y * (7062834065.0 + $y * (-512359803.6 + $y * (10879881.29 + $y * + (-86327.92757 + $y * 228.4622733)))); + $ans2 = 40076544269.0 + $y * (745249964.8 + $y * (7189466.438 + $y * + (47447.26470 + $y * (226.1030244 + $y)))); + + return $ans1 / $ans2 + 0.636619772 * self::callBesselJ($x, 0) * log($x); + } + + $z = 8.0 / $x; + $y = ($z * $z); + $xx = $x - 0.785398164; + $ans1 = 1 + $y * (-0.1098628627e-2 + $y * (0.2734510407e-4 + $y * (-0.2073370639e-5 + $y * 0.2093887211e-6))); + $ans2 = -0.1562499995e-1 + $y * (0.1430488765e-3 + $y * (-0.6911147651e-5 + $y * (0.7621095161e-6 + $y * + (-0.934945152e-7)))); + + return sqrt(0.636619772 / $x) * (sin($xx) * $ans1 + $z * cos($xx) * $ans2); + } + + private static function besselY1(float $x): float + { + if ($x < 8.0) { + $y = ($x * $x); + $ans1 = $x * (-0.4900604943e13 + $y * (0.1275274390e13 + $y * (-0.5153438139e11 + $y * + (0.7349264551e9 + $y * (-0.4237922726e7 + $y * 0.8511937935e4))))); + $ans2 = 0.2499580570e14 + $y * (0.4244419664e12 + $y * (0.3733650367e10 + $y * (0.2245904002e8 + $y * + (0.1020426050e6 + $y * (0.3549632885e3 + $y))))); + + return ($ans1 / $ans2) + 0.636619772 * (self::callBesselJ($x, 1) * log($x) - 1 / $x); + } + + $z = 8.0 / $x; + $y = $z * $z; + $xx = $x - 2.356194491; + $ans1 = 1.0 + $y * (0.183105e-2 + $y * (-0.3516396496e-4 + $y * (0.2457520174e-5 + $y * (-0.240337019e-6)))); + $ans2 = 0.04687499995 + $y * (-0.2002690873e-3 + $y * (0.8449199096e-5 + $y * + (-0.88228987e-6 + $y * 0.105787412e-6))); + + return sqrt(0.636619772 / $x) * (sin($xx) * $ans1 + $z * cos($xx) * $ans2); + } + + private static function besselY2(float $x, int $ord): float + { + $fTox = 2.0 / $x; + $fBym = self::besselY0($x); + $fBy = self::besselY1($x); + for ($n = 1; $n < $ord; ++$n) { + $fByp = $n * $fTox * $fBy - $fBym; + $fBym = $fBy; + $fBy = $fByp; + } + + return $fBy; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Engineering/BitWise.php b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/BitWise.php new file mode 100644 index 0000000..0362649 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/BitWise.php @@ -0,0 +1,277 @@ +getMessage(); + } + $split1 = self::splitNumber($number1); + $split2 = self::splitNumber($number2); + + return self::SPLIT_DIVISOR * ($split1[0] & $split2[0]) + ($split1[1] & $split2[1]); + } + + /** + * BITOR. + * + * Returns the bitwise OR of two integer values. + * + * Excel Function: + * BITOR(number1, number2) + * + * @param array|int $number1 + * Or can be an array of values + * @param array|int $number2 + * Or can be an array of values + * + * @return array|int|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function BITOR($number1, $number2) + { + if (is_array($number1) || is_array($number2)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $number1, $number2); + } + + try { + $number1 = self::validateBitwiseArgument($number1); + $number2 = self::validateBitwiseArgument($number2); + } catch (Exception $e) { + return $e->getMessage(); + } + + $split1 = self::splitNumber($number1); + $split2 = self::splitNumber($number2); + + return self::SPLIT_DIVISOR * ($split1[0] | $split2[0]) + ($split1[1] | $split2[1]); + } + + /** + * BITXOR. + * + * Returns the bitwise XOR of two integer values. + * + * Excel Function: + * BITXOR(number1, number2) + * + * @param array|int $number1 + * Or can be an array of values + * @param array|int $number2 + * Or can be an array of values + * + * @return array|int|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function BITXOR($number1, $number2) + { + if (is_array($number1) || is_array($number2)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $number1, $number2); + } + + try { + $number1 = self::validateBitwiseArgument($number1); + $number2 = self::validateBitwiseArgument($number2); + } catch (Exception $e) { + return $e->getMessage(); + } + + $split1 = self::splitNumber($number1); + $split2 = self::splitNumber($number2); + + return self::SPLIT_DIVISOR * ($split1[0] ^ $split2[0]) + ($split1[1] ^ $split2[1]); + } + + /** + * BITLSHIFT. + * + * Returns the number value shifted left by shift_amount bits. + * + * Excel Function: + * BITLSHIFT(number, shift_amount) + * + * @param array|int $number + * Or can be an array of values + * @param array|int $shiftAmount + * Or can be an array of values + * + * @return array|float|int|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function BITLSHIFT($number, $shiftAmount) + { + if (is_array($number) || is_array($shiftAmount)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $shiftAmount); + } + + try { + $number = self::validateBitwiseArgument($number); + $shiftAmount = self::validateShiftAmount($shiftAmount); + } catch (Exception $e) { + return $e->getMessage(); + } + + $result = floor($number * (2 ** $shiftAmount)); + if ($result > 2 ** 48 - 1) { + return ExcelError::NAN(); + } + + return $result; + } + + /** + * BITRSHIFT. + * + * Returns the number value shifted right by shift_amount bits. + * + * Excel Function: + * BITRSHIFT(number, shift_amount) + * + * @param array|int $number + * Or can be an array of values + * @param array|int $shiftAmount + * Or can be an array of values + * + * @return array|float|int|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function BITRSHIFT($number, $shiftAmount) + { + if (is_array($number) || is_array($shiftAmount)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $shiftAmount); + } + + try { + $number = self::validateBitwiseArgument($number); + $shiftAmount = self::validateShiftAmount($shiftAmount); + } catch (Exception $e) { + return $e->getMessage(); + } + + $result = floor($number / (2 ** $shiftAmount)); + if ($result > 2 ** 48 - 1) { // possible because shiftAmount can be negative + return ExcelError::NAN(); + } + + return $result; + } + + /** + * Validate arguments passed to the bitwise functions. + * + * @param mixed $value + * + * @return float + */ + private static function validateBitwiseArgument($value) + { + $value = self::nullFalseTrueToNumber($value); + + if (is_numeric($value)) { + $value = (float) $value; + if ($value == floor($value)) { + if (($value > 2 ** 48 - 1) || ($value < 0)) { + throw new Exception(ExcelError::NAN()); + } + + return floor($value); + } + + throw new Exception(ExcelError::NAN()); + } + + throw new Exception(ExcelError::VALUE()); + } + + /** + * Validate arguments passed to the bitwise functions. + * + * @param mixed $value + * + * @return int + */ + private static function validateShiftAmount($value) + { + $value = self::nullFalseTrueToNumber($value); + + if (is_numeric($value)) { + if (abs($value) > 53) { + throw new Exception(ExcelError::NAN()); + } + + return (int) $value; + } + + throw new Exception(ExcelError::VALUE()); + } + + /** + * Many functions accept null/false/true argument treated as 0/0/1. + * + * @param mixed $number + * + * @return mixed + */ + private static function nullFalseTrueToNumber(&$number) + { + if ($number === null) { + $number = 0; + } elseif (is_bool($number)) { + $number = (int) $number; + } + + return $number; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Engineering/Compare.php b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/Compare.php new file mode 100644 index 0000000..6aaf1fa --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/Compare.php @@ -0,0 +1,82 @@ +getMessage(); + } + + return (int) (abs($a - $b) < 1.0e-15); + } + + /** + * GESTEP. + * + * Excel Function: + * GESTEP(number[,step]) + * + * Returns 1 if number >= step; returns 0 (zero) otherwise + * Use this function to filter a set of values. For example, by summing several GESTEP + * functions you calculate the count of values that exceed a threshold. + * + * @param array|float $number the value to test against step + * Or can be an array of values + * @param null|array|float $step The threshold value. If you omit a value for step, GESTEP uses zero. + * Or can be an array of values + * + * @return array|int|string (string in the event of an error) + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function GESTEP($number, $step = 0.0) + { + if (is_array($number) || is_array($step)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $step); + } + + try { + $number = EngineeringValidations::validateFloat($number); + $step = EngineeringValidations::validateFloat($step ?? 0.0); + } catch (Exception $e) { + return $e->getMessage(); + } + + return (int) ($number >= $step); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Engineering/Complex.php b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/Complex.php new file mode 100644 index 0000000..691de8b --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/Complex.php @@ -0,0 +1,121 @@ +getMessage(); + } + + if (($suffix == 'i') || ($suffix == 'j') || ($suffix == '')) { + $complex = new ComplexObject($realNumber, $imaginary, $suffix); + + return (string) $complex; + } + + return ExcelError::VALUE(); + } + + /** + * IMAGINARY. + * + * Returns the imaginary coefficient of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMAGINARY(complexNumber) + * + * @param array|string $complexNumber the complex number for which you want the imaginary + * coefficient + * Or can be an array of values + * + * @return array|float|string (string if an error) + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function IMAGINARY($complexNumber) + { + if (is_array($complexNumber)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); + } + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return ExcelError::NAN(); + } + + return $complex->getImaginary(); + } + + /** + * IMREAL. + * + * Returns the real coefficient of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMREAL(complexNumber) + * + * @param array|string $complexNumber the complex number for which you want the real coefficient + * Or can be an array of values + * + * @return array|float|string (string if an error) + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function IMREAL($complexNumber) + { + if (is_array($complexNumber)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); + } + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return ExcelError::NAN(); + } + + return $complex->getReal(); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Engineering/ComplexFunctions.php b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/ComplexFunctions.php new file mode 100644 index 0000000..28a27a0 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/ComplexFunctions.php @@ -0,0 +1,611 @@ +abs(); + } + + /** + * IMARGUMENT. + * + * Returns the argument theta of a complex number, i.e. the angle in radians from the real + * axis to the representation of the number in polar coordinates. + * + * Excel Function: + * IMARGUMENT(complexNumber) + * + * @param array|string $complexNumber the complex number for which you want the argument theta + * Or can be an array of values + * + * @return array|float|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function IMARGUMENT($complexNumber) + { + if (is_array($complexNumber)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); + } + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return ExcelError::NAN(); + } + + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return ExcelError::DIV0(); + } + + return $complex->argument(); + } + + /** + * IMCONJUGATE. + * + * Returns the complex conjugate of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMCONJUGATE(complexNumber) + * + * @param array|string $complexNumber the complex number for which you want the conjugate + * Or can be an array of values + * + * @return array|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function IMCONJUGATE($complexNumber) + { + if (is_array($complexNumber)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); + } + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return ExcelError::NAN(); + } + + return (string) $complex->conjugate(); + } + + /** + * IMCOS. + * + * Returns the cosine of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMCOS(complexNumber) + * + * @param array|string $complexNumber the complex number for which you want the cosine + * Or can be an array of values + * + * @return array|float|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function IMCOS($complexNumber) + { + if (is_array($complexNumber)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); + } + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return ExcelError::NAN(); + } + + return (string) $complex->cos(); + } + + /** + * IMCOSH. + * + * Returns the hyperbolic cosine of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMCOSH(complexNumber) + * + * @param array|string $complexNumber the complex number for which you want the hyperbolic cosine + * Or can be an array of values + * + * @return array|float|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function IMCOSH($complexNumber) + { + if (is_array($complexNumber)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); + } + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return ExcelError::NAN(); + } + + return (string) $complex->cosh(); + } + + /** + * IMCOT. + * + * Returns the cotangent of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMCOT(complexNumber) + * + * @param array|string $complexNumber the complex number for which you want the cotangent + * Or can be an array of values + * + * @return array|float|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function IMCOT($complexNumber) + { + if (is_array($complexNumber)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); + } + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return ExcelError::NAN(); + } + + return (string) $complex->cot(); + } + + /** + * IMCSC. + * + * Returns the cosecant of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMCSC(complexNumber) + * + * @param array|string $complexNumber the complex number for which you want the cosecant + * Or can be an array of values + * + * @return array|float|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function IMCSC($complexNumber) + { + if (is_array($complexNumber)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); + } + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return ExcelError::NAN(); + } + + return (string) $complex->csc(); + } + + /** + * IMCSCH. + * + * Returns the hyperbolic cosecant of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMCSCH(complexNumber) + * + * @param array|string $complexNumber the complex number for which you want the hyperbolic cosecant + * Or can be an array of values + * + * @return array|float|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function IMCSCH($complexNumber) + { + if (is_array($complexNumber)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); + } + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return ExcelError::NAN(); + } + + return (string) $complex->csch(); + } + + /** + * IMSIN. + * + * Returns the sine of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMSIN(complexNumber) + * + * @param array|string $complexNumber the complex number for which you want the sine + * Or can be an array of values + * + * @return array|float|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function IMSIN($complexNumber) + { + if (is_array($complexNumber)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); + } + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return ExcelError::NAN(); + } + + return (string) $complex->sin(); + } + + /** + * IMSINH. + * + * Returns the hyperbolic sine of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMSINH(complexNumber) + * + * @param array|string $complexNumber the complex number for which you want the hyperbolic sine + * Or can be an array of values + * + * @return array|float|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function IMSINH($complexNumber) + { + if (is_array($complexNumber)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); + } + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return ExcelError::NAN(); + } + + return (string) $complex->sinh(); + } + + /** + * IMSEC. + * + * Returns the secant of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMSEC(complexNumber) + * + * @param array|string $complexNumber the complex number for which you want the secant + * Or can be an array of values + * + * @return array|float|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function IMSEC($complexNumber) + { + if (is_array($complexNumber)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); + } + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return ExcelError::NAN(); + } + + return (string) $complex->sec(); + } + + /** + * IMSECH. + * + * Returns the hyperbolic secant of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMSECH(complexNumber) + * + * @param array|string $complexNumber the complex number for which you want the hyperbolic secant + * Or can be an array of values + * + * @return array|float|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function IMSECH($complexNumber) + { + if (is_array($complexNumber)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); + } + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return ExcelError::NAN(); + } + + return (string) $complex->sech(); + } + + /** + * IMTAN. + * + * Returns the tangent of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMTAN(complexNumber) + * + * @param array|string $complexNumber the complex number for which you want the tangent + * Or can be an array of values + * + * @return array|float|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function IMTAN($complexNumber) + { + if (is_array($complexNumber)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); + } + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return ExcelError::NAN(); + } + + return (string) $complex->tan(); + } + + /** + * IMSQRT. + * + * Returns the square root of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMSQRT(complexNumber) + * + * @param array|string $complexNumber the complex number for which you want the square root + * Or can be an array of values + * + * @return array|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function IMSQRT($complexNumber) + { + if (is_array($complexNumber)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); + } + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return ExcelError::NAN(); + } + + $theta = self::IMARGUMENT($complexNumber); + if ($theta === ExcelError::DIV0()) { + return '0'; + } + + return (string) $complex->sqrt(); + } + + /** + * IMLN. + * + * Returns the natural logarithm of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMLN(complexNumber) + * + * @param array|string $complexNumber the complex number for which you want the natural logarithm + * Or can be an array of values + * + * @return array|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function IMLN($complexNumber) + { + if (is_array($complexNumber)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); + } + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return ExcelError::NAN(); + } + + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return ExcelError::NAN(); + } + + return (string) $complex->ln(); + } + + /** + * IMLOG10. + * + * Returns the common logarithm (base 10) of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMLOG10(complexNumber) + * + * @param array|string $complexNumber the complex number for which you want the common logarithm + * Or can be an array of values + * + * @return array|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function IMLOG10($complexNumber) + { + if (is_array($complexNumber)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); + } + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return ExcelError::NAN(); + } + + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return ExcelError::NAN(); + } + + return (string) $complex->log10(); + } + + /** + * IMLOG2. + * + * Returns the base-2 logarithm of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMLOG2(complexNumber) + * + * @param array|string $complexNumber the complex number for which you want the base-2 logarithm + * Or can be an array of values + * + * @return array|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function IMLOG2($complexNumber) + { + if (is_array($complexNumber)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); + } + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return ExcelError::NAN(); + } + + if ($complex->getReal() == 0.0 && $complex->getImaginary() == 0.0) { + return ExcelError::NAN(); + } + + return (string) $complex->log2(); + } + + /** + * IMEXP. + * + * Returns the exponential of a complex number in x + yi or x + yj text format. + * + * Excel Function: + * IMEXP(complexNumber) + * + * @param array|string $complexNumber the complex number for which you want the exponential + * Or can be an array of values + * + * @return array|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function IMEXP($complexNumber) + { + if (is_array($complexNumber)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $complexNumber); + } + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return ExcelError::NAN(); + } + + return (string) $complex->exp(); + } + + /** + * IMPOWER. + * + * Returns a complex number in x + yi or x + yj text format raised to a power. + * + * Excel Function: + * IMPOWER(complexNumber,realNumber) + * + * @param array|string $complexNumber the complex number you want to raise to a power + * Or can be an array of values + * @param array|float|int|string $realNumber the power to which you want to raise the complex number + * Or can be an array of values + * + * @return array|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function IMPOWER($complexNumber, $realNumber) + { + if (is_array($complexNumber) || is_array($realNumber)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $complexNumber, $realNumber); + } + + try { + $complex = new ComplexObject($complexNumber); + } catch (ComplexException $e) { + return ExcelError::NAN(); + } + + if (!is_numeric($realNumber)) { + return ExcelError::VALUE(); + } + + return (string) $complex->pow((float) $realNumber); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Engineering/ComplexOperations.php b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/ComplexOperations.php new file mode 100644 index 0000000..e525b4b --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/ComplexOperations.php @@ -0,0 +1,134 @@ +divideby(new ComplexObject($complexDivisor)); + } catch (ComplexException $e) { + return ExcelError::NAN(); + } + } + + /** + * IMSUB. + * + * Returns the difference of two complex numbers in x + yi or x + yj text format. + * + * Excel Function: + * IMSUB(complexNumber1,complexNumber2) + * + * @param array|string $complexNumber1 the complex number from which to subtract complexNumber2 + * Or can be an array of values + * @param array|string $complexNumber2 the complex number to subtract from complexNumber1 + * Or can be an array of values + * + * @return array|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function IMSUB($complexNumber1, $complexNumber2) + { + if (is_array($complexNumber1) || is_array($complexNumber2)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $complexNumber1, $complexNumber2); + } + + try { + return (string) (new ComplexObject($complexNumber1))->subtract(new ComplexObject($complexNumber2)); + } catch (ComplexException $e) { + return ExcelError::NAN(); + } + } + + /** + * IMSUM. + * + * Returns the sum of two or more complex numbers in x + yi or x + yj text format. + * + * Excel Function: + * IMSUM(complexNumber[,complexNumber[,...]]) + * + * @param string ...$complexNumbers Series of complex numbers to add + * + * @return string + */ + public static function IMSUM(...$complexNumbers) + { + // Return value + $returnValue = new ComplexObject(0.0); + $aArgs = Functions::flattenArray($complexNumbers); + + try { + // Loop through the arguments + foreach ($aArgs as $complex) { + $returnValue = $returnValue->add(new ComplexObject($complex)); + } + } catch (ComplexException $e) { + return ExcelError::NAN(); + } + + return (string) $returnValue; + } + + /** + * IMPRODUCT. + * + * Returns the product of two or more complex numbers in x + yi or x + yj text format. + * + * Excel Function: + * IMPRODUCT(complexNumber[,complexNumber[,...]]) + * + * @param string ...$complexNumbers Series of complex numbers to multiply + * + * @return string + */ + public static function IMPRODUCT(...$complexNumbers) + { + // Return value + $returnValue = new ComplexObject(1.0); + $aArgs = Functions::flattenArray($complexNumbers); + + try { + // Loop through the arguments + foreach ($aArgs as $complex) { + $returnValue = $returnValue->multiply(new ComplexObject($complex)); + } + } catch (ComplexException $e) { + return ExcelError::NAN(); + } + + return (string) $returnValue; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Engineering/Constants.php b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/Constants.php new file mode 100644 index 0000000..a926db6 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/Constants.php @@ -0,0 +1,11 @@ + 10) { + throw new Exception(ExcelError::NAN()); + } + + return (int) $places; + } + + throw new Exception(ExcelError::VALUE()); + } + + /** + * Formats a number base string value with leading zeroes. + * + * @param string $value The "number" to pad + * @param ?int $places The length that we want to pad this value + * + * @return string The padded "number" + */ + protected static function nbrConversionFormat(string $value, ?int $places): string + { + if ($places !== null) { + if (strlen($value) <= $places) { + return substr(str_pad($value, $places, '0', STR_PAD_LEFT), -10); + } + + return ExcelError::NAN(); + } + + return substr($value, -10); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Engineering/ConvertBinary.php b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/ConvertBinary.php new file mode 100644 index 0000000..4741f30 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/ConvertBinary.php @@ -0,0 +1,163 @@ +getMessage(); + } + + if (strlen($value) == 10) { + // Two's Complement + $value = substr($value, -9); + + return '-' . (512 - bindec($value)); + } + + return (string) bindec($value); + } + + /** + * toHex. + * + * Return a binary value as hex. + * + * Excel Function: + * BIN2HEX(x[,places]) + * + * @param array|string $value The binary number (as a string) that you want to convert. The number + * cannot contain more than 10 characters (10 bits). The most significant + * bit of number is the sign bit. The remaining 9 bits are magnitude bits. + * Negative numbers are represented using two's-complement notation. + * If number is not a valid binary number, or if number contains more than + * 10 characters (10 bits), BIN2HEX returns the #NUM! error value. + * Or can be an array of values + * @param array|int $places The number of characters to use. If places is omitted, BIN2HEX uses the + * minimum number of characters necessary. Places is useful for padding the + * return value with leading 0s (zeros). + * If places is not an integer, it is truncated. + * If places is nonnumeric, BIN2HEX returns the #VALUE! error value. + * If places is negative, BIN2HEX returns the #NUM! error value. + * Or can be an array of values + * + * @return array|string Result, or an error + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function toHex($value, $places = null) + { + if (is_array($value) || is_array($places)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places); + } + + try { + $value = self::validateValue($value); + $value = self::validateBinary($value); + $places = self::validatePlaces($places); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (strlen($value) == 10) { + $high2 = substr($value, 0, 2); + $low8 = substr($value, 2); + $xarr = ['00' => '00000000', '01' => '00000001', '10' => 'FFFFFFFE', '11' => 'FFFFFFFF']; + + return $xarr[$high2] . strtoupper(substr('0' . dechex((int) bindec($low8)), -2)); + } + $hexVal = (string) strtoupper(dechex((int) bindec($value))); + + return self::nbrConversionFormat($hexVal, $places); + } + + /** + * toOctal. + * + * Return a binary value as octal. + * + * Excel Function: + * BIN2OCT(x[,places]) + * + * @param array|string $value The binary number (as a string) that you want to convert. The number + * cannot contain more than 10 characters (10 bits). The most significant + * bit of number is the sign bit. The remaining 9 bits are magnitude bits. + * Negative numbers are represented using two's-complement notation. + * If number is not a valid binary number, or if number contains more than + * 10 characters (10 bits), BIN2OCT returns the #NUM! error value. + * Or can be an array of values + * @param array|int $places The number of characters to use. If places is omitted, BIN2OCT uses the + * minimum number of characters necessary. Places is useful for padding the + * return value with leading 0s (zeros). + * If places is not an integer, it is truncated. + * If places is nonnumeric, BIN2OCT returns the #VALUE! error value. + * If places is negative, BIN2OCT returns the #NUM! error value. + * Or can be an array of values + * + * @return array|string Result, or an error + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function toOctal($value, $places = null) + { + if (is_array($value) || is_array($places)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places); + } + + try { + $value = self::validateValue($value); + $value = self::validateBinary($value); + $places = self::validatePlaces($places); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (strlen($value) == 10 && substr($value, 0, 1) === '1') { // Two's Complement + return str_repeat('7', 6) . strtoupper(decoct((int) bindec("11$value"))); + } + $octVal = (string) decoct((int) bindec($value)); + + return self::nbrConversionFormat($octVal, $places); + } + + protected static function validateBinary(string $value): string + { + if ((strlen($value) > preg_match_all('/[01]/', $value)) || (strlen($value) > 10)) { + throw new Exception(ExcelError::NAN()); + } + + return $value; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Engineering/ConvertDecimal.php b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/ConvertDecimal.php new file mode 100644 index 0000000..9b59d39 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/ConvertDecimal.php @@ -0,0 +1,213 @@ + 511, DEC2BIN returns the #NUM! error + * value. + * If number is nonnumeric, DEC2BIN returns the #VALUE! error value. + * If DEC2BIN requires more than places characters, it returns the #NUM! + * error value. + * Or can be an array of values + * @param array|int $places The number of characters to use. If places is omitted, DEC2BIN uses + * the minimum number of characters necessary. Places is useful for + * padding the return value with leading 0s (zeros). + * If places is not an integer, it is truncated. + * If places is nonnumeric, DEC2BIN returns the #VALUE! error value. + * If places is zero or negative, DEC2BIN returns the #NUM! error value. + * Or can be an array of values + * + * @return array|string Result, or an error + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function toBinary($value, $places = null) + { + if (is_array($value) || is_array($places)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places); + } + + try { + $value = self::validateValue($value); + $value = self::validateDecimal($value); + $places = self::validatePlaces($places); + } catch (Exception $e) { + return $e->getMessage(); + } + + $value = (int) floor((float) $value); + if ($value > self::LARGEST_BINARY_IN_DECIMAL || $value < self::SMALLEST_BINARY_IN_DECIMAL) { + return ExcelError::NAN(); + } + + $r = decbin($value); + // Two's Complement + $r = substr($r, -10); + + return self::nbrConversionFormat($r, $places); + } + + /** + * toHex. + * + * Return a decimal value as hex. + * + * Excel Function: + * DEC2HEX(x[,places]) + * + * @param array|string $value The decimal integer you want to convert. If number is negative, + * places is ignored and DEC2HEX returns a 10-character (40-bit) + * hexadecimal number in which the most significant bit is the sign + * bit. The remaining 39 bits are magnitude bits. Negative numbers + * are represented using two's-complement notation. + * If number < -549,755,813,888 or if number > 549,755,813,887, + * DEC2HEX returns the #NUM! error value. + * If number is nonnumeric, DEC2HEX returns the #VALUE! error value. + * If DEC2HEX requires more than places characters, it returns the + * #NUM! error value. + * Or can be an array of values + * @param array|int $places The number of characters to use. If places is omitted, DEC2HEX uses + * the minimum number of characters necessary. Places is useful for + * padding the return value with leading 0s (zeros). + * If places is not an integer, it is truncated. + * If places is nonnumeric, DEC2HEX returns the #VALUE! error value. + * If places is zero or negative, DEC2HEX returns the #NUM! error value. + * Or can be an array of values + * + * @return array|string Result, or an error + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function toHex($value, $places = null) + { + if (is_array($value) || is_array($places)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places); + } + + try { + $value = self::validateValue($value); + $value = self::validateDecimal($value); + $places = self::validatePlaces($places); + } catch (Exception $e) { + return $e->getMessage(); + } + + $value = floor((float) $value); + if ($value > self::LARGEST_HEX_IN_DECIMAL || $value < self::SMALLEST_HEX_IN_DECIMAL) { + return ExcelError::NAN(); + } + $r = strtoupper(dechex((int) $value)); + $r = self::hex32bit($value, $r); + + return self::nbrConversionFormat($r, $places); + } + + public static function hex32bit(float $value, string $hexstr, bool $force = false): string + { + if (PHP_INT_SIZE === 4 || $force) { + if ($value >= 2 ** 32) { + $quotient = (int) ($value / (2 ** 32)); + + return strtoupper(substr('0' . dechex($quotient), -2) . $hexstr); + } + if ($value < -(2 ** 32)) { + $quotient = 256 - (int) ceil((-$value) / (2 ** 32)); + + return strtoupper(substr('0' . dechex($quotient), -2) . substr("00000000$hexstr", -8)); + } + if ($value < 0) { + return "FF$hexstr"; + } + } + + return $hexstr; + } + + /** + * toOctal. + * + * Return an decimal value as octal. + * + * Excel Function: + * DEC2OCT(x[,places]) + * + * @param array|string $value The decimal integer you want to convert. If number is negative, + * places is ignored and DEC2OCT returns a 10-character (30-bit) + * octal number in which the most significant bit is the sign bit. + * The remaining 29 bits are magnitude bits. Negative numbers are + * represented using two's-complement notation. + * If number < -536,870,912 or if number > 536,870,911, DEC2OCT + * returns the #NUM! error value. + * If number is nonnumeric, DEC2OCT returns the #VALUE! error value. + * If DEC2OCT requires more than places characters, it returns the + * #NUM! error value. + * Or can be an array of values + * @param array|int $places The number of characters to use. If places is omitted, DEC2OCT uses + * the minimum number of characters necessary. Places is useful for + * padding the return value with leading 0s (zeros). + * If places is not an integer, it is truncated. + * If places is nonnumeric, DEC2OCT returns the #VALUE! error value. + * If places is zero or negative, DEC2OCT returns the #NUM! error value. + * Or can be an array of values + * + * @return array|string Result, or an error + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function toOctal($value, $places = null) + { + if (is_array($value) || is_array($places)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places); + } + + try { + $value = self::validateValue($value); + $value = self::validateDecimal($value); + $places = self::validatePlaces($places); + } catch (Exception $e) { + return $e->getMessage(); + } + + $value = (int) floor((float) $value); + if ($value > self::LARGEST_OCTAL_IN_DECIMAL || $value < self::SMALLEST_OCTAL_IN_DECIMAL) { + return ExcelError::NAN(); + } + $r = decoct($value); + $r = substr($r, -10); + + return self::nbrConversionFormat($r, $places); + } + + protected static function validateDecimal(string $value): string + { + if (strlen($value) > preg_match_all('/[-0123456789.]/', $value)) { + throw new Exception(ExcelError::VALUE()); + } + + return $value; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Engineering/ConvertHex.php b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/ConvertHex.php new file mode 100644 index 0000000..55ce209 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/ConvertHex.php @@ -0,0 +1,175 @@ +getMessage(); + } + + $dec = self::toDecimal($value); + + return ConvertDecimal::toBinary($dec, $places); + } + + /** + * toDecimal. + * + * Return a hex value as decimal. + * + * Excel Function: + * HEX2DEC(x) + * + * @param array|string $value The hexadecimal number you want to convert. This number cannot + * contain more than 10 characters (40 bits). The most significant + * bit of number is the sign bit. The remaining 39 bits are magnitude + * bits. Negative numbers are represented using two's-complement + * notation. + * If number is not a valid hexadecimal number, HEX2DEC returns the + * #NUM! error value. + * Or can be an array of values + * + * @return array|string Result, or an error + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function toDecimal($value) + { + if (is_array($value)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value); + } + + try { + $value = self::validateValue($value); + $value = self::validateHex($value); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (strlen($value) > 10) { + return ExcelError::NAN(); + } + + $binX = ''; + foreach (str_split($value) as $char) { + $binX .= str_pad(base_convert($char, 16, 2), 4, '0', STR_PAD_LEFT); + } + if (strlen($binX) == 40 && $binX[0] == '1') { + for ($i = 0; $i < 40; ++$i) { + $binX[$i] = ($binX[$i] == '1' ? '0' : '1'); + } + + return (string) ((bindec($binX) + 1) * -1); + } + + return (string) bindec($binX); + } + + /** + * toOctal. + * + * Return a hex value as octal. + * + * Excel Function: + * HEX2OCT(x[,places]) + * + * @param array|string $value The hexadecimal number you want to convert. Number cannot + * contain more than 10 characters. The most significant bit of + * number is the sign bit. The remaining 39 bits are magnitude + * bits. Negative numbers are represented using two's-complement + * notation. + * If number is negative, HEX2OCT ignores places and returns a + * 10-character octal number. + * If number is negative, it cannot be less than FFE0000000, and + * if number is positive, it cannot be greater than 1FFFFFFF. + * If number is not a valid hexadecimal number, HEX2OCT returns + * the #NUM! error value. + * If HEX2OCT requires more than places characters, it returns + * the #NUM! error value. + * Or can be an array of values + * @param array|int $places The number of characters to use. If places is omitted, HEX2OCT + * uses the minimum number of characters necessary. Places is + * useful for padding the return value with leading 0s (zeros). + * If places is not an integer, it is truncated. + * If places is nonnumeric, HEX2OCT returns the #VALUE! error + * value. + * If places is negative, HEX2OCT returns the #NUM! error value. + * Or can be an array of values + * + * @return array|string Result, or an error + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function toOctal($value, $places = null) + { + if (is_array($value) || is_array($places)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places); + } + + try { + $value = self::validateValue($value); + $value = self::validateHex($value); + $places = self::validatePlaces($places); + } catch (Exception $e) { + return $e->getMessage(); + } + + $decimal = self::toDecimal($value); + + return ConvertDecimal::toOctal($decimal, $places); + } + + protected static function validateHex(string $value): string + { + if (strlen($value) > preg_match_all('/[0123456789ABCDEF]/', $value)) { + throw new Exception(ExcelError::NAN()); + } + + return $value; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Engineering/ConvertOctal.php b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/ConvertOctal.php new file mode 100644 index 0000000..add7aba --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/ConvertOctal.php @@ -0,0 +1,174 @@ +getMessage(); + } + + return ConvertDecimal::toBinary(self::toDecimal($value), $places); + } + + /** + * toDecimal. + * + * Return an octal value as decimal. + * + * Excel Function: + * OCT2DEC(x) + * + * @param array|string $value The octal number you want to convert. Number may not contain + * more than 10 octal characters (30 bits). The most significant + * bit of number is the sign bit. The remaining 29 bits are + * magnitude bits. Negative numbers are represented using + * two's-complement notation. + * If number is not a valid octal number, OCT2DEC returns the + * #NUM! error value. + * Or can be an array of values + * + * @return array|string Result, or an error + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function toDecimal($value) + { + if (is_array($value)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value); + } + + try { + $value = self::validateValue($value); + $value = self::validateOctal($value); + } catch (Exception $e) { + return $e->getMessage(); + } + + $binX = ''; + foreach (str_split($value) as $char) { + $binX .= str_pad(decbin((int) $char), 3, '0', STR_PAD_LEFT); + } + if (strlen($binX) == 30 && $binX[0] == '1') { + for ($i = 0; $i < 30; ++$i) { + $binX[$i] = ($binX[$i] == '1' ? '0' : '1'); + } + + return (string) ((bindec($binX) + 1) * -1); + } + + return (string) bindec($binX); + } + + /** + * toHex. + * + * Return an octal value as hex. + * + * Excel Function: + * OCT2HEX(x[,places]) + * + * @param array|string $value The octal number you want to convert. Number may not contain + * more than 10 octal characters (30 bits). The most significant + * bit of number is the sign bit. The remaining 29 bits are + * magnitude bits. Negative numbers are represented using + * two's-complement notation. + * If number is negative, OCT2HEX ignores places and returns a + * 10-character hexadecimal number. + * If number is not a valid octal number, OCT2HEX returns the + * #NUM! error value. + * If OCT2HEX requires more than places characters, it returns + * the #NUM! error value. + * Or can be an array of values + * @param array|int $places The number of characters to use. If places is omitted, OCT2HEX + * uses the minimum number of characters necessary. Places is useful + * for padding the return value with leading 0s (zeros). + * If places is not an integer, it is truncated. + * If places is nonnumeric, OCT2HEX returns the #VALUE! error value. + * If places is negative, OCT2HEX returns the #NUM! error value. + * Or can be an array of values + * + * @return array|string Result, or an error + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function toHex($value, $places = null) + { + if (is_array($value) || is_array($places)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $places); + } + + try { + $value = self::validateValue($value); + $value = self::validateOctal($value); + $places = self::validatePlaces($places); + } catch (Exception $e) { + return $e->getMessage(); + } + + $hexVal = strtoupper(dechex((int) self::toDecimal($value))); + $hexVal = (PHP_INT_SIZE === 4 && strlen($value) === 10 && $value[0] >= '4') ? "FF{$hexVal}" : $hexVal; + + return self::nbrConversionFormat($hexVal, $places); + } + + protected static function validateOctal(string $value): string + { + $numDigits = (int) preg_match_all('/[01234567]/', $value); + if (strlen($value) > $numDigits || $numDigits > 10) { + throw new Exception(ExcelError::NAN()); + } + + return $value; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php new file mode 100644 index 0000000..8541a6c --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/ConvertUOM.php @@ -0,0 +1,694 @@ + ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Gram', 'AllowPrefix' => true], + 'sg' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Slug', 'AllowPrefix' => false], + 'lbm' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Pound mass (avoirdupois)', 'AllowPrefix' => false], + 'u' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'U (atomic mass unit)', 'AllowPrefix' => true], + 'ozm' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Ounce mass (avoirdupois)', 'AllowPrefix' => false], + 'grain' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Grain', 'AllowPrefix' => false], + 'cwt' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'U.S. (short) hundredweight', 'AllowPrefix' => false], + 'shweight' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'U.S. (short) hundredweight', 'AllowPrefix' => false], + 'uk_cwt' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial hundredweight', 'AllowPrefix' => false], + 'lcwt' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial hundredweight', 'AllowPrefix' => false], + 'hweight' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial hundredweight', 'AllowPrefix' => false], + 'stone' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Stone', 'AllowPrefix' => false], + 'ton' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Ton', 'AllowPrefix' => false], + 'uk_ton' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial ton', 'AllowPrefix' => false], + 'LTON' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial ton', 'AllowPrefix' => false], + 'brton' => ['Group' => self::CATEGORY_WEIGHT_AND_MASS, 'Unit Name' => 'Imperial ton', 'AllowPrefix' => false], + // Distance + 'm' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Meter', 'AllowPrefix' => true], + 'mi' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Statute mile', 'AllowPrefix' => false], + 'Nmi' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Nautical mile', 'AllowPrefix' => false], + 'in' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Inch', 'AllowPrefix' => false], + 'ft' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Foot', 'AllowPrefix' => false], + 'yd' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Yard', 'AllowPrefix' => false], + 'ang' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Angstrom', 'AllowPrefix' => true], + 'ell' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Ell', 'AllowPrefix' => false], + 'ly' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Light Year', 'AllowPrefix' => false], + 'parsec' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Parsec', 'AllowPrefix' => false], + 'pc' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Parsec', 'AllowPrefix' => false], + 'Pica' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Pica (1/72 in)', 'AllowPrefix' => false], + 'Picapt' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Pica (1/72 in)', 'AllowPrefix' => false], + 'pica' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'Pica (1/6 in)', 'AllowPrefix' => false], + 'survey_mi' => ['Group' => self::CATEGORY_DISTANCE, 'Unit Name' => 'U.S survey mile (statute mile)', 'AllowPrefix' => false], + // Time + 'yr' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Year', 'AllowPrefix' => false], + 'day' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Day', 'AllowPrefix' => false], + 'd' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Day', 'AllowPrefix' => false], + 'hr' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Hour', 'AllowPrefix' => false], + 'mn' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Minute', 'AllowPrefix' => false], + 'min' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Minute', 'AllowPrefix' => false], + 'sec' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Second', 'AllowPrefix' => true], + 's' => ['Group' => self::CATEGORY_TIME, 'Unit Name' => 'Second', 'AllowPrefix' => true], + // Pressure + 'Pa' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Pascal', 'AllowPrefix' => true], + 'p' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Pascal', 'AllowPrefix' => true], + 'atm' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Atmosphere', 'AllowPrefix' => true], + 'at' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Atmosphere', 'AllowPrefix' => true], + 'mmHg' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'mm of Mercury', 'AllowPrefix' => true], + 'psi' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'PSI', 'AllowPrefix' => true], + 'Torr' => ['Group' => self::CATEGORY_PRESSURE, 'Unit Name' => 'Torr', 'AllowPrefix' => true], + // Force + 'N' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Newton', 'AllowPrefix' => true], + 'dyn' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Dyne', 'AllowPrefix' => true], + 'dy' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Dyne', 'AllowPrefix' => true], + 'lbf' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Pound force', 'AllowPrefix' => false], + 'pond' => ['Group' => self::CATEGORY_FORCE, 'Unit Name' => 'Pond', 'AllowPrefix' => true], + // Energy + 'J' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Joule', 'AllowPrefix' => true], + 'e' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Erg', 'AllowPrefix' => true], + 'c' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Thermodynamic calorie', 'AllowPrefix' => true], + 'cal' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'IT calorie', 'AllowPrefix' => true], + 'eV' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Electron volt', 'AllowPrefix' => true], + 'ev' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Electron volt', 'AllowPrefix' => true], + 'HPh' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Horsepower-hour', 'AllowPrefix' => false], + 'hh' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Horsepower-hour', 'AllowPrefix' => false], + 'Wh' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Watt-hour', 'AllowPrefix' => true], + 'wh' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Watt-hour', 'AllowPrefix' => true], + 'flb' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'Foot-pound', 'AllowPrefix' => false], + 'BTU' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'BTU', 'AllowPrefix' => false], + 'btu' => ['Group' => self::CATEGORY_ENERGY, 'Unit Name' => 'BTU', 'AllowPrefix' => false], + // Power + 'HP' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Horsepower', 'AllowPrefix' => false], + 'h' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Horsepower', 'AllowPrefix' => false], + 'W' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Watt', 'AllowPrefix' => true], + 'w' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Watt', 'AllowPrefix' => true], + 'PS' => ['Group' => self::CATEGORY_POWER, 'Unit Name' => 'Pferdestärke', 'AllowPrefix' => false], + // Magnetism + 'T' => ['Group' => self::CATEGORY_MAGNETISM, 'Unit Name' => 'Tesla', 'AllowPrefix' => true], + 'ga' => ['Group' => self::CATEGORY_MAGNETISM, 'Unit Name' => 'Gauss', 'AllowPrefix' => true], + // Temperature + 'C' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Celsius', 'AllowPrefix' => false], + 'cel' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Celsius', 'AllowPrefix' => false], + 'F' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Fahrenheit', 'AllowPrefix' => false], + 'fah' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Fahrenheit', 'AllowPrefix' => false], + 'K' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Kelvin', 'AllowPrefix' => false], + 'kel' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Kelvin', 'AllowPrefix' => false], + 'Rank' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Rankine', 'AllowPrefix' => false], + 'Reau' => ['Group' => self::CATEGORY_TEMPERATURE, 'Unit Name' => 'Degrees Réaumur', 'AllowPrefix' => false], + // Volume + 'l' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Litre', 'AllowPrefix' => true], + 'L' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Litre', 'AllowPrefix' => true], + 'lt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Litre', 'AllowPrefix' => true], + 'tsp' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Teaspoon', 'AllowPrefix' => false], + 'tspm' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Modern Teaspoon', 'AllowPrefix' => false], + 'tbs' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Tablespoon', 'AllowPrefix' => false], + 'oz' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Fluid Ounce', 'AllowPrefix' => false], + 'cup' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cup', 'AllowPrefix' => false], + 'pt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'U.S. Pint', 'AllowPrefix' => false], + 'us_pt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'U.S. Pint', 'AllowPrefix' => false], + 'uk_pt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'U.K. Pint', 'AllowPrefix' => false], + 'qt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Quart', 'AllowPrefix' => false], + 'uk_qt' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Imperial Quart (UK)', 'AllowPrefix' => false], + 'gal' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Gallon', 'AllowPrefix' => false], + 'uk_gal' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Imperial Gallon (UK)', 'AllowPrefix' => false], + 'ang3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Angstrom', 'AllowPrefix' => true], + 'ang^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Angstrom', 'AllowPrefix' => true], + 'barrel' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'US Oil Barrel', 'AllowPrefix' => false], + 'bushel' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'US Bushel', 'AllowPrefix' => false], + 'in3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Inch', 'AllowPrefix' => false], + 'in^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Inch', 'AllowPrefix' => false], + 'ft3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Foot', 'AllowPrefix' => false], + 'ft^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Foot', 'AllowPrefix' => false], + 'ly3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Light Year', 'AllowPrefix' => false], + 'ly^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Light Year', 'AllowPrefix' => false], + 'm3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Meter', 'AllowPrefix' => true], + 'm^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Meter', 'AllowPrefix' => true], + 'mi3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Mile', 'AllowPrefix' => false], + 'mi^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Mile', 'AllowPrefix' => false], + 'yd3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Yard', 'AllowPrefix' => false], + 'yd^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Yard', 'AllowPrefix' => false], + 'Nmi3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Nautical Mile', 'AllowPrefix' => false], + 'Nmi^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Nautical Mile', 'AllowPrefix' => false], + 'Pica3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Pica', 'AllowPrefix' => false], + 'Pica^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Pica', 'AllowPrefix' => false], + 'Picapt3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Pica', 'AllowPrefix' => false], + 'Picapt^3' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Cubic Pica', 'AllowPrefix' => false], + 'GRT' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Gross Registered Ton', 'AllowPrefix' => false], + 'regton' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Gross Registered Ton', 'AllowPrefix' => false], + 'MTON' => ['Group' => self::CATEGORY_VOLUME, 'Unit Name' => 'Measurement Ton (Freight Ton)', 'AllowPrefix' => false], + // Area + 'ha' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Hectare', 'AllowPrefix' => true], + 'uk_acre' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'International Acre', 'AllowPrefix' => false], + 'us_acre' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'US Survey/Statute Acre', 'AllowPrefix' => false], + 'ang2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Angstrom', 'AllowPrefix' => true], + 'ang^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Angstrom', 'AllowPrefix' => true], + 'ar' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Are', 'AllowPrefix' => true], + 'ft2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Feet', 'AllowPrefix' => false], + 'ft^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Feet', 'AllowPrefix' => false], + 'in2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Inches', 'AllowPrefix' => false], + 'in^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Inches', 'AllowPrefix' => false], + 'ly2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Light Years', 'AllowPrefix' => false], + 'ly^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Light Years', 'AllowPrefix' => false], + 'm2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Meters', 'AllowPrefix' => true], + 'm^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Meters', 'AllowPrefix' => true], + 'Morgen' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Morgen', 'AllowPrefix' => false], + 'mi2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Miles', 'AllowPrefix' => false], + 'mi^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Miles', 'AllowPrefix' => false], + 'Nmi2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Nautical Miles', 'AllowPrefix' => false], + 'Nmi^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Nautical Miles', 'AllowPrefix' => false], + 'Pica2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Pica', 'AllowPrefix' => false], + 'Pica^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Pica', 'AllowPrefix' => false], + 'Picapt2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Pica', 'AllowPrefix' => false], + 'Picapt^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Pica', 'AllowPrefix' => false], + 'yd2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Yards', 'AllowPrefix' => false], + 'yd^2' => ['Group' => self::CATEGORY_AREA, 'Unit Name' => 'Square Yards', 'AllowPrefix' => false], + // Information + 'byte' => ['Group' => self::CATEGORY_INFORMATION, 'Unit Name' => 'Byte', 'AllowPrefix' => true], + 'bit' => ['Group' => self::CATEGORY_INFORMATION, 'Unit Name' => 'Bit', 'AllowPrefix' => true], + // Speed + 'm/s' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Meters per second', 'AllowPrefix' => true], + 'm/sec' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Meters per second', 'AllowPrefix' => true], + 'm/h' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Meters per hour', 'AllowPrefix' => true], + 'm/hr' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Meters per hour', 'AllowPrefix' => true], + 'mph' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Miles per hour', 'AllowPrefix' => false], + 'admkn' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Admiralty Knot', 'AllowPrefix' => false], + 'kn' => ['Group' => self::CATEGORY_SPEED, 'Unit Name' => 'Knot', 'AllowPrefix' => false], + ]; + + /** + * Details of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM(). + * + * @var mixed[] + */ + private static $conversionMultipliers = [ + 'Y' => ['multiplier' => 1E24, 'name' => 'yotta'], + 'Z' => ['multiplier' => 1E21, 'name' => 'zetta'], + 'E' => ['multiplier' => 1E18, 'name' => 'exa'], + 'P' => ['multiplier' => 1E15, 'name' => 'peta'], + 'T' => ['multiplier' => 1E12, 'name' => 'tera'], + 'G' => ['multiplier' => 1E9, 'name' => 'giga'], + 'M' => ['multiplier' => 1E6, 'name' => 'mega'], + 'k' => ['multiplier' => 1E3, 'name' => 'kilo'], + 'h' => ['multiplier' => 1E2, 'name' => 'hecto'], + 'e' => ['multiplier' => 1E1, 'name' => 'dekao'], + 'da' => ['multiplier' => 1E1, 'name' => 'dekao'], + 'd' => ['multiplier' => 1E-1, 'name' => 'deci'], + 'c' => ['multiplier' => 1E-2, 'name' => 'centi'], + 'm' => ['multiplier' => 1E-3, 'name' => 'milli'], + 'u' => ['multiplier' => 1E-6, 'name' => 'micro'], + 'n' => ['multiplier' => 1E-9, 'name' => 'nano'], + 'p' => ['multiplier' => 1E-12, 'name' => 'pico'], + 'f' => ['multiplier' => 1E-15, 'name' => 'femto'], + 'a' => ['multiplier' => 1E-18, 'name' => 'atto'], + 'z' => ['multiplier' => 1E-21, 'name' => 'zepto'], + 'y' => ['multiplier' => 1E-24, 'name' => 'yocto'], + ]; + + /** + * Details of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM(). + * + * @var mixed[] + */ + private static $binaryConversionMultipliers = [ + 'Yi' => ['multiplier' => 2 ** 80, 'name' => 'yobi'], + 'Zi' => ['multiplier' => 2 ** 70, 'name' => 'zebi'], + 'Ei' => ['multiplier' => 2 ** 60, 'name' => 'exbi'], + 'Pi' => ['multiplier' => 2 ** 50, 'name' => 'pebi'], + 'Ti' => ['multiplier' => 2 ** 40, 'name' => 'tebi'], + 'Gi' => ['multiplier' => 2 ** 30, 'name' => 'gibi'], + 'Mi' => ['multiplier' => 2 ** 20, 'name' => 'mebi'], + 'ki' => ['multiplier' => 2 ** 10, 'name' => 'kibi'], + ]; + + /** + * Details of the Units of measure conversion factors, organised by group. + * + * @var mixed[] + */ + private static $unitConversions = [ + // Conversion uses gram (g) as an intermediate unit + self::CATEGORY_WEIGHT_AND_MASS => [ + 'g' => 1.0, + '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 + self::CATEGORY_DISTANCE => [ + 'm' => 1.0, + '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 + self::CATEGORY_TIME => [ + '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.0, + 's' => 1.0, + ], + // Conversion uses Pascal (Pa) as an intermediate unit + self::CATEGORY_PRESSURE => [ + 'Pa' => 1.0, + 'p' => 1.0, + '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 + self::CATEGORY_FORCE => [ + 'N' => 1.0, + 'dyn' => 1.0E+5, + 'dy' => 1.0E+5, + 'lbf' => 2.24808923655339E-01, + 'pond' => 1.01971621297793E+02, + ], + // Conversion uses Joule (J) as an intermediate unit + self::CATEGORY_ENERGY => [ + 'J' => 1.0, + '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 + self::CATEGORY_POWER => [ + 'HP' => 1.0, + 'h' => 1.0, + 'W' => 7.45699871582270E+02, + 'w' => 7.45699871582270E+02, + 'PS' => 1.01386966542400E+00, + ], + // Conversion uses Tesla (T) as an intermediate unit + self::CATEGORY_MAGNETISM => [ + 'T' => 1.0, + 'ga' => 10000.0, + ], + // Conversion uses litre (l) as an intermediate unit + self::CATEGORY_VOLUME => [ + 'l' => 1.0, + 'L' => 1.0, + 'lt' => 1.0, + '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 + self::CATEGORY_AREA => [ + 'ha' => 1.0, + '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 + self::CATEGORY_INFORMATION => [ + 'bit' => 1.0, + 'byte' => 0.125, + ], + // Conversion uses Meters per Second (m/s) as an intermediate unit + self::CATEGORY_SPEED => [ + 'm/s' => 1.0, + 'm/sec' => 1.0, + 'm/h' => 3.60E+03, + 'm/hr' => 3.60E+03, + 'mph' => 2.23693629205440E+00, + 'admkn' => 1.94260256941567E+00, + 'kn' => 1.94384449244060E+00, + ], + ]; + + /** + * getConversionGroups + * Returns a list of the different conversion groups for UOM conversions. + * + * @return array + */ + public static function getConversionCategories() + { + $conversionGroups = []; + foreach (self::$conversionUnits as $conversionUnit) { + $conversionGroups[] = $conversionUnit['Group']; + } + + return array_merge(array_unique($conversionGroups)); + } + + /** + * getConversionGroupUnits + * Returns an array of units of measure, for a specified conversion group, or for all groups. + * + * @param string $category The group whose units of measure you want to retrieve + * + * @return array + */ + public static function getConversionCategoryUnits($category = null) + { + $conversionGroups = []; + foreach (self::$conversionUnits as $conversionUnit => $conversionGroup) { + if (($category === null) || ($conversionGroup['Group'] == $category)) { + $conversionGroups[$conversionGroup['Group']][] = $conversionUnit; + } + } + + return $conversionGroups; + } + + /** + * getConversionGroupUnitDetails. + * + * @param string $category The group whose units of measure you want to retrieve + * + * @return array + */ + public static function getConversionCategoryUnitDetails($category = null) + { + $conversionGroups = []; + foreach (self::$conversionUnits as $conversionUnit => $conversionGroup) { + if (($category === null) || ($conversionGroup['Group'] == $category)) { + $conversionGroups[$conversionGroup['Group']][] = [ + 'unit' => $conversionUnit, + 'description' => $conversionGroup['Unit Name'], + ]; + } + } + + return $conversionGroups; + } + + /** + * getConversionMultipliers + * Returns an array of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM(). + * + * @return mixed[] + */ + public static function getConversionMultipliers() + { + return self::$conversionMultipliers; + } + + /** + * getBinaryConversionMultipliers + * Returns an array of the additional Multiplier prefixes that can be used with Information Units of Measure in CONVERTUOM(). + * + * @return mixed[] + */ + public static function getBinaryConversionMultipliers() + { + return self::$binaryConversionMultipliers; + } + + /** + * CONVERT. + * + * Converts a number from one measurement system to another. + * For example, CONVERT can translate a table of distances in miles to a table of distances + * in kilometers. + * + * Excel Function: + * CONVERT(value,fromUOM,toUOM) + * + * @param array|float|int|string $value the value in fromUOM to convert + * Or can be an array of values + * @param array|string $fromUOM the units for value + * Or can be an array of values + * @param array|string $toUOM the units for the result + * Or can be an array of values + * + * @return array|float|string Result, or a string containing an error + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function CONVERT($value, $fromUOM, $toUOM) + { + if (is_array($value) || is_array($fromUOM) || is_array($toUOM)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $fromUOM, $toUOM); + } + + if (!is_numeric($value)) { + return ExcelError::VALUE(); + } + + try { + [$fromUOM, $fromCategory, $fromMultiplier] = self::getUOMDetails($fromUOM); + [$toUOM, $toCategory, $toMultiplier] = self::getUOMDetails($toUOM); + } catch (Exception $e) { + return ExcelError::NA(); + } + + if ($fromCategory !== $toCategory) { + return ExcelError::NA(); + } + + // @var float $value + $value *= $fromMultiplier; + + if (($fromUOM === $toUOM) && ($fromMultiplier === $toMultiplier)) { + // We've already factored $fromMultiplier into the value, so we need + // to reverse it again + return $value / $fromMultiplier; + } elseif ($fromUOM === $toUOM) { + return $value / $toMultiplier; + } elseif ($fromCategory === self::CATEGORY_TEMPERATURE) { + return self::convertTemperature($fromUOM, $toUOM, /** @scrutinizer ignore-type */ $value); + } + + $baseValue = $value * (1.0 / self::$unitConversions[$fromCategory][$fromUOM]); + + return ($baseValue * self::$unitConversions[$fromCategory][$toUOM]) / $toMultiplier; + } + + private static function getUOMDetails(string $uom) + { + if (isset(self::$conversionUnits[$uom])) { + $unitCategory = self::$conversionUnits[$uom]['Group']; + + return [$uom, $unitCategory, 1.0]; + } + + // Check 1-character standard metric multiplier prefixes + $multiplierType = substr($uom, 0, 1); + $uom = substr($uom, 1); + if (isset(self::$conversionUnits[$uom], self::$conversionMultipliers[$multiplierType])) { + if (self::$conversionUnits[$uom]['AllowPrefix'] === false) { + throw new Exception('Prefix not allowed for UoM'); + } + $unitCategory = self::$conversionUnits[$uom]['Group']; + + return [$uom, $unitCategory, self::$conversionMultipliers[$multiplierType]['multiplier']]; + } + + $multiplierType .= substr($uom, 0, 1); + $uom = substr($uom, 1); + + // Check 2-character standard metric multiplier prefixes + if (isset(self::$conversionUnits[$uom], self::$conversionMultipliers[$multiplierType])) { + if (self::$conversionUnits[$uom]['AllowPrefix'] === false) { + throw new Exception('Prefix not allowed for UoM'); + } + $unitCategory = self::$conversionUnits[$uom]['Group']; + + return [$uom, $unitCategory, self::$conversionMultipliers[$multiplierType]['multiplier']]; + } + + // Check 2-character binary multiplier prefixes + if (isset(self::$conversionUnits[$uom], self::$binaryConversionMultipliers[$multiplierType])) { + if (self::$conversionUnits[$uom]['AllowPrefix'] === false) { + throw new Exception('Prefix not allowed for UoM'); + } + $unitCategory = self::$conversionUnits[$uom]['Group']; + if ($unitCategory !== 'Information') { + throw new Exception('Binary Prefix is only allowed for Information UoM'); + } + + return [$uom, $unitCategory, self::$binaryConversionMultipliers[$multiplierType]['multiplier']]; + } + + throw new Exception('UoM Not Found'); + } + + /** + * @param float|int $value + * + * @return float|int + */ + protected static function convertTemperature(string $fromUOM, string $toUOM, $value) + { + $fromUOM = self::resolveTemperatureSynonyms($fromUOM); + $toUOM = self::resolveTemperatureSynonyms($toUOM); + + if ($fromUOM === $toUOM) { + return $value; + } + + // Convert to Kelvin + switch ($fromUOM) { + case 'F': + $value = ($value - 32) / 1.8 + 273.15; + + break; + case 'C': + $value += 273.15; + + break; + case 'Rank': + $value /= 1.8; + + break; + case 'Reau': + $value = $value * 1.25 + 273.15; + + break; + } + + // Convert from Kelvin + switch ($toUOM) { + case 'F': + $value = ($value - 273.15) * 1.8 + 32.00; + + break; + case 'C': + $value -= 273.15; + + break; + case 'Rank': + $value *= 1.8; + + break; + case 'Reau': + $value = ($value - 273.15) * 0.80000; + + break; + } + + return $value; + } + + private static function resolveTemperatureSynonyms(string $uom) + { + switch ($uom) { + case 'fah': + return 'F'; + case 'cel': + return 'C'; + case 'kel': + return 'K'; + } + + return $uom; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Engineering/EngineeringValidations.php b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/EngineeringValidations.php new file mode 100644 index 0000000..c0202ea --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/EngineeringValidations.php @@ -0,0 +1,33 @@ + 2.2) { + return 1 - ErfC::ERFC($value); + } + $sum = $term = $value; + $xsqr = ($value * $value); + $j = 1; + do { + $term *= $xsqr / $j; + $sum -= $term / (2 * $j + 1); + ++$j; + $term *= $xsqr / $j; + $sum += $term / (2 * $j + 1); + ++$j; + if ($sum == 0.0) { + break; + } + } while (abs($term / $sum) > Functions::PRECISION); + + return self::$twoSqrtPi * $sum; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Engineering/ErfC.php b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/ErfC.php new file mode 100644 index 0000000..eb834b7 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Engineering/ErfC.php @@ -0,0 +1,77 @@ + Functions::PRECISION); + + return self::$oneSqrtPi * exp(-$value * $value) * $q2; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Exception.php b/PhpOffice/PhpSpreadsheet/Calculation/Exception.php old mode 100755 new mode 100644 index fccf0af..a95a452 --- a/PhpOffice/PhpSpreadsheet/Calculation/Exception.php +++ b/PhpOffice/PhpSpreadsheet/Calculation/Exception.php @@ -6,6 +6,8 @@ use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; class Exception extends PhpSpreadsheetException { + public const CALCULATION_ENGINE_PUSH_TO_STACK = 1; + /** * Error handler callback. * @@ -15,7 +17,7 @@ class Exception extends PhpSpreadsheetException * @param mixed $line * @param mixed $context */ - public static function errorHandlerCallback($code, $string, $file, $line, $context) + public static function errorHandlerCallback($code, $string, $file, $line, /** @scrutinizer ignore-unused */ $context): void { $e = new self($string, $code); $e->line = $line; diff --git a/PhpOffice/PhpSpreadsheet/Calculation/ExceptionHandler.php b/PhpOffice/PhpSpreadsheet/Calculation/ExceptionHandler.php old mode 100755 new mode 100644 index 41e51d4..6461961 --- a/PhpOffice/PhpSpreadsheet/Calculation/ExceptionHandler.php +++ b/PhpOffice/PhpSpreadsheet/Calculation/ExceptionHandler.php @@ -9,7 +9,9 @@ class ExceptionHandler */ public function __construct() { - set_error_handler([Exception::class, 'errorHandlerCallback'], E_ALL); + /** @var callable */ + $callable = [Exception::class, 'errorHandlerCallback']; + set_error_handler($callable, E_ALL); } /** diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Financial.php b/PhpOffice/PhpSpreadsheet/Calculation/Financial.php old mode 100755 new mode 100644 index dde87c1..43e27c7 --- a/PhpOffice/PhpSpreadsheet/Calculation/Financial.php +++ b/PhpOffice/PhpSpreadsheet/Calculation/Financial.php @@ -2,177 +2,82 @@ namespace PhpOffice\PhpSpreadsheet\Calculation; -use PhpOffice\PhpSpreadsheet\Shared\Date; +use PhpOffice\PhpSpreadsheet\Calculation\Financial\Amortization; +use PhpOffice\PhpSpreadsheet\Calculation\Financial\Coupons; +use PhpOffice\PhpSpreadsheet\Calculation\Financial\Depreciation; +use PhpOffice\PhpSpreadsheet\Calculation\Financial\Dollar; +use PhpOffice\PhpSpreadsheet\Calculation\Financial\InterestRate; +use PhpOffice\PhpSpreadsheet\Calculation\Financial\Securities; +use PhpOffice\PhpSpreadsheet\Calculation\Financial\TreasuryBill; +/** + * @deprecated 1.18.0 + * + * @codeCoverageIgnore + */ class Financial { - const FINANCIAL_MAX_ITERATIONS = 32; + const FINANCIAL_MAX_ITERATIONS = 128; const FINANCIAL_PRECISION = 1.0e-08; - /** - * isLastDayOfMonth. - * - * Returns a boolean TRUE/FALSE indicating if this date is the last date of the month - * - * @param \DateTime $testDate The date for testing - * - * @return bool - */ - private static function isLastDayOfMonth(\DateTime $testDate) - { - return $testDate->format('d') == $testDate->format('t'); - } - - private static function couponFirstPeriodDate($settlement, $maturity, $frequency, $next) - { - $months = 12 / $frequency; - - $result = Date::excelToDateTimeObject($maturity); - $eom = self::isLastDayOfMonth($result); - - while ($settlement < Date::PHPToExcel($result)) { - $result->modify('-' . $months . ' months'); - } - if ($next) { - $result->modify('+' . $months . ' months'); - } - - if ($eom) { - $result->modify('-1 day'); - } - - return Date::PHPToExcel($result); - } - - private static function isValidFrequency($frequency) - { - if (($frequency == 1) || ($frequency == 2) || ($frequency == 4)) { - return true; - } - if ((Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) && - (($frequency == 6) || ($frequency == 12))) { - return true; - } - - return false; - } - - /** - * daysPerYear. - * - * Returns the number of days in a specified year, as defined by the "basis" value - * - * @param int|string $year The year against which we're testing - * @param int|string $basis The type of day count: - * 0 or omitted US (NASD) 360 - * 1 Actual (365 or 366 in a leap year) - * 2 360 - * 3 365 - * 4 European 360 - * - * @return int - */ - private static function daysPerYear($year, $basis = 0) - { - switch ($basis) { - case 0: - case 2: - case 4: - $daysPerYear = 360; - - break; - case 3: - $daysPerYear = 365; - - break; - case 1: - $daysPerYear = (DateTime::isLeapYear($year)) ? 366 : 365; - - break; - default: - return Functions::NAN(); - } - - return $daysPerYear; - } - - private static function interestAndPrincipal($rate = 0, $per = 0, $nper = 0, $pv = 0, $fv = 0, $type = 0) - { - $pmt = self::PMT($rate, $nper, $pv, $fv, $type); - $capital = $pv; - for ($i = 1; $i <= $per; ++$i) { - $interest = ($type && $i == 1) ? 0 : -$capital * $rate; - $principal = $pmt - $interest; - $capital += $principal; - } - - return [$interest, $principal]; - } - /** * ACCRINT. * * Returns the accrued interest for a security that pays periodic interest. * * Excel Function: - * ACCRINT(issue,firstinterest,settlement,rate,par,frequency[,basis]) + * ACCRINT(issue,firstinterest,settlement,rate,par,frequency[,basis][,calc_method]) * - * @category Financial Functions + * @deprecated 1.18.0 + * Use the periodic() method in the Financial\Securities\AccruedInterest class instead + * @see Securities\AccruedInterest::periodic() * * @param mixed $issue the security's issue date - * @param mixed $firstinterest the security's first interest date + * @param mixed $firstInterest the security's first interest date * @param mixed $settlement The security's settlement date. - * The security settlement date is the date after the issue date - * when the security is traded to the buyer. - * @param float $rate the security's annual coupon rate - * @param float $par The security's par value. - * If you omit par, ACCRINT uses $1,000. - * @param int $frequency the number of coupon payments per year. - * Valid frequency values are: - * 1 Annual - * 2 Semi-Annual - * 4 Quarterly - * If working in Gnumeric Mode, the following frequency options are - * also available - * 6 Bimonthly - * 12 Monthly - * @param int $basis The type of day count to use. - * 0 or omitted US (NASD) 30/360 - * 1 Actual/actual - * 2 Actual/360 - * 3 Actual/365 - * 4 European 30/360 + * The security settlement date is the date after the issue date + * when the security is traded to the buyer. + * @param mixed $rate the security's annual coupon rate + * @param mixed $parValue The security's par value. + * If you omit par, ACCRINT uses $1,000. + * @param mixed $frequency The number of coupon payments per year. + * Valid frequency values are: + * 1 Annual + * 2 Semi-Annual + * 4 Quarterly + * @param mixed $basis The type of day count to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * @param mixed $calcMethod + * If true, use Issue to Settlement + * If false, use FirstInterest to Settlement * - * @return float|string + * @return float|string Result, or a string containing an error */ - public static function ACCRINT($issue, $firstinterest, $settlement, $rate, $par = 1000, $frequency = 1, $basis = 0) - { - $issue = Functions::flattenSingleValue($issue); - $firstinterest = Functions::flattenSingleValue($firstinterest); - $settlement = Functions::flattenSingleValue($settlement); - $rate = Functions::flattenSingleValue($rate); - $par = ($par === null) ? 1000 : Functions::flattenSingleValue($par); - $frequency = ($frequency === null) ? 1 : Functions::flattenSingleValue($frequency); - $basis = ($basis === null) ? 0 : Functions::flattenSingleValue($basis); - - // Validate - if ((is_numeric($rate)) && (is_numeric($par))) { - $rate = (float) $rate; - $par = (float) $par; - if (($rate <= 0) || ($par <= 0)) { - return Functions::NAN(); - } - $daysBetweenIssueAndSettlement = DateTime::YEARFRAC($issue, $settlement, $basis); - if (!is_numeric($daysBetweenIssueAndSettlement)) { - // return date error - return $daysBetweenIssueAndSettlement; - } - - return $par * $rate * $daysBetweenIssueAndSettlement; - } - - return Functions::VALUE(); + public static function ACCRINT( + $issue, + $firstInterest, + $settlement, + $rate, + $parValue = 1000, + $frequency = 1, + $basis = 0, + $calcMethod = true + ) { + return Securities\AccruedInterest::periodic( + $issue, + $firstInterest, + $settlement, + $rate, + $parValue, + $frequency, + $basis, + $calcMethod + ); } /** @@ -183,47 +88,27 @@ class Financial * Excel Function: * ACCRINTM(issue,settlement,rate[,par[,basis]]) * - * @category Financial Functions + * @deprecated 1.18.0 + * Use the atMaturity() method in the Financial\Securities\AccruedInterest class instead + * @see Financial\Securities\AccruedInterest::atMaturity() * * @param mixed $issue The security's issue date * @param mixed $settlement The security's settlement (or maturity) date - * @param float $rate The security's annual coupon rate - * @param float $par The security's par value. - * If you omit par, ACCRINT uses $1,000. - * @param int $basis The type of day count to use. - * 0 or omitted US (NASD) 30/360 - * 1 Actual/actual - * 2 Actual/360 - * 3 Actual/365 - * 4 European 30/360 + * @param mixed $rate The security's annual coupon rate + * @param mixed $parValue The security's par value. + * If you omit par, ACCRINT uses $1,000. + * @param mixed $basis The type of day count to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 * - * @return float|string + * @return float|string Result, or a string containing an error */ - public static function ACCRINTM($issue, $settlement, $rate, $par = 1000, $basis = 0) + public static function ACCRINTM($issue, $settlement, $rate, $parValue = 1000, $basis = 0) { - $issue = Functions::flattenSingleValue($issue); - $settlement = Functions::flattenSingleValue($settlement); - $rate = Functions::flattenSingleValue($rate); - $par = ($par === null) ? 1000 : Functions::flattenSingleValue($par); - $basis = ($basis === null) ? 0 : Functions::flattenSingleValue($basis); - - // Validate - if ((is_numeric($rate)) && (is_numeric($par))) { - $rate = (float) $rate; - $par = (float) $par; - if (($rate <= 0) || ($par <= 0)) { - return Functions::NAN(); - } - $daysBetweenIssueAndSettlement = DateTime::YEARFRAC($issue, $settlement, $basis); - if (!is_numeric($daysBetweenIssueAndSettlement)) { - // return date error - return $daysBetweenIssueAndSettlement; - } - - return $par * $rate * $daysBetweenIssueAndSettlement; - } - - return Functions::VALUE(); + return Securities\AccruedInterest::atMaturity($issue, $settlement, $rate, $parValue, $basis); } /** @@ -241,7 +126,9 @@ class Financial * Excel Function: * AMORDEGRC(cost,purchased,firstPeriod,salvage,period,rate[,basis]) * - * @category Financial Functions + * @deprecated 1.18.0 + * Use the AMORDEGRC() method in the Financial\Amortization class instead + * @see Financial\Amortization::AMORDEGRC() * * @param float $cost The cost of the asset * @param mixed $purchased Date of the purchase of the asset @@ -250,63 +137,17 @@ class Financial * @param float $period The period * @param float $rate Rate of depreciation * @param int $basis The type of day count to use. - * 0 or omitted US (NASD) 30/360 - * 1 Actual/actual - * 2 Actual/360 - * 3 Actual/365 - * 4 European 30/360 + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 * - * @return float + * @return float|string (string containing the error type if there is an error) */ public static function AMORDEGRC($cost, $purchased, $firstPeriod, $salvage, $period, $rate, $basis = 0) { - $cost = Functions::flattenSingleValue($cost); - $purchased = Functions::flattenSingleValue($purchased); - $firstPeriod = Functions::flattenSingleValue($firstPeriod); - $salvage = Functions::flattenSingleValue($salvage); - $period = floor(Functions::flattenSingleValue($period)); - $rate = Functions::flattenSingleValue($rate); - $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis); - - // The depreciation coefficients are: - // Life of assets (1/rate) Depreciation coefficient - // Less than 3 years 1 - // Between 3 and 4 years 1.5 - // Between 5 and 6 years 2 - // More than 6 years 2.5 - $fUsePer = 1.0 / $rate; - if ($fUsePer < 3.0) { - $amortiseCoeff = 1.0; - } elseif ($fUsePer < 5.0) { - $amortiseCoeff = 1.5; - } elseif ($fUsePer <= 6.0) { - $amortiseCoeff = 2.0; - } else { - $amortiseCoeff = 2.5; - } - - $rate *= $amortiseCoeff; - $fNRate = round(DateTime::YEARFRAC($purchased, $firstPeriod, $basis) * $rate * $cost, 0); - $cost -= $fNRate; - $fRest = $cost - $salvage; - - for ($n = 0; $n < $period; ++$n) { - $fNRate = round($rate * $cost, 0); - $fRest -= $fNRate; - - if ($fRest < 0.0) { - switch ($period - $n) { - case 0: - case 1: - return round($cost * 0.5, 0); - default: - return 0.0; - } - } - $cost -= $fNRate; - } - - return $fNRate; + return Amortization::AMORDEGRC($cost, $purchased, $firstPeriod, $salvage, $period, $rate, $basis); } /** @@ -319,7 +160,9 @@ class Financial * Excel Function: * AMORLINC(cost,purchased,firstPeriod,salvage,period,rate[,basis]) * - * @category Financial Functions + * @deprecated 1.18.0 + * Use the AMORLINC() method in the Financial\Amortization class instead + * @see Financial\Amortization::AMORLINC() * * @param float $cost The cost of the asset * @param mixed $purchased Date of the purchase of the asset @@ -328,46 +171,17 @@ class Financial * @param float $period The period * @param float $rate Rate of depreciation * @param int $basis The type of day count to use. - * 0 or omitted US (NASD) 30/360 - * 1 Actual/actual - * 2 Actual/360 - * 3 Actual/365 - * 4 European 30/360 + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 * - * @return float + * @return float|string (string containing the error type if there is an error) */ public static function AMORLINC($cost, $purchased, $firstPeriod, $salvage, $period, $rate, $basis = 0) { - $cost = Functions::flattenSingleValue($cost); - $purchased = Functions::flattenSingleValue($purchased); - $firstPeriod = Functions::flattenSingleValue($firstPeriod); - $salvage = Functions::flattenSingleValue($salvage); - $period = Functions::flattenSingleValue($period); - $rate = Functions::flattenSingleValue($rate); - $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis); - - $fOneRate = $cost * $rate; - $fCostDelta = $cost - $salvage; - // Note, quirky variation for leap years on the YEARFRAC for this function - $purchasedYear = DateTime::YEAR($purchased); - $yearFrac = DateTime::YEARFRAC($purchased, $firstPeriod, $basis); - - if (($basis == 1) && ($yearFrac < 1) && (DateTime::isLeapYear($purchasedYear))) { - $yearFrac *= 365 / 366; - } - - $f0Rate = $yearFrac * $rate * $cost; - $nNumOfFullPeriods = (int) (($cost - $salvage - $f0Rate) / $fOneRate); - - if ($period == 0) { - return $f0Rate; - } elseif ($period <= $nNumOfFullPeriods) { - return $fOneRate; - } elseif ($period == ($nNumOfFullPeriods + 1)) { - return $fCostDelta - $fOneRate * $nNumOfFullPeriods - $f0Rate; - } - - return 0.0; + return Amortization::AMORLINC($cost, $purchased, $firstPeriod, $salvage, $period, $rate, $basis); } /** @@ -378,7 +192,9 @@ class Financial * Excel Function: * COUPDAYBS(settlement,maturity,frequency[,basis]) * - * @category Financial Functions + * @deprecated 1.18.0 + * Use the COUPDAYBS() method in the Financial\Coupons class instead + * @see Financial\Coupons::COUPDAYBS() * * @param mixed $settlement The security's settlement date. * The security settlement date is the date after the issue @@ -390,10 +206,6 @@ class Financial * 1 Annual * 2 Semi-Annual * 4 Quarterly - * If working in Gnumeric Mode, the following frequency options are - * also available - * 6 Bimonthly - * 12 Monthly * @param int $basis The type of day count to use. * 0 or omitted US (NASD) 30/360 * 1 Actual/actual @@ -405,28 +217,7 @@ class Financial */ public static function COUPDAYBS($settlement, $maturity, $frequency, $basis = 0) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $frequency = (int) Functions::flattenSingleValue($frequency); - $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis); - - if (is_string($settlement = DateTime::getDateValue($settlement))) { - return Functions::VALUE(); - } - if (is_string($maturity = DateTime::getDateValue($maturity))) { - return Functions::VALUE(); - } - - if (($settlement >= $maturity) || - (!self::isValidFrequency($frequency)) || - (($basis < 0) || ($basis > 4))) { - return Functions::NAN(); - } - - $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis); - $prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, false); - - return DateTime::YEARFRAC($prev, $settlement, $basis) * $daysPerYear; + return Coupons::COUPDAYBS($settlement, $maturity, $frequency, $basis); } /** @@ -437,7 +228,9 @@ class Financial * Excel Function: * COUPDAYS(settlement,maturity,frequency[,basis]) * - * @category Financial Functions + * @deprecated 1.18.0 + * Use the COUPDAYS() method in the Financial\Coupons class instead + * @see Financial\Coupons::COUPDAYS() * * @param mixed $settlement The security's settlement date. * The security settlement date is the date after the issue @@ -449,10 +242,6 @@ class Financial * 1 Annual * 2 Semi-Annual * 4 Quarterly - * If working in Gnumeric Mode, the following frequency options are - * also available - * 6 Bimonthly - * 12 Monthly * @param int $basis The type of day count to use. * 0 or omitted US (NASD) 30/360 * 1 Actual/actual @@ -464,43 +253,7 @@ class Financial */ public static function COUPDAYS($settlement, $maturity, $frequency, $basis = 0) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $frequency = (int) Functions::flattenSingleValue($frequency); - $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis); - - if (is_string($settlement = DateTime::getDateValue($settlement))) { - return Functions::VALUE(); - } - if (is_string($maturity = DateTime::getDateValue($maturity))) { - return Functions::VALUE(); - } - - if (($settlement >= $maturity) || - (!self::isValidFrequency($frequency)) || - (($basis < 0) || ($basis > 4))) { - return Functions::NAN(); - } - - switch ($basis) { - case 3: - // Actual/365 - return 365 / $frequency; - case 1: - // Actual/actual - if ($frequency == 1) { - $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis); - - return $daysPerYear / $frequency; - } - $prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, false); - $next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, true); - - return $next - $prev; - default: - // US (NASD) 30/360, Actual/360 or European 30/360 - return 360 / $frequency; - } + return Coupons::COUPDAYS($settlement, $maturity, $frequency, $basis); } /** @@ -511,7 +264,9 @@ class Financial * Excel Function: * COUPDAYSNC(settlement,maturity,frequency[,basis]) * - * @category Financial Functions + * @deprecated 1.18.0 + * Use the COUPDAYSNC() method in the Financial\Coupons class instead + * @see Financial\Coupons::COUPDAYSNC() * * @param mixed $settlement The security's settlement date. * The security settlement date is the date after the issue @@ -523,10 +278,6 @@ class Financial * 1 Annual * 2 Semi-Annual * 4 Quarterly - * If working in Gnumeric Mode, the following frequency options are - * also available - * 6 Bimonthly - * 12 Monthly * @param int $basis The type of day count to use. * 0 or omitted US (NASD) 30/360 * 1 Actual/actual @@ -538,28 +289,7 @@ class Financial */ public static function COUPDAYSNC($settlement, $maturity, $frequency, $basis = 0) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $frequency = (int) Functions::flattenSingleValue($frequency); - $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis); - - if (is_string($settlement = DateTime::getDateValue($settlement))) { - return Functions::VALUE(); - } - if (is_string($maturity = DateTime::getDateValue($maturity))) { - return Functions::VALUE(); - } - - if (($settlement >= $maturity) || - (!self::isValidFrequency($frequency)) || - (($basis < 0) || ($basis > 4))) { - return Functions::NAN(); - } - - $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis); - $next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, true); - - return DateTime::YEARFRAC($settlement, $next, $basis) * $daysPerYear; + return Coupons::COUPDAYSNC($settlement, $maturity, $frequency, $basis); } /** @@ -570,7 +300,9 @@ class Financial * Excel Function: * COUPNCD(settlement,maturity,frequency[,basis]) * - * @category Financial Functions + * @deprecated 1.18.0 + * Use the COUPNCD() method in the Financial\Coupons class instead + * @see Financial\Coupons::COUPNCD() * * @param mixed $settlement The security's settlement date. * The security settlement date is the date after the issue @@ -582,10 +314,6 @@ class Financial * 1 Annual * 2 Semi-Annual * 4 Quarterly - * If working in Gnumeric Mode, the following frequency options are - * also available - * 6 Bimonthly - * 12 Monthly * @param int $basis The type of day count to use. * 0 or omitted US (NASD) 30/360 * 1 Actual/actual @@ -598,25 +326,7 @@ class Financial */ public static function COUPNCD($settlement, $maturity, $frequency, $basis = 0) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $frequency = (int) Functions::flattenSingleValue($frequency); - $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis); - - if (is_string($settlement = DateTime::getDateValue($settlement))) { - return Functions::VALUE(); - } - if (is_string($maturity = DateTime::getDateValue($maturity))) { - return Functions::VALUE(); - } - - if (($settlement >= $maturity) || - (!self::isValidFrequency($frequency)) || - (($basis < 0) || ($basis > 4))) { - return Functions::NAN(); - } - - return self::couponFirstPeriodDate($settlement, $maturity, $frequency, true); + return Coupons::COUPNCD($settlement, $maturity, $frequency, $basis); } /** @@ -628,7 +338,9 @@ class Financial * Excel Function: * COUPNUM(settlement,maturity,frequency[,basis]) * - * @category Financial Functions + * @deprecated 1.18.0 + * Use the COUPNUM() method in the Financial\Coupons class instead + * @see Financial\Coupons::COUPNUM() * * @param mixed $settlement The security's settlement date. * The security settlement date is the date after the issue @@ -640,10 +352,6 @@ class Financial * 1 Annual * 2 Semi-Annual * 4 Quarterly - * If working in Gnumeric Mode, the following frequency options are - * also available - * 6 Bimonthly - * 12 Monthly * @param int $basis The type of day count to use. * 0 or omitted US (NASD) 30/360 * 1 Actual/actual @@ -655,37 +363,7 @@ class Financial */ public static function COUPNUM($settlement, $maturity, $frequency, $basis = 0) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $frequency = (int) Functions::flattenSingleValue($frequency); - $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis); - - if (is_string($settlement = DateTime::getDateValue($settlement))) { - return Functions::VALUE(); - } - if (is_string($maturity = DateTime::getDateValue($maturity))) { - return Functions::VALUE(); - } - - if (($settlement >= $maturity) || - (!self::isValidFrequency($frequency)) || - (($basis < 0) || ($basis > 4))) { - return Functions::NAN(); - } - - $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis); - $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis) * $daysPerYear; - - switch ($frequency) { - case 1: // annual payments - case 2: // half-yearly - case 4: // quarterly - case 6: // bimonthly - case 12: // monthly - return ceil($daysBetweenSettlementAndMaturity / $daysPerYear * $frequency); - } - - return Functions::VALUE(); + return Coupons::COUPNUM($settlement, $maturity, $frequency, $basis); } /** @@ -696,7 +374,9 @@ class Financial * Excel Function: * COUPPCD(settlement,maturity,frequency[,basis]) * - * @category Financial Functions + * @deprecated 1.18.0 + * Use the COUPPCD() method in the Financial\Coupons class instead + * @see Financial\Coupons::COUPPCD() * * @param mixed $settlement The security's settlement date. * The security settlement date is the date after the issue @@ -708,10 +388,6 @@ class Financial * 1 Annual * 2 Semi-Annual * 4 Quarterly - * If working in Gnumeric Mode, the following frequency options are - * also available - * 6 Bimonthly - * 12 Monthly * @param int $basis The type of day count to use. * 0 or omitted US (NASD) 30/360 * 1 Actual/actual @@ -724,25 +400,7 @@ class Financial */ public static function COUPPCD($settlement, $maturity, $frequency, $basis = 0) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $frequency = (int) Functions::flattenSingleValue($frequency); - $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis); - - if (is_string($settlement = DateTime::getDateValue($settlement))) { - return Functions::VALUE(); - } - if (is_string($maturity = DateTime::getDateValue($maturity))) { - return Functions::VALUE(); - } - - if (($settlement >= $maturity) || - (!self::isValidFrequency($frequency)) || - (($basis < 0) || ($basis > 4))) { - return Functions::NAN(); - } - - return self::couponFirstPeriodDate($settlement, $maturity, $frequency, false); + return Coupons::COUPPCD($settlement, $maturity, $frequency, $basis); } /** @@ -753,44 +411,25 @@ class Financial * Excel Function: * CUMIPMT(rate,nper,pv,start,end[,type]) * - * @category Financial Functions + * @deprecated 1.18.0 + * Use the interest() method in the Financial\CashFlow\Constant\Periodic\Cumulative class instead + * @see Financial\CashFlow\Constant\Periodic\Cumulative::interest() * * @param float $rate The Interest rate * @param int $nper The total number of payment periods * @param float $pv Present Value * @param int $start The first period in the calculation. - * Payment periods are numbered beginning with 1. + * Payment periods are numbered beginning with 1. * @param int $end the last period in the calculation * @param int $type A number 0 or 1 and indicates when payments are due: - * 0 or omitted At the end of the period. - * 1 At the beginning of the period. + * 0 or omitted At the end of the period. + * 1 At the beginning of the period. * * @return float|string */ public static function CUMIPMT($rate, $nper, $pv, $start, $end, $type = 0) { - $rate = Functions::flattenSingleValue($rate); - $nper = (int) Functions::flattenSingleValue($nper); - $pv = Functions::flattenSingleValue($pv); - $start = (int) Functions::flattenSingleValue($start); - $end = (int) Functions::flattenSingleValue($end); - $type = (int) Functions::flattenSingleValue($type); - - // Validate parameters - if ($type != 0 && $type != 1) { - return Functions::NAN(); - } - if ($start < 1 || $start > $end) { - return Functions::VALUE(); - } - - // Calculate - $interest = 0; - for ($per = $start; $per <= $end; ++$per) { - $interest += self::IPMT($rate, $per, $nper, $pv, 0, $type); - } - - return $interest; + return Financial\CashFlow\Constant\Periodic\Cumulative::interest($rate, $nper, $pv, $start, $end, $type); } /** @@ -801,44 +440,25 @@ class Financial * Excel Function: * CUMPRINC(rate,nper,pv,start,end[,type]) * - * @category Financial Functions + * @deprecated 1.18.0 + * Use the principal() method in the Financial\CashFlow\Constant\Periodic\Cumulative class instead + * @see Financial\CashFlow\Constant\Periodic\Cumulative::principal() * * @param float $rate The Interest rate * @param int $nper The total number of payment periods * @param float $pv Present Value * @param int $start The first period in the calculation. - * Payment periods are numbered beginning with 1. + * Payment periods are numbered beginning with 1. * @param int $end the last period in the calculation * @param int $type A number 0 or 1 and indicates when payments are due: - * 0 or omitted At the end of the period. - * 1 At the beginning of the period. + * 0 or omitted At the end of the period. + * 1 At the beginning of the period. * * @return float|string */ public static function CUMPRINC($rate, $nper, $pv, $start, $end, $type = 0) { - $rate = Functions::flattenSingleValue($rate); - $nper = (int) Functions::flattenSingleValue($nper); - $pv = Functions::flattenSingleValue($pv); - $start = (int) Functions::flattenSingleValue($start); - $end = (int) Functions::flattenSingleValue($end); - $type = (int) Functions::flattenSingleValue($type); - - // Validate parameters - if ($type != 0 && $type != 1) { - return Functions::NAN(); - } - if ($start < 1 || $start > $end) { - return Functions::VALUE(); - } - - // Calculate - $principal = 0; - for ($per = $start; $per <= $end; ++$per) { - $principal += self::PPMT($rate, $per, $nper, $pv, 0, $type); - } - - return $principal; + return Financial\CashFlow\Constant\Periodic\Cumulative::principal($rate, $nper, $pv, $start, $end, $type); } /** @@ -854,7 +474,9 @@ class Financial * Excel Function: * DB(cost,salvage,life,period[,month]) * - * @category Financial Functions + * @deprecated 1.18.0 + * Use the DB() method in the Financial\Depreciation class instead + * @see Financial\Depreciation::DB() * * @param float $cost Initial cost of the asset * @param float $salvage Value at the end of the depreciation. @@ -870,48 +492,7 @@ class Financial */ public static function DB($cost, $salvage, $life, $period, $month = 12) { - $cost = Functions::flattenSingleValue($cost); - $salvage = Functions::flattenSingleValue($salvage); - $life = Functions::flattenSingleValue($life); - $period = Functions::flattenSingleValue($period); - $month = Functions::flattenSingleValue($month); - - // Validate - if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life)) && (is_numeric($period)) && (is_numeric($month))) { - $cost = (float) $cost; - $salvage = (float) $salvage; - $life = (int) $life; - $period = (int) $period; - $month = (int) $month; - if ($cost == 0) { - return 0.0; - } elseif (($cost < 0) || (($salvage / $cost) < 0) || ($life <= 0) || ($period < 1) || ($month < 1)) { - return Functions::NAN(); - } - // Set Fixed Depreciation Rate - $fixedDepreciationRate = 1 - pow(($salvage / $cost), (1 / $life)); - $fixedDepreciationRate = round($fixedDepreciationRate, 3); - - // Loop through each period calculating the depreciation - $previousDepreciation = 0; - for ($per = 1; $per <= $period; ++$per) { - if ($per == 1) { - $depreciation = $cost * $fixedDepreciationRate * $month / 12; - } elseif ($per == ($life + 1)) { - $depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate * (12 - $month) / 12; - } else { - $depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate; - } - $previousDepreciation += $depreciation; - } - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { - $depreciation = round($depreciation, 2); - } - - return $depreciation; - } - - return Functions::VALUE(); + return Depreciation::DB($cost, $salvage, $life, $period, $month); } /** @@ -923,7 +504,9 @@ class Financial * Excel Function: * DDB(cost,salvage,life,period[,factor]) * - * @category Financial Functions + * @deprecated 1.18.0 + * Use the DDB() method in the Financial\Depreciation class instead + * @see Financial\Depreciation::DDB() * * @param float $cost Initial cost of the asset * @param float $salvage Value at the end of the depreciation. @@ -940,40 +523,7 @@ class Financial */ public static function DDB($cost, $salvage, $life, $period, $factor = 2.0) { - $cost = Functions::flattenSingleValue($cost); - $salvage = Functions::flattenSingleValue($salvage); - $life = Functions::flattenSingleValue($life); - $period = Functions::flattenSingleValue($period); - $factor = Functions::flattenSingleValue($factor); - - // Validate - if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life)) && (is_numeric($period)) && (is_numeric($factor))) { - $cost = (float) $cost; - $salvage = (float) $salvage; - $life = (int) $life; - $period = (int) $period; - $factor = (float) $factor; - if (($cost <= 0) || (($salvage / $cost) < 0) || ($life <= 0) || ($period < 1) || ($factor <= 0.0) || ($period > $life)) { - return Functions::NAN(); - } - // Set Fixed Depreciation Rate - $fixedDepreciationRate = 1 - pow(($salvage / $cost), (1 / $life)); - $fixedDepreciationRate = round($fixedDepreciationRate, 3); - - // Loop through each period calculating the depreciation - $previousDepreciation = 0; - for ($per = 1; $per <= $period; ++$per) { - $depreciation = min(($cost - $previousDepreciation) * ($factor / $life), ($cost - $salvage - $previousDepreciation)); - $previousDepreciation += $depreciation; - } - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { - $depreciation = round($depreciation, 2); - } - - return $depreciation; - } - - return Functions::VALUE(); + return Depreciation::DDB($cost, $salvage, $life, $period, $factor); } /** @@ -984,7 +534,9 @@ class Financial * Excel Function: * DISC(settlement,maturity,price,redemption[,basis]) * - * @category Financial Functions + * @deprecated 1.18.0 + * Use the discount() method in the Financial\Securities\Rates class instead + * @see Financial\Securities\Rates::discount() * * @param mixed $settlement The security's settlement date. * The security settlement date is the date after the issue @@ -1004,30 +556,7 @@ class Financial */ public static function DISC($settlement, $maturity, $price, $redemption, $basis = 0) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $price = Functions::flattenSingleValue($price); - $redemption = Functions::flattenSingleValue($redemption); - $basis = Functions::flattenSingleValue($basis); - - // Validate - if ((is_numeric($price)) && (is_numeric($redemption)) && (is_numeric($basis))) { - $price = (float) $price; - $redemption = (float) $redemption; - $basis = (int) $basis; - if (($price <= 0) || ($redemption <= 0)) { - return Functions::NAN(); - } - $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis); - if (!is_numeric($daysBetweenSettlementAndMaturity)) { - // return date error - return $daysBetweenSettlementAndMaturity; - } - - return (1 - $price / $redemption) / $daysBetweenSettlementAndMaturity; - } - - return Functions::VALUE(); + return Financial\Securities\Rates::discount($settlement, $maturity, $price, $redemption, $basis); } /** @@ -1040,32 +569,18 @@ class Financial * Excel Function: * DOLLARDE(fractional_dollar,fraction) * - * @category Financial Functions + * @deprecated 1.18.0 + * Use the decimal() method in the Financial\Dollar class instead + * @see Financial\Dollar::decimal() * - * @param float $fractional_dollar Fractional Dollar - * @param int $fraction Fraction + * @param array|float $fractional_dollar Fractional Dollar + * @param array|int $fraction Fraction * - * @return float|string + * @return array|float|string */ public static function DOLLARDE($fractional_dollar = null, $fraction = 0) { - $fractional_dollar = Functions::flattenSingleValue($fractional_dollar); - $fraction = (int) Functions::flattenSingleValue($fraction); - - // Validate parameters - if ($fractional_dollar === null || $fraction < 0) { - return Functions::NAN(); - } - if ($fraction == 0) { - return Functions::DIV0(); - } - - $dollars = floor($fractional_dollar); - $cents = fmod($fractional_dollar, 1); - $cents /= $fraction; - $cents *= pow(10, ceil(log10($fraction))); - - return $dollars + $cents; + return Dollar::decimal($fractional_dollar, $fraction); } /** @@ -1078,32 +593,18 @@ class Financial * Excel Function: * DOLLARFR(decimal_dollar,fraction) * - * @category Financial Functions + * @deprecated 1.18.0 + * Use the fractional() method in the Financial\Dollar class instead + * @see Financial\Dollar::fractional() * - * @param float $decimal_dollar Decimal Dollar - * @param int $fraction Fraction + * @param array|float $decimal_dollar Decimal Dollar + * @param array|int $fraction Fraction * - * @return float|string + * @return array|float|string */ public static function DOLLARFR($decimal_dollar = null, $fraction = 0) { - $decimal_dollar = Functions::flattenSingleValue($decimal_dollar); - $fraction = (int) Functions::flattenSingleValue($fraction); - - // Validate parameters - if ($decimal_dollar === null || $fraction < 0) { - return Functions::NAN(); - } - if ($fraction == 0) { - return Functions::DIV0(); - } - - $dollars = floor($decimal_dollar); - $cents = fmod($decimal_dollar, 1); - $cents *= $fraction; - $cents *= pow(10, -ceil(log10($fraction))); - - return $dollars + $cents; + return Dollar::fractional($decimal_dollar, $fraction); } /** @@ -1115,24 +616,18 @@ class Financial * Excel Function: * EFFECT(nominal_rate,npery) * - * @category Financial Functions + * @deprecated 1.18.0 + * Use the effective() method in the Financial\InterestRate class instead + * @see Financial\InterestRate::effective() * - * @param float $nominal_rate Nominal interest rate - * @param int $npery Number of compounding payments per year + * @param float $nominalRate Nominal interest rate + * @param int $periodsPerYear Number of compounding payments per year * * @return float|string */ - public static function EFFECT($nominal_rate = 0, $npery = 0) + public static function EFFECT($nominalRate = 0, $periodsPerYear = 0) { - $nominal_rate = Functions::flattenSingleValue($nominal_rate); - $npery = (int) Functions::flattenSingleValue($npery); - - // Validate parameters - if ($nominal_rate <= 0 || $npery < 1) { - return Functions::NAN(); - } - - return pow((1 + $nominal_rate / $npery), $npery) - 1; + return Financial\InterestRate::effective($nominalRate, $periodsPerYear); } /** @@ -1143,7 +638,9 @@ class Financial * Excel Function: * FV(rate,nper,pmt[,pv[,type]]) * - * @category Financial Functions + * @deprecated 1.18.0 + * Use the futureValue() method in the Financial\CashFlow\Constant\Periodic class instead + * @see Financial\CashFlow\Constant\Periodic::futureValue() * * @param float $rate The interest rate per period * @param int $nper Total number of payment periods in an annuity @@ -1160,23 +657,7 @@ class Financial */ public static function FV($rate = 0, $nper = 0, $pmt = 0, $pv = 0, $type = 0) { - $rate = Functions::flattenSingleValue($rate); - $nper = Functions::flattenSingleValue($nper); - $pmt = Functions::flattenSingleValue($pmt); - $pv = Functions::flattenSingleValue($pv); - $type = Functions::flattenSingleValue($type); - - // Validate parameters - if ($type != 0 && $type != 1) { - return Functions::NAN(); - } - - // Calculate - if ($rate !== null && $rate != 0) { - return -$pv * pow(1 + $rate, $nper) - $pmt * (1 + $rate * $type) * (pow(1 + $rate, $nper) - 1) / $rate; - } - - return -$pv - $pmt * $nper; + return Financial\CashFlow\Constant\Periodic::futureValue($rate, $nper, $pmt, $pv, $type); } /** @@ -1188,21 +669,18 @@ class Financial * Excel Function: * FVSCHEDULE(principal,schedule) * + * @deprecated 1.18.0 + * Use the futureValue() method in the Financial\CashFlow\Single class instead + * @see Financial\CashFlow\Single::futureValue() + * * @param float $principal the present value * @param float[] $schedule an array of interest rates to apply * - * @return float + * @return float|string */ public static function FVSCHEDULE($principal, $schedule) { - $principal = Functions::flattenSingleValue($principal); - $schedule = Functions::flattenArray($schedule); - - foreach ($schedule as $rate) { - $principal *= 1 + $rate; - } - - return $principal; + return Financial\CashFlow\Single::futureValue($principal, $schedule); } /** @@ -1213,57 +691,44 @@ class Financial * Excel Function: * INTRATE(settlement,maturity,investment,redemption[,basis]) * + * @deprecated 1.18.0 + * Use the interest() method in the Financial\Securities\Rates class instead + * @see Financial\Securities\Rates::interest() + * * @param mixed $settlement The security's settlement date. - * The security settlement date is the date after the issue date when the security is traded to the buyer. + * The security settlement date is the date after the issue date when the security + * is traded to the buyer. * @param mixed $maturity The security's maturity date. - * The maturity date is the date when the security expires. + * The maturity date is the date when the security expires. * @param int $investment the amount invested in the security * @param int $redemption the amount to be received at maturity * @param int $basis The type of day count to use. - * 0 or omitted US (NASD) 30/360 - * 1 Actual/actual - * 2 Actual/360 - * 3 Actual/365 - * 4 European 30/360 + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 * * @return float|string */ public static function INTRATE($settlement, $maturity, $investment, $redemption, $basis = 0) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $investment = Functions::flattenSingleValue($investment); - $redemption = Functions::flattenSingleValue($redemption); - $basis = Functions::flattenSingleValue($basis); - - // Validate - if ((is_numeric($investment)) && (is_numeric($redemption)) && (is_numeric($basis))) { - $investment = (float) $investment; - $redemption = (float) $redemption; - $basis = (int) $basis; - if (($investment <= 0) || ($redemption <= 0)) { - return Functions::NAN(); - } - $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis); - if (!is_numeric($daysBetweenSettlementAndMaturity)) { - // return date error - return $daysBetweenSettlementAndMaturity; - } - - return (($redemption / $investment) - 1) / ($daysBetweenSettlementAndMaturity); - } - - return Functions::VALUE(); + return Financial\Securities\Rates::interest($settlement, $maturity, $investment, $redemption, $basis); } /** * IPMT. * - * Returns the interest payment for a given period for an investment based on periodic, constant payments and a constant interest rate. + * Returns the interest payment for a given period for an investment based on periodic, constant payments + * and a constant interest rate. * * Excel Function: * IPMT(rate,per,nper,pv[,fv][,type]) * + * @deprecated 1.18.0 + * Use the payment() method in the Financial\CashFlow\Constant\Periodic\Interest class instead + * @see Financial\CashFlow\Constant\Periodic\Interest::payment() + * * @param float $rate Interest rate per period * @param int $per Period for which we want to find the interest * @param int $nper Number of periods @@ -1275,25 +740,7 @@ class Financial */ public static function IPMT($rate, $per, $nper, $pv, $fv = 0, $type = 0) { - $rate = Functions::flattenSingleValue($rate); - $per = (int) Functions::flattenSingleValue($per); - $nper = (int) Functions::flattenSingleValue($nper); - $pv = Functions::flattenSingleValue($pv); - $fv = Functions::flattenSingleValue($fv); - $type = (int) Functions::flattenSingleValue($type); - - // Validate parameters - if ($type != 0 && $type != 1) { - return Functions::NAN(); - } - if ($per <= 0 || $per > $nper) { - return Functions::VALUE(); - } - - // Calculate - $interestAndPrincipal = self::interestAndPrincipal($rate, $per, $nper, $pv, $fv, $type); - - return $interestAndPrincipal[0]; + return Financial\CashFlow\Constant\Periodic\Interest::payment($rate, $per, $nper, $pv, $fv, $type); } /** @@ -1308,63 +755,21 @@ class Financial * Excel Function: * IRR(values[,guess]) * - * @param float[] $values An array or a reference to cells that contain numbers for which you want + * @deprecated 1.18.0 + * Use the rate() method in the Financial\CashFlow\Variable\Periodic class instead + * @see Financial\CashFlow\Variable\Periodic::rate() + * + * @param mixed $values An array or a reference to cells that contain numbers for which you want * to calculate the internal rate of return. * Values must contain at least one positive value and one negative value to * calculate the internal rate of return. - * @param float $guess A number that you guess is close to the result of IRR + * @param mixed $guess A number that you guess is close to the result of IRR * * @return float|string */ public static function IRR($values, $guess = 0.1) { - if (!is_array($values)) { - return Functions::VALUE(); - } - $values = Functions::flattenArray($values); - $guess = Functions::flattenSingleValue($guess); - - // create an initial range, with a root somewhere between 0 and guess - $x1 = 0.0; - $x2 = $guess; - $f1 = self::NPV($x1, $values); - $f2 = self::NPV($x2, $values); - for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) { - if (($f1 * $f2) < 0.0) { - break; - } - if (abs($f1) < abs($f2)) { - $f1 = self::NPV($x1 += 1.6 * ($x1 - $x2), $values); - } else { - $f2 = self::NPV($x2 += 1.6 * ($x2 - $x1), $values); - } - } - if (($f1 * $f2) > 0.0) { - return Functions::VALUE(); - } - - $f = self::NPV($x1, $values); - if ($f < 0.0) { - $rtb = $x1; - $dx = $x2 - $x1; - } else { - $rtb = $x2; - $dx = $x1 - $x2; - } - - for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) { - $dx *= 0.5; - $x_mid = $rtb + $dx; - $f_mid = self::NPV($x_mid, $values); - if ($f_mid <= 0.0) { - $rtb = $x_mid; - } - if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) { - return $x_mid; - } - } - - return Functions::VALUE(); + return Financial\CashFlow\Variable\Periodic::rate($values, $guess); } /** @@ -1373,7 +778,11 @@ class Financial * Returns the interest payment for an investment based on an interest rate and a constant payment schedule. * * Excel Function: - * =ISPMT(interest_rate, period, number_payments, PV) + * =ISPMT(interest_rate, period, number_payments, pv) + * + * @deprecated 1.18.0 + * Use the schedulePayment() method in the Financial\CashFlow\Constant\Periodic\Interest class instead + * @see Financial\CashFlow\Constant\Periodic\Interest::schedulePayment() * * interest_rate is the interest rate for the investment * @@ -1381,32 +790,15 @@ class Financial * * number_payments is the number of payments for the annuity * - * PV is the loan amount or present value of the payments + * pv is the loan amount or present value of the payments + * + * @param array $args + * + * @return float|string */ public static function ISPMT(...$args) { - // Return value - $returnValue = 0; - - // Get the parameters - $aArgs = Functions::flattenArray($args); - $interestRate = array_shift($aArgs); - $period = array_shift($aArgs); - $numberPeriods = array_shift($aArgs); - $principleRemaining = array_shift($aArgs); - - // Calculate - $principlePayment = ($principleRemaining * 1.0) / ($numberPeriods * 1.0); - for ($i = 0; $i <= $period; ++$i) { - $returnValue = $interestRate * $principleRemaining * -1; - $principleRemaining -= $principlePayment; - // principle needs to be 0 after the last payment, don't let floating point screw it up - if ($i == $numberPeriods) { - $returnValue = 0; - } - } - - return $returnValue; + return Financial\CashFlow\Constant\Periodic\Interest::schedulePayment(...$args); } /** @@ -1418,44 +810,21 @@ class Financial * Excel Function: * MIRR(values,finance_rate, reinvestment_rate) * - * @param float[] $values An array or a reference to cells that contain a series of payments and - * income occurring at regular intervals. - * Payments are negative value, income is positive values. - * @param float $finance_rate The interest rate you pay on the money used in the cash flows - * @param float $reinvestment_rate The interest rate you receive on the cash flows as you reinvest them + * @deprecated 1.18.0 + * Use the modifiedRate() method in the Financial\CashFlow\Variable\Periodic class instead + * @see Financial\CashFlow\Variable\Periodic::modifiedRate() * - * @return float|string + * @param mixed $values An array or a reference to cells that contain a series of payments and + * income occurring at regular intervals. + * Payments are negative value, income is positive values. + * @param mixed $finance_rate The interest rate you pay on the money used in the cash flows + * @param mixed $reinvestment_rate The interest rate you receive on the cash flows as you reinvest them + * + * @return float|string Result, or a string containing an error */ public static function MIRR($values, $finance_rate, $reinvestment_rate) { - if (!is_array($values)) { - return Functions::VALUE(); - } - $values = Functions::flattenArray($values); - $finance_rate = Functions::flattenSingleValue($finance_rate); - $reinvestment_rate = Functions::flattenSingleValue($reinvestment_rate); - $n = count($values); - - $rr = 1.0 + $reinvestment_rate; - $fr = 1.0 + $finance_rate; - - $npv_pos = $npv_neg = 0.0; - foreach ($values as $i => $v) { - if ($v >= 0) { - $npv_pos += $v / pow($rr, $i); - } else { - $npv_neg += $v / pow($fr, $i); - } - } - - if (($npv_neg == 0) || ($npv_pos == 0) || ($reinvestment_rate <= -1)) { - return Functions::VALUE(); - } - - $mirr = pow((-$npv_pos * pow($rr, $n)) - / ($npv_neg * ($rr)), (1.0 / ($n - 1))) - 1.0; - - return is_finite($mirr) ? $mirr : Functions::VALUE(); + return Financial\CashFlow\Variable\Periodic::modifiedRate($values, $finance_rate, $reinvestment_rate); } /** @@ -1463,23 +832,21 @@ class Financial * * Returns the nominal interest rate given the effective rate and the number of compounding payments per year. * - * @param float $effect_rate Effective interest rate - * @param int $npery Number of compounding payments per year + * Excel Function: + * NOMINAL(effect_rate, npery) * - * @return float|string + * @deprecated 1.18.0 + * Use the nominal() method in the Financial\InterestRate class instead + * @see Financial\InterestRate::nominal() + * + * @param float $effectiveRate Effective interest rate + * @param int $periodsPerYear Number of compounding payments per year + * + * @return float|string Result, or a string containing an error */ - public static function NOMINAL($effect_rate = 0, $npery = 0) + public static function NOMINAL($effectiveRate = 0, $periodsPerYear = 0) { - $effect_rate = Functions::flattenSingleValue($effect_rate); - $npery = (int) Functions::flattenSingleValue($npery); - - // Validate parameters - if ($effect_rate <= 0 || $npery < 1) { - return Functions::NAN(); - } - - // Calculate - return $npery * (pow($effect_rate + 1, 1 / $npery) - 1); + return InterestRate::nominal($effectiveRate, $periodsPerYear); } /** @@ -1487,40 +854,21 @@ class Financial * * Returns the number of periods for a cash flow with constant periodic payments (annuities), and interest rate. * + * @deprecated 1.18.0 + * Use the periods() method in the Financial\CashFlow\Constant\Periodic class instead + * @see Financial\CashFlow\Constant\Periodic::periods() + * * @param float $rate Interest rate per period * @param int $pmt Periodic payment (annuity) * @param float $pv Present Value * @param float $fv Future Value * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period * - * @return float|string + * @return float|string Result, or a string containing an error */ public static function NPER($rate = 0, $pmt = 0, $pv = 0, $fv = 0, $type = 0) { - $rate = Functions::flattenSingleValue($rate); - $pmt = Functions::flattenSingleValue($pmt); - $pv = Functions::flattenSingleValue($pv); - $fv = Functions::flattenSingleValue($fv); - $type = Functions::flattenSingleValue($type); - - // Validate parameters - if ($type != 0 && $type != 1) { - return Functions::NAN(); - } - - // Calculate - if ($rate !== null && $rate != 0) { - if ($pmt == 0 && $pv == 0) { - return Functions::NAN(); - } - - return log(($pmt * (1 + $rate * $type) / $rate - $fv) / ($pv + $pmt * (1 + $rate * $type) / $rate)) / log(1 + $rate); - } - if ($pmt == 0) { - return Functions::NAN(); - } - - return (-$pv - $fv) / $pmt; + return Financial\CashFlow\Constant\Periodic::periods($rate, $pmt, $pv, $fv, $type); } /** @@ -1528,28 +876,17 @@ class Financial * * Returns the Net Present Value of a cash flow series given a discount rate. * + * @deprecated 1.18.0 + * Use the presentValue() method in the Financial\CashFlow\Variable\Periodic class instead + * @see Financial\CashFlow\Variable\Periodic::presentValue() + * + * @param array $args + * * @return float */ public static function NPV(...$args) { - // Return value - $returnValue = 0; - - // Loop through arguments - $aArgs = Functions::flattenArray($args); - - // Calculate - $rate = array_shift($aArgs); - $countArgs = count($aArgs); - for ($i = 1; $i <= $countArgs; ++$i) { - // Is it a numeric value? - if (is_numeric($aArgs[$i - 1])) { - $returnValue += $aArgs[$i - 1] / pow(1 + $rate, $i); - } - } - - // Return - return $returnValue; + return Financial\CashFlow\Variable\Periodic::presentValue(...$args); } /** @@ -1557,26 +894,19 @@ class Financial * * Calculates the number of periods required for an investment to reach a specified value. * + * @deprecated 1.18.0 + * Use the periods() method in the Financial\CashFlow\Single class instead + * @see Financial\CashFlow\Single::periods() + * * @param float $rate Interest rate per period * @param float $pv Present Value * @param float $fv Future Value * - * @return float|string + * @return float|string Result, or a string containing an error */ public static function PDURATION($rate = 0, $pv = 0, $fv = 0) { - $rate = Functions::flattenSingleValue($rate); - $pv = Functions::flattenSingleValue($pv); - $fv = Functions::flattenSingleValue($fv); - - // Validate parameters - if (!is_numeric($rate) || !is_numeric($pv) || !is_numeric($fv)) { - return Functions::VALUE(); - } elseif ($rate <= 0.0 || $pv <= 0.0 || $fv <= 0.0) { - return Functions::NAN(); - } - - return (log($fv) - log($pv)) / log(1 + $rate); + return Financial\CashFlow\Single::periods($rate, $pv, $fv); } /** @@ -1584,39 +914,32 @@ class Financial * * Returns the constant payment (annuity) for a cash flow with a constant interest rate. * + * @deprecated 1.18.0 + * Use the annuity() method in the Financial\CashFlow\Constant\Periodic\Payments class instead + * @see Financial\CashFlow\Constant\Periodic\Payments::annuity() + * * @param float $rate Interest rate per period * @param int $nper Number of periods * @param float $pv Present Value * @param float $fv Future Value * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period * - * @return float + * @return float|string Result, or a string containing an error */ public static function PMT($rate = 0, $nper = 0, $pv = 0, $fv = 0, $type = 0) { - $rate = Functions::flattenSingleValue($rate); - $nper = Functions::flattenSingleValue($nper); - $pv = Functions::flattenSingleValue($pv); - $fv = Functions::flattenSingleValue($fv); - $type = Functions::flattenSingleValue($type); - - // Validate parameters - if ($type != 0 && $type != 1) { - return Functions::NAN(); - } - - // Calculate - if ($rate !== null && $rate != 0) { - return (-$fv - $pv * pow(1 + $rate, $nper)) / (1 + $rate * $type) / ((pow(1 + $rate, $nper) - 1) / $rate); - } - - return (-$pv - $fv) / $nper; + return Financial\CashFlow\Constant\Periodic\Payments::annuity($rate, $nper, $pv, $fv, $type); } /** * PPMT. * - * Returns the interest payment for a given period for an investment based on periodic, constant payments and a constant interest rate. + * Returns the interest payment for a given period for an investment based on periodic, constant payments + * and a constant interest rate. + * + * @deprecated 1.18.0 + * Use the interestPayment() method in the Financial\CashFlow\Constant\Periodic\Payments class instead + * @see Financial\CashFlow\Constant\Periodic\Payments::interestPayment() * * @param float $rate Interest rate per period * @param int $per Period for which we want to find the interest @@ -1625,70 +948,46 @@ class Financial * @param float $fv Future Value * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period * - * @return float + * @return float|string Result, or a string containing an error */ public static function PPMT($rate, $per, $nper, $pv, $fv = 0, $type = 0) { - $rate = Functions::flattenSingleValue($rate); - $per = (int) Functions::flattenSingleValue($per); - $nper = (int) Functions::flattenSingleValue($nper); - $pv = Functions::flattenSingleValue($pv); - $fv = Functions::flattenSingleValue($fv); - $type = (int) Functions::flattenSingleValue($type); - - // Validate parameters - if ($type != 0 && $type != 1) { - return Functions::NAN(); - } - if ($per <= 0 || $per > $nper) { - return Functions::VALUE(); - } - - // Calculate - $interestAndPrincipal = self::interestAndPrincipal($rate, $per, $nper, $pv, $fv, $type); - - return $interestAndPrincipal[1]; + return Financial\CashFlow\Constant\Periodic\Payments::interestPayment($rate, $per, $nper, $pv, $fv, $type); } + /** + * PRICE. + * + * Returns the price per $100 face value of a security that pays periodic interest. + * + * @deprecated 1.18.0 + * Use the price() method in the Financial\Securities\Price class instead + * @see Financial\Securities\Price::price() + * + * @param mixed $settlement The security's settlement date. + * The security settlement date is the date after the issue date when the security + * is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param float $rate the security's annual coupon rate + * @param float $yield the security's annual yield + * @param float $redemption The number of coupon payments per year. + * For annual payments, frequency = 1; + * for semiannual, frequency = 2; + * for quarterly, frequency = 4. + * @param int $frequency + * @param int $basis The type of day count to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string Result, or a string containing an error + */ public static function PRICE($settlement, $maturity, $rate, $yield, $redemption, $frequency, $basis = 0) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $rate = (float) Functions::flattenSingleValue($rate); - $yield = (float) Functions::flattenSingleValue($yield); - $redemption = (float) Functions::flattenSingleValue($redemption); - $frequency = (int) Functions::flattenSingleValue($frequency); - $basis = ($basis === null) ? 0 : (int) Functions::flattenSingleValue($basis); - - if (is_string($settlement = DateTime::getDateValue($settlement))) { - return Functions::VALUE(); - } - if (is_string($maturity = DateTime::getDateValue($maturity))) { - return Functions::VALUE(); - } - - if (($settlement > $maturity) || - (!self::isValidFrequency($frequency)) || - (($basis < 0) || ($basis > 4))) { - return Functions::NAN(); - } - - $dsc = self::COUPDAYSNC($settlement, $maturity, $frequency, $basis); - $e = self::COUPDAYS($settlement, $maturity, $frequency, $basis); - $n = self::COUPNUM($settlement, $maturity, $frequency, $basis); - $a = self::COUPDAYBS($settlement, $maturity, $frequency, $basis); - - $baseYF = 1.0 + ($yield / $frequency); - $rfp = 100 * ($rate / $frequency); - $de = $dsc / $e; - - $result = $redemption / pow($baseYF, (--$n + $de)); - for ($k = 0; $k <= $n; ++$k) { - $result += $rfp / (pow($baseYF, ($k + $de))); - } - $result -= $rfp * ($a / $e); - - return $result; + return Securities\Price::price($settlement, $maturity, $rate, $yield, $redemption, $frequency, $basis); } /** @@ -1696,10 +995,15 @@ class Financial * * Returns the price per $100 face value of a discounted security. * + * @deprecated 1.18.0 + * Use the priceDiscounted() method in the Financial\Securities\Price class instead + * @see Financial\Securities\Price::priceDiscounted() + * * @param mixed $settlement The security's settlement date. - * The security settlement date is the date after the issue date when the security is traded to the buyer. + * The security settlement date is the date after the issue date when the security + * is traded to the buyer. * @param mixed $maturity The security's maturity date. - * The maturity date is the date when the security expires. + * The maturity date is the date when the security expires. * @param int $discount The security's discount rate * @param int $redemption The security's redemption value per $100 face value * @param int $basis The type of day count to use. @@ -1709,31 +1013,11 @@ class Financial * 3 Actual/365 * 4 European 30/360 * - * @return float + * @return float|string Result, or a string containing an error */ public static function PRICEDISC($settlement, $maturity, $discount, $redemption, $basis = 0) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $discount = (float) Functions::flattenSingleValue($discount); - $redemption = (float) Functions::flattenSingleValue($redemption); - $basis = (int) Functions::flattenSingleValue($basis); - - // Validate - if ((is_numeric($discount)) && (is_numeric($redemption)) && (is_numeric($basis))) { - if (($discount <= 0) || ($redemption <= 0)) { - return Functions::NAN(); - } - $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis); - if (!is_numeric($daysBetweenSettlementAndMaturity)) { - // return date error - return $daysBetweenSettlementAndMaturity; - } - - return $redemption * (1 - $discount * $daysBetweenSettlementAndMaturity); - } - - return Functions::VALUE(); + return Securities\Price::priceDiscounted($settlement, $maturity, $discount, $redemption, $basis); } /** @@ -1741,10 +1025,15 @@ class Financial * * Returns the price per $100 face value of a security that pays interest at maturity. * + * @deprecated 1.18.0 + * Use the priceAtMaturity() method in the Financial\Securities\Price class instead + * @see Financial\Securities\Price::priceAtMaturity() + * * @param mixed $settlement The security's settlement date. - * The security's settlement date is the date after the issue date when the security is traded to the buyer. + * The security's settlement date is the date after the issue date when the security + * is traded to the buyer. * @param mixed $maturity The security's maturity date. - * The maturity date is the date when the security expires. + * The maturity date is the date when the security expires. * @param mixed $issue The security's issue date * @param int $rate The security's interest rate at date of issue * @param int $yield The security's annual yield @@ -1755,51 +1044,11 @@ class Financial * 3 Actual/365 * 4 European 30/360 * - * @return float + * @return float|string Result, or a string containing an error */ public static function PRICEMAT($settlement, $maturity, $issue, $rate, $yield, $basis = 0) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $issue = Functions::flattenSingleValue($issue); - $rate = Functions::flattenSingleValue($rate); - $yield = Functions::flattenSingleValue($yield); - $basis = (int) Functions::flattenSingleValue($basis); - - // Validate - if (is_numeric($rate) && is_numeric($yield)) { - if (($rate <= 0) || ($yield <= 0)) { - return Functions::NAN(); - } - $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis); - if (!is_numeric($daysPerYear)) { - return $daysPerYear; - } - $daysBetweenIssueAndSettlement = DateTime::YEARFRAC($issue, $settlement, $basis); - if (!is_numeric($daysBetweenIssueAndSettlement)) { - // return date error - return $daysBetweenIssueAndSettlement; - } - $daysBetweenIssueAndSettlement *= $daysPerYear; - $daysBetweenIssueAndMaturity = DateTime::YEARFRAC($issue, $maturity, $basis); - if (!is_numeric($daysBetweenIssueAndMaturity)) { - // return date error - return $daysBetweenIssueAndMaturity; - } - $daysBetweenIssueAndMaturity *= $daysPerYear; - $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis); - if (!is_numeric($daysBetweenSettlementAndMaturity)) { - // return date error - return $daysBetweenSettlementAndMaturity; - } - $daysBetweenSettlementAndMaturity *= $daysPerYear; - - return (100 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate * 100)) / - (1 + (($daysBetweenSettlementAndMaturity / $daysPerYear) * $yield)) - - (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate * 100); - } - - return Functions::VALUE(); + return Securities\Price::priceAtMaturity($settlement, $maturity, $issue, $rate, $yield, $basis); } /** @@ -1807,33 +1056,21 @@ class Financial * * Returns the Present Value of a cash flow with constant payments and interest rate (annuities). * + * @deprecated 1.18.0 + * Use the presentValue() method in the Financial\CashFlow\Constant\Periodic class instead + * @see Financial\CashFlow\Constant\Periodic::presentValue() + * * @param float $rate Interest rate per period * @param int $nper Number of periods * @param float $pmt Periodic payment (annuity) * @param float $fv Future Value * @param int $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period * - * @return float + * @return float|string Result, or a string containing an error */ public static function PV($rate = 0, $nper = 0, $pmt = 0, $fv = 0, $type = 0) { - $rate = Functions::flattenSingleValue($rate); - $nper = Functions::flattenSingleValue($nper); - $pmt = Functions::flattenSingleValue($pmt); - $fv = Functions::flattenSingleValue($fv); - $type = Functions::flattenSingleValue($type); - - // Validate parameters - if ($type != 0 && $type != 1) { - return Functions::NAN(); - } - - // Calculate - if ($rate !== null && $rate != 0) { - return (-$pmt * (1 + $rate * $type) * ((pow(1 + $rate, $nper) - 1) / $rate) - $fv) / pow(1 + $rate, $nper); - } - - return -$fv - $pmt * $nper; + return Financial\CashFlow\Constant\Periodic::presentValue($rate, $nper, $pmt, $fv, $type); } /** @@ -1847,113 +1084,61 @@ class Financial * Excel Function: * RATE(nper,pmt,pv[,fv[,type[,guess]]]) * - * @category Financial Functions + * @deprecated 1.18.0 + * Use the rate() method in the Financial\CashFlow\Constant\Periodic\Interest class instead + * @see Financial\CashFlow\Constant\Periodic\Interest::rate() * - * @param float $nper The total number of payment periods in an annuity - * @param float $pmt The payment made each period and cannot change over the life + * @param mixed $nper The total number of payment periods in an annuity + * @param mixed $pmt The payment made each period and cannot change over the life * of the annuity. * Typically, pmt includes principal and interest but no other * fees or taxes. - * @param float $pv The present value - the total amount that a series of future + * @param mixed $pv The present value - the total amount that a series of future * payments is worth now - * @param float $fv The future value, or a cash balance you want to attain after + * @param mixed $fv The future value, or a cash balance you want to attain after * the last payment is made. If fv is omitted, it is assumed * to be 0 (the future value of a loan, for example, is 0). - * @param int $type A number 0 or 1 and indicates when payments are due: + * @param mixed $type A number 0 or 1 and indicates when payments are due: * 0 or omitted At the end of the period. * 1 At the beginning of the period. - * @param float $guess Your guess for what the rate will be. + * @param mixed $guess Your guess for what the rate will be. * If you omit guess, it is assumed to be 10 percent. * - * @return float + * @return float|string */ public static function RATE($nper, $pmt, $pv, $fv = 0.0, $type = 0, $guess = 0.1) { - $nper = (int) Functions::flattenSingleValue($nper); - $pmt = Functions::flattenSingleValue($pmt); - $pv = Functions::flattenSingleValue($pv); - $fv = ($fv === null) ? 0.0 : Functions::flattenSingleValue($fv); - $type = ($type === null) ? 0 : (int) Functions::flattenSingleValue($type); - $guess = ($guess === null) ? 0.1 : Functions::flattenSingleValue($guess); - - $rate = $guess; - if (abs($rate) < self::FINANCIAL_PRECISION) { - $y = $pv * (1 + $nper * $rate) + $pmt * (1 + $rate * $type) * $nper + $fv; - } else { - $f = exp($nper * log(1 + $rate)); - $y = $pv * $f + $pmt * (1 / $rate + $type) * ($f - 1) + $fv; - } - $y0 = $pv + $pmt * $nper + $fv; - $y1 = $pv * $f + $pmt * (1 / $rate + $type) * ($f - 1) + $fv; - - // find root by secant method - $i = $x0 = 0.0; - $x1 = $rate; - while ((abs($y0 - $y1) > self::FINANCIAL_PRECISION) && ($i < self::FINANCIAL_MAX_ITERATIONS)) { - $rate = ($y1 * $x0 - $y0 * $x1) / ($y1 - $y0); - $x0 = $x1; - $x1 = $rate; - if (($nper * abs($pmt)) > ($pv - $fv)) { - $x1 = abs($x1); - } - if (abs($rate) < self::FINANCIAL_PRECISION) { - $y = $pv * (1 + $nper * $rate) + $pmt * (1 + $rate * $type) * $nper + $fv; - } else { - $f = exp($nper * log(1 + $rate)); - $y = $pv * $f + $pmt * (1 / $rate + $type) * ($f - 1) + $fv; - } - - $y0 = $y1; - $y1 = $y; - ++$i; - } - - return $rate; + return Financial\CashFlow\Constant\Periodic\Interest::rate($nper, $pmt, $pv, $fv, $type, $guess); } /** * RECEIVED. * - * Returns the price per $100 face value of a discounted security. + * Returns the amount received at maturity for a fully invested Security. + * + * @deprecated 1.18.0 + * Use the received() method in the Financial\Securities\Price class instead + * @see Financial\Securities\Price::received() * * @param mixed $settlement The security's settlement date. - * The security settlement date is the date after the issue date when the security is traded to the buyer. + * The security settlement date is the date after the issue date when the security + * is traded to the buyer. * @param mixed $maturity The security's maturity date. - * The maturity date is the date when the security expires. - * @param int $investment The amount invested in the security - * @param int $discount The security's discount rate - * @param int $basis The type of day count to use. - * 0 or omitted US (NASD) 30/360 - * 1 Actual/actual - * 2 Actual/360 - * 3 Actual/365 - * 4 European 30/360 + * The maturity date is the date when the security expires. + * @param mixed $investment The amount invested in the security + * @param mixed $discount The security's discount rate + * @param mixed $basis The type of day count to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 * - * @return float + * @return float|string Result, or a string containing an error */ public static function RECEIVED($settlement, $maturity, $investment, $discount, $basis = 0) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $investment = (float) Functions::flattenSingleValue($investment); - $discount = (float) Functions::flattenSingleValue($discount); - $basis = (int) Functions::flattenSingleValue($basis); - - // Validate - if ((is_numeric($investment)) && (is_numeric($discount)) && (is_numeric($basis))) { - if (($investment <= 0) || ($discount <= 0)) { - return Functions::NAN(); - } - $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis); - if (!is_numeric($daysBetweenSettlementAndMaturity)) { - // return date error - return $daysBetweenSettlementAndMaturity; - } - - return $investment / (1 - ($discount * $daysBetweenSettlementAndMaturity)); - } - - return Functions::VALUE(); + return Financial\Securities\Price::received($settlement, $maturity, $investment, $discount, $basis); } /** @@ -1961,26 +1146,19 @@ class Financial * * Calculates the interest rate required for an investment to grow to a specified future value . * + * @deprecated 1.18.0 + * Use the interestRate() method in the Financial\CashFlow\Single class instead + * @see Financial\CashFlow\Single::interestRate() + * * @param float $nper The number of periods over which the investment is made * @param float $pv Present Value * @param float $fv Future Value * - * @return float|string + * @return float|string Result, or a string containing an error */ public static function RRI($nper = 0, $pv = 0, $fv = 0) { - $nper = Functions::flattenSingleValue($nper); - $pv = Functions::flattenSingleValue($pv); - $fv = Functions::flattenSingleValue($fv); - - // Validate parameters - if (!is_numeric($nper) || !is_numeric($pv) || !is_numeric($fv)) { - return Functions::VALUE(); - } elseif ($nper <= 0.0 || $pv <= 0.0 || $fv < 0.0) { - return Functions::NAN(); - } - - return pow($fv / $pv, 1 / $nper) - 1; + return Financial\CashFlow\Single::interestRate($nper, $pv, $fv); } /** @@ -1988,28 +1166,19 @@ class Financial * * Returns the straight-line depreciation of an asset for one period * + * @deprecated 1.18.0 + * Use the SLN() method in the Financial\Depreciation class instead + * @see Financial\Depreciation::SLN() + * * @param mixed $cost Initial cost of the asset * @param mixed $salvage Value at the end of the depreciation * @param mixed $life Number of periods over which the asset is depreciated * - * @return float|string + * @return float|string Result, or a string containing an error */ public static function SLN($cost, $salvage, $life) { - $cost = Functions::flattenSingleValue($cost); - $salvage = Functions::flattenSingleValue($salvage); - $life = Functions::flattenSingleValue($life); - - // Calculate - if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life))) { - if ($life < 0) { - return Functions::NAN(); - } - - return ($cost - $salvage) / $life; - } - - return Functions::VALUE(); + return Depreciation::SLN($cost, $salvage, $life); } /** @@ -2017,30 +1186,20 @@ class Financial * * Returns the sum-of-years' digits depreciation of an asset for a specified period. * + * @deprecated 1.18.0 + * Use the SYD() method in the Financial\Depreciation class instead + * @see Financial\Depreciation::SYD() + * * @param mixed $cost Initial cost of the asset * @param mixed $salvage Value at the end of the depreciation * @param mixed $life Number of periods over which the asset is depreciated * @param mixed $period Period * - * @return float|string + * @return float|string Result, or a string containing an error */ public static function SYD($cost, $salvage, $life, $period) { - $cost = Functions::flattenSingleValue($cost); - $salvage = Functions::flattenSingleValue($salvage); - $life = Functions::flattenSingleValue($life); - $period = Functions::flattenSingleValue($period); - - // Calculate - if ((is_numeric($cost)) && (is_numeric($salvage)) && (is_numeric($life)) && (is_numeric($period))) { - if (($life < 1) || ($period > $life)) { - return Functions::NAN(); - } - - return (($cost - $salvage) * ($life - $period + 1) * 2) / ($life * ($life + 1)); - } - - return Functions::VALUE(); + return Depreciation::SYD($cost, $salvage, $life, $period); } /** @@ -2048,93 +1207,45 @@ class Financial * * Returns the bond-equivalent yield for a Treasury bill. * + * @deprecated 1.18.0 + * Use the bondEquivalentYield() method in the Financial\TreasuryBill class instead + * @see Financial\TreasuryBill::bondEquivalentYield() + * * @param mixed $settlement The Treasury bill's settlement date. - * The Treasury bill's settlement date is the date after the issue date when the Treasury bill is traded to the buyer. + * The Treasury bill's settlement date is the date after the issue date when the + * Treasury bill is traded to the buyer. * @param mixed $maturity The Treasury bill's maturity date. * The maturity date is the date when the Treasury bill expires. * @param int $discount The Treasury bill's discount rate * - * @return float + * @return float|string Result, or a string containing an error */ public static function TBILLEQ($settlement, $maturity, $discount) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $discount = Functions::flattenSingleValue($discount); - - // Use TBILLPRICE for validation - $testValue = self::TBILLPRICE($settlement, $maturity, $discount); - if (is_string($testValue)) { - return $testValue; - } - - if (is_string($maturity = DateTime::getDateValue($maturity))) { - return Functions::VALUE(); - } - - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) { - ++$maturity; - $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity) * 360; - } else { - $daysBetweenSettlementAndMaturity = (DateTime::getDateValue($maturity) - DateTime::getDateValue($settlement)); - } - - return (365 * $discount) / (360 - $discount * $daysBetweenSettlementAndMaturity); + return TreasuryBill::bondEquivalentYield($settlement, $maturity, $discount); } /** * TBILLPRICE. * - * Returns the yield for a Treasury bill. + * Returns the price per $100 face value for a Treasury bill. + * + * @deprecated 1.18.0 + * Use the price() method in the Financial\TreasuryBill class instead + * @see Financial\TreasuryBill::price() * * @param mixed $settlement The Treasury bill's settlement date. - * The Treasury bill's settlement date is the date after the issue date when the Treasury bill is traded to the buyer. + * The Treasury bill's settlement date is the date after the issue date + * when the Treasury bill is traded to the buyer. * @param mixed $maturity The Treasury bill's maturity date. * The maturity date is the date when the Treasury bill expires. * @param int $discount The Treasury bill's discount rate * - * @return float + * @return float|string Result, or a string containing an error */ public static function TBILLPRICE($settlement, $maturity, $discount) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $discount = Functions::flattenSingleValue($discount); - - if (is_string($maturity = DateTime::getDateValue($maturity))) { - return Functions::VALUE(); - } - - // Validate - if (is_numeric($discount)) { - if ($discount <= 0) { - return Functions::NAN(); - } - - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) { - ++$maturity; - $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity) * 360; - if (!is_numeric($daysBetweenSettlementAndMaturity)) { - // return date error - return $daysBetweenSettlementAndMaturity; - } - } else { - $daysBetweenSettlementAndMaturity = (DateTime::getDateValue($maturity) - DateTime::getDateValue($settlement)); - } - - if ($daysBetweenSettlementAndMaturity > 360) { - return Functions::NAN(); - } - - $price = 100 * (1 - (($discount * $daysBetweenSettlementAndMaturity) / 360)); - if ($price <= 0) { - return Functions::NAN(); - } - - return $price; - } - - return Functions::VALUE(); + return TreasuryBill::price($settlement, $maturity, $discount); } /** @@ -2142,99 +1253,48 @@ class Financial * * Returns the yield for a Treasury bill. * + * @deprecated 1.18.0 + * Use the yield() method in the Financial\TreasuryBill class instead + * @see Financial\TreasuryBill::yield() + * * @param mixed $settlement The Treasury bill's settlement date. - * The Treasury bill's settlement date is the date after the issue date when the Treasury bill is traded to the buyer. + * The Treasury bill's settlement date is the date after the issue date + * when the Treasury bill is traded to the buyer. * @param mixed $maturity The Treasury bill's maturity date. * The maturity date is the date when the Treasury bill expires. * @param int $price The Treasury bill's price per $100 face value * - * @return float + * @return float|mixed|string */ public static function TBILLYIELD($settlement, $maturity, $price) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $price = Functions::flattenSingleValue($price); - - // Validate - if (is_numeric($price)) { - if ($price <= 0) { - return Functions::NAN(); - } - - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) { - ++$maturity; - $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity) * 360; - if (!is_numeric($daysBetweenSettlementAndMaturity)) { - // return date error - return $daysBetweenSettlementAndMaturity; - } - } else { - $daysBetweenSettlementAndMaturity = (DateTime::getDateValue($maturity) - DateTime::getDateValue($settlement)); - } - - if ($daysBetweenSettlementAndMaturity > 360) { - return Functions::NAN(); - } - - return ((100 - $price) / $price) * (360 / $daysBetweenSettlementAndMaturity); - } - - return Functions::VALUE(); + return TreasuryBill::yield($settlement, $maturity, $price); } + /** + * XIRR. + * + * Returns the internal rate of return for a schedule of cash flows that is not necessarily periodic. + * + * Excel Function: + * =XIRR(values,dates,guess) + * + * @deprecated 1.18.0 + * Use the rate() method in the Financial\CashFlow\Variable\NonPeriodic class instead + * @see Financial\CashFlow\Variable\NonPeriodic::rate() + * + * @param float[] $values A series of cash flow payments + * The series of values must contain at least one positive value & one negative value + * @param mixed[] $dates A series of payment dates + * The first payment date indicates the beginning of the schedule of payments + * All other dates must be later than this date, but they may occur in any order + * @param float $guess An optional guess at the expected answer + * + * @return float|mixed|string + */ public static function XIRR($values, $dates, $guess = 0.1) { - if ((!is_array($values)) && (!is_array($dates))) { - return Functions::VALUE(); - } - $values = Functions::flattenArray($values); - $dates = Functions::flattenArray($dates); - $guess = Functions::flattenSingleValue($guess); - if (count($values) != count($dates)) { - return Functions::NAN(); - } - - // create an initial range, with a root somewhere between 0 and guess - $x1 = 0.0; - $x2 = $guess; - $f1 = self::XNPV($x1, $values, $dates); - $f2 = self::XNPV($x2, $values, $dates); - for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) { - if (($f1 * $f2) < 0.0) { - break; - } elseif (abs($f1) < abs($f2)) { - $f1 = self::XNPV($x1 += 1.6 * ($x1 - $x2), $values, $dates); - } else { - $f2 = self::XNPV($x2 += 1.6 * ($x2 - $x1), $values, $dates); - } - } - if (($f1 * $f2) > 0.0) { - return Functions::VALUE(); - } - - $f = self::XNPV($x1, $values, $dates); - if ($f < 0.0) { - $rtb = $x1; - $dx = $x2 - $x1; - } else { - $rtb = $x2; - $dx = $x1 - $x2; - } - - for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) { - $dx *= 0.5; - $x_mid = $rtb + $dx; - $f_mid = self::XNPV($x_mid, $values, $dates); - if ($f_mid <= 0.0) { - $rtb = $x_mid; - } - if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) { - return $x_mid; - } - } - - return Functions::VALUE(); + return Financial\CashFlow\Variable\NonPeriodic::rate($values, $dates, $guess); } /** @@ -2246,45 +1306,26 @@ class Financial * Excel Function: * =XNPV(rate,values,dates) * - * @param float $rate the discount rate to apply to the cash flows - * @param array of float $values A series of cash flows that corresponds to a schedule of payments in dates. - * The first payment is optional and corresponds to a cost or payment that occurs at the beginning of the investment. - * If the first value is a cost or payment, it must be a negative value. All succeeding payments are discounted based on a 365-day year. - * The series of values must contain at least one positive value and one negative value. - * @param array of mixed $dates A schedule of payment dates that corresponds to the cash flow payments. - * The first payment date indicates the beginning of the schedule of payments. - * All other dates must be later than this date, but they may occur in any order. + * @deprecated 1.18.0 + * Use the presentValue() method in the Financial\CashFlow\Variable\NonPeriodic class instead + * @see Financial\CashFlow\Variable\NonPeriodic::presentValue() * - * @return float + * @param float $rate the discount rate to apply to the cash flows + * @param float[] $values A series of cash flows that corresponds to a schedule of payments in dates. + * The first payment is optional and corresponds to a cost or payment that occurs + * at the beginning of the investment. + * If the first value is a cost or payment, it must be a negative value. + * All succeeding payments are discounted based on a 365-day year. + * The series of values must contain at least one positive value and one negative value. + * @param mixed[] $dates A schedule of payment dates that corresponds to the cash flow payments. + * The first payment date indicates the beginning of the schedule of payments. + * All other dates must be later than this date, but they may occur in any order. + * + * @return float|mixed|string */ public static function XNPV($rate, $values, $dates) { - $rate = Functions::flattenSingleValue($rate); - if (!is_numeric($rate)) { - return Functions::VALUE(); - } - if ((!is_array($values)) || (!is_array($dates))) { - return Functions::VALUE(); - } - $values = Functions::flattenArray($values); - $dates = Functions::flattenArray($dates); - $valCount = count($values); - if ($valCount != count($dates)) { - return Functions::NAN(); - } - if ((min($values) > 0) || (max($values) < 0)) { - return Functions::VALUE(); - } - - $xnpv = 0.0; - for ($i = 0; $i < $valCount; ++$i) { - if (!is_numeric($values[$i])) { - return Functions::VALUE(); - } - $xnpv += $values[$i] / pow(1 + $rate, DateTime::DATEDIF($dates[0], $dates[$i], 'd') / 365); - } - - return (is_finite($xnpv)) ? $xnpv : Functions::VALUE(); + return Financial\CashFlow\Variable\NonPeriodic::presentValue($rate, $values, $dates); } /** @@ -2292,10 +1333,15 @@ class Financial * * Returns the annual yield of a security that pays interest at maturity. * + * @deprecated 1.18.0 + * Use the yieldDiscounted() method in the Financial\Securities\Yields class instead + * @see Financial\Securities\Yields::yieldDiscounted() + * * @param mixed $settlement The security's settlement date. - * The security's settlement date is the date after the issue date when the security is traded to the buyer. + * The security's settlement date is the date after the issue date when the security + * is traded to the buyer. * @param mixed $maturity The security's maturity date. - * The maturity date is the date when the security expires. + * The maturity date is the date when the security expires. * @param int $price The security's price per $100 face value * @param int $redemption The security's redemption value per $100 face value * @param int $basis The type of day count to use. @@ -2305,36 +1351,11 @@ class Financial * 3 Actual/365 * 4 European 30/360 * - * @return float + * @return float|string Result, or a string containing an error */ public static function YIELDDISC($settlement, $maturity, $price, $redemption, $basis = 0) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $price = Functions::flattenSingleValue($price); - $redemption = Functions::flattenSingleValue($redemption); - $basis = (int) Functions::flattenSingleValue($basis); - - // Validate - if (is_numeric($price) && is_numeric($redemption)) { - if (($price <= 0) || ($redemption <= 0)) { - return Functions::NAN(); - } - $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis); - if (!is_numeric($daysPerYear)) { - return $daysPerYear; - } - $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis); - if (!is_numeric($daysBetweenSettlementAndMaturity)) { - // return date error - return $daysBetweenSettlementAndMaturity; - } - $daysBetweenSettlementAndMaturity *= $daysPerYear; - - return (($redemption - $price) / $price) * ($daysPerYear / $daysBetweenSettlementAndMaturity); - } - - return Functions::VALUE(); + return Securities\Yields::yieldDiscounted($settlement, $maturity, $price, $redemption, $basis); } /** @@ -2342,64 +1363,29 @@ class Financial * * Returns the annual yield of a security that pays interest at maturity. * + * @deprecated 1.18.0 + * Use the yieldAtMaturity() method in the Financial\Securities\Yields class instead + * @see Financial\Securities\Yields::yieldAtMaturity() + * * @param mixed $settlement The security's settlement date. - * The security's settlement date is the date after the issue date when the security is traded to the buyer. + * The security's settlement date is the date after the issue date when the security + * is traded to the buyer. * @param mixed $maturity The security's maturity date. - * The maturity date is the date when the security expires. + * The maturity date is the date when the security expires. * @param mixed $issue The security's issue date * @param int $rate The security's interest rate at date of issue * @param int $price The security's price per $100 face value * @param int $basis The type of day count to use. - * 0 or omitted US (NASD) 30/360 - * 1 Actual/actual - * 2 Actual/360 - * 3 Actual/365 - * 4 European 30/360 + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 * - * @return float + * @return float|string Result, or a string containing an error */ public static function YIELDMAT($settlement, $maturity, $issue, $rate, $price, $basis = 0) { - $settlement = Functions::flattenSingleValue($settlement); - $maturity = Functions::flattenSingleValue($maturity); - $issue = Functions::flattenSingleValue($issue); - $rate = Functions::flattenSingleValue($rate); - $price = Functions::flattenSingleValue($price); - $basis = (int) Functions::flattenSingleValue($basis); - - // Validate - if (is_numeric($rate) && is_numeric($price)) { - if (($rate <= 0) || ($price <= 0)) { - return Functions::NAN(); - } - $daysPerYear = self::daysPerYear(DateTime::YEAR($settlement), $basis); - if (!is_numeric($daysPerYear)) { - return $daysPerYear; - } - $daysBetweenIssueAndSettlement = DateTime::YEARFRAC($issue, $settlement, $basis); - if (!is_numeric($daysBetweenIssueAndSettlement)) { - // return date error - return $daysBetweenIssueAndSettlement; - } - $daysBetweenIssueAndSettlement *= $daysPerYear; - $daysBetweenIssueAndMaturity = DateTime::YEARFRAC($issue, $maturity, $basis); - if (!is_numeric($daysBetweenIssueAndMaturity)) { - // return date error - return $daysBetweenIssueAndMaturity; - } - $daysBetweenIssueAndMaturity *= $daysPerYear; - $daysBetweenSettlementAndMaturity = DateTime::YEARFRAC($settlement, $maturity, $basis); - if (!is_numeric($daysBetweenSettlementAndMaturity)) { - // return date error - return $daysBetweenSettlementAndMaturity; - } - $daysBetweenSettlementAndMaturity *= $daysPerYear; - - return ((1 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate) - (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate))) / - (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate))) * - ($daysPerYear / $daysBetweenSettlementAndMaturity); - } - - return Functions::VALUE(); + return Securities\Yields::yieldAtMaturity($settlement, $maturity, $issue, $rate, $price, $basis); } } diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Financial/Amortization.php b/PhpOffice/PhpSpreadsheet/Calculation/Financial/Amortization.php new file mode 100644 index 0000000..691ba40 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Financial/Amortization.php @@ -0,0 +1,214 @@ +getMessage(); + } + + $yearFracx = DateTimeExcel\YearFrac::fraction($purchased, $firstPeriod, $basis); + if (is_string($yearFracx)) { + return $yearFracx; + } + /** @var float */ + $yearFrac = $yearFracx; + + $amortiseCoeff = self::getAmortizationCoefficient($rate); + + $rate *= $amortiseCoeff; + $fNRate = round($yearFrac * $rate * $cost, 0); + $cost -= $fNRate; + $fRest = $cost - $salvage; + + for ($n = 0; $n < $period; ++$n) { + $fNRate = round($rate * $cost, 0); + $fRest -= $fNRate; + + if ($fRest < 0.0) { + switch ($period - $n) { + case 0: + case 1: + return round($cost * 0.5, 0); + default: + return 0.0; + } + } + $cost -= $fNRate; + } + + return $fNRate; + } + + /** + * AMORLINC. + * + * Returns the depreciation for each accounting period. + * This function is provided for the French accounting system. If an asset is purchased in + * the middle of the accounting period, the prorated depreciation is taken into account. + * + * Excel Function: + * AMORLINC(cost,purchased,firstPeriod,salvage,period,rate[,basis]) + * + * @param mixed $cost The cost of the asset as a float + * @param mixed $purchased Date of the purchase of the asset + * @param mixed $firstPeriod Date of the end of the first period + * @param mixed $salvage The salvage value at the end of the life of the asset + * @param mixed $period The period as a float + * @param mixed $rate Rate of depreciation as float + * @param mixed $basis Integer indicating the type of day count to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string (string containing the error type if there is an error) + */ + public static function AMORLINC( + $cost, + $purchased, + $firstPeriod, + $salvage, + $period, + $rate, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $cost = Functions::flattenSingleValue($cost); + $purchased = Functions::flattenSingleValue($purchased); + $firstPeriod = Functions::flattenSingleValue($firstPeriod); + $salvage = Functions::flattenSingleValue($salvage); + $period = Functions::flattenSingleValue($period); + $rate = Functions::flattenSingleValue($rate); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $cost = FinancialValidations::validateFloat($cost); + $purchased = FinancialValidations::validateDate($purchased); + $firstPeriod = FinancialValidations::validateDate($firstPeriod); + $salvage = FinancialValidations::validateFloat($salvage); + $period = FinancialValidations::validateFloat($period); + $rate = FinancialValidations::validateFloat($rate); + $basis = FinancialValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + $fOneRate = $cost * $rate; + $fCostDelta = $cost - $salvage; + // Note, quirky variation for leap years on the YEARFRAC for this function + $purchasedYear = DateTimeExcel\DateParts::year($purchased); + $yearFracx = DateTimeExcel\YearFrac::fraction($purchased, $firstPeriod, $basis); + if (is_string($yearFracx)) { + return $yearFracx; + } + /** @var float */ + $yearFrac = $yearFracx; + + if ( + ($basis == FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL) && + ($yearFrac < 1) && (Functions::scalar(DateTimeExcel\Helpers::isLeapYear($purchasedYear))) + ) { + $yearFrac *= 365 / 366; + } + + $f0Rate = $yearFrac * $rate * $cost; + $nNumOfFullPeriods = (int) (($cost - $salvage - $f0Rate) / $fOneRate); + + if ($period == 0) { + return $f0Rate; + } elseif ($period <= $nNumOfFullPeriods) { + return $fOneRate; + } elseif ($period == ($nNumOfFullPeriods + 1)) { + return $fCostDelta - $fOneRate * $nNumOfFullPeriods - $f0Rate; + } + + return 0.0; + } + + private static function getAmortizationCoefficient(float $rate): float + { + // The depreciation coefficients are: + // Life of assets (1/rate) Depreciation coefficient + // Less than 3 years 1 + // Between 3 and 4 years 1.5 + // Between 5 and 6 years 2 + // More than 6 years 2.5 + $fUsePer = 1.0 / $rate; + + if ($fUsePer < 3.0) { + return 1.0; + } elseif ($fUsePer < 4.0) { + return 1.5; + } elseif ($fUsePer <= 6.0) { + return 2.0; + } + + return 2.5; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/CashFlowValidations.php b/PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/CashFlowValidations.php new file mode 100644 index 0000000..8ebe9ed --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/CashFlowValidations.php @@ -0,0 +1,53 @@ +getMessage(); + } + + return self::calculateFutureValue($rate, $numberOfPeriods, $payment, $presentValue, $type); + } + + /** + * PV. + * + * Returns the Present Value of a cash flow with constant payments and interest rate (annuities). + * + * @param mixed $rate Interest rate per period + * @param mixed $numberOfPeriods Number of periods as an integer + * @param mixed $payment Periodic payment (annuity) + * @param mixed $futureValue Future Value + * @param mixed $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period + * + * @return float|string Result, or a string containing an error + */ + public static function presentValue( + $rate, + $numberOfPeriods, + $payment = 0.0, + $futureValue = 0.0, + $type = FinancialConstants::PAYMENT_END_OF_PERIOD + ) { + $rate = Functions::flattenSingleValue($rate); + $numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods); + $payment = ($payment === null) ? 0.0 : Functions::flattenSingleValue($payment); + $futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue); + $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type); + + try { + $rate = CashFlowValidations::validateRate($rate); + $numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods); + $payment = CashFlowValidations::validateFloat($payment); + $futureValue = CashFlowValidations::validateFutureValue($futureValue); + $type = CashFlowValidations::validatePeriodType($type); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Validate parameters + if ($numberOfPeriods < 0) { + return ExcelError::NAN(); + } + + return self::calculatePresentValue($rate, $numberOfPeriods, $payment, $futureValue, $type); + } + + /** + * NPER. + * + * Returns the number of periods for a cash flow with constant periodic payments (annuities), and interest rate. + * + * @param mixed $rate Interest rate per period + * @param mixed $payment Periodic payment (annuity) + * @param mixed $presentValue Present Value + * @param mixed $futureValue Future Value + * @param mixed $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period + * + * @return float|string Result, or a string containing an error + */ + public static function periods( + $rate, + $payment, + $presentValue, + $futureValue = 0.0, + $type = FinancialConstants::PAYMENT_END_OF_PERIOD + ) { + $rate = Functions::flattenSingleValue($rate); + $payment = Functions::flattenSingleValue($payment); + $presentValue = Functions::flattenSingleValue($presentValue); + $futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue); + $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type); + + try { + $rate = CashFlowValidations::validateRate($rate); + $payment = CashFlowValidations::validateFloat($payment); + $presentValue = CashFlowValidations::validatePresentValue($presentValue); + $futureValue = CashFlowValidations::validateFutureValue($futureValue); + $type = CashFlowValidations::validatePeriodType($type); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Validate parameters + if ($payment == 0.0) { + return ExcelError::NAN(); + } + + return self::calculatePeriods($rate, $payment, $presentValue, $futureValue, $type); + } + + private static function calculateFutureValue( + float $rate, + int $numberOfPeriods, + float $payment, + float $presentValue, + int $type + ): float { + if ($rate !== null && $rate != 0) { + return -$presentValue * + (1 + $rate) ** $numberOfPeriods - $payment * (1 + $rate * $type) * ((1 + $rate) ** $numberOfPeriods - 1) + / $rate; + } + + return -$presentValue - $payment * $numberOfPeriods; + } + + private static function calculatePresentValue( + float $rate, + int $numberOfPeriods, + float $payment, + float $futureValue, + int $type + ): float { + if ($rate != 0.0) { + return (-$payment * (1 + $rate * $type) + * (((1 + $rate) ** $numberOfPeriods - 1) / $rate) - $futureValue) / (1 + $rate) ** $numberOfPeriods; + } + + return -$futureValue - $payment * $numberOfPeriods; + } + + /** + * @return float|string + */ + private static function calculatePeriods( + float $rate, + float $payment, + float $presentValue, + float $futureValue, + int $type + ) { + if ($rate != 0.0) { + if ($presentValue == 0.0) { + return ExcelError::NAN(); + } + + return log(($payment * (1 + $rate * $type) / $rate - $futureValue) / + ($presentValue + $payment * (1 + $rate * $type) / $rate)) / log(1 + $rate); + } + + return (-$presentValue - $futureValue) / $payment; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Cumulative.php b/PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Cumulative.php new file mode 100644 index 0000000..b7aaffd --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Cumulative.php @@ -0,0 +1,142 @@ +getMessage(); + } + + // Validate parameters + if ($start < 1 || $start > $end) { + return ExcelError::NAN(); + } + + // Calculate + $interest = 0; + for ($per = $start; $per <= $end; ++$per) { + $ipmt = Interest::payment($rate, $per, $periods, $presentValue, 0, $type); + if (is_string($ipmt)) { + return $ipmt; + } + + $interest += $ipmt; + } + + return $interest; + } + + /** + * CUMPRINC. + * + * Returns the cumulative principal paid on a loan between the start and end periods. + * + * Excel Function: + * CUMPRINC(rate,nper,pv,start,end[,type]) + * + * @param mixed $rate The Interest rate + * @param mixed $periods The total number of payment periods as an integer + * @param mixed $presentValue Present Value + * @param mixed $start The first period in the calculation. + * Payment periods are numbered beginning with 1. + * @param mixed $end the last period in the calculation + * @param mixed $type A number 0 or 1 and indicates when payments are due: + * 0 or omitted At the end of the period. + * 1 At the beginning of the period. + * + * @return float|string + */ + public static function principal( + $rate, + $periods, + $presentValue, + $start, + $end, + $type = FinancialConstants::PAYMENT_END_OF_PERIOD + ) { + $rate = Functions::flattenSingleValue($rate); + $periods = Functions::flattenSingleValue($periods); + $presentValue = Functions::flattenSingleValue($presentValue); + $start = Functions::flattenSingleValue($start); + $end = Functions::flattenSingleValue($end); + $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type); + + try { + $rate = CashFlowValidations::validateRate($rate); + $periods = CashFlowValidations::validateInt($periods); + $presentValue = CashFlowValidations::validatePresentValue($presentValue); + $start = CashFlowValidations::validateInt($start); + $end = CashFlowValidations::validateInt($end); + $type = CashFlowValidations::validatePeriodType($type); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Validate parameters + if ($start < 1 || $start > $end) { + return ExcelError::VALUE(); + } + + // Calculate + $principal = 0; + for ($per = $start; $per <= $end; ++$per) { + $ppmt = Payments::interestPayment($rate, $per, $periods, $presentValue, 0, $type); + if (is_string($ppmt)) { + return $ppmt; + } + + $principal += $ppmt; + } + + return $principal; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php b/PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php new file mode 100644 index 0000000..4a82514 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php @@ -0,0 +1,219 @@ +getMessage(); + } + + // Validate parameters + if ($period <= 0 || $period > $numberOfPeriods) { + return ExcelError::NAN(); + } + + // Calculate + $interestAndPrincipal = new InterestAndPrincipal( + $interestRate, + $period, + $numberOfPeriods, + $presentValue, + $futureValue, + $type + ); + + return $interestAndPrincipal->interest(); + } + + /** + * ISPMT. + * + * Returns the interest payment for an investment based on an interest rate and a constant payment schedule. + * + * Excel Function: + * =ISPMT(interest_rate, period, number_payments, pv) + * + * @param mixed $interestRate is the interest rate for the investment + * @param mixed $period is the period to calculate the interest rate. It must be betweeen 1 and number_payments. + * @param mixed $numberOfPeriods is the number of payments for the annuity + * @param mixed $principleRemaining is the loan amount or present value of the payments + * + * @return float|string + */ + public static function schedulePayment($interestRate, $period, $numberOfPeriods, $principleRemaining) + { + $interestRate = Functions::flattenSingleValue($interestRate); + $period = Functions::flattenSingleValue($period); + $numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods); + $principleRemaining = Functions::flattenSingleValue($principleRemaining); + + try { + $interestRate = CashFlowValidations::validateRate($interestRate); + $period = CashFlowValidations::validateInt($period); + $numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods); + $principleRemaining = CashFlowValidations::validateFloat($principleRemaining); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Validate parameters + if ($period <= 0 || $period > $numberOfPeriods) { + return ExcelError::NAN(); + } + + // Return value + $returnValue = 0; + + // Calculate + $principlePayment = ($principleRemaining * 1.0) / ($numberOfPeriods * 1.0); + for ($i = 0; $i <= $period; ++$i) { + $returnValue = $interestRate * $principleRemaining * -1; + $principleRemaining -= $principlePayment; + // principle needs to be 0 after the last payment, don't let floating point screw it up + if ($i == $numberOfPeriods) { + $returnValue = 0.0; + } + } + + return $returnValue; + } + + /** + * RATE. + * + * Returns the interest rate per period of an annuity. + * RATE is calculated by iteration and can have zero or more solutions. + * If the successive results of RATE do not converge to within 0.0000001 after 20 iterations, + * RATE returns the #NUM! error value. + * + * Excel Function: + * RATE(nper,pmt,pv[,fv[,type[,guess]]]) + * + * @param mixed $numberOfPeriods The total number of payment periods in an annuity + * @param mixed $payment The payment made each period and cannot change over the life of the annuity. + * Typically, pmt includes principal and interest but no other fees or taxes. + * @param mixed $presentValue The present value - the total amount that a series of future payments is worth now + * @param mixed $futureValue The future value, or a cash balance you want to attain after the last payment is made. + * If fv is omitted, it is assumed to be 0 (the future value of a loan, + * for example, is 0). + * @param mixed $type A number 0 or 1 and indicates when payments are due: + * 0 or omitted At the end of the period. + * 1 At the beginning of the period. + * @param mixed $guess Your guess for what the rate will be. + * If you omit guess, it is assumed to be 10 percent. + * + * @return float|string + */ + public static function rate( + $numberOfPeriods, + $payment, + $presentValue, + $futureValue = 0.0, + $type = FinancialConstants::PAYMENT_END_OF_PERIOD, + $guess = 0.1 + ) { + $numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods); + $payment = Functions::flattenSingleValue($payment); + $presentValue = Functions::flattenSingleValue($presentValue); + $futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue); + $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type); + $guess = ($guess === null) ? 0.1 : Functions::flattenSingleValue($guess); + + try { + $numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods); + $payment = CashFlowValidations::validateFloat($payment); + $presentValue = CashFlowValidations::validatePresentValue($presentValue); + $futureValue = CashFlowValidations::validateFutureValue($futureValue); + $type = CashFlowValidations::validatePeriodType($type); + $guess = CashFlowValidations::validateFloat($guess); + } catch (Exception $e) { + return $e->getMessage(); + } + + $rate = $guess; + // rest of code adapted from python/numpy + $close = false; + $iter = 0; + while (!$close && $iter < self::FINANCIAL_MAX_ITERATIONS) { + $nextdiff = self::rateNextGuess($rate, $numberOfPeriods, $payment, $presentValue, $futureValue, $type); + if (!is_numeric($nextdiff)) { + break; + } + $rate1 = $rate - $nextdiff; + $close = abs($rate1 - $rate) < self::FINANCIAL_PRECISION; + ++$iter; + $rate = $rate1; + } + + return $close ? $rate : ExcelError::NAN(); + } + + private static function rateNextGuess($rate, $numberOfPeriods, $payment, $presentValue, $futureValue, $type) + { + if ($rate == 0.0) { + return ExcelError::NAN(); + } + $tt1 = ($rate + 1) ** $numberOfPeriods; + $tt2 = ($rate + 1) ** ($numberOfPeriods - 1); + $numerator = $futureValue + $tt1 * $presentValue + $payment * ($tt1 - 1) * ($rate * $type + 1) / $rate; + $denominator = $numberOfPeriods * $tt2 * $presentValue - $payment * ($tt1 - 1) + * ($rate * $type + 1) / ($rate * $rate) + $numberOfPeriods + * $payment * $tt2 * ($rate * $type + 1) / $rate + $payment * ($tt1 - 1) * $type / $rate; + if ($denominator == 0) { + return ExcelError::NAN(); + } + + return $numerator / $denominator; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/InterestAndPrincipal.php b/PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/InterestAndPrincipal.php new file mode 100644 index 0000000..ca989e0 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/InterestAndPrincipal.php @@ -0,0 +1,44 @@ +interest = $interest; + $this->principal = $principal; + } + + public function interest(): float + { + return $this->interest; + } + + public function principal(): float + { + return $this->principal; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Payments.php b/PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Payments.php new file mode 100644 index 0000000..83965f9 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Payments.php @@ -0,0 +1,116 @@ +getMessage(); + } + + // Calculate + if ($interestRate != 0.0) { + return (-$futureValue - $presentValue * (1 + $interestRate) ** $numberOfPeriods) / + (1 + $interestRate * $type) / (((1 + $interestRate) ** $numberOfPeriods - 1) / $interestRate); + } + + return (-$presentValue - $futureValue) / $numberOfPeriods; + } + + /** + * PPMT. + * + * Returns the interest payment for a given period for an investment based on periodic, constant payments + * and a constant interest rate. + * + * @param mixed $interestRate Interest rate per period + * @param mixed $period Period for which we want to find the interest + * @param mixed $numberOfPeriods Number of periods + * @param mixed $presentValue Present Value + * @param mixed $futureValue Future Value + * @param mixed $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period + * + * @return float|string Result, or a string containing an error + */ + public static function interestPayment( + $interestRate, + $period, + $numberOfPeriods, + $presentValue, + $futureValue = 0, + $type = FinancialConstants::PAYMENT_END_OF_PERIOD + ) { + $interestRate = Functions::flattenSingleValue($interestRate); + $period = Functions::flattenSingleValue($period); + $numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods); + $presentValue = Functions::flattenSingleValue($presentValue); + $futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue); + $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type); + + try { + $interestRate = CashFlowValidations::validateRate($interestRate); + $period = CashFlowValidations::validateInt($period); + $numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods); + $presentValue = CashFlowValidations::validatePresentValue($presentValue); + $futureValue = CashFlowValidations::validateFutureValue($futureValue); + $type = CashFlowValidations::validatePeriodType($type); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Validate parameters + if ($period <= 0 || $period > $numberOfPeriods) { + return ExcelError::NAN(); + } + + // Calculate + $interestAndPrincipal = new InterestAndPrincipal( + $interestRate, + $period, + $numberOfPeriods, + $presentValue, + $futureValue, + $type + ); + + return $interestAndPrincipal->principal(); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/Single.php b/PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/Single.php new file mode 100644 index 0000000..058e89c --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/Single.php @@ -0,0 +1,109 @@ +getMessage(); + } + + return $principal; + } + + /** + * PDURATION. + * + * Calculates the number of periods required for an investment to reach a specified value. + * + * @param mixed $rate Interest rate per period + * @param mixed $presentValue Present Value + * @param mixed $futureValue Future Value + * + * @return float|string Result, or a string containing an error + */ + public static function periods($rate, $presentValue, $futureValue) + { + $rate = Functions::flattenSingleValue($rate); + $presentValue = Functions::flattenSingleValue($presentValue); + $futureValue = Functions::flattenSingleValue($futureValue); + + try { + $rate = CashFlowValidations::validateRate($rate); + $presentValue = CashFlowValidations::validatePresentValue($presentValue); + $futureValue = CashFlowValidations::validateFutureValue($futureValue); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Validate parameters + if ($rate <= 0.0 || $presentValue <= 0.0 || $futureValue <= 0.0) { + return ExcelError::NAN(); + } + + return (log($futureValue) - log($presentValue)) / log(1 + $rate); + } + + /** + * RRI. + * + * Calculates the interest rate required for an investment to grow to a specified future value . + * + * @param float $periods The number of periods over which the investment is made + * @param float $presentValue Present Value + * @param float $futureValue Future Value + * + * @return float|string Result, or a string containing an error + */ + public static function interestRate($periods = 0.0, $presentValue = 0.0, $futureValue = 0.0) + { + $periods = Functions::flattenSingleValue($periods); + $presentValue = Functions::flattenSingleValue($presentValue); + $futureValue = Functions::flattenSingleValue($futureValue); + + try { + $periods = CashFlowValidations::validateFloat($periods); + $presentValue = CashFlowValidations::validatePresentValue($presentValue); + $futureValue = CashFlowValidations::validateFutureValue($futureValue); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Validate parameters + if ($periods <= 0.0 || $presentValue <= 0.0 || $futureValue < 0.0) { + return ExcelError::NAN(); + } + + return ($futureValue / $presentValue) ** (1 / $periods) - 1; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php b/PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php new file mode 100644 index 0000000..4441356 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php @@ -0,0 +1,325 @@ + 1; + $datesIsArray = count($dates) > 1; + if (!$valuesIsArray && !$datesIsArray) { + return ExcelError::NA(); + } + if (count($values) != count($dates)) { + return ExcelError::NAN(); + } + + $datesCount = count($dates); + for ($i = 0; $i < $datesCount; ++$i) { + try { + $dates[$i] = DateTimeExcel\Helpers::getDateValue($dates[$i]); + } catch (Exception $e) { + return $e->getMessage(); + } + } + + return self::xirrPart2($values); + } + + private static function xirrPart2(array &$values): string + { + $valCount = count($values); + $foundpos = false; + $foundneg = false; + for ($i = 0; $i < $valCount; ++$i) { + $fld = $values[$i]; + if (!is_numeric($fld)) { + return ExcelError::VALUE(); + } elseif ($fld > 0) { + $foundpos = true; + } elseif ($fld < 0) { + $foundneg = true; + } + } + if (!self::bothNegAndPos($foundneg, $foundpos)) { + return ExcelError::NAN(); + } + + return ''; + } + + /** + * @return float|string + */ + private static function xirrPart3(array $values, array $dates, float $x1, float $x2) + { + $f = self::xnpvOrdered($x1, $values, $dates, false); + if ($f < 0.0) { + $rtb = $x1; + $dx = $x2 - $x1; + } else { + $rtb = $x2; + $dx = $x1 - $x2; + } + + $rslt = ExcelError::VALUE(); + for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) { + $dx *= 0.5; + $x_mid = $rtb + $dx; + $f_mid = (float) self::xnpvOrdered($x_mid, $values, $dates, false); + if ($f_mid <= 0.0) { + $rtb = $x_mid; + } + if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) { + $rslt = $x_mid; + + break; + } + } + + return $rslt; + } + + /** + * @return float|string + */ + private static function xirrBisection(array $values, array $dates, float $x1, float $x2) + { + $rslt = ExcelError::NAN(); + for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) { + $rslt = ExcelError::NAN(); + $f1 = self::xnpvOrdered($x1, $values, $dates, false, true); + $f2 = self::xnpvOrdered($x2, $values, $dates, false, true); + if (!is_numeric($f1) || !is_numeric($f2)) { + break; + } + $f1 = (float) $f1; + $f2 = (float) $f2; + if (abs($f1) < self::FINANCIAL_PRECISION && abs($f2) < self::FINANCIAL_PRECISION) { + break; + } + if ($f1 * $f2 > 0) { + break; + } + $rslt = ($x1 + $x2) / 2; + $f3 = self::xnpvOrdered($rslt, $values, $dates, false, true); + if (!is_float($f3)) { + break; + } + if ($f3 * $f1 < 0) { + $x2 = $rslt; + } else { + $x1 = $rslt; + } + if (abs($f3) < self::FINANCIAL_PRECISION) { + break; + } + } + + return $rslt; + } + + /** + * @param mixed $rate + * @param mixed $values + * @param mixed $dates + * + * @return float|string + */ + private static function xnpvOrdered($rate, $values, $dates, bool $ordered = true, bool $capAtNegative1 = false) + { + $rate = Functions::flattenSingleValue($rate); + $values = Functions::flattenArray($values); + $dates = Functions::flattenArray($dates); + $valCount = count($values); + + try { + self::validateXnpv($rate, $values, $dates); + if ($capAtNegative1 && $rate <= -1) { + $rate = -1.0 + 1.0E-10; + } + $date0 = DateTimeExcel\Helpers::getDateValue($dates[0]); + } catch (Exception $e) { + return $e->getMessage(); + } + + $xnpv = 0.0; + for ($i = 0; $i < $valCount; ++$i) { + if (!is_numeric($values[$i])) { + return ExcelError::VALUE(); + } + + try { + $datei = DateTimeExcel\Helpers::getDateValue($dates[$i]); + } catch (Exception $e) { + return $e->getMessage(); + } + if ($date0 > $datei) { + $dif = $ordered ? ExcelError::NAN() : -((int) DateTimeExcel\Difference::interval($datei, $date0, 'd')); + } else { + $dif = DateTimeExcel\Difference::interval($date0, $datei, 'd'); + } + if (!is_numeric($dif)) { + return $dif; + } + if ($rate <= -1.0) { + $xnpv += -abs($values[$i]) / (-1 - $rate) ** ($dif / 365); + } else { + $xnpv += $values[$i] / (1 + $rate) ** ($dif / 365); + } + } + + return is_finite($xnpv) ? $xnpv : ExcelError::VALUE(); + } + + /** + * @param mixed $rate + */ + private static function validateXnpv($rate, array $values, array $dates): void + { + if (!is_numeric($rate)) { + throw new Exception(ExcelError::VALUE()); + } + $valCount = count($values); + if ($valCount != count($dates)) { + throw new Exception(ExcelError::NAN()); + } + if ($valCount > 1 && ((min($values) > 0) || (max($values) < 0))) { + throw new Exception(ExcelError::NAN()); + } + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/Periodic.php b/PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/Periodic.php new file mode 100644 index 0000000..545102f --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/Periodic.php @@ -0,0 +1,168 @@ + 0.0) { + return ExcelError::VALUE(); + } + + $f = self::presentValue($x1, $values); + if ($f < 0.0) { + $rtb = $x1; + $dx = $x2 - $x1; + } else { + $rtb = $x2; + $dx = $x1 - $x2; + } + + for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) { + $dx *= 0.5; + $x_mid = $rtb + $dx; + $f_mid = self::presentValue($x_mid, $values); + if ($f_mid <= 0.0) { + $rtb = $x_mid; + } + if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) { + return $x_mid; + } + } + + return ExcelError::VALUE(); + } + + /** + * MIRR. + * + * Returns the modified internal rate of return for a series of periodic cash flows. MIRR considers both + * the cost of the investment and the interest received on reinvestment of cash. + * + * Excel Function: + * MIRR(values,finance_rate, reinvestment_rate) + * + * @param mixed $values An array or a reference to cells that contain a series of payments and + * income occurring at regular intervals. + * Payments are negative value, income is positive values. + * @param mixed $financeRate The interest rate you pay on the money used in the cash flows + * @param mixed $reinvestmentRate The interest rate you receive on the cash flows as you reinvest them + * + * @return float|string Result, or a string containing an error + */ + public static function modifiedRate($values, $financeRate, $reinvestmentRate) + { + if (!is_array($values)) { + return ExcelError::DIV0(); + } + $values = Functions::flattenArray($values); + $financeRate = Functions::flattenSingleValue($financeRate); + $reinvestmentRate = Functions::flattenSingleValue($reinvestmentRate); + $n = count($values); + + $rr = 1.0 + $reinvestmentRate; + $fr = 1.0 + $financeRate; + + $npvPos = $npvNeg = self::$zeroPointZero; + foreach ($values as $i => $v) { + if ($v >= 0) { + $npvPos += $v / $rr ** $i; + } else { + $npvNeg += $v / $fr ** $i; + } + } + + if ($npvNeg === self::$zeroPointZero || $npvPos === self::$zeroPointZero) { + return ExcelError::DIV0(); + } + + $mirr = ((-$npvPos * $rr ** $n) + / ($npvNeg * ($rr))) ** (1.0 / ($n - 1)) - 1.0; + + return is_finite($mirr) ? $mirr : ExcelError::NAN(); + } + + /** + * Sop to Scrutinizer. + * + * @var float + */ + private static $zeroPointZero = 0.0; + + /** + * NPV. + * + * Returns the Net Present Value of a cash flow series given a discount rate. + * + * @param mixed $rate + * + * @return float + */ + public static function presentValue($rate, ...$args) + { + $returnValue = 0; + + $rate = Functions::flattenSingleValue($rate); + $aArgs = Functions::flattenArray($args); + + // Calculate + $countArgs = count($aArgs); + for ($i = 1; $i <= $countArgs; ++$i) { + // Is it a numeric value? + if (is_numeric($aArgs[$i - 1])) { + $returnValue += $aArgs[$i - 1] / (1 + $rate) ** $i; + } + } + + return $returnValue; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Financial/Constants.php b/PhpOffice/PhpSpreadsheet/Calculation/Financial/Constants.php new file mode 100644 index 0000000..17740b0 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Financial/Constants.php @@ -0,0 +1,19 @@ +getMessage(); + } + + $daysPerYear = Helpers::daysPerYear(Functions::scalar(DateTimeExcel\DateParts::year($settlement)), $basis); + if (is_string($daysPerYear)) { + return ExcelError::VALUE(); + } + $prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_PREVIOUS); + + if ($basis === FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL) { + return abs((float) DateTimeExcel\Days::between($prev, $settlement)); + } + + return (float) DateTimeExcel\YearFrac::fraction($prev, $settlement, $basis) * $daysPerYear; + } + + /** + * COUPDAYS. + * + * Returns the number of days in the coupon period that contains the settlement date. + * + * Excel Function: + * COUPDAYS(settlement,maturity,frequency[,basis]) + * + * @param mixed $settlement The security's settlement date. + * The security settlement date is the date after the issue + * date when the security is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $frequency The number of coupon payments per year. + * Valid frequency values are: + * 1 Annual + * 2 Semi-Annual + * 4 Quarterly + * @param mixed $basis The type of day count to use (int). + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string + */ + public static function COUPDAYS( + $settlement, + $maturity, + $frequency, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $frequency = Functions::flattenSingleValue($frequency); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $settlement = FinancialValidations::validateSettlementDate($settlement); + $maturity = FinancialValidations::validateMaturityDate($maturity); + self::validateCouponPeriod($settlement, $maturity); + $frequency = FinancialValidations::validateFrequency($frequency); + $basis = FinancialValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + switch ($basis) { + case FinancialConstants::BASIS_DAYS_PER_YEAR_365: + // Actual/365 + return 365 / $frequency; + case FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL: + // Actual/actual + if ($frequency == FinancialConstants::FREQUENCY_ANNUAL) { + $daysPerYear = (int) Helpers::daysPerYear(Functions::scalar(DateTimeExcel\DateParts::year($settlement)), $basis); + + return $daysPerYear / $frequency; + } + $prev = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_PREVIOUS); + $next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_NEXT); + + return $next - $prev; + default: + // US (NASD) 30/360, Actual/360 or European 30/360 + return 360 / $frequency; + } + } + + /** + * COUPDAYSNC. + * + * Returns the number of days from the settlement date to the next coupon date. + * + * Excel Function: + * COUPDAYSNC(settlement,maturity,frequency[,basis]) + * + * @param mixed $settlement The security's settlement date. + * The security settlement date is the date after the issue + * date when the security is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $frequency The number of coupon payments per year. + * Valid frequency values are: + * 1 Annual + * 2 Semi-Annual + * 4 Quarterly + * @param mixed $basis The type of day count to use (int) . + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string + */ + public static function COUPDAYSNC( + $settlement, + $maturity, + $frequency, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $frequency = Functions::flattenSingleValue($frequency); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $settlement = FinancialValidations::validateSettlementDate($settlement); + $maturity = FinancialValidations::validateMaturityDate($maturity); + self::validateCouponPeriod($settlement, $maturity); + $frequency = FinancialValidations::validateFrequency($frequency); + $basis = FinancialValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + /** @var int */ + $daysPerYear = Helpers::daysPerYear(DateTimeExcel\DateParts::year($settlement), $basis); + $next = self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_NEXT); + + if ($basis === FinancialConstants::BASIS_DAYS_PER_YEAR_NASD) { + $settlementDate = Date::excelToDateTimeObject($settlement); + $settlementEoM = Helpers::isLastDayOfMonth($settlementDate); + if ($settlementEoM) { + ++$settlement; + } + } + + return (float) DateTimeExcel\YearFrac::fraction($settlement, $next, $basis) * $daysPerYear; + } + + /** + * COUPNCD. + * + * Returns the next coupon date after the settlement date. + * + * Excel Function: + * COUPNCD(settlement,maturity,frequency[,basis]) + * + * @param mixed $settlement The security's settlement date. + * The security settlement date is the date after the issue + * date when the security is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $frequency The number of coupon payments per year. + * Valid frequency values are: + * 1 Annual + * 2 Semi-Annual + * 4 Quarterly + * @param mixed $basis The type of day count to use (int). + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, + * depending on the value of the ReturnDateType flag + */ + public static function COUPNCD( + $settlement, + $maturity, + $frequency, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $frequency = Functions::flattenSingleValue($frequency); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $settlement = FinancialValidations::validateSettlementDate($settlement); + $maturity = FinancialValidations::validateMaturityDate($maturity); + self::validateCouponPeriod($settlement, $maturity); + $frequency = FinancialValidations::validateFrequency($frequency); + $basis = FinancialValidations::validateBasis($basis); + self::doNothing($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + return self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_NEXT); + } + + /** + * COUPNUM. + * + * Returns the number of coupons payable between the settlement date and maturity date, + * rounded up to the nearest whole coupon. + * + * Excel Function: + * COUPNUM(settlement,maturity,frequency[,basis]) + * + * @param mixed $settlement The security's settlement date. + * The security settlement date is the date after the issue + * date when the security is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $frequency The number of coupon payments per year. + * Valid frequency values are: + * 1 Annual + * 2 Semi-Annual + * 4 Quarterly + * @param mixed $basis The type of day count to use (int). + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return int|string + */ + public static function COUPNUM( + $settlement, + $maturity, + $frequency, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $frequency = Functions::flattenSingleValue($frequency); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $settlement = FinancialValidations::validateSettlementDate($settlement); + $maturity = FinancialValidations::validateMaturityDate($maturity); + self::validateCouponPeriod($settlement, $maturity); + $frequency = FinancialValidations::validateFrequency($frequency); + $basis = FinancialValidations::validateBasis($basis); + self::doNothing($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + $yearsBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::fraction( + $settlement, + $maturity, + FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ); + + return (int) ceil((float) $yearsBetweenSettlementAndMaturity * $frequency); + } + + /** + * COUPPCD. + * + * Returns the previous coupon date before the settlement date. + * + * Excel Function: + * COUPPCD(settlement,maturity,frequency[,basis]) + * + * @param mixed $settlement The security's settlement date. + * The security settlement date is the date after the issue + * date when the security is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $frequency The number of coupon payments per year. + * Valid frequency values are: + * 1 Annual + * 2 Semi-Annual + * 4 Quarterly + * @param mixed $basis The type of day count to use (int). + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object, + * depending on the value of the ReturnDateType flag + */ + public static function COUPPCD( + $settlement, + $maturity, + $frequency, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $frequency = Functions::flattenSingleValue($frequency); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $settlement = FinancialValidations::validateSettlementDate($settlement); + $maturity = FinancialValidations::validateMaturityDate($maturity); + self::validateCouponPeriod($settlement, $maturity); + $frequency = FinancialValidations::validateFrequency($frequency); + $basis = FinancialValidations::validateBasis($basis); + self::doNothing($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + return self::couponFirstPeriodDate($settlement, $maturity, $frequency, self::PERIOD_DATE_PREVIOUS); + } + + private static function monthsDiff(DateTime $result, int $months, string $plusOrMinus, int $day, bool $lastDayFlag): void + { + $result->setDate((int) $result->format('Y'), (int) $result->format('m'), 1); + $result->modify("$plusOrMinus $months months"); + $daysInMonth = (int) $result->format('t'); + $result->setDate((int) $result->format('Y'), (int) $result->format('m'), $lastDayFlag ? $daysInMonth : min($day, $daysInMonth)); + } + + private static function couponFirstPeriodDate(float $settlement, float $maturity, int $frequency, bool $next): float + { + $months = 12 / $frequency; + + $result = Date::excelToDateTimeObject($maturity); + $day = (int) $result->format('d'); + $lastDayFlag = Helpers::isLastDayOfMonth($result); + + while ($settlement < Date::PHPToExcel($result)) { + self::monthsDiff($result, $months, '-', $day, $lastDayFlag); + } + if ($next === true) { + self::monthsDiff($result, $months, '+', $day, $lastDayFlag); + } + + return (float) Date::PHPToExcel($result); + } + + private static function validateCouponPeriod(float $settlement, float $maturity): void + { + if ($settlement >= $maturity) { + throw new Exception(ExcelError::NAN()); + } + } + + /** @param mixed $basis */ + private static function doNothing($basis): bool + { + return $basis; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Financial/Depreciation.php b/PhpOffice/PhpSpreadsheet/Calculation/Financial/Depreciation.php new file mode 100644 index 0000000..8e1a2fc --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Financial/Depreciation.php @@ -0,0 +1,270 @@ +getMessage(); + } + + if ($cost === self::$zeroPointZero) { + return 0.0; + } + + // Set Fixed Depreciation Rate + $fixedDepreciationRate = 1 - ($salvage / $cost) ** (1 / $life); + $fixedDepreciationRate = round($fixedDepreciationRate, 3); + + // Loop through each period calculating the depreciation + // TODO Handle period value between 0 and 1 (e.g. 0.5) + $previousDepreciation = 0; + $depreciation = 0; + for ($per = 1; $per <= $period; ++$per) { + if ($per == 1) { + $depreciation = $cost * $fixedDepreciationRate * $month / 12; + } elseif ($per == ($life + 1)) { + $depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate * (12 - $month) / 12; + } else { + $depreciation = ($cost - $previousDepreciation) * $fixedDepreciationRate; + } + $previousDepreciation += $depreciation; + } + + return $depreciation; + } + + /** + * DDB. + * + * Returns the depreciation of an asset for a specified period using the + * double-declining balance method or some other method you specify. + * + * Excel Function: + * DDB(cost,salvage,life,period[,factor]) + * + * @param mixed $cost Initial cost of the asset + * @param mixed $salvage Value at the end of the depreciation. + * (Sometimes called the salvage value of the asset) + * @param mixed $life Number of periods over which the asset is depreciated. + * (Sometimes called the useful life of the asset) + * @param mixed $period The period for which you want to calculate the + * depreciation. Period must use the same units as life. + * @param mixed $factor The rate at which the balance declines. + * If factor is omitted, it is assumed to be 2 (the + * double-declining balance method). + * + * @return float|string + */ + public static function DDB($cost, $salvage, $life, $period, $factor = 2.0) + { + $cost = Functions::flattenSingleValue($cost); + $salvage = Functions::flattenSingleValue($salvage); + $life = Functions::flattenSingleValue($life); + $period = Functions::flattenSingleValue($period); + $factor = Functions::flattenSingleValue($factor); + + try { + $cost = self::validateCost($cost); + $salvage = self::validateSalvage($salvage); + $life = self::validateLife($life); + $period = self::validatePeriod($period); + $factor = self::validateFactor($factor); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($period > $life) { + return ExcelError::NAN(); + } + + // Loop through each period calculating the depreciation + // TODO Handling for fractional $period values + $previousDepreciation = 0; + $depreciation = 0; + for ($per = 1; $per <= $period; ++$per) { + $depreciation = min( + ($cost - $previousDepreciation) * ($factor / $life), + ($cost - $salvage - $previousDepreciation) + ); + $previousDepreciation += $depreciation; + } + + return $depreciation; + } + + /** + * SLN. + * + * Returns the straight-line depreciation of an asset for one period + * + * @param mixed $cost Initial cost of the asset + * @param mixed $salvage Value at the end of the depreciation + * @param mixed $life Number of periods over which the asset is depreciated + * + * @return float|string Result, or a string containing an error + */ + public static function SLN($cost, $salvage, $life) + { + $cost = Functions::flattenSingleValue($cost); + $salvage = Functions::flattenSingleValue($salvage); + $life = Functions::flattenSingleValue($life); + + try { + $cost = self::validateCost($cost, true); + $salvage = self::validateSalvage($salvage, true); + $life = self::validateLife($life, true); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($life === self::$zeroPointZero) { + return ExcelError::DIV0(); + } + + return ($cost - $salvage) / $life; + } + + /** + * SYD. + * + * Returns the sum-of-years' digits depreciation of an asset for a specified period. + * + * @param mixed $cost Initial cost of the asset + * @param mixed $salvage Value at the end of the depreciation + * @param mixed $life Number of periods over which the asset is depreciated + * @param mixed $period Period + * + * @return float|string Result, or a string containing an error + */ + public static function SYD($cost, $salvage, $life, $period) + { + $cost = Functions::flattenSingleValue($cost); + $salvage = Functions::flattenSingleValue($salvage); + $life = Functions::flattenSingleValue($life); + $period = Functions::flattenSingleValue($period); + + try { + $cost = self::validateCost($cost, true); + $salvage = self::validateSalvage($salvage); + $life = self::validateLife($life); + $period = self::validatePeriod($period); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($period > $life) { + return ExcelError::NAN(); + } + + $syd = (($cost - $salvage) * ($life - $period + 1) * 2) / ($life * ($life + 1)); + + return $syd; + } + + private static function validateCost($cost, bool $negativeValueAllowed = false): float + { + $cost = FinancialValidations::validateFloat($cost); + if ($cost < 0.0 && $negativeValueAllowed === false) { + throw new Exception(ExcelError::NAN()); + } + + return $cost; + } + + private static function validateSalvage($salvage, bool $negativeValueAllowed = false): float + { + $salvage = FinancialValidations::validateFloat($salvage); + if ($salvage < 0.0 && $negativeValueAllowed === false) { + throw new Exception(ExcelError::NAN()); + } + + return $salvage; + } + + private static function validateLife($life, bool $negativeValueAllowed = false): float + { + $life = FinancialValidations::validateFloat($life); + if ($life < 0.0 && $negativeValueAllowed === false) { + throw new Exception(ExcelError::NAN()); + } + + return $life; + } + + private static function validatePeriod($period, bool $negativeValueAllowed = false): float + { + $period = FinancialValidations::validateFloat($period); + if ($period <= 0.0 && $negativeValueAllowed === false) { + throw new Exception(ExcelError::NAN()); + } + + return $period; + } + + private static function validateMonth($month): int + { + $month = FinancialValidations::validateInt($month); + if ($month < 1) { + throw new Exception(ExcelError::NAN()); + } + + return $month; + } + + private static function validateFactor($factor): float + { + $factor = FinancialValidations::validateFloat($factor); + if ($factor <= 0.0) { + throw new Exception(ExcelError::NAN()); + } + + return $factor; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Financial/Dollar.php b/PhpOffice/PhpSpreadsheet/Calculation/Financial/Dollar.php new file mode 100644 index 0000000..b1f0d25 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Financial/Dollar.php @@ -0,0 +1,132 @@ +getMessage(); + } + + // Additional parameter validations + if ($fraction < 0) { + return ExcelError::NAN(); + } + if ($fraction == 0) { + return ExcelError::DIV0(); + } + + $dollars = ($fractionalDollar < 0) ? ceil($fractionalDollar) : floor($fractionalDollar); + $cents = fmod($fractionalDollar, 1.0); + $cents /= $fraction; + $cents *= 10 ** ceil(log10($fraction)); + + return $dollars + $cents; + } + + /** + * DOLLARFR. + * + * Converts a dollar price expressed as a decimal number into a dollar price + * expressed as a fraction. + * Fractional dollar numbers are sometimes used for security prices. + * + * Excel Function: + * DOLLARFR(decimal_dollar,fraction) + * + * @param mixed $decimalDollar Decimal Dollar + * Or can be an array of values + * @param mixed $fraction Fraction + * Or can be an array of values + * + * @return array|float|string + */ + public static function fractional($decimalDollar = null, $fraction = 0) + { + if (is_array($decimalDollar) || is_array($fraction)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $decimalDollar, $fraction); + } + + try { + $decimalDollar = FinancialValidations::validateFloat( + Functions::flattenSingleValue($decimalDollar) ?? 0.0 + ); + $fraction = FinancialValidations::validateInt(Functions::flattenSingleValue($fraction)); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Additional parameter validations + if ($fraction < 0) { + return ExcelError::NAN(); + } + if ($fraction == 0) { + return ExcelError::DIV0(); + } + + $dollars = ($decimalDollar < 0.0) ? ceil($decimalDollar) : floor($decimalDollar); + $cents = fmod($decimalDollar, 1); + $cents *= $fraction; + $cents *= 10 ** (-ceil(log10($fraction))); + + return $dollars + $cents; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Financial/FinancialValidations.php b/PhpOffice/PhpSpreadsheet/Calculation/Financial/FinancialValidations.php new file mode 100644 index 0000000..1b04419 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Financial/FinancialValidations.php @@ -0,0 +1,158 @@ + 4)) { + throw new Exception(ExcelError::NAN()); + } + + return $basis; + } + + /** + * @param mixed $price + */ + public static function validatePrice($price): float + { + $price = self::validateFloat($price); + if ($price < 0.0) { + throw new Exception(ExcelError::NAN()); + } + + return $price; + } + + /** + * @param mixed $parValue + */ + public static function validateParValue($parValue): float + { + $parValue = self::validateFloat($parValue); + if ($parValue < 0.0) { + throw new Exception(ExcelError::NAN()); + } + + return $parValue; + } + + /** + * @param mixed $yield + */ + public static function validateYield($yield): float + { + $yield = self::validateFloat($yield); + if ($yield < 0.0) { + throw new Exception(ExcelError::NAN()); + } + + return $yield; + } + + /** + * @param mixed $discount + */ + public static function validateDiscount($discount): float + { + $discount = self::validateFloat($discount); + if ($discount <= 0.0) { + throw new Exception(ExcelError::NAN()); + } + + return $discount; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Financial/Helpers.php b/PhpOffice/PhpSpreadsheet/Calculation/Financial/Helpers.php new file mode 100644 index 0000000..c7f1f46 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Financial/Helpers.php @@ -0,0 +1,58 @@ +format('d') === $date->format('t'); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Financial/InterestRate.php b/PhpOffice/PhpSpreadsheet/Calculation/Financial/InterestRate.php new file mode 100644 index 0000000..1cbe265 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Financial/InterestRate.php @@ -0,0 +1,73 @@ +getMessage(); + } + + if ($nominalRate <= 0 || $periodsPerYear < 1) { + return ExcelError::NAN(); + } + + return ((1 + $nominalRate / $periodsPerYear) ** $periodsPerYear) - 1; + } + + /** + * NOMINAL. + * + * Returns the nominal interest rate given the effective rate and the number of compounding payments per year. + * + * @param mixed $effectiveRate Effective interest rate as a float + * @param mixed $periodsPerYear Integer number of compounding payments per year + * + * @return float|string Result, or a string containing an error + */ + public static function nominal($effectiveRate = 0, $periodsPerYear = 0) + { + $effectiveRate = Functions::flattenSingleValue($effectiveRate); + $periodsPerYear = Functions::flattenSingleValue($periodsPerYear); + + try { + $effectiveRate = FinancialValidations::validateFloat($effectiveRate); + $periodsPerYear = FinancialValidations::validateInt($periodsPerYear); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($effectiveRate <= 0 || $periodsPerYear < 1) { + return ExcelError::NAN(); + } + + // Calculate + return $periodsPerYear * (($effectiveRate + 1) ** (1 / $periodsPerYear) - 1); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Financial/Securities/AccruedInterest.php b/PhpOffice/PhpSpreadsheet/Calculation/Financial/Securities/AccruedInterest.php new file mode 100644 index 0000000..e1bf04b --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Financial/Securities/AccruedInterest.php @@ -0,0 +1,159 @@ +getMessage(); + } + + $daysBetweenIssueAndSettlement = Functions::scalar(YearFrac::fraction($issue, $settlement, $basis)); + if (!is_numeric($daysBetweenIssueAndSettlement)) { + // return date error + return $daysBetweenIssueAndSettlement; + } + $daysBetweenFirstInterestAndSettlement = Functions::scalar(YearFrac::fraction($firstInterest, $settlement, $basis)); + if (!is_numeric($daysBetweenFirstInterestAndSettlement)) { + // return date error + return $daysBetweenFirstInterestAndSettlement; + } + + return $parValue * $rate * $daysBetweenIssueAndSettlement; + } + + /** + * ACCRINTM. + * + * Returns the accrued interest for a security that pays interest at maturity. + * + * Excel Function: + * ACCRINTM(issue,settlement,rate[,par[,basis]]) + * + * @param mixed $issue The security's issue date + * @param mixed $settlement The security's settlement (or maturity) date + * @param mixed $rate The security's annual coupon rate + * @param mixed $parValue The security's par value. + * If you omit parValue, ACCRINT uses $1,000. + * @param mixed $basis The type of day count to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string Result, or a string containing an error + */ + public static function atMaturity( + $issue, + $settlement, + $rate, + $parValue = 1000, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $issue = Functions::flattenSingleValue($issue); + $settlement = Functions::flattenSingleValue($settlement); + $rate = Functions::flattenSingleValue($rate); + $parValue = ($parValue === null) ? 1000 : Functions::flattenSingleValue($parValue); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $issue = SecurityValidations::validateIssueDate($issue); + $settlement = SecurityValidations::validateSettlementDate($settlement); + SecurityValidations::validateSecurityPeriod($issue, $settlement); + $rate = SecurityValidations::validateRate($rate); + $parValue = SecurityValidations::validateParValue($parValue); + $basis = SecurityValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + $daysBetweenIssueAndSettlement = Functions::scalar(YearFrac::fraction($issue, $settlement, $basis)); + if (!is_numeric($daysBetweenIssueAndSettlement)) { + // return date error + return $daysBetweenIssueAndSettlement; + } + + return $parValue * $rate * $daysBetweenIssueAndSettlement; + } + + /** @param mixed $arg */ + private static function doNothing($arg): bool + { + return (bool) $arg; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Financial/Securities/Price.php b/PhpOffice/PhpSpreadsheet/Calculation/Financial/Securities/Price.php new file mode 100644 index 0000000..de1748a --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Financial/Securities/Price.php @@ -0,0 +1,284 @@ +getMessage(); + } + + $dsc = Coupons::COUPDAYSNC($settlement, $maturity, $frequency, $basis); + $e = Coupons::COUPDAYS($settlement, $maturity, $frequency, $basis); + $n = Coupons::COUPNUM($settlement, $maturity, $frequency, $basis); + $a = Coupons::COUPDAYBS($settlement, $maturity, $frequency, $basis); + + $baseYF = 1.0 + ($yield / $frequency); + $rfp = 100 * ($rate / $frequency); + $de = $dsc / $e; + + $result = $redemption / $baseYF ** (--$n + $de); + for ($k = 0; $k <= $n; ++$k) { + $result += $rfp / ($baseYF ** ($k + $de)); + } + $result -= $rfp * ($a / $e); + + return $result; + } + + /** + * PRICEDISC. + * + * Returns the price per $100 face value of a discounted security. + * + * @param mixed $settlement The security's settlement date. + * The security settlement date is the date after the issue date when the security + * is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $discount The security's discount rate + * @param mixed $redemption The security's redemption value per $100 face value + * @param mixed $basis The type of day count to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string Result, or a string containing an error + */ + public static function priceDiscounted( + $settlement, + $maturity, + $discount, + $redemption, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $discount = Functions::flattenSingleValue($discount); + $redemption = Functions::flattenSingleValue($redemption); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $settlement = SecurityValidations::validateSettlementDate($settlement); + $maturity = SecurityValidations::validateMaturityDate($maturity); + SecurityValidations::validateSecurityPeriod($settlement, $maturity); + $discount = SecurityValidations::validateDiscount($discount); + $redemption = SecurityValidations::validateRedemption($redemption); + $basis = SecurityValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + $daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis)); + if (!is_numeric($daysBetweenSettlementAndMaturity)) { + // return date error + return $daysBetweenSettlementAndMaturity; + } + + return $redemption * (1 - $discount * $daysBetweenSettlementAndMaturity); + } + + /** + * PRICEMAT. + * + * Returns the price per $100 face value of a security that pays interest at maturity. + * + * @param mixed $settlement The security's settlement date. + * The security's settlement date is the date after the issue date when the + * security is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $issue The security's issue date + * @param mixed $rate The security's interest rate at date of issue + * @param mixed $yield The security's annual yield + * @param mixed $basis The type of day count to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string Result, or a string containing an error + */ + public static function priceAtMaturity( + $settlement, + $maturity, + $issue, + $rate, + $yield, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $issue = Functions::flattenSingleValue($issue); + $rate = Functions::flattenSingleValue($rate); + $yield = Functions::flattenSingleValue($yield); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $settlement = SecurityValidations::validateSettlementDate($settlement); + $maturity = SecurityValidations::validateMaturityDate($maturity); + SecurityValidations::validateSecurityPeriod($settlement, $maturity); + $issue = SecurityValidations::validateIssueDate($issue); + $rate = SecurityValidations::validateRate($rate); + $yield = SecurityValidations::validateYield($yield); + $basis = SecurityValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + $daysPerYear = Functions::scalar(Helpers::daysPerYear(DateTimeExcel\DateParts::year($settlement), $basis)); + if (!is_numeric($daysPerYear)) { + return $daysPerYear; + } + $daysBetweenIssueAndSettlement = Functions::scalar(DateTimeExcel\YearFrac::fraction($issue, $settlement, $basis)); + if (!is_numeric($daysBetweenIssueAndSettlement)) { + // return date error + return $daysBetweenIssueAndSettlement; + } + $daysBetweenIssueAndSettlement *= $daysPerYear; + $daysBetweenIssueAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($issue, $maturity, $basis)); + if (!is_numeric($daysBetweenIssueAndMaturity)) { + // return date error + return $daysBetweenIssueAndMaturity; + } + $daysBetweenIssueAndMaturity *= $daysPerYear; + $daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis)); + if (!is_numeric($daysBetweenSettlementAndMaturity)) { + // return date error + return $daysBetweenSettlementAndMaturity; + } + $daysBetweenSettlementAndMaturity *= $daysPerYear; + + return (100 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate * 100)) / + (1 + (($daysBetweenSettlementAndMaturity / $daysPerYear) * $yield)) - + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate * 100); + } + + /** + * RECEIVED. + * + * Returns the amount received at maturity for a fully invested Security. + * + * @param mixed $settlement The security's settlement date. + * The security settlement date is the date after the issue date when the security + * is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $investment The amount invested in the security + * @param mixed $discount The security's discount rate + * @param mixed $basis The type of day count to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string Result, or a string containing an error + */ + public static function received( + $settlement, + $maturity, + $investment, + $discount, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $investment = Functions::flattenSingleValue($investment); + $discount = Functions::flattenSingleValue($discount); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $settlement = SecurityValidations::validateSettlementDate($settlement); + $maturity = SecurityValidations::validateMaturityDate($maturity); + SecurityValidations::validateSecurityPeriod($settlement, $maturity); + $investment = SecurityValidations::validateFloat($investment); + $discount = SecurityValidations::validateDiscount($discount); + $basis = SecurityValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($investment <= 0) { + return ExcelError::NAN(); + } + $daysBetweenSettlementAndMaturity = DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis); + if (!is_numeric($daysBetweenSettlementAndMaturity)) { + // return date error + return $daysBetweenSettlementAndMaturity; + } + + return $investment / (1 - ($discount * $daysBetweenSettlementAndMaturity)); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Financial/Securities/Rates.php b/PhpOffice/PhpSpreadsheet/Calculation/Financial/Securities/Rates.php new file mode 100644 index 0000000..f8d8673 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Financial/Securities/Rates.php @@ -0,0 +1,138 @@ +getMessage(); + } + + if ($price <= 0.0) { + return ExcelError::NAN(); + } + + $daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis)); + if (!is_numeric($daysBetweenSettlementAndMaturity)) { + // return date error + return $daysBetweenSettlementAndMaturity; + } + + return (1 - $price / $redemption) / $daysBetweenSettlementAndMaturity; + } + + /** + * INTRATE. + * + * Returns the interest rate for a fully invested security. + * + * Excel Function: + * INTRATE(settlement,maturity,investment,redemption[,basis]) + * + * @param mixed $settlement The security's settlement date. + * The security settlement date is the date after the issue date when the security + * is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $investment the amount invested in the security + * @param mixed $redemption the amount to be received at maturity + * @param mixed $basis The type of day count to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string + */ + public static function interest( + $settlement, + $maturity, + $investment, + $redemption, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $investment = Functions::flattenSingleValue($investment); + $redemption = Functions::flattenSingleValue($redemption); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $settlement = SecurityValidations::validateSettlementDate($settlement); + $maturity = SecurityValidations::validateMaturityDate($maturity); + SecurityValidations::validateSecurityPeriod($settlement, $maturity); + $investment = SecurityValidations::validateFloat($investment); + $redemption = SecurityValidations::validateRedemption($redemption); + $basis = SecurityValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($investment <= 0) { + return ExcelError::NAN(); + } + + $daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis)); + if (!is_numeric($daysBetweenSettlementAndMaturity)) { + // return date error + return $daysBetweenSettlementAndMaturity; + } + + return (($redemption / $investment) - 1) / ($daysBetweenSettlementAndMaturity); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Financial/Securities/SecurityValidations.php b/PhpOffice/PhpSpreadsheet/Calculation/Financial/Securities/SecurityValidations.php new file mode 100644 index 0000000..d3196f0 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Financial/Securities/SecurityValidations.php @@ -0,0 +1,42 @@ += $maturity) { + throw new Exception(ExcelError::NAN()); + } + } + + /** + * @param mixed $redemption + */ + public static function validateRedemption($redemption): float + { + $redemption = self::validateFloat($redemption); + if ($redemption <= 0.0) { + throw new Exception(ExcelError::NAN()); + } + + return $redemption; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Financial/Securities/Yields.php b/PhpOffice/PhpSpreadsheet/Calculation/Financial/Securities/Yields.php new file mode 100644 index 0000000..bb2e8ae --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Financial/Securities/Yields.php @@ -0,0 +1,153 @@ +getMessage(); + } + + $daysPerYear = Helpers::daysPerYear(DateTimeExcel\DateParts::year($settlement), $basis); + if (!is_numeric($daysPerYear)) { + return $daysPerYear; + } + $daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis)); + if (!is_numeric($daysBetweenSettlementAndMaturity)) { + // return date error + return $daysBetweenSettlementAndMaturity; + } + $daysBetweenSettlementAndMaturity *= $daysPerYear; + + return (($redemption - $price) / $price) * ($daysPerYear / $daysBetweenSettlementAndMaturity); + } + + /** + * YIELDMAT. + * + * Returns the annual yield of a security that pays interest at maturity. + * + * @param mixed $settlement The security's settlement date. + * The security's settlement date is the date after the issue date when the security + * is traded to the buyer. + * @param mixed $maturity The security's maturity date. + * The maturity date is the date when the security expires. + * @param mixed $issue The security's issue date + * @param mixed $rate The security's interest rate at date of issue + * @param mixed $price The security's price per $100 face value + * @param mixed $basis The type of day count to use. + * 0 or omitted US (NASD) 30/360 + * 1 Actual/actual + * 2 Actual/360 + * 3 Actual/365 + * 4 European 30/360 + * + * @return float|string Result, or a string containing an error + */ + public static function yieldAtMaturity( + $settlement, + $maturity, + $issue, + $rate, + $price, + $basis = FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + ) { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $issue = Functions::flattenSingleValue($issue); + $rate = Functions::flattenSingleValue($rate); + $price = Functions::flattenSingleValue($price); + $basis = ($basis === null) + ? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD + : Functions::flattenSingleValue($basis); + + try { + $settlement = SecurityValidations::validateSettlementDate($settlement); + $maturity = SecurityValidations::validateMaturityDate($maturity); + SecurityValidations::validateSecurityPeriod($settlement, $maturity); + $issue = SecurityValidations::validateIssueDate($issue); + $rate = SecurityValidations::validateRate($rate); + $price = SecurityValidations::validatePrice($price); + $basis = SecurityValidations::validateBasis($basis); + } catch (Exception $e) { + return $e->getMessage(); + } + + $daysPerYear = Helpers::daysPerYear(DateTimeExcel\DateParts::year($settlement), $basis); + if (!is_numeric($daysPerYear)) { + return $daysPerYear; + } + $daysBetweenIssueAndSettlement = Functions::scalar(DateTimeExcel\YearFrac::fraction($issue, $settlement, $basis)); + if (!is_numeric($daysBetweenIssueAndSettlement)) { + // return date error + return $daysBetweenIssueAndSettlement; + } + $daysBetweenIssueAndSettlement *= $daysPerYear; + $daysBetweenIssueAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($issue, $maturity, $basis)); + if (!is_numeric($daysBetweenIssueAndMaturity)) { + // return date error + return $daysBetweenIssueAndMaturity; + } + $daysBetweenIssueAndMaturity *= $daysPerYear; + $daysBetweenSettlementAndMaturity = Functions::scalar(DateTimeExcel\YearFrac::fraction($settlement, $maturity, $basis)); + if (!is_numeric($daysBetweenSettlementAndMaturity)) { + // return date error + return $daysBetweenSettlementAndMaturity; + } + $daysBetweenSettlementAndMaturity *= $daysPerYear; + + return ((1 + (($daysBetweenIssueAndMaturity / $daysPerYear) * $rate) - + (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate))) / + (($price / 100) + (($daysBetweenIssueAndSettlement / $daysPerYear) * $rate))) * + ($daysPerYear / $daysBetweenSettlementAndMaturity); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Financial/TreasuryBill.php b/PhpOffice/PhpSpreadsheet/Calculation/Financial/TreasuryBill.php new file mode 100644 index 0000000..7ee34f7 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Financial/TreasuryBill.php @@ -0,0 +1,148 @@ +getMessage(); + } + + if ($discount <= 0) { + return ExcelError::NAN(); + } + + $daysBetweenSettlementAndMaturity = $maturity - $settlement; + $daysPerYear = Helpers::daysPerYear( + Functions::scalar(DateTimeExcel\DateParts::year($maturity)), + FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL + ); + + if ($daysBetweenSettlementAndMaturity > $daysPerYear || $daysBetweenSettlementAndMaturity < 0) { + return ExcelError::NAN(); + } + + return (365 * $discount) / (360 - $discount * $daysBetweenSettlementAndMaturity); + } + + /** + * TBILLPRICE. + * + * Returns the price per $100 face value for a Treasury bill. + * + * @param mixed $settlement The Treasury bill's settlement date. + * The Treasury bill's settlement date is the date after the issue date + * when the Treasury bill is traded to the buyer. + * @param mixed $maturity The Treasury bill's maturity date. + * The maturity date is the date when the Treasury bill expires. + * @param mixed $discount The Treasury bill's discount rate + * + * @return float|string Result, or a string containing an error + */ + public static function price($settlement, $maturity, $discount) + { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $discount = Functions::flattenSingleValue($discount); + + try { + $settlement = FinancialValidations::validateSettlementDate($settlement); + $maturity = FinancialValidations::validateMaturityDate($maturity); + $discount = FinancialValidations::validateFloat($discount); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($discount <= 0) { + return ExcelError::NAN(); + } + + $daysBetweenSettlementAndMaturity = $maturity - $settlement; + $daysPerYear = Helpers::daysPerYear( + Functions::scalar(DateTimeExcel\DateParts::year($maturity)), + FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL + ); + + if ($daysBetweenSettlementAndMaturity > $daysPerYear || $daysBetweenSettlementAndMaturity < 0) { + return ExcelError::NAN(); + } + + $price = 100 * (1 - (($discount * $daysBetweenSettlementAndMaturity) / 360)); + if ($price < 0.0) { + return ExcelError::NAN(); + } + + return $price; + } + + /** + * TBILLYIELD. + * + * Returns the yield for a Treasury bill. + * + * @param mixed $settlement The Treasury bill's settlement date. + * The Treasury bill's settlement date is the date after the issue date when + * the Treasury bill is traded to the buyer. + * @param mixed $maturity The Treasury bill's maturity date. + * The maturity date is the date when the Treasury bill expires. + * @param mixed $price The Treasury bill's price per $100 face value + * + * @return float|string + */ + public static function yield($settlement, $maturity, $price) + { + $settlement = Functions::flattenSingleValue($settlement); + $maturity = Functions::flattenSingleValue($maturity); + $price = Functions::flattenSingleValue($price); + + try { + $settlement = FinancialValidations::validateSettlementDate($settlement); + $maturity = FinancialValidations::validateMaturityDate($maturity); + $price = FinancialValidations::validatePrice($price); + } catch (Exception $e) { + return $e->getMessage(); + } + + $daysBetweenSettlementAndMaturity = $maturity - $settlement; + $daysPerYear = Helpers::daysPerYear( + Functions::scalar(DateTimeExcel\DateParts::year($maturity)), + FinancialConstants::BASIS_DAYS_PER_YEAR_ACTUAL + ); + + if ($daysBetweenSettlementAndMaturity > $daysPerYear || $daysBetweenSettlementAndMaturity < 0) { + return ExcelError::NAN(); + } + + return ((100 - $price) / $price) * (360 / $daysBetweenSettlementAndMaturity); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/FormulaParser.php b/PhpOffice/PhpSpreadsheet/Calculation/FormulaParser.php old mode 100755 new mode 100644 index 9b3c66e..14c5ae5 --- a/PhpOffice/PhpSpreadsheet/Calculation/FormulaParser.php +++ b/PhpOffice/PhpSpreadsheet/Calculation/FormulaParser.php @@ -61,19 +61,17 @@ class FormulaParser /** * Create a new FormulaParser. * - * @param string $pFormula Formula to parse - * - * @throws Exception + * @param ?string $formula Formula to parse */ - public function __construct($pFormula = '') + public function __construct($formula = '') { // Check parameters - if ($pFormula === null) { + if ($formula === null) { throw new Exception('Invalid parameter passed: formula'); } // Initialise values - $this->formula = trim($pFormula); + $this->formula = trim($formula); // Parse! $this->parseToTokens(); } @@ -91,19 +89,15 @@ class FormulaParser /** * Get Token. * - * @param int $pId Token id - * - * @throws Exception - * - * @return string + * @param int $id Token id */ - public function getToken($pId = 0) + public function getToken(int $id = 0): FormulaToken { - if (isset($this->tokens[$pId])) { - return $this->tokens[$pId]; + if (isset($this->tokens[$id])) { + return $this->tokens[$id]; } - throw new Exception("Token with id $pId does not exist."); + throw new Exception("Token with id $id does not exist."); } /** @@ -129,7 +123,7 @@ class FormulaParser /** * Parse to tokens. */ - private function parseToTokens() + private function parseToTokens(): void { // No attempt is made to verify formulas; assumes formulas are derived from Excel, where // they can only exist if valid; stack overflows/underflows sunk as nulls without exceptions. @@ -143,7 +137,8 @@ class FormulaParser // Helper variables $tokens1 = $tokens2 = $stack = []; $inString = $inPath = $inRange = $inError = false; - $token = $previousToken = $nextToken = null; + $nextToken = null; + //$token = $previousToken = null; $index = 1; $value = ''; @@ -494,11 +489,13 @@ class FormulaParser continue; } - if (!( - (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) && ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) || + if ( + !( + (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) && ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) || (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_SUBEXPRESSION) && ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) || ($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND) - )) { + ) + ) { continue; } @@ -506,11 +503,13 @@ class FormulaParser continue; } - if (!( - (($nextToken->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) && ($nextToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_START)) || + if ( + !( + (($nextToken->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) && ($nextToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_START)) || (($nextToken->getTokenType() == FormulaToken::TOKEN_TYPE_SUBEXPRESSION) && ($nextToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_START)) || ($nextToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND) - )) { + ) + ) { continue; } @@ -529,11 +528,11 @@ class FormulaParser } else { $previousToken = null; } - if (isset($tokens2[$i + 1])) { - $nextToken = $tokens2[$i + 1]; - } else { - $nextToken = null; - } + //if (isset($tokens2[$i + 1])) { + // $nextToken = $tokens2[$i + 1]; + //} else { + // $nextToken = null; + //} if ($token === null) { continue; @@ -542,12 +541,14 @@ class FormulaParser if ($token->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORINFIX && $token->getValue() == '-') { if ($i == 0) { $token->setTokenType(FormulaToken::TOKEN_TYPE_OPERATORPREFIX); - } elseif ((($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) && + } elseif ( + (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) && ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) || (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_SUBEXPRESSION) && ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) || ($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORPOSTFIX) || - ($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND)) { + ($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND) + ) { $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_MATH); } else { $token->setTokenType(FormulaToken::TOKEN_TYPE_OPERATORPREFIX); @@ -561,12 +562,14 @@ class FormulaParser if ($token->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORINFIX && $token->getValue() == '+') { if ($i == 0) { continue; - } elseif ((($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) && + } elseif ( + (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) && ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) || (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_SUBEXPRESSION) && ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) || ($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORPOSTFIX) || - ($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND)) { + ($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND) + ) { $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_MATH); } else { continue; @@ -577,8 +580,10 @@ class FormulaParser continue; } - if ($token->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORINFIX && - $token->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_NOTHING) { + if ( + $token->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORINFIX && + $token->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_NOTHING + ) { if (strpos('<>=', substr($token->getValue(), 0, 1)) !== false) { $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_LOGICAL); } elseif ($token->getValue() == '&') { @@ -592,8 +597,10 @@ class FormulaParser continue; } - if ($token->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND && - $token->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_NOTHING) { + if ( + $token->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND && + $token->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_NOTHING + ) { if (!is_numeric($token->getValue())) { if (strtoupper($token->getValue()) == 'TRUE' || strtoupper($token->getValue()) == 'FALSE') { $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_LOGICAL); diff --git a/PhpOffice/PhpSpreadsheet/Calculation/FormulaToken.php b/PhpOffice/PhpSpreadsheet/Calculation/FormulaToken.php old mode 100755 new mode 100644 index 66618d4..68e5eea --- a/PhpOffice/PhpSpreadsheet/Calculation/FormulaToken.php +++ b/PhpOffice/PhpSpreadsheet/Calculation/FormulaToken.php @@ -76,16 +76,16 @@ class FormulaToken /** * Create a new FormulaToken. * - * @param string $pValue - * @param string $pTokenType Token type (represented by TOKEN_TYPE_*) - * @param string $pTokenSubType Token Subtype (represented by TOKEN_SUBTYPE_*) + * @param string $value + * @param string $tokenType Token type (represented by TOKEN_TYPE_*) + * @param string $tokenSubType Token Subtype (represented by TOKEN_SUBTYPE_*) */ - public function __construct($pValue, $pTokenType = self::TOKEN_TYPE_UNKNOWN, $pTokenSubType = self::TOKEN_SUBTYPE_NOTHING) + public function __construct($value, $tokenType = self::TOKEN_TYPE_UNKNOWN, $tokenSubType = self::TOKEN_SUBTYPE_NOTHING) { // Initialise values - $this->value = $pValue; - $this->tokenType = $pTokenType; - $this->tokenSubType = $pTokenSubType; + $this->value = $value; + $this->tokenType = $tokenType; + $this->tokenSubType = $tokenSubType; } /** @@ -103,7 +103,7 @@ class FormulaToken * * @param string $value */ - public function setValue($value) + public function setValue($value): void { $this->value = $value; } @@ -123,7 +123,7 @@ class FormulaToken * * @param string $value */ - public function setTokenType($value) + public function setTokenType($value): void { $this->tokenType = $value; } @@ -143,7 +143,7 @@ class FormulaToken * * @param string $value */ - public function setTokenSubType($value) + public function setTokenSubType($value): void { $this->tokenSubType = $value; } diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Functions.php b/PhpOffice/PhpSpreadsheet/Calculation/Functions.php old mode 100755 new mode 100644 index 40e7d96..747a1f4 --- a/PhpOffice/PhpSpreadsheet/Calculation/Functions.php +++ b/PhpOffice/PhpSpreadsheet/Calculation/Functions.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheet\Calculation; use PhpOffice\PhpSpreadsheet\Cell\Cell; +use PhpOffice\PhpSpreadsheet\Shared\Date; class Functions { @@ -13,12 +14,13 @@ class Functions */ const M_2DIVPI = 0.63661977236758134307553505349006; - /** constants */ const COMPATIBILITY_EXCEL = 'Excel'; const COMPATIBILITY_GNUMERIC = 'Gnumeric'; const COMPATIBILITY_OPENOFFICE = 'OpenOfficeCalc'; + /** Use of RETURNDATE_PHP_NUMERIC is discouraged - not 32-bit Y2038-safe, no timezone. */ const RETURNDATE_PHP_NUMERIC = 'P'; + /** Use of RETURNDATE_UNIX_TIMESTAMP is discouraged - not 32-bit Y2038-safe, no timezone. */ const RETURNDATE_UNIX_TIMESTAMP = 'P'; const RETURNDATE_PHP_OBJECT = 'O'; const RETURNDATE_PHP_DATETIME_OBJECT = 'O'; @@ -38,38 +40,21 @@ class Functions */ protected static $returnDateType = self::RETURNDATE_EXCEL; - /** - * List of error codes. - * - * @var array - */ - protected static $errorCodes = [ - 'null' => '#NULL!', - 'divisionbyzero' => '#DIV/0!', - 'value' => '#VALUE!', - 'reference' => '#REF!', - 'name' => '#NAME?', - 'num' => '#NUM!', - 'na' => '#N/A', - 'gettingdata' => '#GETTING_DATA', - ]; - /** * Set the Compatibility Mode. * - * @category Function Configuration - * * @param string $compatibilityMode Compatibility Mode - * Permitted values are: - * Functions::COMPATIBILITY_EXCEL 'Excel' - * Functions::COMPATIBILITY_GNUMERIC 'Gnumeric' - * Functions::COMPATIBILITY_OPENOFFICE 'OpenOfficeCalc' + * Permitted values are: + * Functions::COMPATIBILITY_EXCEL 'Excel' + * Functions::COMPATIBILITY_GNUMERIC 'Gnumeric' + * Functions::COMPATIBILITY_OPENOFFICE 'OpenOfficeCalc' * * @return bool (Success or Failure) */ public static function setCompatibilityMode($compatibilityMode) { - if (($compatibilityMode == self::COMPATIBILITY_EXCEL) || + if ( + ($compatibilityMode == self::COMPATIBILITY_EXCEL) || ($compatibilityMode == self::COMPATIBILITY_GNUMERIC) || ($compatibilityMode == self::COMPATIBILITY_OPENOFFICE) ) { @@ -84,13 +69,11 @@ class Functions /** * Return the current Compatibility Mode. * - * @category Function Configuration - * * @return string Compatibility Mode - * Possible Return values are: - * Functions::COMPATIBILITY_EXCEL 'Excel' - * Functions::COMPATIBILITY_GNUMERIC 'Gnumeric' - * Functions::COMPATIBILITY_OPENOFFICE 'OpenOfficeCalc' + * Possible Return values are: + * Functions::COMPATIBILITY_EXCEL 'Excel' + * Functions::COMPATIBILITY_GNUMERIC 'Gnumeric' + * Functions::COMPATIBILITY_OPENOFFICE 'OpenOfficeCalc' */ public static function getCompatibilityMode() { @@ -98,21 +81,20 @@ class Functions } /** - * Set the Return Date Format used by functions that return a date/time (Excel, PHP Serialized Numeric or PHP Object). - * - * @category Function Configuration + * Set the Return Date Format used by functions that return a date/time (Excel, PHP Serialized Numeric or PHP DateTime Object). * * @param string $returnDateType Return Date Format - * Permitted values are: - * Functions::RETURNDATE_UNIX_TIMESTAMP 'P' - * Functions::RETURNDATE_PHP_DATETIME_OBJECT 'O' - * Functions::RETURNDATE_EXCEL 'E' + * Permitted values are: + * Functions::RETURNDATE_UNIX_TIMESTAMP 'P' + * Functions::RETURNDATE_PHP_DATETIME_OBJECT 'O' + * Functions::RETURNDATE_EXCEL 'E' * * @return bool Success or failure */ public static function setReturnDateType($returnDateType) { - if (($returnDateType == self::RETURNDATE_UNIX_TIMESTAMP) || + if ( + ($returnDateType == self::RETURNDATE_UNIX_TIMESTAMP) || ($returnDateType == self::RETURNDATE_PHP_DATETIME_OBJECT) || ($returnDateType == self::RETURNDATE_EXCEL) ) { @@ -127,13 +109,11 @@ class Functions /** * Return the current Return Date Format for functions that return a date/time (Excel, PHP Serialized Numeric or PHP Object). * - * @category Function Configuration - * * @return string Return Date Format - * Possible Return values are: - * Functions::RETURNDATE_UNIX_TIMESTAMP 'P' - * Functions::RETURNDATE_PHP_DATETIME_OBJECT 'O' - * Functions::RETURNDATE_EXCEL 'E' + * Possible Return values are: + * Functions::RETURNDATE_UNIX_TIMESTAMP 'P' + * Functions::RETURNDATE_PHP_DATETIME_OBJECT 'O' + * Functions::RETURNDATE_EXCEL ' 'E' */ public static function getReturnDateType() { @@ -143,8 +123,6 @@ class Functions /** * DUMMY. * - * @category Error Returns - * * @return string #Not Yet Implemented */ public static function DUMMY() @@ -152,16 +130,120 @@ class Functions return '#Not Yet Implemented'; } - /** - * DIV0. - * - * @category Error Returns - * - * @return string #Not Yet Implemented - */ - public static function DIV0() + public static function isMatrixValue($idx) { - return self::$errorCodes['divisionbyzero']; + return (substr_count($idx, '.') <= 1) || (preg_match('/\.[A-Z]/', $idx) > 0); + } + + public static function isValue($idx) + { + return substr_count($idx, '.') === 0; + } + + public static function isCellValue($idx) + { + return substr_count($idx, '.') > 1; + } + + public static function ifCondition($condition) + { + $condition = self::flattenSingleValue($condition); + + if ($condition === '') { + return '=""'; + } + if (!is_string($condition) || !in_array($condition[0], ['>', '<', '='], true)) { + $condition = self::operandSpecialHandling($condition); + if (is_bool($condition)) { + return '=' . ($condition ? 'TRUE' : 'FALSE'); + } elseif (!is_numeric($condition)) { + if ($condition !== '""') { // Not an empty string + // Escape any quotes in the string value + $condition = (string) preg_replace('/"/ui', '""', $condition); + } + $condition = Calculation::wrapResult(strtoupper($condition)); + } + + return str_replace('""""', '""', '=' . $condition); + } + preg_match('/(=|<[>=]?|>=?)(.*)/', $condition, $matches); + [, $operator, $operand] = $matches; + + $operand = self::operandSpecialHandling($operand); + if (is_numeric(trim($operand, '"'))) { + $operand = trim($operand, '"'); + } elseif (!is_numeric($operand) && $operand !== 'FALSE' && $operand !== 'TRUE') { + $operand = str_replace('"', '""', $operand); + $operand = Calculation::wrapResult(strtoupper($operand)); + } + + return str_replace('""""', '""', $operator . $operand); + } + + private static function operandSpecialHandling($operand) + { + if (is_numeric($operand) || is_bool($operand)) { + return $operand; + } elseif (strtoupper($operand) === Calculation::getTRUE() || strtoupper($operand) === Calculation::getFALSE()) { + return strtoupper($operand); + } + + // Check for percentage + if (preg_match('/^\-?\d*\.?\d*\s?\%$/', $operand)) { + return ((float) rtrim($operand, '%')) / 100; + } + + // Check for dates + if (($dateValueOperand = Date::stringToExcel($operand)) !== false) { + return $dateValueOperand; + } + + return $operand; + } + + /** + * NULL. + * + * Returns the error value #NULL! + * + * @deprecated 1.23.0 Use the null() method in the Information\ExcelError class instead + * @see Information\ExcelError::null() + * + * @return string #NULL! + */ + public static function null() + { + return Information\ExcelError::null(); + } + + /** + * NaN. + * + * Returns the error value #NUM! + * + * @deprecated 1.23.0 Use the NAN() method in the Information\Error class instead + * @see Information\ExcelError::NAN() + * + * @return string #NUM! + */ + public static function NAN() + { + return Information\ExcelError::NAN(); + } + + /** + * REF. + * + * Returns the error value #REF! + * + * @deprecated 1.23.0 Use the REF() method in the Information\ExcelError class instead + * @see Information\ExcelError::REF() + * + * @return string #REF! + */ + public static function REF() + { + return Information\ExcelError::REF(); } /** @@ -173,69 +255,14 @@ class Functions * Returns the error value #N/A * #N/A is the error value that means "no value is available." * - * @category Logical Functions + * @deprecated 1.23.0 Use the NA() method in the Information\ExcelError class instead + * @see Information\ExcelError::NA() * * @return string #N/A! */ public static function NA() { - return self::$errorCodes['na']; - } - - /** - * NaN. - * - * Returns the error value #NUM! - * - * @category Error Returns - * - * @return string #NUM! - */ - public static function NAN() - { - return self::$errorCodes['num']; - } - - /** - * NAME. - * - * Returns the error value #NAME? - * - * @category Error Returns - * - * @return string #NAME? - */ - public static function NAME() - { - return self::$errorCodes['name']; - } - - /** - * REF. - * - * Returns the error value #REF! - * - * @category Error Returns - * - * @return string #REF! - */ - public static function REF() - { - return self::$errorCodes['reference']; - } - - /** - * NULL. - * - * Returns the error value #NULL! - * - * @category Error Returns - * - * @return string #NULL! - */ - public static function null() - { - return self::$errorCodes['null']; + return Information\ExcelError::NA(); } /** @@ -243,54 +270,42 @@ class Functions * * Returns the error value #VALUE! * - * @category Error Returns + * @deprecated 1.23.0 Use the VALUE() method in the Information\ExcelError class instead + * @see Information\ExcelError::VALUE() * * @return string #VALUE! */ public static function VALUE() { - return self::$errorCodes['value']; + return Information\ExcelError::VALUE(); } - public static function isMatrixValue($idx) + /** + * NAME. + * + * Returns the error value #NAME? + * + * @deprecated 1.23.0 Use the NAME() method in the Information\ExcelError class instead + * @see Information\ExcelError::NAME() + * + * @return string #NAME? + */ + public static function NAME() { - return (substr_count($idx, '.') <= 1) || (preg_match('/\.[A-Z]/', $idx) > 0); + return Information\ExcelError::NAME(); } - public static function isValue($idx) + /** + * DIV0. + * + * @deprecated 1.23.0 Use the DIV0() method in the Information\ExcelError class instead + * @see Information\ExcelError::DIV0() + * + * @return string #Not Yet Implemented + */ + public static function DIV0() { - return substr_count($idx, '.') == 0; - } - - public static function isCellValue($idx) - { - return substr_count($idx, '.') > 1; - } - - public static function ifCondition($condition) - { - $condition = self::flattenSingleValue($condition); - if (!isset($condition[0]) && !is_numeric($condition)) { - $condition = '=""'; - } - if (!in_array($condition[0], ['>', '<', '='])) { - if (!is_numeric($condition)) { - $condition = Calculation::wrapResult(strtoupper($condition)); - } - - return '=' . $condition; - } - preg_match('/(=|<[>=]?|>=?)(.*)/', $condition, $matches); - [, $operator, $operand] = $matches; - - if (is_numeric(trim($operand, '"'))) { - $operand = trim($operand, '"'); - } elseif (!is_numeric($operand)) { - $operand = str_replace('"', '""', $operand); - $operand = Calculation::wrapResult(strtoupper($operand)); - } - - return $operator . $operand; + return Information\ExcelError::DIV0(); } /** @@ -298,21 +313,14 @@ class Functions * * @param mixed $value Value to check * - * @return bool + * @deprecated 1.23.0 Use the type() method in the Information\ExcelError class instead + * @see Information\ExcelError::type() + * + * @return array|int|string */ public static function errorType($value = '') { - $value = self::flattenSingleValue($value); - - $i = 1; - foreach (self::$errorCodes as $errorCode) { - if ($value === $errorCode) { - return $i; - } - ++$i; - } - - return self::NA(); + return Information\ExcelError::type($value); } /** @@ -320,15 +328,14 @@ class Functions * * @param mixed $value Value to check * - * @return bool + * @deprecated 1.23.0 Use the isBlank() method in the Information\Value class instead + * @see Information\Value::isBlank() + * + * @return array|bool */ public static function isBlank($value = null) { - if ($value !== null) { - $value = self::flattenSingleValue($value); - } - - return $value === null; + return Information\Value::isBlank($value); } /** @@ -336,13 +343,14 @@ class Functions * * @param mixed $value Value to check * - * @return bool + * @deprecated 1.23.0 Use the isErr() method in the Information\ErrorValue class instead + * @see Information\ErrorValue::isErr() + * + * @return array|bool */ public static function isErr($value = '') { - $value = self::flattenSingleValue($value); - - return self::isError($value) && (!self::isNa(($value))); + return Information\ErrorValue::isErr($value); } /** @@ -350,17 +358,14 @@ class Functions * * @param mixed $value Value to check * - * @return bool + * @deprecated 1.23.0 Use the isError() method in the Information\ErrorValue class instead + * @see Information\ErrorValue::isError() + * + * @return array|bool */ public static function isError($value = '') { - $value = self::flattenSingleValue($value); - - if (!is_string($value)) { - return false; - } - - return in_array($value, self::$errorCodes); + return Information\ErrorValue::isError($value); } /** @@ -368,13 +373,14 @@ class Functions * * @param mixed $value Value to check * - * @return bool + * @deprecated 1.23.0 Use the isNa() method in the Information\ErrorValue class instead + * @see Information\ErrorValue::isNa() + * + * @return array|bool */ public static function isNa($value = '') { - $value = self::flattenSingleValue($value); - - return $value === self::NA(); + return Information\ErrorValue::isNa($value); } /** @@ -382,19 +388,14 @@ class Functions * * @param mixed $value Value to check * - * @return bool|string + * @deprecated 1.23.0 Use the isEven() method in the Information\Value class instead + * @see Information\Value::isEven() + * + * @return array|bool|string */ public static function isEven($value = null) { - $value = self::flattenSingleValue($value); - - if ($value === null) { - return self::NAME(); - } elseif ((is_bool($value)) || ((is_string($value)) && (!is_numeric($value)))) { - return self::VALUE(); - } - - return $value % 2 == 0; + return Information\Value::isEven($value); } /** @@ -402,19 +403,14 @@ class Functions * * @param mixed $value Value to check * - * @return bool|string + * @deprecated 1.23.0 Use the isOdd() method in the Information\Value class instead + * @see Information\Value::isOdd() + * + * @return array|bool|string */ public static function isOdd($value = null) { - $value = self::flattenSingleValue($value); - - if ($value === null) { - return self::NAME(); - } elseif ((is_bool($value)) || ((is_string($value)) && (!is_numeric($value)))) { - return self::VALUE(); - } - - return abs($value) % 2 == 1; + return Information\Value::isOdd($value); } /** @@ -422,17 +418,14 @@ class Functions * * @param mixed $value Value to check * - * @return bool + * @deprecated 1.23.0 Use the isNumber() method in the Information\Value class instead + * @see Information\Value::isNumber() + * + * @return array|bool */ public static function isNumber($value = null) { - $value = self::flattenSingleValue($value); - - if (is_string($value)) { - return false; - } - - return is_numeric($value); + return Information\Value::isNumber($value); } /** @@ -440,13 +433,14 @@ class Functions * * @param mixed $value Value to check * - * @return bool + * @deprecated 1.23.0 Use the isLogical() method in the Information\Value class instead + * @see Information\Value::isLogical() + * + * @return array|bool */ public static function isLogical($value = null) { - $value = self::flattenSingleValue($value); - - return is_bool($value); + return Information\Value::isLogical($value); } /** @@ -454,13 +448,14 @@ class Functions * * @param mixed $value Value to check * - * @return bool + * @deprecated 1.23.0 Use the isText() method in the Information\Value class instead + * @see Information\Value::isText() + * + * @return array|bool */ public static function isText($value = null) { - $value = self::flattenSingleValue($value); - - return is_string($value) && !self::isError($value); + return Information\Value::isText($value); } /** @@ -468,11 +463,14 @@ class Functions * * @param mixed $value Value to check * - * @return bool + * @deprecated 1.23.0 Use the isNonText() method in the Information\Value class instead + * @see Information\Value::isNonText() + * + * @return array|bool */ public static function isNonText($value = null) { - return !self::isText($value); + return Information\Value::isNonText($value); } /** @@ -480,9 +478,12 @@ class Functions * * Returns a value converted to a number * + * @deprecated 1.23.0 Use the asNumber() method in the Information\Value class instead + * @see Information\Value::asNumber() + * * @param null|mixed $value The value you want converted * - * @return number N converts values listed in the following table + * @return number|string N converts values listed in the following table * If value is or refers to N returns * A number That number * A date The serial number of that date @@ -493,27 +494,7 @@ class Functions */ public static function n($value = null) { - while (is_array($value)) { - $value = array_shift($value); - } - - switch (gettype($value)) { - case 'double': - case 'float': - case 'integer': - return $value; - case 'boolean': - return (int) $value; - case 'string': - // Errors - if ((strlen($value) > 0) && ($value[0] == '#')) { - return $value; - } - - break; - } - - return 0; + return Information\Value::asNumber($value); } /** @@ -521,6 +502,9 @@ class Functions * * Returns a number that identifies the type of a value * + * @deprecated 1.23.0 Use the type() method in the Information\Value class instead + * @see Information\Value::type() + * * @param null|mixed $value The value you want tested * * @return number N converts values listed in the following table @@ -533,45 +517,13 @@ class Functions */ public static function TYPE($value = null) { - $value = self::flattenArrayIndexed($value); - if (is_array($value) && (count($value) > 1)) { - end($value); - $a = key($value); - // Range of cells is an error - if (self::isCellValue($a)) { - return 16; - // Test for Matrix - } elseif (self::isMatrixValue($a)) { - return 64; - } - } elseif (empty($value)) { - // Empty Cell - return 1; - } - $value = self::flattenSingleValue($value); - - if (($value === null) || (is_float($value)) || (is_int($value))) { - return 1; - } elseif (is_bool($value)) { - return 4; - } elseif (is_array($value)) { - return 64; - } elseif (is_string($value)) { - // Errors - if ((strlen($value) > 0) && ($value[0] == '#')) { - return 16; - } - - return 2; - } - - return 0; + return Information\Value::type($value); } /** * Convert a multi-dimensional array to a simple 1-dimensional array. * - * @param array $array Array to be flattened + * @param array|mixed $array Array to be flattened * * @return array Flattened array */ @@ -581,30 +533,44 @@ class Functions return (array) $array; } - $arrayValues = []; - foreach ($array as $value) { + $flattened = []; + $stack = array_values($array); + + while (!empty($stack)) { + $value = array_shift($stack); + if (is_array($value)) { - foreach ($value as $val) { - if (is_array($val)) { - foreach ($val as $v) { - $arrayValues[] = $v; - } - } else { - $arrayValues[] = $val; - } - } + array_unshift($stack, ...array_values($value)); } else { - $arrayValues[] = $value; + $flattened[] = $value; } } - return $arrayValues; + return $flattened; + } + + /** + * @param mixed $value + * + * @return null|mixed + */ + public static function scalar($value) + { + if (!is_array($value)) { + return $value; + } + + do { + $value = array_pop($value); + } while (is_array($value)); + + return $value; } /** * Convert a multi-dimensional array to a simple 1-dimensional array, but retain an element of indexing. * - * @param array $array Array to be flattened + * @param array|mixed $array Array to be flattened * * @return array Flattened array */ @@ -644,7 +610,7 @@ class Functions public static function flattenSingleValue($value = '') { while (is_array($value)) { - $value = array_pop($value); + $value = array_shift($value); } return $value; @@ -653,26 +619,50 @@ class Functions /** * ISFORMULA. * - * @param mixed $cellReference The cell to check - * @param Cell $pCell The current cell (containing this formula) + * @deprecated 1.23.0 Use the isFormula() method in the Information\Value class instead + * @see Information\Value::isFormula() * - * @return bool|string + * @param mixed $cellReference The cell to check + * @param ?Cell $cell The current cell (containing this formula) + * + * @return array|bool|string */ - public static function isFormula($cellReference = '', Cell $pCell = null) + public static function isFormula($cellReference = '', ?Cell $cell = null) { - if ($pCell === null) { - return self::REF(); + return Information\Value::isFormula($cellReference, $cell); + } + + public static function expandDefinedName(string $coordinate, Cell $cell): string + { + $worksheet = $cell->getWorksheet(); + $spreadsheet = $worksheet->getParent(); + // Uppercase coordinate + $pCoordinatex = strtoupper($coordinate); + // Eliminate leading equal sign + $pCoordinatex = (string) preg_replace('/^=/', '', $pCoordinatex); + $defined = $spreadsheet->getDefinedName($pCoordinatex, $worksheet); + if ($defined !== null) { + $worksheet2 = $defined->getWorkSheet(); + if (!$defined->isFormula() && $worksheet2 !== null) { + $coordinate = "'" . $worksheet2->getTitle() . "'!" . + (string) preg_replace('/^=/', '', str_replace('$', '', $defined->getValue())); + } } - preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellReference, $matches); + return $coordinate; + } - $cellReference = $matches[6] . $matches[7]; - $worksheetName = trim($matches[3], "'"); + public static function trimTrailingRange(string $coordinate): string + { + return (string) preg_replace('/:[\\w\$]+$/', '', $coordinate); + } - $worksheet = (!empty($worksheetName)) - ? $pCell->getWorksheet()->getParent()->getSheetByName($worksheetName) - : $pCell->getWorksheet(); + public static function trimSheetFromCellReference(string $coordinate): string + { + if (strpos($coordinate, '!') !== false) { + $coordinate = substr($coordinate, strrpos($coordinate, '!') + 1); + } - return $worksheet->getCell($cellReference)->isFormula(); + return $coordinate; } } diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Information/ErrorValue.php b/PhpOffice/PhpSpreadsheet/Calculation/Information/ErrorValue.php new file mode 100644 index 0000000..4b9f818 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Information/ErrorValue.php @@ -0,0 +1,71 @@ + + */ + public const ERROR_CODES = [ + 'null' => '#NULL!', // 1 + 'divisionbyzero' => '#DIV/0!', // 2 + 'value' => '#VALUE!', // 3 + 'reference' => '#REF!', // 4 + 'name' => '#NAME?', // 5 + 'num' => '#NUM!', // 6 + 'na' => '#N/A', // 7 + 'gettingdata' => '#GETTING_DATA', // 8 + 'spill' => '#SPILL!', // 9 + 'connect' => '#CONNECT!', //10 + 'blocked' => '#BLOCKED!', //11 + 'unknown' => '#UNKNOWN!', //12 + 'field' => '#FIELD!', //13 + 'calculation' => '#CALC!', //14 + ]; + + /** + * List of error codes. Replaced by constant; + * previously it was public and updateable, allowing + * user to make inappropriate alterations. + * + * @deprecated 1.25.0 Use ERROR_CODES constant instead. + * + * @var array + */ + public static $errorCodes = self::ERROR_CODES; + + /** + * @param mixed $value + */ + public static function throwError($value): string + { + return in_array($value, self::ERROR_CODES, true) ? $value : self::ERROR_CODES['value']; + } + + /** + * ERROR_TYPE. + * + * @param mixed $value Value to check + * + * @return array|int|string + */ + public static function type($value = '') + { + if (is_array($value)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value); + } + + $i = 1; + foreach (self::ERROR_CODES as $errorCode) { + if ($value === $errorCode) { + return $i; + } + ++$i; + } + + return self::NA(); + } + + /** + * NULL. + * + * Returns the error value #NULL! + * + * @return string #NULL! + */ + public static function null(): string + { + return self::ERROR_CODES['null']; + } + + /** + * NaN. + * + * Returns the error value #NUM! + * + * @return string #NUM! + */ + public static function NAN(): string + { + return self::ERROR_CODES['num']; + } + + /** + * REF. + * + * Returns the error value #REF! + * + * @return string #REF! + */ + public static function REF(): string + { + return self::ERROR_CODES['reference']; + } + + /** + * NA. + * + * Excel Function: + * =NA() + * + * Returns the error value #N/A + * #N/A is the error value that means "no value is available." + * + * @return string #N/A! + */ + public static function NA(): string + { + return self::ERROR_CODES['na']; + } + + /** + * VALUE. + * + * Returns the error value #VALUE! + * + * @return string #VALUE! + */ + public static function VALUE(): string + { + return self::ERROR_CODES['value']; + } + + /** + * NAME. + * + * Returns the error value #NAME? + * + * @return string #NAME? + */ + public static function NAME(): string + { + return self::ERROR_CODES['name']; + } + + /** + * DIV0. + * + * @return string #DIV/0! + */ + public static function DIV0(): string + { + return self::ERROR_CODES['divisionbyzero']; + } + + /** + * CALC. + * + * @return string #CALC! + */ + public static function CALC(): string + { + return self::ERROR_CODES['calculation']; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Information/Value.php b/PhpOffice/PhpSpreadsheet/Calculation/Information/Value.php new file mode 100644 index 0000000..2e524db --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Information/Value.php @@ -0,0 +1,328 @@ +getCoordinate()) { + return false; + } + + $cellValue = Functions::trimTrailingRange($value); + if (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/ui', $cellValue) === 1) { + [$worksheet, $cellValue] = Worksheet::extractSheetTitle($cellValue, true); + if (!empty($worksheet) && $cell->getWorksheet()->getParent()->getSheetByName($worksheet) === null) { + return false; + } + [$column, $row] = Coordinate::indexesFromString($cellValue); + if ($column > 16384 || $row > 1048576) { + return false; + } + + return true; + } + + $namedRange = $cell->getWorksheet()->getParent()->getNamedRange($value); + + return $namedRange instanceof NamedRange; + } + + /** + * IS_EVEN. + * + * @param mixed $value Value to check + * Or can be an array of values + * + * @return array|bool|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function isEven($value = null) + { + if (is_array($value)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value); + } + + if ($value === null) { + return ExcelError::NAME(); + } elseif ((is_bool($value)) || ((is_string($value)) && (!is_numeric($value)))) { + return ExcelError::VALUE(); + } + + return ((int) fmod($value, 2)) === 0; + } + + /** + * IS_ODD. + * + * @param mixed $value Value to check + * Or can be an array of values + * + * @return array|bool|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function isOdd($value = null) + { + if (is_array($value)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value); + } + + if ($value === null) { + return ExcelError::NAME(); + } elseif ((is_bool($value)) || ((is_string($value)) && (!is_numeric($value)))) { + return ExcelError::VALUE(); + } + + return ((int) fmod($value, 2)) !== 0; + } + + /** + * IS_NUMBER. + * + * @param mixed $value Value to check + * Or can be an array of values + * + * @return array|bool + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function isNumber($value = null) + { + if (is_array($value)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value); + } + + if (is_string($value)) { + return false; + } + + return is_numeric($value); + } + + /** + * IS_LOGICAL. + * + * @param mixed $value Value to check + * Or can be an array of values + * + * @return array|bool + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function isLogical($value = null) + { + if (is_array($value)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value); + } + + return is_bool($value); + } + + /** + * IS_TEXT. + * + * @param mixed $value Value to check + * Or can be an array of values + * + * @return array|bool + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function isText($value = null) + { + if (is_array($value)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value); + } + + return is_string($value) && !ErrorValue::isError($value); + } + + /** + * IS_NONTEXT. + * + * @param mixed $value Value to check + * Or can be an array of values + * + * @return array|bool + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function isNonText($value = null) + { + if (is_array($value)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value); + } + + return !self::isText($value); + } + + /** + * ISFORMULA. + * + * @param mixed $cellReference The cell to check + * @param ?Cell $cell The current cell (containing this formula) + * + * @return array|bool|string + */ + public static function isFormula($cellReference = '', ?Cell $cell = null) + { + if ($cell === null) { + return ExcelError::REF(); + } + + $fullCellReference = Functions::expandDefinedName((string) $cellReference, $cell); + + if (strpos($cellReference, '!') !== false) { + $cellReference = Functions::trimSheetFromCellReference($cellReference); + $cellReferences = Coordinate::extractAllCellReferencesInRange($cellReference); + if (count($cellReferences) > 1) { + return self::evaluateArrayArgumentsSubset([self::class, __FUNCTION__], 1, $cellReferences, $cell); + } + } + + $fullCellReference = Functions::trimTrailingRange($fullCellReference); + + preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $fullCellReference, $matches); + + $fullCellReference = $matches[6] . $matches[7]; + $worksheetName = str_replace("''", "'", trim($matches[2], "'")); + + $worksheet = (!empty($worksheetName)) + ? $cell->getWorksheet()->getParent()->getSheetByName($worksheetName) + : $cell->getWorksheet(); + + return ($worksheet !== null) ? $worksheet->getCell($fullCellReference)->isFormula() : ExcelError::REF(); + } + + /** + * N. + * + * Returns a value converted to a number + * + * @param null|mixed $value The value you want converted + * + * @return number|string N converts values listed in the following table + * If value is or refers to N returns + * A number That number value + * A date The Excel serialized number of that date + * TRUE 1 + * FALSE 0 + * An error value The error value + * Anything else 0 + */ + public static function asNumber($value = null) + { + while (is_array($value)) { + $value = array_shift($value); + } + + switch (gettype($value)) { + case 'double': + case 'float': + case 'integer': + return $value; + case 'boolean': + return (int) $value; + case 'string': + // Errors + if ((strlen($value) > 0) && ($value[0] == '#')) { + return $value; + } + + break; + } + + return 0; + } + + /** + * TYPE. + * + * Returns a number that identifies the type of a value + * + * @param null|mixed $value The value you want tested + * + * @return number N converts values listed in the following table + * If value is or refers to N returns + * A number 1 + * Text 2 + * Logical Value 4 + * An error value 16 + * Array or Matrix 64 + */ + public static function type($value = null) + { + $value = Functions::flattenArrayIndexed($value); + if (is_array($value) && (count($value) > 1)) { + end($value); + $a = key($value); + // Range of cells is an error + if (Functions::isCellValue($a)) { + return 16; + // Test for Matrix + } elseif (Functions::isMatrixValue($a)) { + return 64; + } + } elseif (empty($value)) { + // Empty Cell + return 1; + } + + $value = Functions::flattenSingleValue($value); + if (($value === null) || (is_float($value)) || (is_int($value))) { + return 1; + } elseif (is_bool($value)) { + return 4; + } elseif (is_array($value)) { + return 64; + } elseif (is_string($value)) { + // Errors + if ((strlen($value) > 0) && ($value[0] == '#')) { + return 16; + } + + return 2; + } + + return 0; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Internal/MakeMatrix.php b/PhpOffice/PhpSpreadsheet/Calculation/Internal/MakeMatrix.php new file mode 100644 index 0000000..8b53464 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Internal/MakeMatrix.php @@ -0,0 +1,11 @@ + 0) && ($returnValue == $argCount); + return Logical\Operations::logicalAnd(...$args); } /** @@ -120,10 +89,12 @@ class Logical * * Boolean arguments are treated as True or False as appropriate * Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False - * If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string holds - * the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value + * If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string + * holds the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value * - * @category Logical Functions + * @deprecated 1.17.0 + * Use the logicalOr() method in the Logical\Operations class instead + * @see Logical\Operations::logicalOr() * * @param mixed $args Data values * @@ -131,29 +102,15 @@ class Logical */ public static function logicalOr(...$args) { - $args = Functions::flattenArray($args); - - if (count($args) == 0) { - return Functions::VALUE(); - } - - $args = array_filter($args, function ($value) { - return $value !== null || (is_string($value) && trim($value) == ''); - }); - - $returnValue = self::countTrueValues($args); - if (is_string($returnValue)) { - return $returnValue; - } - - return $returnValue > 0; + return Logical\Operations::logicalOr(...$args); } /** * LOGICAL_XOR. * * 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. + * i.e. the Xor function returns TRUE if an odd number of the supplied conditions evaluate to TRUE, + * and FALSE otherwise. * * Excel Function: * =XOR(logical1[,logical2[, ...]]) @@ -163,10 +120,12 @@ class Logical * * Boolean arguments are treated as True or False as appropriate * Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False - * If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string holds - * the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value + * If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string + * holds the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value * - * @category Logical Functions + * @deprecated 1.17.0 + * Use the logicalXor() method in the Logical\Operations class instead + * @see Logical\Operations::logicalXor() * * @param mixed $args Data values * @@ -174,22 +133,7 @@ class Logical */ public static function logicalXor(...$args) { - $args = Functions::flattenArray($args); - - if (count($args) == 0) { - return Functions::VALUE(); - } - - $args = array_filter($args, function ($value) { - return $value !== null || (is_string($value) && trim($value) == ''); - }); - - $returnValue = self::countTrueValues($args); - if (is_string($returnValue)) { - return $returnValue; - } - - return $returnValue % 2 == 1; + return Logical\Operations::logicalXor(...$args); } /** @@ -204,31 +148,20 @@ class Logical * * Boolean arguments are treated as True or False as appropriate * Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False - * If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string holds - * the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value + * If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string + * holds the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value * - * @category Logical Functions + * @deprecated 1.17.0 + * Use the NOT() method in the Logical\Operations class instead + * @see Logical\Operations::NOT() * * @param mixed $logical A value or expression that can be evaluated to TRUE or FALSE * - * @return bool|string the boolean inverse of the argument + * @return array|bool|string the boolean inverse of the argument */ public static function NOT($logical = false) { - $logical = Functions::flattenSingleValue($logical); - - if (is_string($logical)) { - $logical = strtoupper($logical); - if (($logical == 'TRUE') || ($logical == Calculation::getTRUE())) { - return false; - } elseif (($logical == 'FALSE') || ($logical == Calculation::getFALSE())) { - return true; - } - - return Functions::VALUE(); - } - - return !$logical; + return Logical\Operations::NOT($logical); } /** @@ -244,19 +177,21 @@ class Logical * the expression evaluates to TRUE. Otherwise, the expression evaluates to FALSE. * This argument can use any comparison calculation operator. * ReturnIfTrue is the value that is returned if condition evaluates to TRUE. - * For example, if this argument is the text string "Within budget" and the condition argument evaluates to TRUE, - * then the IF function returns the text "Within budget" - * If condition is TRUE and ReturnIfTrue is blank, this argument returns 0 (zero). To display the word TRUE, use - * the logical value TRUE for this argument. + * For example, if this argument is the text string "Within budget" and the condition argument + * evaluates to TRUE, then the IF function returns the text "Within budget" + * If condition is TRUE and ReturnIfTrue is blank, this argument returns 0 (zero). + * To display the word TRUE, use the logical value TRUE for this argument. * ReturnIfTrue can be another formula. * ReturnIfFalse is the value that is returned if condition evaluates to FALSE. - * For example, if this argument is the text string "Over budget" and the condition argument evaluates to FALSE, - * then the IF function returns the text "Over budget". + * For example, if this argument is the text string "Over budget" and the condition argument + * evaluates to FALSE, then the IF function returns the text "Over budget". * If condition is FALSE and ReturnIfFalse is omitted, then the logical value FALSE is returned. * If condition is FALSE and ReturnIfFalse is blank, then the value 0 (zero) is returned. * ReturnIfFalse can be another formula. * - * @category Logical Functions + * @deprecated 1.17.0 + * Use the statementIf() method in the Logical\Conditional class instead + * @see Logical\Conditional::statementIf() * * @param mixed $condition Condition to evaluate * @param mixed $returnIfTrue Value to return when condition is true @@ -266,11 +201,7 @@ class Logical */ public static function statementIf($condition = true, $returnIfTrue = 0, $returnIfFalse = false) { - $condition = ($condition === null) ? true : (bool) Functions::flattenSingleValue($condition); - $returnIfTrue = ($returnIfTrue === null) ? 0 : Functions::flattenSingleValue($returnIfTrue); - $returnIfFalse = ($returnIfFalse === null) ? false : Functions::flattenSingleValue($returnIfFalse); - - return ($condition) ? $returnIfTrue : $returnIfFalse; + return Logical\Conditional::statementIf($condition, $returnIfTrue, $returnIfFalse); } /** @@ -284,13 +215,18 @@ class Logical * Expression * The expression to compare to a list of values. * value1, value2, ... value_n - * A list of values that are compared to expression. The SWITCH function is looking for the first value that matches the expression. + * A list of values that are compared to expression. + * The SWITCH function is looking for the first value that matches the expression. * result1, result2, ... result_n - * A list of results. The SWITCH function returns the corresponding result when a value matches expression. + * A list of results. The SWITCH function returns the corresponding result when a value + * matches expression. * default - * Optional. It is the default to return if expression does not match any of the values (value1, value2, ... value_n). + * Optional. It is the default to return if expression does not match any of the values + * (value1, value2, ... value_n). * - * @category Logical Functions + * @deprecated 1.17.0 + * Use the statementSwitch() method in the Logical\Conditional class instead + * @see Logical\Conditional::statementSwitch() * * @param mixed $arguments Statement arguments * @@ -298,33 +234,7 @@ class Logical */ public static function statementSwitch(...$arguments) { - $result = Functions::VALUE(); - - if (count($arguments) > 0) { - $targetValue = Functions::flattenSingleValue($arguments[0]); - $argc = count($arguments) - 1; - $switchCount = floor($argc / 2); - $switchSatisfied = false; - $hasDefaultClause = $argc % 2 !== 0; - $defaultClause = $argc % 2 === 0 ? null : $arguments[count($arguments) - 1]; - - if ($switchCount) { - for ($index = 0; $index < $switchCount; ++$index) { - if ($targetValue == $arguments[$index * 2 + 1]) { - $result = $arguments[$index * 2 + 2]; - $switchSatisfied = true; - - break; - } - } - } - - if (!$switchSatisfied) { - $result = $hasDefaultClause ? $defaultClause : Functions::NA(); - } - } - - return $result; + return Logical\Conditional::statementSwitch(...$arguments); } /** @@ -333,7 +243,9 @@ class Logical * Excel Function: * =IFERROR(testValue,errorpart) * - * @category Logical Functions + * @deprecated 1.17.0 + * Use the IFERROR() method in the Logical\Conditional class instead + * @see Logical\Conditional::IFERROR() * * @param mixed $testValue Value to check, is also the value returned when no error * @param mixed $errorpart Value to return when testValue is an error condition @@ -342,10 +254,7 @@ class Logical */ public static function IFERROR($testValue = '', $errorpart = '') { - $testValue = ($testValue === null) ? '' : Functions::flattenSingleValue($testValue); - $errorpart = ($errorpart === null) ? '' : Functions::flattenSingleValue($errorpart); - - return self::statementIf(Functions::isError($testValue), $errorpart, $testValue); + return Logical\Conditional::IFERROR($testValue, $errorpart); } /** @@ -354,7 +263,9 @@ class Logical * Excel Function: * =IFNA(testValue,napart) * - * @category Logical Functions + * @deprecated 1.17.0 + * Use the IFNA() method in the Logical\Conditional class instead + * @see Logical\Conditional::IFNA() * * @param mixed $testValue Value to check, is also the value returned when not an NA * @param mixed $napart Value to return when testValue is an NA condition @@ -363,9 +274,30 @@ class Logical */ public static function IFNA($testValue = '', $napart = '') { - $testValue = ($testValue === null) ? '' : Functions::flattenSingleValue($testValue); - $napart = ($napart === null) ? '' : Functions::flattenSingleValue($napart); + return Logical\Conditional::IFNA($testValue, $napart); + } - return self::statementIf(Functions::isNa($testValue), $napart, $testValue); + /** + * IFS. + * + * Excel Function: + * =IFS(testValue1;returnIfTrue1;testValue2;returnIfTrue2;...;testValue_n;returnIfTrue_n) + * + * testValue1 ... testValue_n + * Conditions to Evaluate + * returnIfTrue1 ... returnIfTrue_n + * Value returned if corresponding testValue (nth) was true + * + * @deprecated 1.17.0 + * Use the IFS() method in the Logical\Conditional class instead + * @see Logical\Conditional::IFS() + * + * @param mixed ...$arguments Statement arguments + * + * @return mixed|string The value of returnIfTrue_n, if testValue_n was true. #N/A if none of testValues was true + */ + public static function IFS(...$arguments) + { + return Logical\Conditional::IFS(...$arguments); } } diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Logical/Boolean.php b/PhpOffice/PhpSpreadsheet/Calculation/Logical/Boolean.php new file mode 100644 index 0000000..8f1e935 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Logical/Boolean.php @@ -0,0 +1,36 @@ + 0) { + $targetValue = Functions::flattenSingleValue($arguments[0]); + $argc = count($arguments) - 1; + $switchCount = floor($argc / 2); + $hasDefaultClause = $argc % 2 !== 0; + $defaultClause = $argc % 2 === 0 ? null : $arguments[$argc]; + + $switchSatisfied = false; + if ($switchCount > 0) { + for ($index = 0; $index < $switchCount; ++$index) { + if ($targetValue == Functions::flattenSingleValue($arguments[$index * 2 + 1])) { + $result = $arguments[$index * 2 + 2]; + $switchSatisfied = true; + + break; + } + } + } + + if ($switchSatisfied !== true) { + $result = $hasDefaultClause ? $defaultClause : ExcelError::NA(); + } + } + + return $result; + } + + /** + * IFERROR. + * + * Excel Function: + * =IFERROR(testValue,errorpart) + * + * @param mixed $testValue Value to check, is also the value returned when no error + * Or can be an array of values + * @param mixed $errorpart Value to return when testValue is an error condition + * Note that this can be an array value to be returned + * + * @return mixed The value of errorpart or testValue determined by error condition + * If an array of values is passed as the $testValue argument, then the returned result will also be + * an array with the same dimensions + */ + public static function IFERROR($testValue = '', $errorpart = '') + { + if (is_array($testValue)) { + return self::evaluateArrayArgumentsSubset([self::class, __FUNCTION__], 1, $testValue, $errorpart); + } + + $errorpart = $errorpart ?? ''; + $testValue = $testValue ?? 0; // this is how Excel handles empty cell + + return self::statementIf(ErrorValue::isError($testValue), $errorpart, $testValue); + } + + /** + * IFNA. + * + * Excel Function: + * =IFNA(testValue,napart) + * + * @param mixed $testValue Value to check, is also the value returned when not an NA + * Or can be an array of values + * @param mixed $napart Value to return when testValue is an NA condition + * Note that this can be an array value to be returned + * + * @return mixed The value of errorpart or testValue determined by error condition + * If an array of values is passed as the $testValue argument, then the returned result will also be + * an array with the same dimensions + */ + public static function IFNA($testValue = '', $napart = '') + { + if (is_array($testValue)) { + return self::evaluateArrayArgumentsSubset([self::class, __FUNCTION__], 1, $testValue, $napart); + } + + $napart = $napart ?? ''; + $testValue = $testValue ?? 0; // this is how Excel handles empty cell + + return self::statementIf(ErrorValue::isNa($testValue), $napart, $testValue); + } + + /** + * IFS. + * + * Excel Function: + * =IFS(testValue1;returnIfTrue1;testValue2;returnIfTrue2;...;testValue_n;returnIfTrue_n) + * + * testValue1 ... testValue_n + * Conditions to Evaluate + * returnIfTrue1 ... returnIfTrue_n + * Value returned if corresponding testValue (nth) was true + * + * @param mixed ...$arguments Statement arguments + * Note that this can be an array value to be returned + * + * @return mixed|string The value of returnIfTrue_n, if testValue_n was true. #N/A if none of testValues was true + */ + public static function IFS(...$arguments) + { + $argumentCount = count($arguments); + + if ($argumentCount % 2 != 0) { + return ExcelError::NA(); + } + // We use instance of Exception as a falseValue in order to prevent string collision with value in cell + $falseValueException = new Exception(); + for ($i = 0; $i < $argumentCount; $i += 2) { + $testValue = ($arguments[$i] === null) ? '' : Functions::flattenSingleValue($arguments[$i]); + $returnIfTrue = ($arguments[$i + 1] === null) ? '' : $arguments[$i + 1]; + $result = self::statementIf($testValue, $returnIfTrue, $falseValueException); + + if ($result !== $falseValueException) { + return $result; + } + } + + return ExcelError::NA(); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Logical/Operations.php b/PhpOffice/PhpSpreadsheet/Calculation/Logical/Operations.php new file mode 100644 index 0000000..2e2faa1 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Logical/Operations.php @@ -0,0 +1,207 @@ + 0) && ($returnValue == $argCount); + } + + /** + * LOGICAL_OR. + * + * Returns boolean TRUE if any argument is TRUE; returns FALSE if all arguments are FALSE. + * + * Excel Function: + * =OR(logical1[,logical2[, ...]]) + * + * The arguments must evaluate to logical values such as TRUE or FALSE, or the arguments must be arrays + * or references that contain logical values. + * + * Boolean arguments are treated as True or False as appropriate + * Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False + * If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string + * holds the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value + * + * @param mixed $args Data values + * + * @return bool|string the logical OR of the arguments + */ + public static function logicalOr(...$args) + { + $args = Functions::flattenArray($args); + + if (count($args) == 0) { + return ExcelError::VALUE(); + } + + $args = array_filter($args, function ($value) { + return $value !== null || (is_string($value) && trim($value) == ''); + }); + + $returnValue = self::countTrueValues($args); + if (is_string($returnValue)) { + return $returnValue; + } + + return $returnValue > 0; + } + + /** + * LOGICAL_XOR. + * + * 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. + * + * Excel Function: + * =XOR(logical1[,logical2[, ...]]) + * + * The arguments must evaluate to logical values such as TRUE or FALSE, or the arguments must be arrays + * or references that contain logical values. + * + * Boolean arguments are treated as True or False as appropriate + * Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False + * If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string + * holds the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value + * + * @param mixed $args Data values + * + * @return bool|string the logical XOR of the arguments + */ + public static function logicalXor(...$args) + { + $args = Functions::flattenArray($args); + + if (count($args) == 0) { + return ExcelError::VALUE(); + } + + $args = array_filter($args, function ($value) { + return $value !== null || (is_string($value) && trim($value) == ''); + }); + + $returnValue = self::countTrueValues($args); + if (is_string($returnValue)) { + return $returnValue; + } + + return $returnValue % 2 == 1; + } + + /** + * NOT. + * + * Returns the boolean inverse of the argument. + * + * Excel Function: + * =NOT(logical) + * + * The argument must evaluate to a logical value such as TRUE or FALSE + * + * Boolean arguments are treated as True or False as appropriate + * Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False + * If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string + * holds the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value + * + * @param mixed $logical A value or expression that can be evaluated to TRUE or FALSE + * Or can be an array of values + * + * @return array|bool|string the boolean inverse of the argument + * If an array of values is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function NOT($logical = false) + { + if (is_array($logical)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $logical); + } + + if (is_string($logical)) { + $logical = mb_strtoupper($logical, 'UTF-8'); + if (($logical == 'TRUE') || ($logical == Calculation::getTRUE())) { + return false; + } elseif (($logical == 'FALSE') || ($logical == Calculation::getFALSE())) { + return true; + } + + return ExcelError::VALUE(); + } + + return !$logical; + } + + /** + * @return int|string + */ + private static function countTrueValues(array $args) + { + $trueValueCount = 0; + + foreach ($args as $arg) { + // Is it a boolean value? + if (is_bool($arg)) { + $trueValueCount += $arg; + } elseif ((is_numeric($arg)) && (!is_string($arg))) { + $trueValueCount += ((int) $arg != 0); + } elseif (is_string($arg)) { + $arg = mb_strtoupper($arg, 'UTF-8'); + if (($arg == 'TRUE') || ($arg == Calculation::getTRUE())) { + $arg = true; + } elseif (($arg == 'FALSE') || ($arg == Calculation::getFALSE())) { + $arg = false; + } else { + return ExcelError::VALUE(); + } + $trueValueCount += ($arg != 0); + } + } + + return $trueValueCount; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/LookupRef.php b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef.php old mode 100755 new mode 100644 index 4843430..354cb83 --- a/PhpOffice/PhpSpreadsheet/Calculation/LookupRef.php +++ b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef.php @@ -2,11 +2,20 @@ namespace PhpOffice\PhpSpreadsheet\Calculation; +use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Address; +use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\HLookup; +use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Indirect; +use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Lookup; +use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Matrix; +use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Offset; +use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\RowColumnInformation; +use PhpOffice\PhpSpreadsheet\Calculation\LookupRef\VLookup; use PhpOffice\PhpSpreadsheet\Cell\Cell; -use PhpOffice\PhpSpreadsheet\Cell\Coordinate; -use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +/** + * @deprecated 1.18.0 + */ class LookupRef { /** @@ -17,103 +26,53 @@ class LookupRef * Excel Function: * =ADDRESS(row, column, [relativity], [referenceStyle], [sheetText]) * + * @deprecated 1.18.0 + * Use the cell() method in the LookupRef\Address class instead + * @see LookupRef\Address::cell() + * * @param mixed $row Row number to use in the cell reference * @param mixed $column Column number to use in the cell reference * @param int $relativity Flag indicating the type of reference to return * 1 or omitted Absolute - * 2 Absolute row; relative column - * 3 Relative row; absolute column - * 4 Relative + * 2 Absolute row; relative column + * 3 Relative row; absolute column + * 4 Relative * @param bool $referenceStyle A logical value that specifies the A1 or R1C1 reference style. - * TRUE or omitted CELL_ADDRESS returns an A1-style reference + * TRUE or omitted CELL_ADDRESS returns an A1-style reference * FALSE CELL_ADDRESS returns an R1C1-style reference - * @param string $sheetText Optional Name of worksheet to use + * @param array|string $sheetText Optional Name of worksheet to use * - * @return string + * @return array|string */ public static function cellAddress($row, $column, $relativity = 1, $referenceStyle = true, $sheetText = '') { - $row = Functions::flattenSingleValue($row); - $column = Functions::flattenSingleValue($column); - $relativity = Functions::flattenSingleValue($relativity); - $sheetText = Functions::flattenSingleValue($sheetText); - - if (($row < 1) || ($column < 1)) { - return Functions::VALUE(); - } - - if ($sheetText > '') { - if (strpos($sheetText, ' ') !== false) { - $sheetText = "'" . $sheetText . "'"; - } - $sheetText .= '!'; - } - if ((!is_bool($referenceStyle)) || $referenceStyle) { - $rowRelative = $columnRelative = '$'; - $column = Coordinate::stringFromColumnIndex($column); - if (($relativity == 2) || ($relativity == 4)) { - $columnRelative = ''; - } - if (($relativity == 3) || ($relativity == 4)) { - $rowRelative = ''; - } - - return $sheetText . $columnRelative . $column . $rowRelative . $row; - } - if (($relativity == 2) || ($relativity == 4)) { - $column = '[' . $column . ']'; - } - if (($relativity == 3) || ($relativity == 4)) { - $row = '[' . $row . ']'; - } - - return $sheetText . 'R' . $row . 'C' . $column; + return Address::cell($row, $column, $relativity, $referenceStyle, $sheetText); } /** * COLUMN. * * Returns the column number of the given cell reference - * If the cell reference is a range of cells, COLUMN returns the column numbers of each column in the reference as a horizontal array. - * If cell reference is omitted, and the function is being called through the calculation engine, then it is assumed to be the - * reference of the cell in which the COLUMN function appears; otherwise this function returns 0. + * If the cell reference is a range of cells, COLUMN returns the column numbers of each column + * in the reference as a horizontal array. + * If cell reference is omitted, and the function is being called through the calculation engine, + * then it is assumed to be the reference of the cell in which the COLUMN function appears; + * otherwise this function returns 1. * * Excel Function: * =COLUMN([cellAddress]) * + * @deprecated 1.18.0 + * Use the COLUMN() method in the LookupRef\RowColumnInformation class instead + * @see LookupRef\RowColumnInformation::COLUMN() + * * @param null|array|string $cellAddress A reference to a range of cells for which you want the column numbers * - * @return int|int[] + * @return int|int[]|string */ - public static function COLUMN($cellAddress = null) + public static function COLUMN($cellAddress = null, ?Cell $cell = null) { - if ($cellAddress === null || trim($cellAddress) === '') { - return 0; - } - - if (is_array($cellAddress)) { - foreach ($cellAddress as $columnKey => $value) { - $columnKey = preg_replace('/[^a-z]/i', '', $columnKey); - - return (int) Coordinate::columnIndexFromString($columnKey); - } - } else { - [$sheet, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); - if (strpos($cellAddress, ':') !== false) { - [$startAddress, $endAddress] = explode(':', $cellAddress); - $startAddress = preg_replace('/[^a-z]/i', '', $startAddress); - $endAddress = preg_replace('/[^a-z]/i', '', $endAddress); - $returnValue = []; - do { - $returnValue[] = (int) Coordinate::columnIndexFromString($startAddress); - } while ($startAddress++ != $endAddress); - - return $returnValue; - } - $cellAddress = preg_replace('/[^a-z]/i', '', $cellAddress); - - return (int) Coordinate::columnIndexFromString($cellAddress); - } + return RowColumnInformation::COLUMN($cellAddress, $cell); } /** @@ -124,73 +83,44 @@ class LookupRef * Excel Function: * =COLUMNS(cellAddress) * - * @param null|array|string $cellAddress An array or array formula, or a reference to a range of cells for which you want the number of columns + * @deprecated 1.18.0 + * Use the COLUMNS() method in the LookupRef\RowColumnInformation class instead + * @see LookupRef\RowColumnInformation::COLUMNS() * - * @return int The number of columns in cellAddress + * @param null|array|string $cellAddress An array or array formula, or a reference to a range of cells + * for which you want the number of columns + * + * @return int|string The number of columns in cellAddress, or a string if arguments are invalid */ public static function COLUMNS($cellAddress = null) { - if ($cellAddress === null || $cellAddress === '') { - return 1; - } elseif (!is_array($cellAddress)) { - return Functions::VALUE(); - } - - reset($cellAddress); - $isMatrix = (is_numeric(key($cellAddress))); - [$columns, $rows] = Calculation::getMatrixDimensions($cellAddress); - - if ($isMatrix) { - return $rows; - } - - return $columns; + return RowColumnInformation::COLUMNS($cellAddress); } /** * ROW. * * Returns the row number of the given cell reference - * If the cell reference is a range of cells, ROW returns the row numbers of each row in the reference as a vertical array. - * If cell reference is omitted, and the function is being called through the calculation engine, then it is assumed to be the - * reference of the cell in which the ROW function appears; otherwise this function returns 0. + * If the cell reference is a range of cells, ROW returns the row numbers of each row in the reference + * as a vertical array. + * If cell reference is omitted, and the function is being called through the calculation engine, + * then it is assumed to be the reference of the cell in which the ROW function appears; + * otherwise this function returns 1. * * Excel Function: * =ROW([cellAddress]) * + * @deprecated 1.18.0 + * Use the ROW() method in the LookupRef\RowColumnInformation class instead + * @see LookupRef\RowColumnInformation::ROW() + * * @param null|array|string $cellAddress A reference to a range of cells for which you want the row numbers * - * @return int or array of integer + * @return int|mixed[]|string */ - public static function ROW($cellAddress = null) + public static function ROW($cellAddress = null, ?Cell $cell = null) { - if ($cellAddress === null || trim($cellAddress) === '') { - return 0; - } - - if (is_array($cellAddress)) { - foreach ($cellAddress as $columnKey => $rowValue) { - foreach ($rowValue as $rowKey => $cellValue) { - return (int) preg_replace('/\D/', '', $rowKey); - } - } - } else { - [$sheet, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); - if (strpos($cellAddress, ':') !== false) { - [$startAddress, $endAddress] = explode(':', $cellAddress); - $startAddress = preg_replace('/\D/', '', $startAddress); - $endAddress = preg_replace('/\D/', '', $endAddress); - $returnValue = []; - do { - $returnValue[][] = (int) $startAddress; - } while ($startAddress++ != $endAddress); - - return $returnValue; - } - [$cellAddress] = explode(':', $cellAddress); - - return (int) preg_replace('/\D/', '', $cellAddress); - } + return RowColumnInformation::ROW($cellAddress, $cell); } /** @@ -201,27 +131,18 @@ class LookupRef * Excel Function: * =ROWS(cellAddress) * - * @param null|array|string $cellAddress An array or array formula, or a reference to a range of cells for which you want the number of rows + * @deprecated 1.18.0 + * Use the ROWS() method in the LookupRef\RowColumnInformation class instead + * @see LookupRef\RowColumnInformation::ROWS() * - * @return int The number of rows in cellAddress + * @param null|array|string $cellAddress An array or array formula, or a reference to a range of cells + * for which you want the number of rows + * + * @return int|string The number of rows in cellAddress, or a string if arguments are invalid */ public static function ROWS($cellAddress = null) { - if ($cellAddress === null || $cellAddress === '') { - return 1; - } elseif (!is_array($cellAddress)) { - return Functions::VALUE(); - } - - reset($cellAddress); - $isMatrix = (is_numeric(key($cellAddress))); - [$columns, $rows] = Calculation::getMatrixDimensions($cellAddress); - - if ($isMatrix) { - return $columns; - } - - return $rows; + return RowColumnInformation::ROWS($cellAddress); } /** @@ -230,31 +151,19 @@ class LookupRef * Excel Function: * =HYPERLINK(linkURL,displayName) * - * @category Logical Functions + * @deprecated 1.18.0 + * Use the set() method in the LookupRef\Hyperlink class instead + * @see LookupRef\Hyperlink::set() * - * @param string $linkURL Value to check, is also the value returned when no error - * @param string $displayName Value to return when testValue is an error condition - * @param Cell $pCell The cell to set the hyperlink in + * @param mixed $linkURL Expect string. Value to check, is also the value returned when no error + * @param mixed $displayName Expect string. Value to return when testValue is an error condition + * @param Cell $cell The cell to set the hyperlink in * - * @return mixed The value of $displayName (or $linkURL if $displayName was blank) + * @return string The value of $displayName (or $linkURL if $displayName was blank) */ - public static function HYPERLINK($linkURL = '', $displayName = null, Cell $pCell = null) + public static function HYPERLINK($linkURL = '', $displayName = null, ?Cell $cell = null) { - $linkURL = ($linkURL === null) ? '' : Functions::flattenSingleValue($linkURL); - $displayName = ($displayName === null) ? '' : Functions::flattenSingleValue($displayName); - - if ((!is_object($pCell)) || (trim($linkURL) == '')) { - return Functions::REF(); - } - - if ((is_object($displayName)) || trim($displayName) == '') { - $displayName = $linkURL; - } - - $pCell->getHyperlink()->setUrl($linkURL); - $pCell->getHyperlink()->setTooltip($displayName); - - return $displayName; + return LookupRef\Hyperlink::set($linkURL, $displayName, $cell); } /** @@ -266,54 +175,20 @@ class LookupRef * Excel Function: * =INDIRECT(cellAddress) * + * @deprecated 1.18.0 + * Use the INDIRECT() method in the LookupRef\Indirect class instead + * @see LookupRef\Indirect::INDIRECT() + * + * @param array|string $cellAddress $cellAddress The cell address of the current cell (containing this formula) + * @param Cell $cell The current cell (containing this formula) + * + * @return array|string An array containing a cell or range of cells, or a string on error + * * NOTE - INDIRECT() does not yet support the optional a1 parameter introduced in Excel 2010 - * - * @param null|array|string $cellAddress $cellAddress The cell address of the current cell (containing this formula) - * @param Cell $pCell The current cell (containing this formula) - * - * @return mixed The cells referenced by cellAddress - * - * @todo Support for the optional a1 parameter introduced in Excel 2010 */ - public static function INDIRECT($cellAddress = null, Cell $pCell = null) + public static function INDIRECT($cellAddress, Cell $cell) { - $cellAddress = Functions::flattenSingleValue($cellAddress); - if ($cellAddress === null || $cellAddress === '') { - return Functions::REF(); - } - - $cellAddress1 = $cellAddress; - $cellAddress2 = null; - if (strpos($cellAddress, ':') !== false) { - [$cellAddress1, $cellAddress2] = explode(':', $cellAddress); - } - - if ((!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellAddress1, $matches)) || - (($cellAddress2 !== null) && (!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellAddress2, $matches)))) { - if (!preg_match('/^' . Calculation::CALCULATION_REGEXP_NAMEDRANGE . '$/i', $cellAddress1, $matches)) { - return Functions::REF(); - } - - if (strpos($cellAddress, '!') !== false) { - [$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); - $sheetName = trim($sheetName, "'"); - $pSheet = $pCell->getWorksheet()->getParent()->getSheetByName($sheetName); - } else { - $pSheet = $pCell->getWorksheet(); - } - - return Calculation::getInstance()->extractNamedRange($cellAddress, $pSheet, false); - } - - if (strpos($cellAddress, '!') !== false) { - [$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); - $sheetName = trim($sheetName, "'"); - $pSheet = $pCell->getWorksheet()->getParent()->getSheetByName($sheetName); - } else { - $pSheet = $pCell->getWorksheet(); - } - - return Calculation::getInstance()->extractCellRange($cellAddress, $pSheet, false); + return Indirect::INDIRECT($cellAddress, true, $cell); } /** @@ -326,88 +201,33 @@ class LookupRef * Excel Function: * =OFFSET(cellAddress, rows, cols, [height], [width]) * - * @param null|string $cellAddress The reference from which you want to base the offset. Reference must refer to a cell or - * range of adjacent cells; otherwise, OFFSET returns the #VALUE! error value. - * @param mixed $rows The number of rows, up or down, that you want the upper-left cell to refer to. - * Using 5 as the rows argument specifies that the upper-left cell in the reference is - * five rows below reference. Rows can be positive (which means below the starting reference) - * or negative (which means above the starting reference). - * @param mixed $columns The number of columns, to the left or right, that you want the upper-left cell of the result - * to refer to. Using 5 as the cols argument specifies that the upper-left cell in the - * reference is five columns to the right of reference. Cols can be positive (which means - * to the right of the starting reference) or negative (which means to the left of the - * starting reference). - * @param mixed $height The height, in number of rows, that you want the returned reference to be. Height must be a positive number. - * @param mixed $width The width, in number of columns, that you want the returned reference to be. Width must be a positive number. - * @param null|Cell $pCell + * @deprecated 1.18.0 + * Use the OFFSET() method in the LookupRef\Offset class instead + * @see LookupRef\Offset::OFFSET() * - * @return string A reference to a cell or range of cells + * @param null|string $cellAddress The reference from which you want to base the offset. + * Reference must refer to a cell or range of adjacent cells; + * otherwise, OFFSET returns the #VALUE! error value. + * @param mixed $rows The number of rows, up or down, that you want the upper-left cell to refer to. + * Using 5 as the rows argument specifies that the upper-left cell in the + * reference is five rows below reference. Rows can be positive (which means + * below the starting reference) or negative (which means above the starting + * reference). + * @param mixed $columns The number of columns, to the left or right, that you want the upper-left cell + * of the result to refer to. Using 5 as the cols argument specifies that the + * upper-left cell in the reference is five columns to the right of reference. + * Cols can be positive (which means to the right of the starting reference) + * or negative (which means to the left of the starting reference). + * @param mixed $height The height, in number of rows, that you want the returned reference to be. + * Height must be a positive number. + * @param mixed $width The width, in number of columns, that you want the returned reference to be. + * Width must be a positive number. + * + * @return array|string An array containing a cell or range of cells, or a string on error */ - public static function OFFSET($cellAddress = null, $rows = 0, $columns = 0, $height = null, $width = null, Cell $pCell = null) + public static function OFFSET($cellAddress = null, $rows = 0, $columns = 0, $height = null, $width = null, ?Cell $cell = null) { - $rows = Functions::flattenSingleValue($rows); - $columns = Functions::flattenSingleValue($columns); - $height = Functions::flattenSingleValue($height); - $width = Functions::flattenSingleValue($width); - if ($cellAddress === null) { - return 0; - } - - if (!is_object($pCell)) { - return Functions::REF(); - } - - $sheetName = null; - if (strpos($cellAddress, '!')) { - [$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); - $sheetName = trim($sheetName, "'"); - } - if (strpos($cellAddress, ':')) { - [$startCell, $endCell] = explode(':', $cellAddress); - } else { - $startCell = $endCell = $cellAddress; - } - [$startCellColumn, $startCellRow] = Coordinate::coordinateFromString($startCell); - [$endCellColumn, $endCellRow] = Coordinate::coordinateFromString($endCell); - - $startCellRow += $rows; - $startCellColumn = Coordinate::columnIndexFromString($startCellColumn) - 1; - $startCellColumn += $columns; - - if (($startCellRow <= 0) || ($startCellColumn < 0)) { - return Functions::REF(); - } - $endCellColumn = Coordinate::columnIndexFromString($endCellColumn) - 1; - if (($width != null) && (!is_object($width))) { - $endCellColumn = $startCellColumn + $width - 1; - } else { - $endCellColumn += $columns; - } - $startCellColumn = Coordinate::stringFromColumnIndex($startCellColumn + 1); - - if (($height != null) && (!is_object($height))) { - $endCellRow = $startCellRow + $height - 1; - } else { - $endCellRow += $rows; - } - - if (($endCellRow <= 0) || ($endCellColumn < 0)) { - return Functions::REF(); - } - $endCellColumn = Coordinate::stringFromColumnIndex($endCellColumn + 1); - - $cellAddress = $startCellColumn . $startCellRow; - if (($startCellColumn != $endCellColumn) || ($startCellRow != $endCellRow)) { - $cellAddress .= ':' . $endCellColumn . $endCellRow; - } - - if ($sheetName !== null) { - $pSheet = $pCell->getWorksheet()->getParent()->getSheetByName($sheetName); - } else { - $pSheet = $pCell->getWorksheet(); - } - - return Calculation::getInstance()->extractCellRange($cellAddress, $pSheet, false); + return Offset::OFFSET($cellAddress, $rows, $columns, $height, $width, $cell); } /** @@ -419,39 +239,15 @@ class LookupRef * Excel Function: * =CHOOSE(index_num, value1, [value2], ...) * - * @param mixed $index_num Specifies which value argument is selected. - * Index_num must be a number between 1 and 254, or a formula or reference to a cell containing a number - * between 1 and 254. - * @param mixed $value1 ... Value1 is required, subsequent values are optional. - * Between 1 to 254 value arguments from which CHOOSE selects a value or an action to perform based on - * index_num. The arguments can be numbers, cell references, defined names, formulas, functions, or - * text. + * @deprecated 1.18.0 + * Use the choose() method in the LookupRef\Selection class instead + * @see LookupRef\Selection::choose() * * @return mixed The selected value */ public static function CHOOSE(...$chooseArgs) { - $chosenEntry = Functions::flattenArray(array_shift($chooseArgs)); - $entryCount = count($chooseArgs) - 1; - - if (is_array($chosenEntry)) { - $chosenEntry = array_shift($chosenEntry); - } - if ((is_numeric($chosenEntry)) && (!is_bool($chosenEntry))) { - --$chosenEntry; - } else { - return Functions::VALUE(); - } - $chosenEntry = floor($chosenEntry); - if (($chosenEntry < 0) || ($chosenEntry > $entryCount)) { - return Functions::VALUE(); - } - - if (is_array($chooseArgs[$chosenEntry])) { - return Functions::flattenArray($chooseArgs[$chosenEntry]); - } - - return $chooseArgs[$chosenEntry]; + return LookupRef\Selection::choose(...$chooseArgs); } /** @@ -462,153 +258,20 @@ class LookupRef * Excel Function: * =MATCH(lookup_value, lookup_array, [match_type]) * + * @deprecated 1.18.0 + * Use the MATCH() method in the LookupRef\ExcelMatch class instead + * @see LookupRef\ExcelMatch::MATCH() + * * @param mixed $lookupValue The value that you want to match in lookup_array * @param mixed $lookupArray The range of cells being searched * @param mixed $matchType The number -1, 0, or 1. -1 means above, 0 means exact match, 1 means below. * If match_type is 1 or -1, the list has to be ordered. * - * @return int|string The relative position of the found item + * @return array|int|string The relative position of the found item */ public static function MATCH($lookupValue, $lookupArray, $matchType = 1) { - $lookupArray = Functions::flattenArray($lookupArray); - $lookupValue = Functions::flattenSingleValue($lookupValue); - $matchType = ($matchType === null) ? 1 : (int) Functions::flattenSingleValue($matchType); - - // MATCH is not case sensitive, so we convert lookup value to be lower cased in case it's string type. - if (is_string($lookupValue)) { - $lookupValue = StringHelper::strToLower($lookupValue); - } - - // Lookup_value type has to be number, text, or logical values - if ((!is_numeric($lookupValue)) && (!is_string($lookupValue)) && (!is_bool($lookupValue))) { - return Functions::NA(); - } - - // Match_type is 0, 1 or -1 - if (($matchType !== 0) && ($matchType !== -1) && ($matchType !== 1)) { - return Functions::NA(); - } - - // Lookup_array should not be empty - $lookupArraySize = count($lookupArray); - if ($lookupArraySize <= 0) { - return Functions::NA(); - } - - // Lookup_array should contain only number, text, or logical values, or empty (null) cells - foreach ($lookupArray as $i => $lookupArrayValue) { - // check the type of the value - if ((!is_numeric($lookupArrayValue)) && (!is_string($lookupArrayValue)) && - (!is_bool($lookupArrayValue)) && ($lookupArrayValue !== null) - ) { - return Functions::NA(); - } - // Convert strings to lowercase for case-insensitive testing - if (is_string($lookupArrayValue)) { - $lookupArray[$i] = StringHelper::strToLower($lookupArrayValue); - } - if (($lookupArrayValue === null) && (($matchType == 1) || ($matchType == -1))) { - $lookupArray = array_slice($lookupArray, 0, $i - 1); - } - } - - if ($matchType == 1) { - // If match_type is 1 the list has to be processed from last to first - - $lookupArray = array_reverse($lookupArray); - $keySet = array_reverse(array_keys($lookupArray)); - } - - // ** - // find the match - // ** - - if ($matchType === 0 || $matchType === 1) { - foreach ($lookupArray as $i => $lookupArrayValue) { - $typeMatch = gettype($lookupValue) === gettype($lookupArrayValue); - $exactTypeMatch = $typeMatch && $lookupArrayValue === $lookupValue; - $nonOnlyNumericExactMatch = !$typeMatch && $lookupArrayValue === $lookupValue; - $exactMatch = $exactTypeMatch || $nonOnlyNumericExactMatch; - - if ($matchType === 0) { - if ($typeMatch && is_string($lookupValue) && (bool) preg_match('/([\?\*])/', $lookupValue)) { - $splitString = $lookupValue; - $chars = array_map(function ($i) use ($splitString) { - return mb_substr($splitString, $i, 1); - }, range(0, mb_strlen($splitString) - 1)); - - $length = count($chars); - $pattern = '/^'; - for ($j = 0; $j < $length; ++$j) { - if ($chars[$j] === '~') { - if (isset($chars[$j + 1])) { - if ($chars[$j + 1] === '*') { - $pattern .= preg_quote($chars[$j + 1], '/'); - ++$j; - } elseif ($chars[$j + 1] === '?') { - $pattern .= preg_quote($chars[$j + 1], '/'); - ++$j; - } - } else { - $pattern .= preg_quote($chars[$j], '/'); - } - } elseif ($chars[$j] === '*') { - $pattern .= '.*'; - } elseif ($chars[$j] === '?') { - $pattern .= '.{1}'; - } else { - $pattern .= preg_quote($chars[$j], '/'); - } - } - - $pattern .= '$/'; - if ((bool) preg_match($pattern, $lookupArrayValue)) { - // exact match - return $i + 1; - } - } elseif ($exactMatch) { - // exact match - return $i + 1; - } - } elseif (($matchType === 1) && $typeMatch && ($lookupArrayValue <= $lookupValue)) { - $i = array_search($i, $keySet); - - // The current value is the (first) match - return $i + 1; - } - } - } else { - $maxValueKey = null; - - // The basic algorithm is: - // Iterate and keep the highest match until the next element is smaller than the searched value. - // Return immediately if perfect match is found - foreach ($lookupArray as $i => $lookupArrayValue) { - $typeMatch = gettype($lookupValue) === gettype($lookupArrayValue); - $exactTypeMatch = $typeMatch && $lookupArrayValue === $lookupValue; - $nonOnlyNumericExactMatch = !$typeMatch && $lookupArrayValue === $lookupValue; - $exactMatch = $exactTypeMatch || $nonOnlyNumericExactMatch; - - if ($exactMatch) { - // Another "special" case. If a perfect match is found, - // the algorithm gives up immediately - return $i + 1; - } elseif ($typeMatch & $lookupArrayValue >= $lookupValue) { - $maxValueKey = $i + 1; - } elseif ($typeMatch & $lookupArrayValue < $lookupValue) { - //Excel algorithm gives up immediately if the first element is smaller than the searched value - break; - } - } - - if ($maxValueKey !== null) { - return $maxValueKey; - } - } - - // Unsuccessful in finding a match, return #N/A error value - return Functions::NA(); + return LookupRef\ExcelMatch::MATCH($lookupValue, $lookupArray, $matchType); } /** @@ -619,254 +282,94 @@ class LookupRef * Excel Function: * =INDEX(range_array, row_num, [column_num]) * - * @param mixed $arrayValues A range of cells or an array constant - * @param mixed $rowNum The row in array from which to return a value. If row_num is omitted, column_num is required. - * @param mixed $columnNum The column in array from which to return a value. If column_num is omitted, row_num is required. + * @deprecated 1.18.0 + * Use the index() method in the LookupRef\Matrix class instead + * @see LookupRef\Matrix::index() + * + * @param mixed $rowNum The row in the array or range from which to return a value. + * If row_num is omitted, column_num is required. + * @param mixed $columnNum The column in the array or range from which to return a value. + * If column_num is omitted, row_num is required. + * @param mixed $matrix * * @return mixed the value of a specified cell or array of cells */ - public static function INDEX($arrayValues, $rowNum = 0, $columnNum = 0) + public static function INDEX($matrix, $rowNum = 0, $columnNum = 0) { - $rowNum = Functions::flattenSingleValue($rowNum); - $columnNum = Functions::flattenSingleValue($columnNum); - - if (($rowNum < 0) || ($columnNum < 0)) { - return Functions::VALUE(); - } - - if (!is_array($arrayValues) || ($rowNum > count($arrayValues))) { - return Functions::REF(); - } - - $rowKeys = array_keys($arrayValues); - $columnKeys = @array_keys($arrayValues[$rowKeys[0]]); - - if ($columnNum > count($columnKeys)) { - return Functions::VALUE(); - } elseif ($columnNum == 0) { - if ($rowNum == 0) { - return $arrayValues; - } - $rowNum = $rowKeys[--$rowNum]; - $returnArray = []; - foreach ($arrayValues as $arrayColumn) { - if (is_array($arrayColumn)) { - if (isset($arrayColumn[$rowNum])) { - $returnArray[] = $arrayColumn[$rowNum]; - } else { - return [$rowNum => $arrayValues[$rowNum]]; - } - } else { - return $arrayValues[$rowNum]; - } - } - - return $returnArray; - } - $columnNum = $columnKeys[--$columnNum]; - if ($rowNum > count($rowKeys)) { - return Functions::VALUE(); - } elseif ($rowNum == 0) { - return $arrayValues[$columnNum]; - } - $rowNum = $rowKeys[--$rowNum]; - - return $arrayValues[$rowNum][$columnNum]; + return Matrix::index($matrix, $rowNum, $columnNum); } /** * TRANSPOSE. * + * @deprecated 1.18.0 + * Use the transpose() method in the LookupRef\Matrix class instead + * @see LookupRef\Matrix::transpose() + * * @param array $matrixData A matrix of values * * @return array * - * Unlike the Excel TRANSPOSE function, which will only work on a single row or column, this function will transpose a full matrix + * Unlike the Excel TRANSPOSE function, which will only work on a single row or column, + * this function will transpose a full matrix */ public static function TRANSPOSE($matrixData) { - $returnMatrix = []; - if (!is_array($matrixData)) { - $matrixData = [[$matrixData]]; - } - - $column = 0; - foreach ($matrixData as $matrixRow) { - $row = 0; - foreach ($matrixRow as $matrixCell) { - $returnMatrix[$row][$column] = $matrixCell; - ++$row; - } - ++$column; - } - - return $returnMatrix; - } - - private static function vlookupSort($a, $b) - { - reset($a); - $firstColumn = key($a); - $aLower = StringHelper::strToLower($a[$firstColumn]); - $bLower = StringHelper::strToLower($b[$firstColumn]); - if ($aLower == $bLower) { - return 0; - } - - return ($aLower < $bLower) ? -1 : 1; + return Matrix::transpose($matrixData); } /** * VLOOKUP - * The VLOOKUP function searches for value in the left-most column of lookup_array and returns the value in the same row based on the index_number. + * The VLOOKUP function searches for value in the left-most column of lookup_array and returns the value + * in the same row based on the index_number. + * + * @deprecated 1.18.0 + * Use the lookup() method in the LookupRef\VLookup class instead + * @see LookupRef\VLookup::lookup() * * @param mixed $lookup_value The value that you want to match in lookup_array * @param mixed $lookup_array The range of cells being searched - * @param mixed $index_number The column number in table_array from which the matching value must be returned. The first column is 1. + * @param mixed $index_number The column number in table_array from which the matching value must be returned. + * The first column is 1. * @param mixed $not_exact_match determines if you are looking for an exact match based on lookup_value * * @return mixed The value of the found cell */ public static function VLOOKUP($lookup_value, $lookup_array, $index_number, $not_exact_match = true) { - $lookup_value = Functions::flattenSingleValue($lookup_value); - $index_number = Functions::flattenSingleValue($index_number); - $not_exact_match = Functions::flattenSingleValue($not_exact_match); - - // index_number must be greater than or equal to 1 - if ($index_number < 1) { - return Functions::VALUE(); - } - - // index_number must be less than or equal to the number of columns in lookup_array - if ((!is_array($lookup_array)) || (empty($lookup_array))) { - return Functions::REF(); - } - $f = array_keys($lookup_array); - $firstRow = array_pop($f); - if ((!is_array($lookup_array[$firstRow])) || ($index_number > count($lookup_array[$firstRow]))) { - return Functions::REF(); - } - $columnKeys = array_keys($lookup_array[$firstRow]); - $returnColumn = $columnKeys[--$index_number]; - $firstColumn = array_shift($columnKeys); - - if (!$not_exact_match) { - uasort($lookup_array, ['self', 'vlookupSort']); - } - - $lookupLower = StringHelper::strToLower($lookup_value); - $rowNumber = $rowValue = false; - foreach ($lookup_array as $rowKey => $rowData) { - $firstLower = StringHelper::strToLower($rowData[$firstColumn]); - - // break if we have passed possible keys - if ((is_numeric($lookup_value) && is_numeric($rowData[$firstColumn]) && ($rowData[$firstColumn] > $lookup_value)) || - (!is_numeric($lookup_value) && !is_numeric($rowData[$firstColumn]) && ($firstLower > $lookupLower))) { - break; - } - // remember the last key, but only if datatypes match - if ((is_numeric($lookup_value) && is_numeric($rowData[$firstColumn])) || - (!is_numeric($lookup_value) && !is_numeric($rowData[$firstColumn]))) { - if ($not_exact_match) { - $rowNumber = $rowKey; - - continue; - } elseif (($firstLower == $lookupLower) - // Spreadsheets software returns first exact match, - // we have sorted and we might have broken key orders - // we want the first one (by its initial index) - && (($rowNumber == false) || ($rowKey < $rowNumber)) - ) { - $rowNumber = $rowKey; - } - } - } - - if ($rowNumber !== false) { - // return the appropriate value - return $lookup_array[$rowNumber][$returnColumn]; - } - - return Functions::NA(); + return VLookup::lookup($lookup_value, $lookup_array, $index_number, $not_exact_match); } /** * HLOOKUP - * The HLOOKUP function searches for value in the top-most row of lookup_array and returns the value in the same column based on the index_number. + * The HLOOKUP function searches for value in the top-most row of lookup_array and returns the value + * in the same column based on the index_number. + * + * @deprecated 1.18.0 + * Use the lookup() method in the LookupRef\HLookup class instead + * @see LookupRef\HLookup::lookup() * * @param mixed $lookup_value The value that you want to match in lookup_array * @param mixed $lookup_array The range of cells being searched - * @param mixed $index_number The row number in table_array from which the matching value must be returned. The first row is 1. + * @param mixed $index_number The row number in table_array from which the matching value must be returned. + * The first row is 1. * @param mixed $not_exact_match determines if you are looking for an exact match based on lookup_value * * @return mixed The value of the found cell */ public static function HLOOKUP($lookup_value, $lookup_array, $index_number, $not_exact_match = true) { - $lookup_value = Functions::flattenSingleValue($lookup_value); - $index_number = Functions::flattenSingleValue($index_number); - $not_exact_match = Functions::flattenSingleValue($not_exact_match); - - // index_number must be greater than or equal to 1 - if ($index_number < 1) { - return Functions::VALUE(); - } - - // index_number must be less than or equal to the number of columns in lookup_array - if ((!is_array($lookup_array)) || (empty($lookup_array))) { - return Functions::REF(); - } - $f = array_keys($lookup_array); - $firstRow = array_pop($f); - if ((!is_array($lookup_array[$firstRow])) || ($index_number > count($lookup_array))) { - return Functions::REF(); - } - - $firstkey = $f[0] - 1; - $returnColumn = $firstkey + $index_number; - $firstColumn = array_shift($f); - $rowNumber = null; - foreach ($lookup_array[$firstColumn] as $rowKey => $rowData) { - // break if we have passed possible keys - $bothNumeric = is_numeric($lookup_value) && is_numeric($rowData); - $bothNotNumeric = !is_numeric($lookup_value) && !is_numeric($rowData); - $lookupLower = StringHelper::strToLower($lookup_value); - $rowDataLower = StringHelper::strToLower($rowData); - - if ($not_exact_match && ( - ($bothNumeric && $rowData > $lookup_value) || - ($bothNotNumeric && $rowDataLower > $lookupLower) - )) { - break; - } - - // Remember the last key, but only if datatypes match (as in VLOOKUP) - if ($bothNumeric || $bothNotNumeric) { - if ($not_exact_match) { - $rowNumber = $rowKey; - - continue; - } elseif ($rowDataLower === $lookupLower - && ($rowNumber === null || $rowKey < $rowNumber) - ) { - $rowNumber = $rowKey; - } - } - } - - if ($rowNumber !== null) { - // otherwise return the appropriate value - return $lookup_array[$returnColumn][$rowNumber]; - } - - return Functions::NA(); + return HLookup::lookup($lookup_value, $lookup_array, $index_number, $not_exact_match); } /** * LOOKUP * The LOOKUP function searches for value either from a one-row or one-column range or from an array. * + * @deprecated 1.18.0 + * Use the lookup() method in the LookupRef\Lookup class instead + * @see LookupRef\Lookup::lookup() + * * @param mixed $lookup_value The value that you want to match in lookup_array * @param mixed $lookup_vector The range of cells being searched * @param null|mixed $result_vector The column from which the matching value must be returned @@ -875,94 +378,23 @@ class LookupRef */ public static function LOOKUP($lookup_value, $lookup_vector, $result_vector = null) { - $lookup_value = Functions::flattenSingleValue($lookup_value); - - if (!is_array($lookup_vector)) { - return Functions::NA(); - } - $hasResultVector = isset($result_vector); - $lookupRows = count($lookup_vector); - $l = array_keys($lookup_vector); - $l = array_shift($l); - $lookupColumns = count($lookup_vector[$l]); - // we correctly orient our results - if (($lookupRows === 1 && $lookupColumns > 1) || (!$hasResultVector && $lookupRows === 2 && $lookupColumns !== 2)) { - $lookup_vector = self::TRANSPOSE($lookup_vector); - $lookupRows = count($lookup_vector); - $l = array_keys($lookup_vector); - $lookupColumns = count($lookup_vector[array_shift($l)]); - } - - if ($result_vector === null) { - $result_vector = $lookup_vector; - } - $resultRows = count($result_vector); - $l = array_keys($result_vector); - $l = array_shift($l); - $resultColumns = count($result_vector[$l]); - // we correctly orient our results - if ($resultRows === 1 && $resultColumns > 1) { - $result_vector = self::TRANSPOSE($result_vector); - $resultRows = count($result_vector); - $r = array_keys($result_vector); - $resultColumns = count($result_vector[array_shift($r)]); - } - - if ($lookupRows === 2 && !$hasResultVector) { - $result_vector = array_pop($lookup_vector); - $lookup_vector = array_shift($lookup_vector); - } - - if ($lookupColumns !== 2) { - foreach ($lookup_vector as &$value) { - if (is_array($value)) { - $k = array_keys($value); - $key1 = $key2 = array_shift($k); - ++$key2; - $dataValue1 = $value[$key1]; - } else { - $key1 = 0; - $key2 = 1; - $dataValue1 = $value; - } - $dataValue2 = array_shift($result_vector); - if (is_array($dataValue2)) { - $dataValue2 = array_shift($dataValue2); - } - $value = [$key1 => $dataValue1, $key2 => $dataValue2]; - } - unset($value); - } - - return self::VLOOKUP($lookup_value, $lookup_vector, 2); + return Lookup::lookup($lookup_value, $lookup_vector, $result_vector); } /** * FORMULATEXT. * + * @deprecated 1.18.0 + * Use the text() method in the LookupRef\Formula class instead + * @see LookupRef\Formula::text() + * * @param mixed $cellReference The cell to check - * @param Cell $pCell The current cell (containing this formula) + * @param Cell $cell The current cell (containing this formula) * * @return string */ - public static function FORMULATEXT($cellReference = '', Cell $pCell = null) + public static function FORMULATEXT($cellReference = '', ?Cell $cell = null) { - if ($pCell === null) { - return Functions::REF(); - } - - preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellReference, $matches); - - $cellReference = $matches[6] . $matches[7]; - $worksheetName = trim($matches[3], "'"); - $worksheet = (!empty($worksheetName)) - ? $pCell->getWorksheet()->getParent()->getSheetByName($worksheetName) - : $pCell->getWorksheet(); - - if (!$worksheet->getCell($cellReference)->isFormula()) { - return Functions::NA(); - } - - return $worksheet->getCell($cellReference)->getValue(); + return LookupRef\Formula::text($cellReference, $cell); } } diff --git a/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Address.php b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Address.php new file mode 100644 index 0000000..0d2db8b --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Address.php @@ -0,0 +1,124 @@ + '') { + if (strpos($sheetName, ' ') !== false || strpos($sheetName, '[') !== false) { + $sheetName = "'{$sheetName}'"; + } + $sheetName .= '!'; + } + + return $sheetName; + } + + private static function formatAsA1(int $row, int $column, int $relativity, string $sheetName): string + { + $rowRelative = $columnRelative = '$'; + if (($relativity == self::ADDRESS_COLUMN_RELATIVE) || ($relativity == self::ADDRESS_RELATIVE)) { + $columnRelative = ''; + } + if (($relativity == self::ADDRESS_ROW_RELATIVE) || ($relativity == self::ADDRESS_RELATIVE)) { + $rowRelative = ''; + } + $column = Coordinate::stringFromColumnIndex($column); + + return "{$sheetName}{$columnRelative}{$column}{$rowRelative}{$row}"; + } + + private static function formatAsR1C1(int $row, int $column, int $relativity, string $sheetName): string + { + if (($relativity == self::ADDRESS_COLUMN_RELATIVE) || ($relativity == self::ADDRESS_RELATIVE)) { + $column = "[{$column}]"; + } + if (($relativity == self::ADDRESS_ROW_RELATIVE) || ($relativity == self::ADDRESS_RELATIVE)) { + $row = "[{$row}]"; + } + [$rowChar, $colChar] = AddressHelper::getRowAndColumnChars(); + + return "{$sheetName}$rowChar{$row}$colChar{$column}"; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php new file mode 100644 index 0000000..e09477c --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/ExcelMatch.php @@ -0,0 +1,282 @@ +getMessage(); + } + + // MATCH() is not case sensitive, so we convert lookup value to be lower cased if it's a string type. + if (is_string($lookupValue)) { + $lookupValue = StringHelper::strToLower($lookupValue); + } + + $valueKey = null; + switch ($matchType) { + case self::MATCHTYPE_LARGEST_VALUE: + $valueKey = self::matchLargestValue($lookupArray, $lookupValue, $keySet); + + break; + case self::MATCHTYPE_FIRST_VALUE: + $valueKey = self::matchFirstValue($lookupArray, $lookupValue); + + break; + case self::MATCHTYPE_SMALLEST_VALUE: + default: + $valueKey = self::matchSmallestValue($lookupArray, $lookupValue); + } + + if ($valueKey !== null) { + return ++$valueKey; + } + + // Unsuccessful in finding a match, return #N/A error value + return ExcelError::NA(); + } + + /** + * @param mixed $lookupValue + * + * @return mixed + */ + private static function matchFirstValue(array $lookupArray, $lookupValue) + { + if (is_string($lookupValue)) { + $valueIsString = true; + $wildcard = WildcardMatch::wildcard($lookupValue); + } else { + $valueIsString = false; + $wildcard = ''; + } + + $valueIsNumeric = is_int($lookupValue) || is_float($lookupValue); + foreach ($lookupArray as $i => $lookupArrayValue) { + if ( + $valueIsString + && is_string($lookupArrayValue) + ) { + if (WildcardMatch::compare($lookupArrayValue, $wildcard)) { + return $i; // wildcard match + } + } else { + if ($lookupArrayValue === $lookupValue) { + return $i; // exact match + } + if ( + $valueIsNumeric + && (is_float($lookupArrayValue) || is_int($lookupArrayValue)) + && $lookupArrayValue == $lookupValue + ) { + return $i; // exact match + } + } + } + + return null; + } + + /** + * @param mixed $lookupValue + * + * @return mixed + */ + private static function matchLargestValue(array $lookupArray, $lookupValue, array $keySet) + { + if (is_string($lookupValue)) { + if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) { + $wildcard = WildcardMatch::wildcard($lookupValue); + foreach (array_reverse($lookupArray) as $i => $lookupArrayValue) { + if (is_string($lookupArrayValue) && WildcardMatch::compare($lookupArrayValue, $wildcard)) { + return $i; + } + } + } else { + foreach ($lookupArray as $i => $lookupArrayValue) { + if ($lookupArrayValue === $lookupValue) { + return $keySet[$i]; + } + } + } + } + $valueIsNumeric = is_int($lookupValue) || is_float($lookupValue); + foreach ($lookupArray as $i => $lookupArrayValue) { + if ($valueIsNumeric && (is_int($lookupArrayValue) || is_float($lookupArrayValue))) { + if ($lookupArrayValue <= $lookupValue) { + return array_search($i, $keySet); + } + } + $typeMatch = gettype($lookupValue) === gettype($lookupArrayValue); + if ($typeMatch && ($lookupArrayValue <= $lookupValue)) { + return array_search($i, $keySet); + } + } + + return null; + } + + /** + * @param mixed $lookupValue + * + * @return mixed + */ + private static function matchSmallestValue(array $lookupArray, $lookupValue) + { + $valueKey = null; + if (is_string($lookupValue)) { + if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) { + $wildcard = WildcardMatch::wildcard($lookupValue); + foreach ($lookupArray as $i => $lookupArrayValue) { + if (is_string($lookupArrayValue) && WildcardMatch::compare($lookupArrayValue, $wildcard)) { + return $i; + } + } + } + } + + $valueIsNumeric = is_int($lookupValue) || is_float($lookupValue); + // The basic algorithm is: + // Iterate and keep the highest match until the next element is smaller than the searched value. + // Return immediately if perfect match is found + foreach ($lookupArray as $i => $lookupArrayValue) { + $typeMatch = gettype($lookupValue) === gettype($lookupArrayValue); + $bothNumeric = $valueIsNumeric && (is_int($lookupArrayValue) || is_float($lookupArrayValue)); + + if ($lookupArrayValue === $lookupValue) { + // Another "special" case. If a perfect match is found, + // the algorithm gives up immediately + return $i; + } + if ($bothNumeric && $lookupValue == $lookupArrayValue) { + return $i; // exact match, as above + } + if (($typeMatch || $bothNumeric) && $lookupArrayValue >= $lookupValue) { + $valueKey = $i; + } elseif ($typeMatch && $lookupArrayValue < $lookupValue) { + //Excel algorithm gives up immediately if the first element is smaller than the searched value + break; + } + } + + return $valueKey; + } + + /** + * @param mixed $lookupValue + */ + private static function validateLookupValue($lookupValue): void + { + // Lookup_value type has to be number, text, or logical values + if ((!is_numeric($lookupValue)) && (!is_string($lookupValue)) && (!is_bool($lookupValue))) { + throw new Exception(ExcelError::NA()); + } + } + + /** + * @param mixed $matchType + */ + private static function validateMatchType($matchType): int + { + // Match_type is 0, 1 or -1 + // However Excel accepts other numeric values, + // including numeric strings and floats. + // It seems to just be interested in the sign. + if (!is_numeric($matchType)) { + throw new Exception(ExcelError::Value()); + } + if ($matchType > 0) { + return self::MATCHTYPE_LARGEST_VALUE; + } + if ($matchType < 0) { + return self::MATCHTYPE_SMALLEST_VALUE; + } + + return self::MATCHTYPE_FIRST_VALUE; + } + + private static function validateLookupArray(array $lookupArray): void + { + // Lookup_array should not be empty + $lookupArraySize = count($lookupArray); + if ($lookupArraySize <= 0) { + throw new Exception(ExcelError::NA()); + } + } + + /** + * @param mixed $matchType + */ + private static function prepareLookupArray(array $lookupArray, $matchType): array + { + // Lookup_array should contain only number, text, or logical values, or empty (null) cells + foreach ($lookupArray as $i => $value) { + // check the type of the value + if ((!is_numeric($value)) && (!is_string($value)) && (!is_bool($value)) && ($value !== null)) { + throw new Exception(ExcelError::NA()); + } + // Convert strings to lowercase for case-insensitive testing + if (is_string($value)) { + $lookupArray[$i] = StringHelper::strToLower($value); + } + if ( + ($value === null) && + (($matchType == self::MATCHTYPE_LARGEST_VALUE) || ($matchType == self::MATCHTYPE_SMALLEST_VALUE)) + ) { + unset($lookupArray[$i]); + } + } + + return $lookupArray; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Filter.php b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Filter.php new file mode 100644 index 0000000..74fa832 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Filter.php @@ -0,0 +1,81 @@ +getWorksheet()->getParent()->getSheetByName($worksheetName) + : $cell->getWorksheet(); + + if ( + $worksheet === null || + !$worksheet->cellExists($cellReference) || + !$worksheet->getCell($cellReference)->isFormula() + ) { + return ExcelError::NA(); + } + + return $worksheet->getCell($cellReference)->getValue(); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/HLookup.php b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/HLookup.php new file mode 100644 index 0000000..e2d27bd --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/HLookup.php @@ -0,0 +1,121 @@ +getMessage(); + } + + $f = array_keys($lookupArray); + $firstRow = reset($f); + if ((!is_array($lookupArray[$firstRow])) || ($indexNumber > count($lookupArray))) { + return ExcelError::REF(); + } + + $firstkey = $f[0] - 1; + $returnColumn = $firstkey + $indexNumber; + $firstColumn = array_shift($f) ?? 1; + $rowNumber = self::hLookupSearch($lookupValue, $lookupArray, $firstColumn, $notExactMatch); + + if ($rowNumber !== null) { + // otherwise return the appropriate value + return $lookupArray[$returnColumn][Coordinate::stringFromColumnIndex($rowNumber)]; + } + + return ExcelError::NA(); + } + + /** + * @param mixed $lookupValue The value that you want to match in lookup_array + * @param int|string $column + */ + private static function hLookupSearch($lookupValue, array $lookupArray, $column, bool $notExactMatch): ?int + { + $lookupLower = StringHelper::strToLower((string) $lookupValue); + + $rowNumber = null; + foreach ($lookupArray[$column] as $rowKey => $rowData) { + // break if we have passed possible keys + $bothNumeric = is_numeric($lookupValue) && is_numeric($rowData); + $bothNotNumeric = !is_numeric($lookupValue) && !is_numeric($rowData); + $cellDataLower = StringHelper::strToLower((string) $rowData); + + if ( + $notExactMatch && + (($bothNumeric && $rowData > $lookupValue) || ($bothNotNumeric && $cellDataLower > $lookupLower)) + ) { + break; + } + + $rowNumber = self::checkMatch( + $bothNumeric, + $bothNotNumeric, + $notExactMatch, + Coordinate::columnIndexFromString($rowKey), + $cellDataLower, + $lookupLower, + $rowNumber + ); + } + + return $rowNumber; + } + + private static function convertLiteralArray(array $lookupArray): array + { + if (array_key_exists(0, $lookupArray)) { + $lookupArray2 = []; + $row = 0; + foreach ($lookupArray as $arrayVal) { + ++$row; + if (!is_array($arrayVal)) { + $arrayVal = [$arrayVal]; + } + $arrayVal2 = []; + foreach ($arrayVal as $key2 => $val2) { + $index = Coordinate::stringFromColumnIndex($key2 + 1); + $arrayVal2[$index] = $val2; + } + $lookupArray2[$row] = $arrayVal2; + } + $lookupArray = $lookupArray2; + } + + return $lookupArray; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Helpers.php b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Helpers.php new file mode 100644 index 0000000..76a194b --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Helpers.php @@ -0,0 +1,74 @@ +getWorkSheet(); + $sheetTitle = ($workSheet === null) ? '' : $workSheet->getTitle(); + $value = (string) preg_replace('/^=/', '', $namedRange->getValue()); + self::adjustSheetTitle($sheetTitle, $value); + $cellAddress1 = $sheetTitle . $value; + $cellAddress = $cellAddress1; + $a1 = self::CELLADDRESS_USE_A1; + } + if (strpos($cellAddress, ':') !== false) { + [$cellAddress1, $cellAddress2] = explode(':', $cellAddress); + } + $cellAddress = self::convertR1C1($cellAddress1, $cellAddress2, $a1, $baseRow, $baseCol); + + return [$cellAddress1, $cellAddress2, $cellAddress]; + } + + public static function extractWorksheet(string $cellAddress, Cell $cell): array + { + $sheetName = ''; + if (strpos($cellAddress, '!') !== false) { + [$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); + $sheetName = trim($sheetName, "'"); + } + + $worksheet = ($sheetName !== '') + ? $cell->getWorksheet()->getParent()->getSheetByName($sheetName) + : $cell->getWorksheet(); + + return [$cellAddress, $worksheet, $sheetName]; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Hyperlink.php b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Hyperlink.php new file mode 100644 index 0000000..5387833 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Hyperlink.php @@ -0,0 +1,41 @@ +getHyperlink()->setUrl($linkURL); + $cell->getHyperlink()->setTooltip($displayName); + + return $displayName; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Indirect.php b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Indirect.php new file mode 100644 index 0000000..bd76ce9 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Indirect.php @@ -0,0 +1,130 @@ +getCoordinate()); + + try { + $a1 = self::a1Format($a1fmt); + $cellAddress = self::validateAddress($cellAddress); + } catch (Exception $e) { + return $e->getMessage(); + } + + [$cellAddress, $worksheet, $sheetName] = Helpers::extractWorksheet($cellAddress, $cell); + + if (preg_match('/^' . Calculation::CALCULATION_REGEXP_COLUMNRANGE_RELATIVE . '$/miu', $cellAddress, $matches)) { + $cellAddress = self::handleRowColumnRanges($worksheet, ...explode(':', $cellAddress)); + } elseif (preg_match('/^' . Calculation::CALCULATION_REGEXP_ROWRANGE_RELATIVE . '$/miu', $cellAddress, $matches)) { + $cellAddress = self::handleRowColumnRanges($worksheet, ...explode(':', $cellAddress)); + } + + try { + [$cellAddress1, $cellAddress2, $cellAddress] = Helpers::extractCellAddresses($cellAddress, $a1, $cell->getWorkSheet(), $sheetName, $baseRow, $baseCol); + } catch (Exception $e) { + return ExcelError::REF(); + } + + if ( + (!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/miu', $cellAddress1, $matches)) || + (($cellAddress2 !== null) && (!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/miu', $cellAddress2, $matches))) + ) { + return ExcelError::REF(); + } + + return self::extractRequiredCells($worksheet, $cellAddress); + } + + /** + * Extract range values. + * + * @return mixed Array of values in range if range contains more than one element. + * Otherwise, a single value is returned. + */ + private static function extractRequiredCells(?Worksheet $worksheet, string $cellAddress) + { + return Calculation::getInstance($worksheet !== null ? $worksheet->getParent() : null) + ->extractCellRange($cellAddress, $worksheet, false); + } + + private static function handleRowColumnRanges(?Worksheet $worksheet, string $start, string $end): string + { + // Being lazy, we're only checking a single row/column to get the max + if (ctype_digit($start) && $start <= 1048576) { + // Max 16,384 columns for Excel2007 + $endColRef = ($worksheet !== null) ? $worksheet->getHighestDataColumn((int) $start) : AddressRange::MAX_COLUMN; + + return "A{$start}:{$endColRef}{$end}"; + } elseif (ctype_alpha($start) && strlen($start) <= 3) { + // Max 1,048,576 rows for Excel2007 + $endRowRef = ($worksheet !== null) ? $worksheet->getHighestDataRow($start) : AddressRange::MAX_ROW; + + return "{$start}1:{$end}{$endRowRef}"; + } + + return "{$start}:{$end}"; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Lookup.php b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Lookup.php new file mode 100644 index 0000000..76a360b --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Lookup.php @@ -0,0 +1,110 @@ + 1) || (!$hasResultVector && $lookupRows === 2 && $lookupColumns !== 2)) { + $lookupVector = LookupRef\Matrix::transpose($lookupVector); + $lookupRows = self::rowCount($lookupVector); + $lookupColumns = self::columnCount($lookupVector); + } + + $resultVector = self::verifyResultVector($lookupVector, $resultVector); + + if ($lookupRows === 2 && !$hasResultVector) { + $resultVector = array_pop($lookupVector); + $lookupVector = array_shift($lookupVector); + } + + if ($lookupColumns !== 2) { + $lookupVector = self::verifyLookupValues($lookupVector, $resultVector); + } + + return VLookup::lookup($lookupValue, $lookupVector, 2); + } + + private static function verifyLookupValues(array $lookupVector, array $resultVector): array + { + foreach ($lookupVector as &$value) { + if (is_array($value)) { + $k = array_keys($value); + $key1 = $key2 = array_shift($k); + ++$key2; + $dataValue1 = $value[$key1]; + } else { + $key1 = 0; + $key2 = 1; + $dataValue1 = $value; + } + + $dataValue2 = array_shift($resultVector); + if (is_array($dataValue2)) { + $dataValue2 = array_shift($dataValue2); + } + $value = [$key1 => $dataValue1, $key2 => $dataValue2]; + } + unset($value); + + return $lookupVector; + } + + private static function verifyResultVector(array $lookupVector, $resultVector) + { + if ($resultVector === null) { + $resultVector = $lookupVector; + } + + $resultRows = self::rowCount($resultVector); + $resultColumns = self::columnCount($resultVector); + + // we correctly orient our results + if ($resultRows === 1 && $resultColumns > 1) { + $resultVector = LookupRef\Matrix::transpose($resultVector); + } + + return $resultVector; + } + + private static function rowCount(array $dataArray): int + { + return count($dataArray); + } + + private static function columnCount(array $dataArray): int + { + $rowKeys = array_keys($dataArray); + $row = array_shift($rowKeys); + + return count($dataArray[$row]); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php new file mode 100644 index 0000000..5fe1676 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php @@ -0,0 +1,66 @@ + 1 && + (count($values, COUNT_NORMAL) === 1 || count($values, COUNT_RECURSIVE) === count($values, COUNT_NORMAL)); + } + + /** + * TRANSPOSE. + * + * @param array|mixed $matrixData A matrix of values + * + * @return array + */ + public static function transpose($matrixData) + { + $returnMatrix = []; + if (!is_array($matrixData)) { + $matrixData = [[$matrixData]]; + } + + $column = 0; + foreach ($matrixData as $matrixRow) { + $row = 0; + foreach ($matrixRow as $matrixCell) { + $returnMatrix[$row][$column] = $matrixCell; + ++$row; + } + ++$column; + } + + return $returnMatrix; + } + + /** + * INDEX. + * + * Uses an index to choose a value from a reference or array + * + * Excel Function: + * =INDEX(range_array, row_num, [column_num], [area_num]) + * + * @param mixed $matrix A range of cells or an array constant + * @param mixed $rowNum The row in the array or range from which to return a value. + * If row_num is omitted, column_num is required. + * Or can be an array of values + * @param mixed $columnNum The column in the array or range from which to return a value. + * If column_num is omitted, row_num is required. + * Or can be an array of values + * + * TODO Provide support for area_num, currently not supported + * + * @return mixed the value of a specified cell or array of cells + * If an array of values is passed as the $rowNum and/or $columnNum arguments, then the returned result + * will also be an array with the same dimensions + */ + public static function index($matrix, $rowNum = 0, $columnNum = null) + { + if (is_array($rowNum) || is_array($columnNum)) { + return self::evaluateArrayArgumentsSubsetFrom([self::class, __FUNCTION__], 1, $matrix, $rowNum, $columnNum); + } + + $rowNum = $rowNum ?? 0; + $originalColumnNum = $columnNum; + $columnNum = $columnNum ?? 0; + + try { + $rowNum = LookupRefValidations::validatePositiveInt($rowNum); + $columnNum = LookupRefValidations::validatePositiveInt($columnNum); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (!is_array($matrix) || ($rowNum > count($matrix))) { + return ExcelError::REF(); + } + + $rowKeys = array_keys($matrix); + $columnKeys = @array_keys($matrix[$rowKeys[0]]); + + if ($columnNum > count($columnKeys)) { + return ExcelError::REF(); + } + if ($originalColumnNum === null && 1 < count($columnKeys)) { + return ExcelError::REF(); + } + + if ($columnNum === 0) { + return self::extractRowValue($matrix, $rowKeys, $rowNum); + } + + $columnNum = $columnKeys[--$columnNum]; + if ($rowNum === 0) { + return array_map( + function ($value) { + return [$value]; + }, + array_column($matrix, $columnNum) + ); + } + $rowNum = $rowKeys[--$rowNum]; + + return $matrix[$rowNum][$columnNum]; + } + + private static function extractRowValue(array $matrix, array $rowKeys, int $rowNum) + { + if ($rowNum === 0) { + return $matrix; + } + + $rowNum = $rowKeys[--$rowNum]; + $row = $matrix[$rowNum]; + if (is_array($row)) { + return [$rowNum => $row]; + } + + return $row; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Offset.php b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Offset.php new file mode 100644 index 0000000..02a2558 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Offset.php @@ -0,0 +1,148 @@ +getParent() : null) + ->extractCellRange($cellAddress, $worksheet, false); + } + + private static function extractWorksheet($cellAddress, Cell $cell): array + { + $cellAddress = self::assessCellAddress($cellAddress, $cell); + + $sheetName = ''; + if (strpos($cellAddress, '!') !== false) { + [$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); + $sheetName = trim($sheetName, "'"); + } + + $worksheet = ($sheetName !== '') + ? $cell->getWorksheet()->getParent()->getSheetByName($sheetName) + : $cell->getWorksheet(); + + return [$cellAddress, $worksheet]; + } + + private static function assessCellAddress(string $cellAddress, Cell $cell): string + { + if (preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/mui', $cellAddress) !== false) { + $cellAddress = Functions::expandDefinedName($cellAddress, $cell); + } + + return $cellAddress; + } + + private static function adjustEndCellColumnForWidth(string $endCellColumn, $width, int $startCellColumn, $columns) + { + $endCellColumn = Coordinate::columnIndexFromString($endCellColumn) - 1; + if (($width !== null) && (!is_object($width))) { + $endCellColumn = $startCellColumn + (int) $width - 1; + } else { + $endCellColumn += (int) $columns; + } + + return $endCellColumn; + } + + private static function adustEndCellRowForHeight($height, int $startCellRow, $rows, $endCellRow): int + { + if (($height !== null) && (!is_object($height))) { + $endCellRow = $startCellRow + (int) $height - 1; + } else { + $endCellRow += (int) $rows; + } + + return $endCellRow; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php new file mode 100644 index 0000000..8bce07e --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/RowColumnInformation.php @@ -0,0 +1,209 @@ +getColumn()) : 1; + } + + /** + * COLUMN. + * + * Returns the column number of the given cell reference + * If the cell reference is a range of cells, COLUMN returns the column numbers of each column + * in the reference as a horizontal array. + * If cell reference is omitted, and the function is being called through the calculation engine, + * then it is assumed to be the reference of the cell in which the COLUMN function appears; + * otherwise this function returns 1. + * + * Excel Function: + * =COLUMN([cellAddress]) + * + * @param null|array|string $cellAddress A reference to a range of cells for which you want the column numbers + * + * @return int|int[] + */ + public static function COLUMN($cellAddress = null, ?Cell $cell = null) + { + if (self::cellAddressNullOrWhitespace($cellAddress)) { + return self::cellColumn($cell); + } + + if (is_array($cellAddress)) { + foreach ($cellAddress as $columnKey => $value) { + $columnKey = (string) preg_replace('/[^a-z]/i', '', $columnKey); + + return (int) Coordinate::columnIndexFromString($columnKey); + } + + return self::cellColumn($cell); + } + + $cellAddress = $cellAddress ?? ''; + if ($cell != null) { + [,, $sheetName] = Helpers::extractWorksheet($cellAddress, $cell); + [,, $cellAddress] = Helpers::extractCellAddresses($cellAddress, true, $cell->getWorksheet(), $sheetName); + } + [, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); + if (strpos($cellAddress, ':') !== false) { + [$startAddress, $endAddress] = explode(':', $cellAddress); + $startAddress = (string) preg_replace('/[^a-z]/i', '', $startAddress); + $endAddress = (string) preg_replace('/[^a-z]/i', '', $endAddress); + + return range( + (int) Coordinate::columnIndexFromString($startAddress), + (int) Coordinate::columnIndexFromString($endAddress) + ); + } + + $cellAddress = (string) preg_replace('/[^a-z]/i', '', $cellAddress); + + return (int) Coordinate::columnIndexFromString($cellAddress); + } + + /** + * COLUMNS. + * + * Returns the number of columns in an array or reference. + * + * Excel Function: + * =COLUMNS(cellAddress) + * + * @param null|array|string $cellAddress An array or array formula, or a reference to a range of cells + * for which you want the number of columns + * + * @return int|string The number of columns in cellAddress, or a string if arguments are invalid + */ + public static function COLUMNS($cellAddress = null) + { + if (self::cellAddressNullOrWhitespace($cellAddress)) { + return 1; + } + if (!is_array($cellAddress)) { + return ExcelError::VALUE(); + } + + reset($cellAddress); + $isMatrix = (is_numeric(key($cellAddress))); + [$columns, $rows] = Calculation::getMatrixDimensions($cellAddress); + + if ($isMatrix) { + return $rows; + } + + return $columns; + } + + private static function cellRow(?Cell $cell): int + { + return ($cell !== null) ? $cell->getRow() : 1; + } + + /** + * ROW. + * + * Returns the row number of the given cell reference + * If the cell reference is a range of cells, ROW returns the row numbers of each row in the reference + * as a vertical array. + * If cell reference is omitted, and the function is being called through the calculation engine, + * then it is assumed to be the reference of the cell in which the ROW function appears; + * otherwise this function returns 1. + * + * Excel Function: + * =ROW([cellAddress]) + * + * @param null|array|string $cellAddress A reference to a range of cells for which you want the row numbers + * + * @return int|mixed[]|string + */ + public static function ROW($cellAddress = null, ?Cell $cell = null) + { + if (self::cellAddressNullOrWhitespace($cellAddress)) { + return self::cellRow($cell); + } + + if (is_array($cellAddress)) { + foreach ($cellAddress as $rowKey => $rowValue) { + foreach ($rowValue as $columnKey => $cellValue) { + return (int) preg_replace('/\D/', '', $rowKey); + } + } + + return self::cellRow($cell); + } + + $cellAddress = $cellAddress ?? ''; + if ($cell !== null) { + [,, $sheetName] = Helpers::extractWorksheet($cellAddress, $cell); + [,, $cellAddress] = Helpers::extractCellAddresses($cellAddress, true, $cell->getWorksheet(), $sheetName); + } + [, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); + if (strpos($cellAddress, ':') !== false) { + [$startAddress, $endAddress] = explode(':', $cellAddress); + $startAddress = (string) preg_replace('/\D/', '', $startAddress); + $endAddress = (string) preg_replace('/\D/', '', $endAddress); + + return array_map( + function ($value) { + return [$value]; + }, + range($startAddress, $endAddress) + ); + } + [$cellAddress] = explode(':', $cellAddress); + + return (int) preg_replace('/\D/', '', $cellAddress); + } + + /** + * ROWS. + * + * Returns the number of rows in an array or reference. + * + * Excel Function: + * =ROWS(cellAddress) + * + * @param null|array|string $cellAddress An array or array formula, or a reference to a range of cells + * for which you want the number of rows + * + * @return int|string The number of rows in cellAddress, or a string if arguments are invalid + */ + public static function ROWS($cellAddress = null) + { + if (self::cellAddressNullOrWhitespace($cellAddress)) { + return 1; + } + if (!is_array($cellAddress)) { + return ExcelError::VALUE(); + } + + reset($cellAddress); + $isMatrix = (is_numeric(key($cellAddress))); + [$columns, $rows] = Calculation::getMatrixDimensions($cellAddress); + + if ($isMatrix) { + return $columns; + } + + return $rows; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Selection.php b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Selection.php new file mode 100644 index 0000000..0ac9177 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Selection.php @@ -0,0 +1,51 @@ + $entryCount)) { + return ExcelError::VALUE(); + } + + if (is_array($chooseArgs[$chosenEntry])) { + return Functions::flattenArray($chooseArgs[$chosenEntry]); + } + + return $chooseArgs[$chosenEntry]; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Sort.php b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Sort.php new file mode 100644 index 0000000..ff78fbe --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Sort.php @@ -0,0 +1,342 @@ +getMessage(); + } + + // We want a simple, enumrated array of arrays where we can reference column by its index number. + $sortArray = array_values(array_map('array_values', $sortArray)); + + return ($byColumn === true) + ? self::sortByColumn($sortArray, $sortIndex, $sortOrder) + : self::sortByRow($sortArray, $sortIndex, $sortOrder); + } + + /** + * SORTBY + * The SORTBY function sorts the contents of a range or array based on the values in a corresponding range or array. + * The returned array is the same shape as the provided array argument. + * Both $sortIndex and $sortOrder can be arrays, to provide multi-level sorting. + * + * @param mixed $sortArray The range of cells being sorted + * @param mixed $args + * At least one additional argument must be provided, The vector or range to sort on + * After that, arguments are passed as pairs: + * sort order: ascending or descending + * Ascending = 1 (self::ORDER_ASCENDING) + * Descending = -1 (self::ORDER_DESCENDING) + * additional arrays or ranges for multi-level sorting + * + * @return mixed The sorted values from the sort range + */ + public static function sortBy($sortArray, ...$args) + { + if (!is_array($sortArray)) { + // Scalars are always returned "as is" + return $sortArray; + } + + $sortArray = self::enumerateArrayKeys($sortArray); + + $lookupArraySize = count($sortArray); + $argumentCount = count($args); + + try { + $sortBy = $sortOrder = []; + for ($i = 0; $i < $argumentCount; $i += 2) { + $sortBy[] = self::validateSortVector($args[$i], $lookupArraySize); + $sortOrder[] = self::validateSortOrder($args[$i + 1] ?? self::ORDER_ASCENDING); + } + } catch (Exception $e) { + return $e->getMessage(); + } + + return self::processSortBy($sortArray, $sortBy, $sortOrder); + } + + private static function enumerateArrayKeys(array $sortArray): array + { + array_walk( + $sortArray, + function (&$columns): void { + if (is_array($columns)) { + $columns = array_values($columns); + } + } + ); + + return array_values($sortArray); + } + + /** + * @param mixed $sortIndex + * @param mixed $sortOrder + */ + private static function validateScalarArgumentsForSort(&$sortIndex, &$sortOrder, int $sortArraySize): void + { + if (is_array($sortIndex) || is_array($sortOrder)) { + throw new Exception(ExcelError::VALUE()); + } + + $sortIndex = self::validatePositiveInt($sortIndex, false); + + if ($sortIndex > $sortArraySize) { + throw new Exception(ExcelError::VALUE()); + } + + $sortOrder = self::validateSortOrder($sortOrder); + } + + /** + * @param mixed $sortVector + */ + private static function validateSortVector($sortVector, int $sortArraySize): array + { + if (!is_array($sortVector)) { + throw new Exception(ExcelError::VALUE()); + } + + // It doesn't matter if it's a row or a column vectors, it works either way + $sortVector = Functions::flattenArray($sortVector); + if (count($sortVector) !== $sortArraySize) { + throw new Exception(ExcelError::VALUE()); + } + + return $sortVector; + } + + /** + * @param mixed $sortOrder + */ + private static function validateSortOrder($sortOrder): int + { + $sortOrder = self::validateInt($sortOrder); + if (($sortOrder == self::ORDER_ASCENDING || $sortOrder === self::ORDER_DESCENDING) === false) { + throw new Exception(ExcelError::VALUE()); + } + + return $sortOrder; + } + + /** + * @param array $sortIndex + * @param mixed $sortOrder + */ + private static function validateArrayArgumentsForSort(&$sortIndex, &$sortOrder, int $sortArraySize): void + { + // It doesn't matter if they're row or column vectors, it works either way + $sortIndex = Functions::flattenArray($sortIndex); + $sortOrder = Functions::flattenArray($sortOrder); + + if ( + count($sortOrder) === 0 || count($sortOrder) > $sortArraySize || + (count($sortOrder) > count($sortIndex)) + ) { + throw new Exception(ExcelError::VALUE()); + } + + if (count($sortIndex) > count($sortOrder)) { + // If $sortOrder has fewer elements than $sortIndex, then the last order element is repeated. + $sortOrder = array_merge( + $sortOrder, + array_fill(0, count($sortIndex) - count($sortOrder), array_pop($sortOrder)) + ); + } + + foreach ($sortIndex as $key => &$value) { + self::validateScalarArgumentsForSort($value, $sortOrder[$key], $sortArraySize); + } + } + + private static function prepareSortVectorValues(array $sortVector): array + { + // Strings should be sorted case-insensitive; with booleans converted to locale-strings + return array_map( + function ($value) { + if (is_bool($value)) { + return ($value) ? Calculation::getTRUE() : Calculation::getFALSE(); + } elseif (is_string($value)) { + return StringHelper::strToLower($value); + } + + return $value; + }, + $sortVector + ); + } + + /** + * @param array[] $sortIndex + * @param int[] $sortOrder + */ + private static function processSortBy(array $sortArray, array $sortIndex, $sortOrder): array + { + $sortArguments = []; + $sortData = []; + foreach ($sortIndex as $index => $sortValues) { + $sortData[] = $sortValues; + $sortArguments[] = self::prepareSortVectorValues($sortValues); + $sortArguments[] = $sortOrder[$index] === self::ORDER_ASCENDING ? SORT_ASC : SORT_DESC; + } + $sortArguments = self::applyPHP7Patch($sortArray, $sortArguments); + + $sortVector = self::executeVectorSortQuery($sortData, $sortArguments); + + return self::sortLookupArrayFromVector($sortArray, $sortVector); + } + + /** + * @param int[] $sortIndex + * @param int[] $sortOrder + */ + private static function sortByRow(array $sortArray, array $sortIndex, array $sortOrder): array + { + $sortVector = self::buildVectorForSort($sortArray, $sortIndex, $sortOrder); + + return self::sortLookupArrayFromVector($sortArray, $sortVector); + } + + /** + * @param int[] $sortIndex + * @param int[] $sortOrder + */ + private static function sortByColumn(array $sortArray, array $sortIndex, array $sortOrder): array + { + $sortArray = Matrix::transpose($sortArray); + $result = self::sortByRow($sortArray, $sortIndex, $sortOrder); + + return Matrix::transpose($result); + } + + /** + * @param int[] $sortIndex + * @param int[] $sortOrder + */ + private static function buildVectorForSort(array $sortArray, array $sortIndex, array $sortOrder): array + { + $sortArguments = []; + $sortData = []; + foreach ($sortIndex as $index => $sortIndexValue) { + $sortValues = array_column($sortArray, $sortIndexValue - 1); + $sortData[] = $sortValues; + $sortArguments[] = self::prepareSortVectorValues($sortValues); + $sortArguments[] = $sortOrder[$index] === self::ORDER_ASCENDING ? SORT_ASC : SORT_DESC; + } + $sortArguments = self::applyPHP7Patch($sortArray, $sortArguments); + + $sortData = self::executeVectorSortQuery($sortData, $sortArguments); + + return $sortData; + } + + private static function executeVectorSortQuery(array $sortData, array $sortArguments): array + { + $sortData = Matrix::transpose($sortData); + + // We need to set an index that can be retained, as array_multisort doesn't maintain numeric keys. + $sortDataIndexed = []; + foreach ($sortData as $key => $value) { + $sortDataIndexed[Coordinate::stringFromColumnIndex($key + 1)] = $value; + } + unset($sortData); + + $sortArguments[] = &$sortDataIndexed; + + array_multisort(...$sortArguments); + + // After the sort, we restore the numeric keys that will now be in the correct, sorted order + $sortedData = []; + foreach (array_keys($sortDataIndexed) as $key) { + $sortedData[] = Coordinate::columnIndexFromString($key) - 1; + } + + return $sortedData; + } + + private static function sortLookupArrayFromVector(array $sortArray, array $sortVector): array + { + // Building a new array in the correct (sorted) order works; but may be memory heavy for larger arrays + $sortedArray = []; + foreach ($sortVector as $index) { + $sortedArray[] = $sortArray[$index]; + } + + return $sortedArray; + +// uksort( +// $lookupArray, +// function (int $a, int $b) use (array $sortVector) { +// return $sortVector[$a] <=> $sortVector[$b]; +// } +// ); +// +// return $lookupArray; + } + + /** + * Hack to handle PHP 7: + * From PHP 8.0.0, If two members compare as equal in a sort, they retain their original order; + * but prior to PHP 8.0.0, their relative order in the sorted array was undefined. + * MS Excel replicates the PHP 8.0.0 behaviour, retaining the original order of matching elements. + * To replicate that behaviour with PHP 7, we add an extra sort based on the row index. + */ + private static function applyPHP7Patch(array $sortArray, array $sortArguments): array + { + if (PHP_VERSION_ID < 80000) { + $sortArguments[] = range(1, count($sortArray)); + $sortArguments[] = SORT_ASC; + } + + return $sortArguments; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Unique.php b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Unique.php new file mode 100644 index 0000000..2ba5128 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/Unique.php @@ -0,0 +1,141 @@ + count($flattenedLookupVector, COUNT_RECURSIVE) + 1) { + // We're looking at a full column check (multiple rows) + $transpose = Matrix::transpose($lookupVector); + $result = self::uniqueByRow($transpose, $exactlyOnce); + + return (is_array($result)) ? Matrix::transpose($result) : $result; + } + + $result = self::countValuesCaseInsensitive($flattenedLookupVector); + + if ($exactlyOnce === true) { + $result = self::exactlyOnceFilter($result); + } + + if (count($result) === 0) { + return ExcelError::CALC(); + } + + $result = array_keys($result); + + return $result; + } + + private static function countValuesCaseInsensitive(array $caseSensitiveLookupValues): array + { + $caseInsensitiveCounts = array_count_values( + array_map( + function (string $value) { + return StringHelper::strToUpper($value); + }, + $caseSensitiveLookupValues + ) + ); + + $caseSensitiveCounts = []; + foreach ($caseInsensitiveCounts as $caseInsensitiveKey => $count) { + if (is_numeric($caseInsensitiveKey)) { + $caseSensitiveCounts[$caseInsensitiveKey] = $count; + } else { + foreach ($caseSensitiveLookupValues as $caseSensitiveValue) { + if ($caseInsensitiveKey === StringHelper::strToUpper($caseSensitiveValue)) { + $caseSensitiveCounts[$caseSensitiveValue] = $count; + + break; + } + } + } + } + + return $caseSensitiveCounts; + } + + private static function exactlyOnceFilter(array $values): array + { + return array_filter( + $values, + function ($value) { + return $value === 1; + } + ); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/VLookup.php b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/VLookup.php new file mode 100644 index 0000000..edeb1aa --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/LookupRef/VLookup.php @@ -0,0 +1,117 @@ +getMessage(); + } + + $f = array_keys($lookupArray); + $firstRow = array_pop($f); + if ((!is_array($lookupArray[$firstRow])) || ($indexNumber > count($lookupArray[$firstRow]))) { + return ExcelError::REF(); + } + $columnKeys = array_keys($lookupArray[$firstRow]); + $returnColumn = $columnKeys[--$indexNumber]; + $firstColumn = array_shift($columnKeys) ?? 1; + + if (!$notExactMatch) { + /** @var callable */ + $callable = [self::class, 'vlookupSort']; + uasort($lookupArray, $callable); + } + + $rowNumber = self::vLookupSearch($lookupValue, $lookupArray, $firstColumn, $notExactMatch); + + if ($rowNumber !== null) { + // return the appropriate value + return $lookupArray[$rowNumber][$returnColumn]; + } + + return ExcelError::NA(); + } + + private static function vlookupSort(array $a, array $b): int + { + reset($a); + $firstColumn = key($a); + $aLower = StringHelper::strToLower((string) $a[$firstColumn]); + $bLower = StringHelper::strToLower((string) $b[$firstColumn]); + + if ($aLower == $bLower) { + return 0; + } + + return ($aLower < $bLower) ? -1 : 1; + } + + /** + * @param mixed $lookupValue The value that you want to match in lookup_array + * @param int|string $column + */ + private static function vLookupSearch($lookupValue, array $lookupArray, $column, bool $notExactMatch): ?int + { + $lookupLower = StringHelper::strToLower((string) $lookupValue); + + $rowNumber = null; + foreach ($lookupArray as $rowKey => $rowData) { + $bothNumeric = is_numeric($lookupValue) && is_numeric($rowData[$column]); + $bothNotNumeric = !is_numeric($lookupValue) && !is_numeric($rowData[$column]); + $cellDataLower = StringHelper::strToLower((string) $rowData[$column]); + + // break if we have passed possible keys + if ( + $notExactMatch && + (($bothNumeric && ($rowData[$column] > $lookupValue)) || + ($bothNotNumeric && ($cellDataLower > $lookupLower))) + ) { + break; + } + + $rowNumber = self::checkMatch( + $bothNumeric, + $bothNotNumeric, + $notExactMatch, + $rowKey, + $cellDataLower, + $lookupLower, + $rowNumber + ); + } + + return $rowNumber; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig.php old mode 100755 new mode 100644 index 4e384ea..1790b21 --- a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig.php +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig.php @@ -2,40 +2,30 @@ namespace PhpOffice\PhpSpreadsheet\Calculation; -use Matrix\Exception as MatrixException; -use Matrix\Matrix; - +/** + * @deprecated 1.18.0 + */ class MathTrig { - // - // Private method to return an array of the factors of the input value - // - private static function factors($value) + /** + * ARABIC. + * + * Converts a Roman numeral to an Arabic numeral. + * + * Excel Function: + * ARABIC(text) + * + * @deprecated 1.18.0 + * Use the evaluate method in the MathTrig\Arabic class instead + * @see MathTrig\Arabic::evaluate() + * + * @param array|string $roman + * + * @return array|int|string the arabic numberal contrived from the roman numeral + */ + public static function ARABIC($roman) { - $startVal = floor(sqrt($value)); - - $factorArray = []; - for ($i = $startVal; $i > 1; --$i) { - if (($value % $i) == 0) { - $factorArray = array_merge($factorArray, self::factors($value / $i)); - $factorArray = array_merge($factorArray, self::factors($i)); - if ($i <= sqrt($value)) { - break; - } - } - } - if (!empty($factorArray)) { - rsort($factorArray); - - return $factorArray; - } - - return [(int) $value]; - } - - private static function romanCut($num, $n) - { - return ($num - ($num % $n)) / $n; + return MathTrig\Arabic::evaluate($roman); } /** @@ -54,34 +44,41 @@ class MathTrig * Excel Function: * ATAN2(xCoordinate,yCoordinate) * - * @category Mathematical and Trigonometric Functions + * @deprecated 1.18.0 + * Use the atan2 method in the MathTrig\Trig\Tangent class instead + * @see MathTrig\Trig\Tangent::atan2() * - * @param float $xCoordinate the x-coordinate of the point - * @param float $yCoordinate the y-coordinate of the point + * @param array|float $xCoordinate the x-coordinate of the point + * @param array|float $yCoordinate the y-coordinate of the point * - * @return float the inverse tangent of the specified x- and y-coordinates + * @return array|float|string the inverse tangent of the specified x- and y-coordinates, or a string containing an error */ public static function ATAN2($xCoordinate = null, $yCoordinate = null) { - $xCoordinate = Functions::flattenSingleValue($xCoordinate); - $yCoordinate = Functions::flattenSingleValue($yCoordinate); + return MathTrig\Trig\Tangent::atan2($xCoordinate, $yCoordinate); + } - $xCoordinate = ($xCoordinate !== null) ? $xCoordinate : 0.0; - $yCoordinate = ($yCoordinate !== null) ? $yCoordinate : 0.0; - - if (((is_numeric($xCoordinate)) || (is_bool($xCoordinate))) && - ((is_numeric($yCoordinate))) || (is_bool($yCoordinate))) { - $xCoordinate = (float) $xCoordinate; - $yCoordinate = (float) $yCoordinate; - - if (($xCoordinate == 0) && ($yCoordinate == 0)) { - return Functions::DIV0(); - } - - return atan2($yCoordinate, $xCoordinate); - } - - return Functions::VALUE(); + /** + * BASE. + * + * Converts a number into a text representation with the given radix (base). + * + * Excel Function: + * BASE(Number, Radix [Min_length]) + * + * @deprecated 1.18.0 + * Use the evaluate method in the MathTrig\Base class instead + * @see MathTrig\Base::evaluate() + * + * @param float $number + * @param float $radix + * @param int $minLength + * + * @return array|string the text representation with the given radix (base) + */ + public static function BASE($number, $radix, $minLength = null) + { + return MathTrig\Base::evaluate($number, $radix, $minLength); } /** @@ -95,34 +92,18 @@ class MathTrig * Excel Function: * CEILING(number[,significance]) * - * @category Mathematical and Trigonometric Functions + * @deprecated 1.17.0 + * Use the ceiling() method in the MathTrig\Ceiling class instead + * @see MathTrig\Ceiling::ceiling() * * @param float $number the number you want to round * @param float $significance the multiple to which you want to round * - * @return float Rounded Number + * @return array|float|string Rounded Number, or a string containing an error */ public static function CEILING($number, $significance = null) { - $number = Functions::flattenSingleValue($number); - $significance = Functions::flattenSingleValue($significance); - - if (($significance === null) && - (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC)) { - $significance = $number / abs($number); - } - - if ((is_numeric($number)) && (is_numeric($significance))) { - if (($number == 0.0) || ($significance == 0.0)) { - return 0.0; - } elseif (self::SIGN($number) == self::SIGN($significance)) { - return ceil($number / $significance) * $significance; - } - - return Functions::NAN(); - } - - return Functions::VALUE(); + return MathTrig\Ceiling::ceiling($number, $significance); } /** @@ -134,29 +115,18 @@ class MathTrig * Excel Function: * COMBIN(numObjs,numInSet) * - * @category Mathematical and Trigonometric Functions + * @deprecated 1.18.0 + * Use the withoutRepetition() method in the MathTrig\Combinations class instead + * @see MathTrig\Combinations::withoutRepetition() * - * @param int $numObjs Number of different objects - * @param int $numInSet Number of objects in each combination + * @param array|int $numObjs Number of different objects + * @param array|int $numInSet Number of objects in each combination * - * @return int Number of combinations + * @return array|float|int|string Number of combinations, or a string containing an error */ public static function COMBIN($numObjs, $numInSet) { - $numObjs = Functions::flattenSingleValue($numObjs); - $numInSet = Functions::flattenSingleValue($numInSet); - - if ((is_numeric($numObjs)) && (is_numeric($numInSet))) { - if ($numObjs < $numInSet) { - return Functions::NAN(); - } elseif ($numInSet < 0) { - return Functions::NAN(); - } - - return round(self::FACT($numObjs) / self::FACT($numObjs - $numInSet)) / self::FACT($numInSet); - } - - return Functions::VALUE(); + return MathTrig\Combinations::withoutRepetition($numObjs, $numInSet); } /** @@ -171,29 +141,29 @@ class MathTrig * Excel Function: * EVEN(number) * - * @category Mathematical and Trigonometric Functions + * @deprecated 1.18.0 + * Use the even() method in the MathTrig\Round class instead + * @see MathTrig\Round::even() * - * @param float $number Number to round + * @param array|float $number Number to round * - * @return int Rounded Number + * @return array|float|int|string Rounded Number, or a string containing an error */ public static function EVEN($number) { - $number = Functions::flattenSingleValue($number); + return MathTrig\Round::even($number); + } - if ($number === null) { - return 0; - } elseif (is_bool($number)) { - $number = (int) $number; - } - - if (is_numeric($number)) { - $significance = 2 * self::SIGN($number); - - return (int) self::CEILING($number, $significance); - } - - return Functions::VALUE(); + /** + * Helper function for Even. + * + * @deprecated 1.18.0 + * Use the evaluate() method in the MathTrig\Helpers class instead + * @see MathTrig\Helpers::getEven() + */ + public static function getEven(float $number): int + { + return (int) MathTrig\Helpers::getEven($number); } /** @@ -205,36 +175,17 @@ class MathTrig * Excel Function: * FACT(factVal) * - * @category Mathematical and Trigonometric Functions + * @deprecated 1.18.0 + * Use the fact() method in the MathTrig\Factorial class instead + * @see MathTrig\Factorial::fact() * - * @param float $factVal Factorial Value + * @param array|float $factVal Factorial Value * - * @return int Factorial + * @return array|float|int|string Factorial, or a string containing an error */ public static function FACT($factVal) { - $factVal = Functions::flattenSingleValue($factVal); - - if (is_numeric($factVal)) { - if ($factVal < 0) { - return Functions::NAN(); - } - $factLoop = floor($factVal); - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { - if ($factVal > $factLoop) { - return Functions::NAN(); - } - } - - $factorial = 1; - while ($factLoop > 1) { - $factorial *= $factLoop--; - } - - return $factorial; - } - - return Functions::VALUE(); + return MathTrig\Factorial::fact($factVal); } /** @@ -245,31 +196,17 @@ class MathTrig * Excel Function: * FACTDOUBLE(factVal) * - * @category Mathematical and Trigonometric Functions + * @deprecated 1.18.0 + * Use the factDouble() method in the MathTrig\Factorial class instead + * @see MathTrig\Factorial::factDouble() * - * @param float $factVal Factorial Value + * @param array|float $factVal Factorial Value * - * @return int Double Factorial + * @return array|float|int|string Double Factorial, or a string containing an error */ public static function FACTDOUBLE($factVal) { - $factLoop = Functions::flattenSingleValue($factVal); - - if (is_numeric($factLoop)) { - $factLoop = floor($factLoop); - if ($factVal < 0) { - return Functions::NAN(); - } - $factorial = 1; - while ($factLoop > 1) { - $factorial *= $factLoop--; - --$factLoop; - } - - return $factorial; - } - - return Functions::VALUE(); + return MathTrig\Factorial::factDouble($factVal); } /** @@ -280,41 +217,84 @@ class MathTrig * Excel Function: * FLOOR(number[,significance]) * - * @category Mathematical and Trigonometric Functions + * @deprecated 1.17.0 + * Use the floor() method in the MathTrig\Floor class instead + * @see MathTrig\Floor::floor() * * @param float $number Number to round * @param float $significance Significance * - * @return float Rounded Number + * @return array|float|string Rounded Number, or a string containing an error */ public static function FLOOR($number, $significance = null) { - $number = Functions::flattenSingleValue($number); - $significance = Functions::flattenSingleValue($significance); - - if (($significance === null) && - (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC)) { - $significance = $number / abs($number); - } - - if ((is_numeric($number)) && (is_numeric($significance))) { - if ($significance == 0.0) { - return Functions::DIV0(); - } elseif ($number == 0.0) { - return 0.0; - } elseif (self::SIGN($number) == self::SIGN($significance)) { - return floor($number / $significance) * $significance; - } - - return Functions::NAN(); - } - - return Functions::VALUE(); + return MathTrig\Floor::floor($number, $significance); } - private static function evaluateGCD($a, $b) + /** + * FLOOR.MATH. + * + * Round a number down to the nearest integer or to the nearest multiple of significance. + * + * Excel Function: + * FLOOR.MATH(number[,significance[,mode]]) + * + * @deprecated 1.17.0 + * Use the math() method in the MathTrig\Floor class instead + * @see MathTrig\Floor::math() + * + * @param float $number Number to round + * @param float $significance Significance + * @param int $mode direction to round negative numbers + * + * @return array|float|string Rounded Number, or a string containing an error + */ + public static function FLOORMATH($number, $significance = null, $mode = 0) { - return $b ? self::evaluateGCD($b, $a % $b) : $a; + return MathTrig\Floor::math($number, $significance, $mode); + } + + /** + * FLOOR.PRECISE. + * + * Rounds number down, toward zero, to the nearest multiple of significance. + * + * Excel Function: + * FLOOR.PRECISE(number[,significance]) + * + * @deprecated 1.17.0 + * Use the precise() method in the MathTrig\Floor class instead + * @see MathTrig\Floor::precise() + * + * @param float $number Number to round + * @param float $significance Significance + * + * @return array|float|string Rounded Number, or a string containing an error + */ + public static function FLOORPRECISE($number, $significance = 1) + { + return MathTrig\Floor::precise($number, $significance); + } + + /** + * INT. + * + * Casts a floating point value to an integer + * + * Excel Function: + * INT(number) + * + * @deprecated 1.17.0 + * Use the evaluate() method in the MathTrig\IntClass class instead + * @see MathTrig\IntClass::evaluate() + * + * @param array|float $number Number to cast to an integer + * + * @return array|int|string Integer value, or a string containing an error + */ + public static function INT($number) + { + return MathTrig\IntClass::evaluate($number); } /** @@ -327,60 +307,17 @@ class MathTrig * Excel Function: * GCD(number1[,number2[, ...]]) * - * @category Mathematical and Trigonometric Functions + * @deprecated 1.18.0 + * Use the evaluate() method in the MathTrig\Gcd class instead + * @see MathTrig\Gcd::evaluate() * * @param mixed ...$args Data values * - * @return int Greatest Common Divisor + * @return int|mixed|string Greatest Common Divisor, or a string containing an error */ public static function GCD(...$args) { - $args = Functions::flattenArray($args); - // Loop through arguments - foreach (Functions::flattenArray($args) as $value) { - if (!is_numeric($value)) { - return Functions::VALUE(); - } elseif ($value < 0) { - return Functions::NAN(); - } - } - - $gcd = (int) array_pop($args); - do { - $gcd = self::evaluateGCD($gcd, (int) array_pop($args)); - } while (!empty($args)); - - return $gcd; - } - - /** - * INT. - * - * Casts a floating point value to an integer - * - * Excel Function: - * INT(number) - * - * @category Mathematical and Trigonometric Functions - * - * @param float $number Number to cast to an integer - * - * @return int Integer value - */ - public static function INT($number) - { - $number = Functions::flattenSingleValue($number); - - if ($number === null) { - return 0; - } elseif (is_bool($number)) { - return (int) $number; - } - if (is_numeric($number)) { - return (int) floor($number); - } - - return Functions::VALUE(); + return MathTrig\Gcd::evaluate(...$args); } /** @@ -394,47 +331,17 @@ class MathTrig * Excel Function: * LCM(number1[,number2[, ...]]) * - * @category Mathematical and Trigonometric Functions + * @deprecated 1.18.0 + * Use the evaluate() method in the MathTrig\Lcm class instead + * @see MathTrig\Lcm::evaluate() * * @param mixed ...$args Data values * - * @return int Lowest Common Multiplier + * @return int|string Lowest Common Multiplier, or a string containing an error */ public static function LCM(...$args) { - $returnValue = 1; - $allPoweredFactors = []; - // Loop through arguments - foreach (Functions::flattenArray($args) as $value) { - if (!is_numeric($value)) { - return Functions::VALUE(); - } - if ($value == 0) { - return 0; - } elseif ($value < 0) { - return Functions::NAN(); - } - $myFactors = self::factors(floor($value)); - $myCountedFactors = array_count_values($myFactors); - $myPoweredFactors = []; - foreach ($myCountedFactors as $myCountedFactor => $myCountedPower) { - $myPoweredFactors[$myCountedFactor] = pow($myCountedFactor, $myCountedPower); - } - foreach ($myPoweredFactors as $myPoweredValue => $myPoweredFactor) { - if (isset($allPoweredFactors[$myPoweredValue])) { - if ($allPoweredFactors[$myPoweredValue] < $myPoweredFactor) { - $allPoweredFactors[$myPoweredValue] = $myPoweredFactor; - } - } else { - $allPoweredFactors[$myPoweredValue] = $myPoweredFactor; - } - } - } - foreach ($allPoweredFactors as $allPoweredFactor) { - $returnValue *= (int) $allPoweredFactor; - } - - return $returnValue; + return MathTrig\Lcm::evaluate(...$args); } /** @@ -445,26 +352,18 @@ class MathTrig * Excel Function: * LOG(number[,base]) * - * @category Mathematical and Trigonometric Functions + * @deprecated 1.18.0 + * Use the withBase() method in the MathTrig\Logarithms class instead + * @see MathTrig\Logarithms::withBase() * * @param float $number The positive real number for which you want the logarithm * @param float $base The base of the logarithm. If base is omitted, it is assumed to be 10. * - * @return float + * @return array|float|string The result, or a string containing an error */ - public static function logBase($number = null, $base = 10) + public static function logBase($number, $base = 10) { - $number = Functions::flattenSingleValue($number); - $base = ($base === null) ? 10 : (float) Functions::flattenSingleValue($base); - - if ((!is_numeric($base)) || (!is_numeric($number))) { - return Functions::VALUE(); - } - if (($base <= 0) || ($number <= 0)) { - return Functions::NAN(); - } - - return log($number, $base); + return MathTrig\Logarithms::withBase($number, $base); } /** @@ -475,48 +374,17 @@ class MathTrig * Excel Function: * MDETERM(array) * - * @category Mathematical and Trigonometric Functions + * @deprecated 1.18.0 + * Use the determinant() method in the MathTrig\MatrixFunctions class instead + * @see MathTrig\MatrixFunctions::determinant() * * @param array $matrixValues A matrix of values * - * @return float + * @return float|string The result, or a string containing an error */ public static function MDETERM($matrixValues) { - $matrixData = []; - if (!is_array($matrixValues)) { - $matrixValues = [[$matrixValues]]; - } - - $row = $maxColumn = 0; - foreach ($matrixValues as $matrixRow) { - if (!is_array($matrixRow)) { - $matrixRow = [$matrixRow]; - } - $column = 0; - foreach ($matrixRow as $matrixCell) { - if ((is_string($matrixCell)) || ($matrixCell === null)) { - return Functions::VALUE(); - } - $matrixData[$row][$column] = $matrixCell; - ++$column; - } - if ($column > $maxColumn) { - $maxColumn = $column; - } - ++$row; - } - - $matrix = new Matrix($matrixData); - if (!$matrix->isSquare()) { - return Functions::VALUE(); - } - - try { - return $matrix->determinant(); - } catch (MatrixException $ex) { - return Functions::VALUE(); - } + return MathTrig\MatrixFunctions::determinant($matrixValues); } /** @@ -527,138 +395,51 @@ class MathTrig * Excel Function: * MINVERSE(array) * - * @category Mathematical and Trigonometric Functions + * @deprecated 1.18.0 + * Use the inverse() method in the MathTrig\MatrixFunctions class instead + * @see MathTrig\MatrixFunctions::inverse() * * @param array $matrixValues A matrix of values * - * @return array + * @return array|string The result, or a string containing an error */ public static function MINVERSE($matrixValues) { - $matrixData = []; - if (!is_array($matrixValues)) { - $matrixValues = [[$matrixValues]]; - } - - $row = $maxColumn = 0; - foreach ($matrixValues as $matrixRow) { - if (!is_array($matrixRow)) { - $matrixRow = [$matrixRow]; - } - $column = 0; - foreach ($matrixRow as $matrixCell) { - if ((is_string($matrixCell)) || ($matrixCell === null)) { - return Functions::VALUE(); - } - $matrixData[$row][$column] = $matrixCell; - ++$column; - } - if ($column > $maxColumn) { - $maxColumn = $column; - } - ++$row; - } - - $matrix = new Matrix($matrixData); - if (!$matrix->isSquare()) { - return Functions::VALUE(); - } - - if ($matrix->determinant() == 0.0) { - return Functions::NAN(); - } - - try { - return $matrix->inverse()->toArray(); - } catch (MatrixException $ex) { - return Functions::VALUE(); - } + return MathTrig\MatrixFunctions::inverse($matrixValues); } /** * MMULT. * + * @deprecated 1.18.0 + * Use the multiply() method in the MathTrig\MatrixFunctions class instead + * @see MathTrig\MatrixFunctions::multiply() + * * @param array $matrixData1 A matrix of values * @param array $matrixData2 A matrix of values * - * @return array + * @return array|string The result, or a string containing an error */ public static function MMULT($matrixData1, $matrixData2) { - $matrixAData = $matrixBData = []; - if (!is_array($matrixData1)) { - $matrixData1 = [[$matrixData1]]; - } - if (!is_array($matrixData2)) { - $matrixData2 = [[$matrixData2]]; - } - - try { - $rowA = 0; - foreach ($matrixData1 as $matrixRow) { - if (!is_array($matrixRow)) { - $matrixRow = [$matrixRow]; - } - $columnA = 0; - foreach ($matrixRow as $matrixCell) { - if ((!is_numeric($matrixCell)) || ($matrixCell === null)) { - return Functions::VALUE(); - } - $matrixAData[$rowA][$columnA] = $matrixCell; - ++$columnA; - } - ++$rowA; - } - $matrixA = new Matrix($matrixAData); - $rowB = 0; - foreach ($matrixData2 as $matrixRow) { - if (!is_array($matrixRow)) { - $matrixRow = [$matrixRow]; - } - $columnB = 0; - foreach ($matrixRow as $matrixCell) { - if ((!is_numeric($matrixCell)) || ($matrixCell === null)) { - return Functions::VALUE(); - } - $matrixBData[$rowB][$columnB] = $matrixCell; - ++$columnB; - } - ++$rowB; - } - $matrixB = new Matrix($matrixBData); - - if ($columnA != $rowB) { - return Functions::VALUE(); - } - - return $matrixA->multiply($matrixB)->toArray(); - } catch (MatrixException $ex) { - return Functions::VALUE(); - } + return MathTrig\MatrixFunctions::multiply($matrixData1, $matrixData2); } /** * MOD. * + * @deprecated 1.18.0 + * Use the mod() method in the MathTrig\Operations class instead + * @see MathTrig\Operations::mod() + * * @param int $a Dividend * @param int $b Divisor * - * @return int Remainder + * @return array|float|int|string Remainder, or a string containing an error */ public static function MOD($a = 1, $b = 1) { - $a = (float) Functions::flattenSingleValue($a); - $b = (float) Functions::flattenSingleValue($b); - - if ($b == 0.0) { - return Functions::DIV0(); - } elseif (($a < 0.0) && ($b > 0.0)) { - return $b - fmod(abs($a), $b); - } elseif (($a > 0.0) && ($b < 0.0)) { - return $b + fmod($a, abs($b)); - } - - return fmod($a, $b); + return MathTrig\Operations::mod($a, $b); } /** @@ -666,30 +447,18 @@ class MathTrig * * Rounds a number to the nearest multiple of a specified value * - * @param float $number Number to round - * @param int $multiple Multiple to which you want to round $number + * @deprecated 1.17.0 + * Use the multiple() method in the MathTrig\Mround class instead + * @see MathTrig\Round::multiple() * - * @return float Rounded Number + * @param float $number Number to round + * @param array|int $multiple Multiple to which you want to round $number + * + * @return array|float|string Rounded Number, or a string containing an error */ public static function MROUND($number, $multiple) { - $number = Functions::flattenSingleValue($number); - $multiple = Functions::flattenSingleValue($multiple); - - if ((is_numeric($number)) && (is_numeric($multiple))) { - if ($multiple == 0) { - return 0; - } - if ((self::SIGN($number)) == (self::SIGN($multiple))) { - $multiplier = 1 / $multiple; - - return round($number * $multiplier) / $multiplier; - } - - return Functions::NAN(); - } - - return Functions::VALUE(); + return MathTrig\Round::multiple($number, $multiple); } /** @@ -697,36 +466,17 @@ class MathTrig * * Returns the ratio of the factorial of a sum of values to the product of factorials. * - * @param array of mixed Data Series + * @deprecated 1.18.0 + * Use the multinomial method in the MathTrig\Factorial class instead + * @see MathTrig\Factorial::multinomial() * - * @return float + * @param mixed[] $args An array of mixed values for the Data Series + * + * @return float|string The result, or a string containing an error */ public static function MULTINOMIAL(...$args) { - $summer = 0; - $divisor = 1; - // Loop through arguments - foreach (Functions::flattenArray($args) as $arg) { - // Is it a numeric value? - if (is_numeric($arg)) { - if ($arg < 1) { - return Functions::NAN(); - } - $summer += floor($arg); - $divisor *= self::FACT($arg); - } else { - return Functions::VALUE(); - } - } - - // Return - if ($summer > 0) { - $summer = self::FACT($summer); - - return $summer / $divisor; - } - - return 0; + return MathTrig\Factorial::multinomial(...$args); } /** @@ -734,33 +484,17 @@ class MathTrig * * Returns number rounded up to the nearest odd integer. * - * @param float $number Number to round + * @deprecated 1.18.0 + * Use the odd method in the MathTrig\Round class instead + * @see MathTrig\Round::odd() * - * @return int Rounded Number + * @param array|float $number Number to round + * + * @return array|float|int|string Rounded Number, or a string containing an error */ public static function ODD($number) { - $number = Functions::flattenSingleValue($number); - - if ($number === null) { - return 1; - } elseif (is_bool($number)) { - return 1; - } elseif (is_numeric($number)) { - $significance = self::SIGN($number); - if ($significance == 0) { - return 1; - } - - $result = self::CEILING($number, $significance); - if ($result == self::EVEN($result)) { - $result += $significance; - } - - return (int) $result; - } - - return Functions::VALUE(); + return MathTrig\Round::odd($number); } /** @@ -768,27 +502,18 @@ class MathTrig * * Computes x raised to the power y. * + * @deprecated 1.18.0 + * Use the evaluate method in the MathTrig\Power class instead + * @see MathTrig\Operations::power() + * * @param float $x * @param float $y * - * @return float + * @return array|float|int|string The result, or a string containing an error */ public static function POWER($x = 0, $y = 2) { - $x = Functions::flattenSingleValue($x); - $y = Functions::flattenSingleValue($y); - - // Validate parameters - if ($x == 0.0 && $y == 0.0) { - return Functions::NAN(); - } elseif ($x == 0.0 && $y < 0.0) { - return Functions::DIV0(); - } - - // Return - $result = pow($x, $y); - - return (!is_nan($result) && !is_infinite($result)) ? $result : Functions::NAN(); + return MathTrig\Operations::power($x, $y); } /** @@ -796,38 +521,20 @@ class MathTrig * * PRODUCT returns the product of all the values and cells referenced in the argument list. * + * @deprecated 1.18.0 + * Use the product method in the MathTrig\Operations class instead + * @see MathTrig\Operations::product() + * * Excel Function: * PRODUCT(value1[,value2[, ...]]) * - * @category Mathematical and Trigonometric Functions - * * @param mixed ...$args Data values * - * @return float + * @return float|string */ public static function PRODUCT(...$args) { - // Return value - $returnValue = null; - - // Loop through arguments - foreach (Functions::flattenArray($args) as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - if ($returnValue === null) { - $returnValue = $arg; - } else { - $returnValue *= $arg; - } - } - } - - // Return - if ($returnValue === null) { - return 0; - } - - return $returnValue; + return MathTrig\Operations::product(...$args); } /** @@ -836,90 +543,57 @@ class MathTrig * QUOTIENT function returns the integer portion of a division. Numerator is the divided number * and denominator is the divisor. * + * @deprecated 1.18.0 + * Use the quotient method in the MathTrig\Operations class instead + * @see MathTrig\Operations::quotient() + * * Excel Function: * QUOTIENT(value1[,value2[, ...]]) * - * @category Mathematical and Trigonometric Functions + * @param mixed $numerator + * @param mixed $denominator * - * @param mixed ...$args Data values - * - * @return float + * @return array|int|string */ - public static function QUOTIENT(...$args) + public static function QUOTIENT($numerator, $denominator) { - // Return value - $returnValue = null; - - // Loop through arguments - foreach (Functions::flattenArray($args) as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - if ($returnValue === null) { - $returnValue = ($arg == 0) ? 0 : $arg; - } else { - if (($returnValue == 0) || ($arg == 0)) { - $returnValue = 0; - } else { - $returnValue /= $arg; - } - } - } - } - - // Return - return (int) $returnValue; + return MathTrig\Operations::quotient($numerator, $denominator); } /** - * RAND. + * RAND/RANDBETWEEN. + * + * @deprecated 1.18.0 + * Use the randBetween or randBetween method in the MathTrig\Random class instead + * @see MathTrig\Random::randBetween() * * @param int $min Minimal value * @param int $max Maximal value * - * @return int Random number + * @return array|float|int|string Random number */ public static function RAND($min = 0, $max = 0) { - $min = Functions::flattenSingleValue($min); - $max = Functions::flattenSingleValue($max); - - if ($min == 0 && $max == 0) { - return (mt_rand(0, 10000000)) / 10000000; - } - - return mt_rand($min, $max); + return MathTrig\Random::randBetween($min, $max); } + /** + * ROMAN. + * + * Converts a number to Roman numeral + * + * @deprecated 1.17.0 + * Use the evaluate() method in the MathTrig\Roman class instead + * @see MathTrig\Roman::evaluate() + * + * @param mixed $aValue Number to convert + * @param mixed $style Number indicating one of five possible forms + * + * @return array|string Roman numeral, or a string containing an error + */ public static function ROMAN($aValue, $style = 0) { - $aValue = Functions::flattenSingleValue($aValue); - $style = ($style === null) ? 0 : (int) Functions::flattenSingleValue($style); - if ((!is_numeric($aValue)) || ($aValue < 0) || ($aValue >= 4000)) { - return Functions::VALUE(); - } - $aValue = (int) $aValue; - if ($aValue == 0) { - return ''; - } - - $mill = ['', 'M', 'MM', 'MMM', 'MMMM', 'MMMMM']; - $cent = ['', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM']; - $tens = ['', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC']; - $ones = ['', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX']; - - $roman = ''; - while ($aValue > 5999) { - $roman .= 'M'; - $aValue -= 1000; - } - $m = self::romanCut($aValue, 1000); - $aValue %= 1000; - $c = self::romanCut($aValue, 100); - $aValue %= 100; - $t = self::romanCut($aValue, 10); - $aValue %= 10; - - return $roman . $mill[$m] . $cent[$c] . $tens[$t] . $ones[$aValue]; + return MathTrig\Roman::evaluate($aValue, $style); } /** @@ -927,26 +601,18 @@ class MathTrig * * Rounds a number up to a specified number of decimal places * - * @param float $number Number to round - * @param int $digits Number of digits to which you want to round $number + * @deprecated 1.17.0 + * Use the up() method in the MathTrig\Round class instead + * @see MathTrig\Round::up() * - * @return float Rounded Number + * @param array|float $number Number to round + * @param array|int $digits Number of digits to which you want to round $number + * + * @return array|float|string Rounded Number, or a string containing an error */ public static function ROUNDUP($number, $digits) { - $number = Functions::flattenSingleValue($number); - $digits = Functions::flattenSingleValue($digits); - - if ((is_numeric($number)) && (is_numeric($digits))) { - $significance = pow(10, (int) $digits); - if ($number < 0.0) { - return floor($number * $significance) / $significance; - } - - return ceil($number * $significance) / $significance; - } - - return Functions::VALUE(); + return MathTrig\Round::up($number, $digits); } /** @@ -954,26 +620,18 @@ class MathTrig * * Rounds a number down to a specified number of decimal places * - * @param float $number Number to round - * @param int $digits Number of digits to which you want to round $number + * @deprecated 1.17.0 + * Use the down() method in the MathTrig\Round class instead + * @see MathTrig\Round::down() * - * @return float Rounded Number + * @param array|float $number Number to round + * @param array|int $digits Number of digits to which you want to round $number + * + * @return array|float|string Rounded Number, or a string containing an error */ public static function ROUNDDOWN($number, $digits) { - $number = Functions::flattenSingleValue($number); - $digits = Functions::flattenSingleValue($digits); - - if ((is_numeric($number)) && (is_numeric($digits))) { - $significance = pow(10, (int) $digits); - if ($number < 0.0) { - return ceil($number * $significance) / $significance; - } - - return floor($number * $significance) / $significance; - } - - return Functions::VALUE(); + return MathTrig\Round::down($number, $digits); } /** @@ -981,40 +639,20 @@ class MathTrig * * Returns the sum of a power series * - * @param float $x Input value to the power series - * @param float $n Initial power to which you want to raise $x - * @param float $m Step by which to increase $n for each term in the series - * @param array of mixed Data Series + * @deprecated 1.18.0 + * Use the evaluate method in the MathTrig\SeriesSum class instead + * @see MathTrig\SeriesSum::evaluate() * - * @return float + * @param mixed $x Input value + * @param mixed $n Initial power + * @param mixed $m Step + * @param mixed[] $args An array of coefficients for the Data Series + * + * @return array|float|string The result, or a string containing an error */ - public static function SERIESSUM(...$args) + public static function SERIESSUM($x, $n, $m, ...$args) { - $returnValue = 0; - - // Loop through arguments - $aArgs = Functions::flattenArray($args); - - $x = array_shift($aArgs); - $n = array_shift($aArgs); - $m = array_shift($aArgs); - - if ((is_numeric($x)) && (is_numeric($n)) && (is_numeric($m))) { - // Calculate - $i = 0; - foreach ($aArgs as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - $returnValue += $arg * pow($x, $n + ($m * $i++)); - } else { - return Functions::VALUE(); - } - } - - return $returnValue; - } - - return Functions::VALUE(); + return MathTrig\SeriesSum::evaluate($x, $n, $m, ...$args); } /** @@ -1023,26 +661,29 @@ class MathTrig * Determines the sign of a number. Returns 1 if the number is positive, zero (0) * if the number is 0, and -1 if the number is negative. * - * @param float $number Number to round + * @deprecated 1.18.0 + * Use the evaluate method in the MathTrig\Sign class instead + * @see MathTrig\Sign::evaluate() * - * @return int sign value + * @param array|float $number Number to round + * + * @return array|int|string sign value, or a string containing an error */ public static function SIGN($number) { - $number = Functions::flattenSingleValue($number); + return MathTrig\Sign::evaluate($number); + } - if (is_bool($number)) { - return (int) $number; - } - if (is_numeric($number)) { - if ($number == 0.0) { - return 0; - } - - return $number / abs($number); - } - - return Functions::VALUE(); + /** + * returnSign = returns 0/-1/+1. + * + * @deprecated 1.18.0 + * Use the returnSign method in the MathTrig\Helpers class instead + * @see MathTrig\Helpers::returnSign() + */ + public static function returnSign(float $number): int + { + return MathTrig\Helpers::returnSign($number); } /** @@ -1050,57 +691,17 @@ class MathTrig * * Returns the square root of (number * pi). * - * @param float $number Number + * @deprecated 1.18.0 + * Use the pi method in the MathTrig\Sqrt class instead + * @see MathTrig\Sqrt::sqrt() * - * @return float Square Root of Number * Pi + * @param array|float $number Number + * + * @return array|float|string Square Root of Number * Pi, or a string containing an error */ public static function SQRTPI($number) { - $number = Functions::flattenSingleValue($number); - - if (is_numeric($number)) { - if ($number < 0) { - return Functions::NAN(); - } - - return sqrt($number * M_PI); - } - - return Functions::VALUE(); - } - - protected static function filterHiddenArgs($cellReference, $args) - { - return array_filter( - $args, - function ($index) use ($cellReference) { - [, $row, $column] = explode('.', $index); - - return $cellReference->getWorksheet()->getRowDimension($row)->getVisible() && - $cellReference->getWorksheet()->getColumnDimension($column)->getVisible(); - }, - ARRAY_FILTER_USE_KEY - ); - } - - protected static function filterFormulaArgs($cellReference, $args) - { - return array_filter( - $args, - function ($index) use ($cellReference) { - [, $row, $column] = explode('.', $index); - if ($cellReference->getWorksheet()->cellExists($column . $row)) { - //take this cell out if it contains the SUBTOTAL or AGGREGATE functions in a formula - $isFormula = $cellReference->getWorksheet()->getCell($column . $row)->isFormula(); - $cellFormula = !preg_match('/^=.*\b(SUBTOTAL|AGGREGATE)\s*\(/i', $cellReference->getWorksheet()->getCell($column . $row)->getValue()); - - return !$isFormula || $cellFormula; - } - - return true; - }, - ARRAY_FILTER_USE_KEY - ); + return MathTrig\Sqrt::pi($number); } /** @@ -1108,57 +709,24 @@ class MathTrig * * Returns a subtotal in a list or database. * - * @param int the number 1 to 11 that specifies which function to + * @deprecated 1.18.0 + * Use the evaluate method in the MathTrig\Subtotal class instead + * @see MathTrig\Subtotal::evaluate() + * + * @param int $functionType + * A number 1 to 11 that specifies which function to * use in calculating subtotals within a range * list * Numbers 101 to 111 shadow the functions of 1 to 11 * but ignore any values in the range that are * in hidden rows or columns - * @param array of mixed Data Series + * @param mixed[] $args A mixed data series of values * * @return float|string */ - public static function SUBTOTAL(...$args) + public static function SUBTOTAL($functionType, ...$args) { - $cellReference = array_pop($args); - $aArgs = Functions::flattenArrayIndexed($args); - $subtotal = array_shift($aArgs); - - // Calculate - if ((is_numeric($subtotal)) && (!is_string($subtotal))) { - if ($subtotal > 100) { - $aArgs = self::filterHiddenArgs($cellReference, $aArgs); - $subtotal -= 100; - } - - $aArgs = self::filterFormulaArgs($cellReference, $aArgs); - switch ($subtotal) { - case 1: - return Statistical::AVERAGE($aArgs); - case 2: - return Statistical::COUNT($aArgs); - case 3: - return Statistical::COUNTA($aArgs); - case 4: - return Statistical::MAX($aArgs); - case 5: - return Statistical::MIN($aArgs); - case 6: - return self::PRODUCT($aArgs); - case 7: - return Statistical::STDEV($aArgs); - case 8: - return Statistical::STDEVP($aArgs); - case 9: - return self::SUM($aArgs); - case 10: - return Statistical::VARFunc($aArgs); - case 11: - return Statistical::VARP($aArgs); - } - } - - return Functions::VALUE(); + return MathTrig\Subtotal::evaluate($functionType, ...$args); } /** @@ -1166,134 +734,64 @@ class MathTrig * * SUM computes the sum of all the values and cells referenced in the argument list. * + * @deprecated 1.18.0 + * Use the sumErroringStrings method in the MathTrig\Sum class instead + * @see MathTrig\Sum::sumErroringStrings() + * * Excel Function: * SUM(value1[,value2[, ...]]) * - * @category Mathematical and Trigonometric Functions - * * @param mixed ...$args Data values * - * @return float + * @return float|string */ public static function SUM(...$args) { - $returnValue = 0; - - // Loop through the arguments - foreach (Functions::flattenArray($args) as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - $returnValue += $arg; - } - } - - return $returnValue; + return MathTrig\Sum::sumIgnoringStrings(...$args); } /** * SUMIF. * - * Counts the number of cells that contain numbers within the list of arguments + * Totals the values of cells that contain numbers within the list of arguments * * Excel Function: - * SUMIF(value1[,value2[, ...]],condition) + * SUMIF(range, criteria, [sum_range]) * - * @category Mathematical and Trigonometric Functions + * @deprecated 1.17.0 + * Use the SUMIF() method in the Statistical\Conditional class instead + * @see Statistical\Conditional::SUMIF() * - * @param mixed $aArgs Data values - * @param string $condition the criteria that defines which cells will be summed - * @param mixed $sumArgs + * @param mixed $range Data values + * @param string $criteria the criteria that defines which cells will be summed + * @param mixed $sumRange * - * @return float + * @return float|string */ - public static function SUMIF($aArgs, $condition, $sumArgs = []) + public static function SUMIF($range, $criteria, $sumRange = []) { - $returnValue = 0; - - $aArgs = Functions::flattenArray($aArgs); - $sumArgs = Functions::flattenArray($sumArgs); - if (empty($sumArgs)) { - $sumArgs = $aArgs; - } - $condition = Functions::ifCondition($condition); - // Loop through arguments - foreach ($aArgs as $key => $arg) { - if (!is_numeric($arg)) { - $arg = str_replace('"', '""', $arg); - $arg = Calculation::wrapResult(strtoupper($arg)); - } - - $testCondition = '=' . $arg . $condition; - $sumValue = array_key_exists($key, $sumArgs) ? $sumArgs[$key] : 0; - - if (is_numeric($sumValue) && - Calculation::getInstance()->_calculateFormulaValue($testCondition)) { - // Is it a value within our criteria and only numeric can be added to the result - $returnValue += $sumValue; - } - } - - return $returnValue; + return Statistical\Conditional::SUMIF($range, $criteria, $sumRange); } /** * SUMIFS. * - * Counts the number of cells that contain numbers within the list of arguments + * Totals the values of cells that contain numbers within the list of arguments * * Excel Function: - * SUMIFS(value1[,value2[, ...]],condition) + * SUMIFS(sum_range, criteria_range1, criteria1, [criteria_range2, criteria2], ...) * - * @category Mathematical and Trigonometric Functions + * @deprecated 1.17.0 + * Use the SUMIFS() method in the Statistical\Conditional class instead + * @see Statistical\Conditional::SUMIFS() * * @param mixed $args Data values - * @param string $condition the criteria that defines which cells will be summed * - * @return float + * @return null|float|string */ public static function SUMIFS(...$args) { - $arrayList = $args; - - // Return value - $returnValue = 0; - - $sumArgs = Functions::flattenArray(array_shift($arrayList)); - $aArgsArray = []; - $conditions = []; - - while (count($arrayList) > 0) { - $aArgsArray[] = Functions::flattenArray(array_shift($arrayList)); - $conditions[] = Functions::ifCondition(array_shift($arrayList)); - } - - // Loop through each sum and see if arguments and conditions are true - foreach ($sumArgs as $index => $value) { - $valid = true; - - foreach ($conditions as $cidx => $condition) { - $arg = $aArgsArray[$cidx][$index]; - - // Loop through arguments - if (!is_numeric($arg)) { - $arg = Calculation::wrapResult(strtoupper($arg)); - } - $testCondition = '=' . $arg . $condition; - if (!Calculation::getInstance()->_calculateFormulaValue($testCondition)) { - // Is not a value within our criteria - $valid = false; - - break; // if false found, don't need to check other conditions - } - } - - if ($valid) { - $returnValue += $value; - } - } - - // Return - return $returnValue; + return Statistical\Conditional::SUMIFS(...$args); } /** @@ -1302,41 +800,17 @@ class MathTrig * Excel Function: * SUMPRODUCT(value1[,value2[, ...]]) * - * @category Mathematical and Trigonometric Functions + * @deprecated 1.18.0 + * Use the product method in the MathTrig\Sum class instead + * @see MathTrig\Sum::product() * * @param mixed ...$args Data values * - * @return float + * @return float|string The result, or a string containing an error */ public static function SUMPRODUCT(...$args) { - $arrayList = $args; - - $wrkArray = Functions::flattenArray(array_shift($arrayList)); - $wrkCellCount = count($wrkArray); - - for ($i = 0; $i < $wrkCellCount; ++$i) { - if ((!is_numeric($wrkArray[$i])) || (is_string($wrkArray[$i]))) { - $wrkArray[$i] = 0; - } - } - - foreach ($arrayList as $matrixData) { - $array2 = Functions::flattenArray($matrixData); - $count = count($array2); - if ($wrkCellCount != $count) { - return Functions::VALUE(); - } - - foreach ($array2 as $i => $val) { - if ((!is_numeric($val)) || (is_string($val))) { - $val = 0; - } - $wrkArray[$i] *= $val; - } - } - - return array_sum($wrkArray); + return MathTrig\Sum::product(...$args); } /** @@ -1344,103 +818,71 @@ class MathTrig * * SUMSQ returns the sum of the squares of the arguments * + * @deprecated 1.18.0 + * Use the sumSquare method in the MathTrig\SumSquares class instead + * @see MathTrig\SumSquares::sumSquare() + * * Excel Function: * SUMSQ(value1[,value2[, ...]]) * - * @category Mathematical and Trigonometric Functions - * * @param mixed ...$args Data values * - * @return float + * @return float|string */ public static function SUMSQ(...$args) { - $returnValue = 0; - - // Loop through arguments - foreach (Functions::flattenArray($args) as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - $returnValue += ($arg * $arg); - } - } - - return $returnValue; + return MathTrig\SumSquares::sumSquare(...$args); } /** * SUMX2MY2. * + * @deprecated 1.18.0 + * Use the sumXSquaredMinusYSquared method in the MathTrig\SumSquares class instead + * @see MathTrig\SumSquares::sumXSquaredMinusYSquared() + * * @param mixed[] $matrixData1 Matrix #1 * @param mixed[] $matrixData2 Matrix #2 * - * @return float + * @return float|string */ public static function SUMX2MY2($matrixData1, $matrixData2) { - $array1 = Functions::flattenArray($matrixData1); - $array2 = Functions::flattenArray($matrixData2); - $count = min(count($array1), count($array2)); - - $result = 0; - for ($i = 0; $i < $count; ++$i) { - if (((is_numeric($array1[$i])) && (!is_string($array1[$i]))) && - ((is_numeric($array2[$i])) && (!is_string($array2[$i])))) { - $result += ($array1[$i] * $array1[$i]) - ($array2[$i] * $array2[$i]); - } - } - - return $result; + return MathTrig\SumSquares::sumXSquaredMinusYSquared($matrixData1, $matrixData2); } /** * SUMX2PY2. * + * @deprecated 1.18.0 + * Use the sumXSquaredPlusYSquared method in the MathTrig\SumSquares class instead + * @see MathTrig\SumSquares::sumXSquaredPlusYSquared() + * * @param mixed[] $matrixData1 Matrix #1 * @param mixed[] $matrixData2 Matrix #2 * - * @return float + * @return float|string */ public static function SUMX2PY2($matrixData1, $matrixData2) { - $array1 = Functions::flattenArray($matrixData1); - $array2 = Functions::flattenArray($matrixData2); - $count = min(count($array1), count($array2)); - - $result = 0; - for ($i = 0; $i < $count; ++$i) { - if (((is_numeric($array1[$i])) && (!is_string($array1[$i]))) && - ((is_numeric($array2[$i])) && (!is_string($array2[$i])))) { - $result += ($array1[$i] * $array1[$i]) + ($array2[$i] * $array2[$i]); - } - } - - return $result; + return MathTrig\SumSquares::sumXSquaredPlusYSquared($matrixData1, $matrixData2); } /** * SUMXMY2. * + * @deprecated 1.18.0 + * Use the sumXMinusYSquared method in the MathTrig\SumSquares class instead + * @see MathTrig\SumSquares::sumXMinusYSquared() + * * @param mixed[] $matrixData1 Matrix #1 * @param mixed[] $matrixData2 Matrix #2 * - * @return float + * @return float|string */ public static function SUMXMY2($matrixData1, $matrixData2) { - $array1 = Functions::flattenArray($matrixData1); - $array2 = Functions::flattenArray($matrixData2); - $count = min(count($array1), count($array2)); - - $result = 0; - for ($i = 0; $i < $count; ++$i) { - if (((is_numeric($array1[$i])) && (!is_string($array1[$i]))) && - ((is_numeric($array2[$i])) && (!is_string($array2[$i])))) { - $result += ($array1[$i] - $array2[$i]) * ($array1[$i] - $array2[$i]); - } - } - - return $result; + return MathTrig\SumSquares::sumXMinusYSquared($matrixData1, $matrixData2); } /** @@ -1448,30 +890,18 @@ class MathTrig * * Truncates value to the number of fractional digits by number_digits. * + * @deprecated 1.17.0 + * Use the evaluate() method in the MathTrig\Trunc class instead + * @see MathTrig\Trunc::evaluate() + * * @param float $value * @param int $digits * - * @return float Truncated value + * @return array|float|string Truncated value, or a string containing an error */ public static function TRUNC($value = 0, $digits = 0) { - $value = Functions::flattenSingleValue($value); - $digits = Functions::flattenSingleValue($digits); - - // Validate parameters - if ((!is_numeric($value)) || (!is_numeric($digits))) { - return Functions::VALUE(); - } - $digits = floor($digits); - - // Truncate - $adjust = pow(10, $digits); - - if (($digits > 0) && (rtrim((int) ((abs($value) - abs((int) $value)) * $adjust), '0') < $adjust / 10)) { - return $value; - } - - return ((int) ($value * $adjust)) / $adjust; + return MathTrig\Trunc::evaluate($value, $digits); } /** @@ -1479,21 +909,17 @@ class MathTrig * * Returns the secant of an angle. * - * @param float $angle Number + * @deprecated 1.18.0 + * Use the sec method in the MathTrig\Trig\Secant class instead + * @see MathTrig\Trig\Secant::sec() * - * @return float|string The secant of the angle + * @param array|float $angle Number + * + * @return array|float|string The secant of the angle */ public static function SEC($angle) { - $angle = Functions::flattenSingleValue($angle); - - if (!is_numeric($angle)) { - return Functions::VALUE(); - } - - $result = cos($angle); - - return ($result == 0.0) ? Functions::DIV0() : 1 / $result; + return MathTrig\Trig\Secant::sec($angle); } /** @@ -1501,21 +927,17 @@ class MathTrig * * Returns the hyperbolic secant of an angle. * - * @param float $angle Number + * @deprecated 1.18.0 + * Use the sech method in the MathTrig\Trig\Secant class instead + * @see MathTrig\Trig\Secant::sech() * - * @return float|string The hyperbolic secant of the angle + * @param array|float $angle Number + * + * @return array|float|string The hyperbolic secant of the angle */ public static function SECH($angle) { - $angle = Functions::flattenSingleValue($angle); - - if (!is_numeric($angle)) { - return Functions::VALUE(); - } - - $result = cosh($angle); - - return ($result == 0.0) ? Functions::DIV0() : 1 / $result; + return MathTrig\Trig\Secant::sech($angle); } /** @@ -1523,21 +945,17 @@ class MathTrig * * Returns the cosecant of an angle. * - * @param float $angle Number + * @deprecated 1.18.0 + * Use the csc method in the MathTrig\Trig\Cosecant class instead + * @see MathTrig\Trig\Cosecant::csc() * - * @return float|string The cosecant of the angle + * @param array|float $angle Number + * + * @return array|float|string The cosecant of the angle */ public static function CSC($angle) { - $angle = Functions::flattenSingleValue($angle); - - if (!is_numeric($angle)) { - return Functions::VALUE(); - } - - $result = sin($angle); - - return ($result == 0.0) ? Functions::DIV0() : 1 / $result; + return MathTrig\Trig\Cosecant::csc($angle); } /** @@ -1545,21 +963,17 @@ class MathTrig * * Returns the hyperbolic cosecant of an angle. * - * @param float $angle Number + * @deprecated 1.18.0 + * Use the csch method in the MathTrig\Trig\Cosecant class instead + * @see MathTrig\Trig\Cosecant::csch() * - * @return float|string The hyperbolic cosecant of the angle + * @param array|float $angle Number + * + * @return array|float|string The hyperbolic cosecant of the angle */ public static function CSCH($angle) { - $angle = Functions::flattenSingleValue($angle); - - if (!is_numeric($angle)) { - return Functions::VALUE(); - } - - $result = sinh($angle); - - return ($result == 0.0) ? Functions::DIV0() : 1 / $result; + return MathTrig\Trig\Cosecant::csch($angle); } /** @@ -1567,21 +981,17 @@ class MathTrig * * Returns the cotangent of an angle. * - * @param float $angle Number + * @deprecated 1.18.0 + * Use the cot method in the MathTrig\Trig\Cotangent class instead + * @see MathTrig\Trig\Cotangent::cot() * - * @return float|string The cotangent of the angle + * @param array|float $angle Number + * + * @return array|float|string The cotangent of the angle */ public static function COT($angle) { - $angle = Functions::flattenSingleValue($angle); - - if (!is_numeric($angle)) { - return Functions::VALUE(); - } - - $result = tan($angle); - - return ($result == 0.0) ? Functions::DIV0() : 1 / $result; + return MathTrig\Trig\Cotangent::cot($angle); } /** @@ -1589,21 +999,17 @@ class MathTrig * * Returns the hyperbolic cotangent of an angle. * - * @param float $angle Number + * @deprecated 1.18.0 + * Use the coth method in the MathTrig\Trig\Cotangent class instead + * @see MathTrig\Trig\Cotangent::coth() * - * @return float|string The hyperbolic cotangent of the angle + * @param array|float $angle Number + * + * @return array|float|string The hyperbolic cotangent of the angle */ public static function COTH($angle) { - $angle = Functions::flattenSingleValue($angle); - - if (!is_numeric($angle)) { - return Functions::VALUE(); - } - - $result = tanh($angle); - - return ($result == 0.0) ? Functions::DIV0() : 1 / $result; + return MathTrig\Trig\Cotangent::coth($angle); } /** @@ -1611,19 +1017,33 @@ class MathTrig * * Returns the arccotangent of a number. * - * @param float $number Number + * @deprecated 1.18.0 + * Use the acot method in the MathTrig\Trig\Cotangent class instead + * @see MathTrig\Trig\Cotangent::acot() * - * @return float|string The arccotangent of the number + * @param array|float $number Number + * + * @return array|float|string The arccotangent of the number */ public static function ACOT($number) { - $number = Functions::flattenSingleValue($number); + return MathTrig\Trig\Cotangent::acot($number); + } - if (!is_numeric($number)) { - return Functions::VALUE(); - } - - return (M_PI / 2) - atan($number); + /** + * Return NAN or value depending on argument. + * + * @deprecated 1.18.0 + * Use the numberOrNan method in the MathTrig\Helpers class instead + * @see MathTrig\Helpers::numberOrNan() + * + * @param float $result Number + * + * @return float|string + */ + public static function numberOrNan($result) + { + return MathTrig\Helpers::numberOrNan($result); } /** @@ -1631,20 +1051,396 @@ class MathTrig * * Returns the hyperbolic arccotangent of a number. * - * @param float $number Number + * @deprecated 1.18.0 + * Use the acoth method in the MathTrig\Trig\Cotangent class instead + * @see MathTrig\Trig\Cotangent::acoth() * - * @return float|string The hyperbolic arccotangent of the number + * @param array|float $number Number + * + * @return array|float|string The hyperbolic arccotangent of the number */ public static function ACOTH($number) + { + return MathTrig\Trig\Cotangent::acoth($number); + } + + /** + * ROUND. + * + * Returns the result of builtin function round after validating args. + * + * @deprecated 1.17.0 + * Use the round() method in the MathTrig\Round class instead + * @see MathTrig\Round::round() + * + * @param array|mixed $number Should be numeric + * @param array|mixed $precision Should be int + * + * @return array|float|string Rounded number + */ + public static function builtinROUND($number, $precision) + { + return MathTrig\Round::round($number, $precision); + } + + /** + * ABS. + * + * Returns the result of builtin function abs after validating args. + * + * @deprecated 1.18.0 + * Use the evaluate method in the MathTrig\Absolute class instead + * @see MathTrig\Absolute::evaluate() + * + * @param array|mixed $number Should be numeric + * + * @return array|float|int|string Rounded number + */ + public static function builtinABS($number) + { + return MathTrig\Absolute::evaluate($number); + } + + /** + * ACOS. + * + * @deprecated 1.18.0 + * Use the acos method in the MathTrig\Trig\Cosine class instead + * @see MathTrig\Trig\Cosine::acos() + * + * Returns the result of builtin function acos after validating args. + * + * @param array|float $number Should be numeric + * + * @return array|float|string Rounded number + */ + public static function builtinACOS($number) + { + return MathTrig\Trig\Cosine::acos($number); + } + + /** + * ACOSH. + * + * Returns the result of builtin function acosh after validating args. + * + * @deprecated 1.18.0 + * Use the acosh method in the MathTrig\Trig\Cosine class instead + * @see MathTrig\Trig\Cosine::acosh() + * + * @param array|float $number Should be numeric + * + * @return array|float|string Rounded number + */ + public static function builtinACOSH($number) + { + return MathTrig\Trig\Cosine::acosh($number); + } + + /** + * ASIN. + * + * Returns the result of builtin function asin after validating args. + * + * @deprecated 1.18.0 + * Use the asin method in the MathTrig\Trig\Sine class instead + * @see MathTrig\Trig\Sine::asin() + * + * @param array|float $number Should be numeric + * + * @return array|float|string Rounded number + */ + public static function builtinASIN($number) + { + return MathTrig\Trig\Sine::asin($number); + } + + /** + * ASINH. + * + * Returns the result of builtin function asinh after validating args. + * + * @deprecated 1.18.0 + * Use the asinh method in the MathTrig\Trig\Sine class instead + * @see MathTrig\Trig\Sine::asinh() + * + * @param array|float $number Should be numeric + * + * @return array|float|string Rounded number + */ + public static function builtinASINH($number) + { + return MathTrig\Trig\Sine::asinh($number); + } + + /** + * ATAN. + * + * Returns the result of builtin function atan after validating args. + * + * @deprecated 1.18.0 + * Use the atan method in the MathTrig\Trig\Tangent class instead + * @see MathTrig\Trig\Tangent::atan() + * + * @param array|float $number Should be numeric + * + * @return array|float|string Rounded number + */ + public static function builtinATAN($number) + { + return MathTrig\Trig\Tangent::atan($number); + } + + /** + * ATANH. + * + * Returns the result of builtin function atanh after validating args. + * + * @deprecated 1.18.0 + * Use the atanh method in the MathTrig\Trig\Tangent class instead + * @see MathTrig\Trig\Tangent::atanh() + * + * @param array|float $number Should be numeric + * + * @return array|float|string Rounded number + */ + public static function builtinATANH($number) + { + return MathTrig\Trig\Tangent::atanh($number); + } + + /** + * COS. + * + * Returns the result of builtin function cos after validating args. + * + * @deprecated 1.18.0 + * Use the cos method in the MathTrig\Trig\Cosine class instead + * @see MathTrig\Trig\Cosine::cos() + * + * @param array|mixed $number Should be numeric + * + * @return array|float|string Rounded number + */ + public static function builtinCOS($number) + { + return MathTrig\Trig\Cosine::cos($number); + } + + /** + * COSH. + * + * Returns the result of builtin function cos after validating args. + * + * @deprecated 1.18.0 + * Use the cosh method in the MathTrig\Trig\Cosine class instead + * @see MathTrig\Trig\Cosine::cosh() + * + * @param array|mixed $number Should be numeric + * + * @return array|float|string Rounded number + */ + public static function builtinCOSH($number) + { + return MathTrig\Trig\Cosine::cosh($number); + } + + /** + * DEGREES. + * + * Returns the result of builtin function rad2deg after validating args. + * + * @deprecated 1.18.0 + * Use the toDegrees method in the MathTrig\Angle class instead + * @see MathTrig\Angle::toDegrees() + * + * @param array|mixed $number Should be numeric + * + * @return array|float|string Rounded number + */ + public static function builtinDEGREES($number) + { + return MathTrig\Angle::toDegrees($number); + } + + /** + * EXP. + * + * Returns the result of builtin function exp after validating args. + * + * @deprecated 1.18.0 + * Use the evaluate method in the MathTrig\Exp class instead + * @see MathTrig\Exp::evaluate() + * + * @param array|mixed $number Should be numeric + * + * @return array|float|string Rounded number + */ + public static function builtinEXP($number) + { + return MathTrig\Exp::evaluate($number); + } + + /** + * LN. + * + * Returns the result of builtin function log after validating args. + * + * @deprecated 1.18.0 + * Use the natural method in the MathTrig\Logarithms class instead + * @see MathTrig\Logarithms::natural() + * + * @param mixed $number Should be numeric + * + * @return array|float|string Rounded number + */ + public static function builtinLN($number) + { + return MathTrig\Logarithms::natural($number); + } + + /** + * LOG10. + * + * Returns the result of builtin function log after validating args. + * + * @deprecated 1.18.0 + * Use the natural method in the MathTrig\Logarithms class instead + * @see MathTrig\Logarithms::base10() + * + * @param mixed $number Should be numeric + * + * @return array|float|string Rounded number + */ + public static function builtinLOG10($number) + { + return MathTrig\Logarithms::base10($number); + } + + /** + * RADIANS. + * + * Returns the result of builtin function deg2rad after validating args. + * + * @deprecated 1.18.0 + * Use the toRadians method in the MathTrig\Angle class instead + * @see MathTrig\Angle::toRadians() + * + * @param array|mixed $number Should be numeric + * + * @return array|float|string Rounded number + */ + public static function builtinRADIANS($number) + { + return MathTrig\Angle::toRadians($number); + } + + /** + * SIN. + * + * Returns the result of builtin function sin after validating args. + * + * @deprecated 1.18.0 + * Use the sin method in the MathTrig\Trig\Sine class instead + * @see MathTrig\Trig\Sine::evaluate() + * + * @param array|mixed $number Should be numeric + * + * @return array|float|string sine + */ + public static function builtinSIN($number) + { + return MathTrig\Trig\Sine::sin($number); + } + + /** + * SINH. + * + * Returns the result of builtin function sinh after validating args. + * + * @deprecated 1.18.0 + * Use the sinh method in the MathTrig\Trig\Sine class instead + * @see MathTrig\Trig\Sine::sinh() + * + * @param array|mixed $number Should be numeric + * + * @return array|float|string Rounded number + */ + public static function builtinSINH($number) + { + return MathTrig\Trig\Sine::sinh($number); + } + + /** + * SQRT. + * + * Returns the result of builtin function sqrt after validating args. + * + * @deprecated 1.18.0 + * Use the sqrt method in the MathTrig\Sqrt class instead + * @see MathTrig\Sqrt::sqrt() + * + * @param array|mixed $number Should be numeric + * + * @return array|float|string Rounded number + */ + public static function builtinSQRT($number) + { + return MathTrig\Sqrt::sqrt($number); + } + + /** + * TAN. + * + * Returns the result of builtin function tan after validating args. + * + * @deprecated 1.18.0 + * Use the tan method in the MathTrig\Trig\Tangent class instead + * @see MathTrig\Trig\Tangent::tan() + * + * @param array|mixed $number Should be numeric + * + * @return array|float|string Rounded number + */ + public static function builtinTAN($number) + { + return MathTrig\Trig\Tangent::tan($number); + } + + /** + * TANH. + * + * Returns the result of builtin function sinh after validating args. + * + * @deprecated 1.18.0 + * Use the tanh method in the MathTrig\Trig\Tangent class instead + * @see MathTrig\Trig\Tangent::tanh() + * + * @param array|mixed $number Should be numeric + * + * @return array|float|string Rounded number + */ + public static function builtinTANH($number) + { + return MathTrig\Trig\Tangent::tanh($number); + } + + /** + * Many functions accept null/false/true argument treated as 0/0/1. + * + * @deprecated 1.18.0 + * Use the validateNumericNullBool method in the MathTrig\Helpers class instead + * @see MathTrig\Helpers::validateNumericNullBool() + * + * @param mixed $number + */ + public static function nullFalseTrueToNumber(&$number): void { $number = Functions::flattenSingleValue($number); - - if (!is_numeric($number)) { - return Functions::VALUE(); + if ($number === null) { + $number = 0; + } elseif (is_bool($number)) { + $number = (int) $number; } - - $result = log(($number + 1) / ($number - 1)) / 2; - - return is_nan($result) ? Functions::NAN() : $result; } } diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Absolute.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Absolute.php new file mode 100644 index 0000000..f21c6b7 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Absolute.php @@ -0,0 +1,37 @@ +getMessage(); + } + + return abs($number); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Angle.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Angle.php new file mode 100644 index 0000000..cbeec6f --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Angle.php @@ -0,0 +1,63 @@ +getMessage(); + } + + return rad2deg($number); + } + + /** + * RADIANS. + * + * Returns the result of builtin function deg2rad after validating args. + * + * @param mixed $number Should be numeric, or can be an array of numbers + * + * @return array|float|string Rounded number + * If an array of numbers is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function toRadians($number) + { + if (is_array($number)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number); + } + + try { + $number = Helpers::validateNumericNullBool($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + return deg2rad($number); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Arabic.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Arabic.php new file mode 100644 index 0000000..ee48850 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Arabic.php @@ -0,0 +1,112 @@ + 1000, + 'D' => 500, + 'C' => 100, + 'L' => 50, + 'X' => 10, + 'V' => 5, + 'I' => 1, + ]; + + /** + * Recursively calculate the arabic value of a roman numeral. + * + * @param int $sum + * @param int $subtract + * + * @return int + */ + private static function calculateArabic(array $roman, &$sum = 0, $subtract = 0) + { + $numeral = array_shift($roman); + if (!isset(self::ROMAN_LOOKUP[$numeral])) { + throw new Exception('Invalid character detected'); + } + + $arabic = self::ROMAN_LOOKUP[$numeral]; + if (count($roman) > 0 && isset(self::ROMAN_LOOKUP[$roman[0]]) && $arabic < self::ROMAN_LOOKUP[$roman[0]]) { + $subtract += $arabic; + } else { + $sum += ($arabic - $subtract); + $subtract = 0; + } + + if (count($roman) > 0) { + self::calculateArabic($roman, $sum, $subtract); + } + + return $sum; + } + + /** + * @param mixed $value + */ + private static function mollifyScrutinizer($value): array + { + return is_array($value) ? $value : []; + } + + private static function strSplit(string $roman): array + { + $rslt = str_split($roman); + + return self::mollifyScrutinizer($rslt); + } + + /** + * ARABIC. + * + * Converts a Roman numeral to an Arabic numeral. + * + * Excel Function: + * ARABIC(text) + * + * @param mixed $roman Should be a string, or can be an array of strings + * + * @return array|int|string the arabic numberal contrived from the roman numeral + * If an array of numbers is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function evaluate($roman) + { + if (is_array($roman)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $roman); + } + + // An empty string should return 0 + $roman = substr(trim(strtoupper((string) $roman)), 0, 255); + if ($roman === '') { + return 0; + } + + // Convert the roman numeral to an arabic number + $negativeNumber = $roman[0] === '-'; + if ($negativeNumber) { + $roman = substr($roman, 1); + } + + try { + $arabic = self::calculateArabic(self::strSplit($roman)); + } catch (Exception $e) { + return ExcelError::VALUE(); // Invalid character detected + } + + if ($negativeNumber) { + $arabic *= -1; // The number should be negative + } + + return $arabic; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Base.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Base.php new file mode 100644 index 0000000..2fec947 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Base.php @@ -0,0 +1,68 @@ +getMessage(); + } + + return self::calculate($number, $radix, $minLength); + } + + /** + * @param mixed $minLength + */ + private static function calculate(float $number, int $radix, $minLength): string + { + if ($minLength === null || is_numeric($minLength)) { + if ($number < 0 || $number >= 2 ** 53 || $radix < 2 || $radix > 36) { + return ExcelError::NAN(); // Numeric range constraints + } + + $outcome = strtoupper((string) base_convert("$number", 10, $radix)); + if ($minLength !== null) { + $outcome = str_pad($outcome, (int) $minLength, '0', STR_PAD_LEFT); // String padding + } + + return $outcome; + } + + return ExcelError::VALUE(); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Ceiling.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Ceiling.php new file mode 100644 index 0000000..635f1bb --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Ceiling.php @@ -0,0 +1,167 @@ +getMessage(); + } + + return self::argumentsOk((float) $number, (float) $significance); + } + + /** + * CEILING.MATH. + * + * Round a number down to the nearest integer or to the nearest multiple of significance. + * + * Excel Function: + * CEILING.MATH(number[,significance[,mode]]) + * + * @param mixed $number Number to round + * Or can be an array of values + * @param mixed $significance Significance + * Or can be an array of values + * @param array|int $mode direction to round negative numbers + * Or can be an array of values + * + * @return array|float|string Rounded Number, or a string containing an error + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function math($number, $significance = null, $mode = 0) + { + if (is_array($number) || is_array($significance) || is_array($mode)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $significance, $mode); + } + + try { + $number = Helpers::validateNumericNullBool($number); + $significance = Helpers::validateNumericNullSubstitution($significance, ($number < 0) ? -1 : 1); + $mode = Helpers::validateNumericNullSubstitution($mode, null); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (empty($significance * $number)) { + return 0.0; + } + if (self::ceilingMathTest((float) $significance, (float) $number, (int) $mode)) { + return floor($number / $significance) * $significance; + } + + return ceil($number / $significance) * $significance; + } + + /** + * CEILING.PRECISE. + * + * Rounds number up, away from zero, to the nearest multiple of significance. + * + * Excel Function: + * CEILING.PRECISE(number[,significance]) + * + * @param mixed $number the number you want to round + * Or can be an array of values + * @param array|float $significance the multiple to which you want to round + * Or can be an array of values + * + * @return array|float|string Rounded Number, or a string containing an error + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function precise($number, $significance = 1) + { + if (is_array($number) || is_array($significance)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $significance); + } + + try { + $number = Helpers::validateNumericNullBool($number); + $significance = Helpers::validateNumericNullSubstitution($significance, null); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (!$significance) { + return 0.0; + } + $result = $number / abs($significance); + + return ceil($result) * $significance * (($significance < 0) ? -1 : 1); + } + + /** + * Let CEILINGMATH complexity pass Scrutinizer. + */ + private static function ceilingMathTest(float $significance, float $number, int $mode): bool + { + return ((float) $significance < 0) || ((float) $number < 0 && !empty($mode)); + } + + /** + * Avoid Scrutinizer problems concerning complexity. + * + * @return float|string + */ + private static function argumentsOk(float $number, float $significance) + { + if (empty($number * $significance)) { + return 0.0; + } + if (Helpers::returnSign($number) == Helpers::returnSign($significance)) { + return ceil($number / $significance) * $significance; + } + + return ExcelError::NAN(); + } + + private static function floorCheck1Arg(): void + { + $compatibility = Functions::getCompatibilityMode(); + if ($compatibility === Functions::COMPATIBILITY_EXCEL) { + throw new Exception('Excel requires 2 arguments for CEILING'); + } + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Combinations.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Combinations.php new file mode 100644 index 0000000..5a652da --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Combinations.php @@ -0,0 +1,91 @@ +getMessage(); + } + + return round(Factorial::fact($numObjs) / Factorial::fact($numObjs - $numInSet)) / Factorial::fact($numInSet); + } + + /** + * COMBINA. + * + * Returns the number of combinations for a given number of items. Use COMBIN to + * determine the total possible number of groups for a given number of items. + * + * Excel Function: + * COMBINA(numObjs,numInSet) + * + * @param mixed $numObjs Number of different objects, or can be an array of numbers + * @param mixed $numInSet Number of objects in each combination, or can be an array of numbers + * + * @return array|float|int|string Number of combinations, or a string containing an error + * If an array of numbers is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function withRepetition($numObjs, $numInSet) + { + if (is_array($numObjs) || is_array($numInSet)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $numObjs, $numInSet); + } + + try { + $numObjs = Helpers::validateNumericNullSubstitution($numObjs, null); + $numInSet = Helpers::validateNumericNullSubstitution($numInSet, null); + Helpers::validateNotNegative($numInSet); + Helpers::validateNotNegative($numObjs); + $numObjs = (int) $numObjs; + $numInSet = (int) $numInSet; + // Microsoft documentation says following is true, but Excel + // does not enforce this restriction. + //Helpers::validateNotNegative($numObjs - $numInSet); + if ($numObjs === 0) { + Helpers::validateNotNegative(-$numInSet); + + return 1; + } + } catch (Exception $e) { + return $e->getMessage(); + } + + return round( + Factorial::fact($numObjs + $numInSet - 1) / Factorial::fact($numObjs - 1) + ) / Factorial::fact($numInSet); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Exp.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Exp.php new file mode 100644 index 0000000..f65c2c1 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Exp.php @@ -0,0 +1,37 @@ +getMessage(); + } + + return exp($number); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Factorial.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Factorial.php new file mode 100644 index 0000000..b6883e2 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Factorial.php @@ -0,0 +1,125 @@ +getMessage(); + } + + $factLoop = floor($factVal); + if ($factVal > $factLoop) { + if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { + return Statistical\Distributions\Gamma::gammaValue($factVal + 1); + } + } + + $factorial = 1; + while ($factLoop > 1) { + $factorial *= $factLoop--; + } + + return $factorial; + } + + /** + * FACTDOUBLE. + * + * Returns the double factorial of a number. + * + * Excel Function: + * FACTDOUBLE(factVal) + * + * @param array|float $factVal Factorial Value, or can be an array of numbers + * + * @return array|float|int|string Double Factorial, or a string containing an error + * If an array of numbers is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function factDouble($factVal) + { + if (is_array($factVal)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $factVal); + } + + try { + $factVal = Helpers::validateNumericNullSubstitution($factVal, 0); + Helpers::validateNotNegative($factVal); + } catch (Exception $e) { + return $e->getMessage(); + } + + $factLoop = floor($factVal); + $factorial = 1; + while ($factLoop > 1) { + $factorial *= $factLoop; + $factLoop -= 2; + } + + return $factorial; + } + + /** + * MULTINOMIAL. + * + * Returns the ratio of the factorial of a sum of values to the product of factorials. + * + * @param mixed[] $args An array of mixed values for the Data Series + * + * @return float|string The result, or a string containing an error + */ + public static function multinomial(...$args) + { + $summer = 0; + $divisor = 1; + + try { + // Loop through arguments + foreach (Functions::flattenArray($args) as $argx) { + $arg = Helpers::validateNumericNullSubstitution($argx, null); + Helpers::validateNotNegative($arg); + $arg = (int) $arg; + $summer += $arg; + $divisor *= self::fact($arg); + } + } catch (Exception $e) { + return $e->getMessage(); + } + + $summer = self::fact($summer); + + return $summer / $divisor; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Floor.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Floor.php new file mode 100644 index 0000000..2199dda --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Floor.php @@ -0,0 +1,195 @@ +getMessage(); + } + + return self::argumentsOk((float) $number, (float) $significance); + } + + /** + * FLOOR.MATH. + * + * Round a number down to the nearest integer or to the nearest multiple of significance. + * + * Excel Function: + * FLOOR.MATH(number[,significance[,mode]]) + * + * @param mixed $number Number to round + * Or can be an array of values + * @param mixed $significance Significance + * Or can be an array of values + * @param mixed $mode direction to round negative numbers + * Or can be an array of values + * + * @return array|float|string Rounded Number, or a string containing an error + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function math($number, $significance = null, $mode = 0) + { + if (is_array($number) || is_array($significance) || is_array($mode)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $significance, $mode); + } + + try { + $number = Helpers::validateNumericNullBool($number); + $significance = Helpers::validateNumericNullSubstitution($significance, ($number < 0) ? -1 : 1); + $mode = Helpers::validateNumericNullSubstitution($mode, null); + } catch (Exception $e) { + return $e->getMessage(); + } + + return self::argsOk((float) $number, (float) $significance, (int) $mode); + } + + /** + * FLOOR.PRECISE. + * + * Rounds number down, toward zero, to the nearest multiple of significance. + * + * Excel Function: + * FLOOR.PRECISE(number[,significance]) + * + * @param array|float $number Number to round + * Or can be an array of values + * @param array|float $significance Significance + * Or can be an array of values + * + * @return array|float|string Rounded Number, or a string containing an error + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function precise($number, $significance = 1) + { + if (is_array($number) || is_array($significance)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $significance); + } + + try { + $number = Helpers::validateNumericNullBool($number); + $significance = Helpers::validateNumericNullSubstitution($significance, null); + } catch (Exception $e) { + return $e->getMessage(); + } + + return self::argumentsOkPrecise((float) $number, (float) $significance); + } + + /** + * Avoid Scrutinizer problems concerning complexity. + * + * @return float|string + */ + private static function argumentsOkPrecise(float $number, float $significance) + { + if ($significance == 0.0) { + return ExcelError::DIV0(); + } + if ($number == 0.0) { + return 0.0; + } + + return floor($number / abs($significance)) * abs($significance); + } + + /** + * Avoid Scrutinizer complexity problems. + * + * @return float|string Rounded Number, or a string containing an error + */ + private static function argsOk(float $number, float $significance, int $mode) + { + if (!$significance) { + return ExcelError::DIV0(); + } + if (!$number) { + return 0.0; + } + if (self::floorMathTest($number, $significance, $mode)) { + return ceil($number / $significance) * $significance; + } + + return floor($number / $significance) * $significance; + } + + /** + * Let FLOORMATH complexity pass Scrutinizer. + */ + private static function floorMathTest(float $number, float $significance, int $mode): bool + { + return Helpers::returnSign($significance) == -1 || (Helpers::returnSign($number) == -1 && !empty($mode)); + } + + /** + * Avoid Scrutinizer problems concerning complexity. + * + * @return float|string + */ + private static function argumentsOk(float $number, float $significance) + { + if ($significance == 0.0) { + return ExcelError::DIV0(); + } + if ($number == 0.0) { + return 0.0; + } + if (Helpers::returnSign($significance) == 1) { + return floor($number / $significance) * $significance; + } + if (Helpers::returnSign($number) == -1 && Helpers::returnSign($significance) == -1) { + return floor($number / $significance) * $significance; + } + + return ExcelError::NAN(); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Gcd.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Gcd.php new file mode 100644 index 0000000..f703599 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Gcd.php @@ -0,0 +1,70 @@ +getMessage(); + } + + if (count($arrayArgs) <= 0) { + return ExcelError::VALUE(); + } + $gcd = (int) array_pop($arrayArgs); + do { + $gcd = self::evaluateGCD($gcd, (int) array_pop($arrayArgs)); + } while (!empty($arrayArgs)); + + return $gcd; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Helpers.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Helpers.php new file mode 100644 index 0000000..f34f159 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Helpers.php @@ -0,0 +1,130 @@ += 0. + * + * @param float|int $number + */ + public static function validateNotNegative($number, ?string $except = null): void + { + if ($number >= 0) { + return; + } + + throw new Exception($except ?? ExcelError::NAN()); + } + + /** + * Confirm number > 0. + * + * @param float|int $number + */ + public static function validatePositive($number, ?string $except = null): void + { + if ($number > 0) { + return; + } + + throw new Exception($except ?? ExcelError::NAN()); + } + + /** + * Confirm number != 0. + * + * @param float|int $number + */ + public static function validateNotZero($number): void + { + if ($number) { + return; + } + + throw new Exception(ExcelError::DIV0()); + } + + public static function returnSign(float $number): int + { + return $number ? (($number > 0) ? 1 : -1) : 0; + } + + public static function getEven(float $number): float + { + $significance = 2 * self::returnSign($number); + + return $significance ? (ceil($number / $significance) * $significance) : 0; + } + + /** + * Return NAN or value depending on argument. + * + * @param float $result Number + * + * @return float|string + */ + public static function numberOrNan($result) + { + return is_nan($result) ? ExcelError::NAN() : $result; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/IntClass.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/IntClass.php new file mode 100644 index 0000000..f7f7764 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/IntClass.php @@ -0,0 +1,40 @@ +getMessage(); + } + + return (int) floor($number); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Lcm.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Lcm.php new file mode 100644 index 0000000..3b23c1d --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Lcm.php @@ -0,0 +1,111 @@ + 1; --$i) { + if (($value % $i) == 0) { + $factorArray = array_merge($factorArray, self::factors($value / $i)); + $factorArray = array_merge($factorArray, self::factors($i)); + if ($i <= sqrt($value)) { + break; + } + } + } + if (!empty($factorArray)) { + rsort($factorArray); + + return $factorArray; + } + + return [(int) $value]; + } + + /** + * LCM. + * + * Returns the lowest common multiplier of a series of numbers + * The least common multiple is the smallest positive integer that is a multiple + * of all integer arguments number1, number2, and so on. Use LCM to add fractions + * with different denominators. + * + * Excel Function: + * LCM(number1[,number2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return int|string Lowest Common Multiplier, or a string containing an error + */ + public static function evaluate(...$args) + { + try { + $arrayArgs = []; + $anyZeros = 0; + $anyNonNulls = 0; + foreach (Functions::flattenArray($args) as $value1) { + $anyNonNulls += (int) ($value1 !== null); + $value = Helpers::validateNumericNullSubstitution($value1, 1); + Helpers::validateNotNegative($value); + $arrayArgs[] = (int) $value; + $anyZeros += (int) !((bool) $value); + } + self::testNonNulls($anyNonNulls); + if ($anyZeros) { + return 0; + } + } catch (Exception $e) { + return $e->getMessage(); + } + + $returnValue = 1; + $allPoweredFactors = []; + // Loop through arguments + foreach ($arrayArgs as $value) { + $myFactors = self::factors(floor($value)); + $myCountedFactors = array_count_values($myFactors); + $myPoweredFactors = []; + foreach ($myCountedFactors as $myCountedFactor => $myCountedPower) { + $myPoweredFactors[$myCountedFactor] = $myCountedFactor ** $myCountedPower; + } + self::processPoweredFactors($allPoweredFactors, $myPoweredFactors); + } + foreach ($allPoweredFactors as $allPoweredFactor) { + $returnValue *= (int) $allPoweredFactor; + } + + return $returnValue; + } + + private static function processPoweredFactors(array &$allPoweredFactors, array &$myPoweredFactors): void + { + foreach ($myPoweredFactors as $myPoweredValue => $myPoweredFactor) { + if (isset($allPoweredFactors[$myPoweredValue])) { + if ($allPoweredFactors[$myPoweredValue] < $myPoweredFactor) { + $allPoweredFactors[$myPoweredValue] = $myPoweredFactor; + } + } else { + $allPoweredFactors[$myPoweredValue] = $myPoweredFactor; + } + } + } + + private static function testNonNulls(int $anyNonNulls): void + { + if (!$anyNonNulls) { + throw new Exception(ExcelError::VALUE()); + } + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Logarithms.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Logarithms.php new file mode 100644 index 0000000..7b07f09 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Logarithms.php @@ -0,0 +1,102 @@ +getMessage(); + } + + return log($number, $base); + } + + /** + * LOG10. + * + * Returns the result of builtin function log after validating args. + * + * @param mixed $number Should be numeric + * Or can be an array of values + * + * @return array|float|string Rounded number + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function base10($number) + { + if (is_array($number)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number); + } + + try { + $number = Helpers::validateNumericNullBool($number); + Helpers::validatePositive($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + return log10($number); + } + + /** + * LN. + * + * Returns the result of builtin function log after validating args. + * + * @param mixed $number Should be numeric + * Or can be an array of values + * + * @return array|float|string Rounded number + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function natural($number) + { + if (is_array($number)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number); + } + + try { + $number = Helpers::validateNumericNullBool($number); + Helpers::validatePositive($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + return log($number); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php new file mode 100644 index 0000000..5a5125a --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php @@ -0,0 +1,179 @@ +getMessage(); + } + + if ($step === 0) { + return array_chunk( + array_fill(0, $rows * $columns, $start), + max($columns, 1) + ); + } + + return array_chunk( + range($start, $start + (($rows * $columns - 1) * $step), $step), + max($columns, 1) + ); + } + + /** + * MDETERM. + * + * Returns the matrix determinant of an array. + * + * Excel Function: + * MDETERM(array) + * + * @param mixed $matrixValues A matrix of values + * + * @return float|string The result, or a string containing an error + */ + public static function determinant($matrixValues) + { + try { + $matrix = self::getMatrix($matrixValues); + + return $matrix->determinant(); + } catch (MatrixException $ex) { + return ExcelError::VALUE(); + } catch (Exception $e) { + return $e->getMessage(); + } + } + + /** + * MINVERSE. + * + * Returns the inverse matrix for the matrix stored in an array. + * + * Excel Function: + * MINVERSE(array) + * + * @param mixed $matrixValues A matrix of values + * + * @return array|string The result, or a string containing an error + */ + public static function inverse($matrixValues) + { + try { + $matrix = self::getMatrix($matrixValues); + + return $matrix->inverse()->toArray(); + } catch (MatrixDiv0Exception $e) { + return ExcelError::NAN(); + } catch (MatrixException $e) { + return ExcelError::VALUE(); + } catch (Exception $e) { + return $e->getMessage(); + } + } + + /** + * MMULT. + * + * @param mixed $matrixData1 A matrix of values + * @param mixed $matrixData2 A matrix of values + * + * @return array|string The result, or a string containing an error + */ + public static function multiply($matrixData1, $matrixData2) + { + try { + $matrixA = self::getMatrix($matrixData1); + $matrixB = self::getMatrix($matrixData2); + + return $matrixA->multiply($matrixB)->toArray(); + } catch (MatrixException $ex) { + return ExcelError::VALUE(); + } catch (Exception $e) { + return $e->getMessage(); + } + } + + /** + * MUnit. + * + * @param mixed $dimension Number of rows and columns + * + * @return array|string The result, or a string containing an error + */ + public static function identity($dimension) + { + try { + $dimension = (int) Helpers::validateNumericNullBool($dimension); + Helpers::validatePositive($dimension, ExcelError::VALUE()); + $matrix = Builder::createIdentityMatrix($dimension, 0)->toArray(); + + return $matrix; + } catch (Exception $e) { + return $e->getMessage(); + } + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Operations.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Operations.php new file mode 100644 index 0000000..0625845 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Operations.php @@ -0,0 +1,162 @@ +getMessage(); + } + + if (($dividend < 0.0) && ($divisor > 0.0)) { + return $divisor - fmod(abs($dividend), $divisor); + } + if (($dividend > 0.0) && ($divisor < 0.0)) { + return $divisor + fmod($dividend, abs($divisor)); + } + + return fmod($dividend, $divisor); + } + + /** + * POWER. + * + * Computes x raised to the power y. + * + * @param array|float|int $x + * Or can be an array of values + * @param array|float|int $y + * Or can be an array of values + * + * @return array|float|int|string The result, or a string containing an error + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function power($x, $y) + { + if (is_array($x) || is_array($y)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $x, $y); + } + + try { + $x = Helpers::validateNumericNullBool($x); + $y = Helpers::validateNumericNullBool($y); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Validate parameters + if (!$x && !$y) { + return ExcelError::NAN(); + } + if (!$x && $y < 0.0) { + return ExcelError::DIV0(); + } + + // Return + $result = $x ** $y; + + return Helpers::numberOrNan($result); + } + + /** + * PRODUCT. + * + * PRODUCT returns the product of all the values and cells referenced in the argument list. + * + * Excel Function: + * PRODUCT(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float|string + */ + public static function product(...$args) + { + $args = array_filter( + Functions::flattenArray($args), + function ($value) { + return $value !== null; + } + ); + + // Return value + $returnValue = (count($args) === 0) ? 0.0 : 1.0; + + // Loop through arguments + foreach ($args as $arg) { + // Is it a numeric value? + if (is_numeric($arg)) { + $returnValue *= $arg; + } else { + return ExcelError::throwError($arg); + } + } + + return (float) $returnValue; + } + + /** + * QUOTIENT. + * + * QUOTIENT function returns the integer portion of a division. Numerator is the divided number + * and denominator is the divisor. + * + * Excel Function: + * QUOTIENT(value1,value2) + * + * @param mixed $numerator Expect float|int + * Or can be an array of values + * @param mixed $denominator Expect float|int + * Or can be an array of values + * + * @return array|int|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function quotient($numerator, $denominator) + { + if (is_array($numerator) || is_array($denominator)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $numerator, $denominator); + } + + try { + $numerator = Helpers::validateNumericNullSubstitution($numerator, 0); + $denominator = Helpers::validateNumericNullSubstitution($denominator, 0); + Helpers::validateNotZero($denominator); + } catch (Exception $e) { + return $e->getMessage(); + } + + return (int) ($numerator / $denominator); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Random.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Random.php new file mode 100644 index 0000000..22cad2c --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Random.php @@ -0,0 +1,99 @@ +getMessage(); + } + + return mt_rand($min, $max); + } + + /** + * RANDARRAY. + * + * Generates a list of sequential numbers in an array. + * + * Excel Function: + * RANDARRAY([rows],[columns],[start],[step]) + * + * @param mixed $rows the number of rows to return, defaults to 1 + * @param mixed $columns the number of columns to return, defaults to 1 + * @param mixed $min the minimum number to be returned, defaults to 0 + * @param mixed $max the maximum number to be returned, defaults to 1 + * @param bool $wholeNumber the type of numbers to return: + * False - Decimal numbers to 15 decimal places. (default) + * True - Whole (integer) numbers + * + * @return array|string The resulting array, or a string containing an error + */ + public static function randArray($rows = 1, $columns = 1, $min = 0, $max = 1, $wholeNumber = false) + { + try { + $rows = (int) Helpers::validateNumericNullSubstitution($rows, 1); + Helpers::validatePositive($rows); + $columns = (int) Helpers::validateNumericNullSubstitution($columns, 1); + Helpers::validatePositive($columns); + $min = Helpers::validateNumericNullSubstitution($min, 1); + $max = Helpers::validateNumericNullSubstitution($max, 1); + + if ($max <= $min) { + return ExcelError::VALUE(); + } + } catch (Exception $e) { + return $e->getMessage(); + } + + return array_chunk( + array_map( + function () use ($min, $max, $wholeNumber) { + return $wholeNumber + ? mt_rand((int) $min, (int) $max) + : (mt_rand() / mt_getrandmax()) * ($max - $min) + $min; + }, + array_fill(0, $rows * $columns, $min) + ), + max($columns, 1) + ); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Roman.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Roman.php new file mode 100644 index 0000000..0541548 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Roman.php @@ -0,0 +1,846 @@ + ['VL'], + 46 => ['VLI'], + 47 => ['VLII'], + 48 => ['VLIII'], + 49 => ['VLIV', 'IL'], + 95 => ['VC'], + 96 => ['VCI'], + 97 => ['VCII'], + 98 => ['VCIII'], + 99 => ['VCIV', 'IC'], + 145 => ['CVL'], + 146 => ['CVLI'], + 147 => ['CVLII'], + 148 => ['CVLIII'], + 149 => ['CVLIV', 'CIL'], + 195 => ['CVC'], + 196 => ['CVCI'], + 197 => ['CVCII'], + 198 => ['CVCIII'], + 199 => ['CVCIV', 'CIC'], + 245 => ['CCVL'], + 246 => ['CCVLI'], + 247 => ['CCVLII'], + 248 => ['CCVLIII'], + 249 => ['CCVLIV', 'CCIL'], + 295 => ['CCVC'], + 296 => ['CCVCI'], + 297 => ['CCVCII'], + 298 => ['CCVCIII'], + 299 => ['CCVCIV', 'CCIC'], + 345 => ['CCCVL'], + 346 => ['CCCVLI'], + 347 => ['CCCVLII'], + 348 => ['CCCVLIII'], + 349 => ['CCCVLIV', 'CCCIL'], + 395 => ['CCCVC'], + 396 => ['CCCVCI'], + 397 => ['CCCVCII'], + 398 => ['CCCVCIII'], + 399 => ['CCCVCIV', 'CCCIC'], + 445 => ['CDVL'], + 446 => ['CDVLI'], + 447 => ['CDVLII'], + 448 => ['CDVLIII'], + 449 => ['CDVLIV', 'CDIL'], + 450 => ['LD'], + 451 => ['LDI'], + 452 => ['LDII'], + 453 => ['LDIII'], + 454 => ['LDIV'], + 455 => ['LDV'], + 456 => ['LDVI'], + 457 => ['LDVII'], + 458 => ['LDVIII'], + 459 => ['LDIX'], + 460 => ['LDX'], + 461 => ['LDXI'], + 462 => ['LDXII'], + 463 => ['LDXIII'], + 464 => ['LDXIV'], + 465 => ['LDXV'], + 466 => ['LDXVI'], + 467 => ['LDXVII'], + 468 => ['LDXVIII'], + 469 => ['LDXIX'], + 470 => ['LDXX'], + 471 => ['LDXXI'], + 472 => ['LDXXII'], + 473 => ['LDXXIII'], + 474 => ['LDXXIV'], + 475 => ['LDXXV'], + 476 => ['LDXXVI'], + 477 => ['LDXXVII'], + 478 => ['LDXXVIII'], + 479 => ['LDXXIX'], + 480 => ['LDXXX'], + 481 => ['LDXXXI'], + 482 => ['LDXXXII'], + 483 => ['LDXXXIII'], + 484 => ['LDXXXIV'], + 485 => ['LDXXXV'], + 486 => ['LDXXXVI'], + 487 => ['LDXXXVII'], + 488 => ['LDXXXVIII'], + 489 => ['LDXXXIX'], + 490 => ['LDXL', 'XD'], + 491 => ['LDXLI', 'XDI'], + 492 => ['LDXLII', 'XDII'], + 493 => ['LDXLIII', 'XDIII'], + 494 => ['LDXLIV', 'XDIV'], + 495 => ['LDVL', 'XDV', 'VD'], + 496 => ['LDVLI', 'XDVI', 'VDI'], + 497 => ['LDVLII', 'XDVII', 'VDII'], + 498 => ['LDVLIII', 'XDVIII', 'VDIII'], + 499 => ['LDVLIV', 'XDIX', 'VDIV', 'ID'], + 545 => ['DVL'], + 546 => ['DVLI'], + 547 => ['DVLII'], + 548 => ['DVLIII'], + 549 => ['DVLIV', 'DIL'], + 595 => ['DVC'], + 596 => ['DVCI'], + 597 => ['DVCII'], + 598 => ['DVCIII'], + 599 => ['DVCIV', 'DIC'], + 645 => ['DCVL'], + 646 => ['DCVLI'], + 647 => ['DCVLII'], + 648 => ['DCVLIII'], + 649 => ['DCVLIV', 'DCIL'], + 695 => ['DCVC'], + 696 => ['DCVCI'], + 697 => ['DCVCII'], + 698 => ['DCVCIII'], + 699 => ['DCVCIV', 'DCIC'], + 745 => ['DCCVL'], + 746 => ['DCCVLI'], + 747 => ['DCCVLII'], + 748 => ['DCCVLIII'], + 749 => ['DCCVLIV', 'DCCIL'], + 795 => ['DCCVC'], + 796 => ['DCCVCI'], + 797 => ['DCCVCII'], + 798 => ['DCCVCIII'], + 799 => ['DCCVCIV', 'DCCIC'], + 845 => ['DCCCVL'], + 846 => ['DCCCVLI'], + 847 => ['DCCCVLII'], + 848 => ['DCCCVLIII'], + 849 => ['DCCCVLIV', 'DCCCIL'], + 895 => ['DCCCVC'], + 896 => ['DCCCVCI'], + 897 => ['DCCCVCII'], + 898 => ['DCCCVCIII'], + 899 => ['DCCCVCIV', 'DCCCIC'], + 945 => ['CMVL'], + 946 => ['CMVLI'], + 947 => ['CMVLII'], + 948 => ['CMVLIII'], + 949 => ['CMVLIV', 'CMIL'], + 950 => ['LM'], + 951 => ['LMI'], + 952 => ['LMII'], + 953 => ['LMIII'], + 954 => ['LMIV'], + 955 => ['LMV'], + 956 => ['LMVI'], + 957 => ['LMVII'], + 958 => ['LMVIII'], + 959 => ['LMIX'], + 960 => ['LMX'], + 961 => ['LMXI'], + 962 => ['LMXII'], + 963 => ['LMXIII'], + 964 => ['LMXIV'], + 965 => ['LMXV'], + 966 => ['LMXVI'], + 967 => ['LMXVII'], + 968 => ['LMXVIII'], + 969 => ['LMXIX'], + 970 => ['LMXX'], + 971 => ['LMXXI'], + 972 => ['LMXXII'], + 973 => ['LMXXIII'], + 974 => ['LMXXIV'], + 975 => ['LMXXV'], + 976 => ['LMXXVI'], + 977 => ['LMXXVII'], + 978 => ['LMXXVIII'], + 979 => ['LMXXIX'], + 980 => ['LMXXX'], + 981 => ['LMXXXI'], + 982 => ['LMXXXII'], + 983 => ['LMXXXIII'], + 984 => ['LMXXXIV'], + 985 => ['LMXXXV'], + 986 => ['LMXXXVI'], + 987 => ['LMXXXVII'], + 988 => ['LMXXXVIII'], + 989 => ['LMXXXIX'], + 990 => ['LMXL', 'XM'], + 991 => ['LMXLI', 'XMI'], + 992 => ['LMXLII', 'XMII'], + 993 => ['LMXLIII', 'XMIII'], + 994 => ['LMXLIV', 'XMIV'], + 995 => ['LMVL', 'XMV', 'VM'], + 996 => ['LMVLI', 'XMVI', 'VMI'], + 997 => ['LMVLII', 'XMVII', 'VMII'], + 998 => ['LMVLIII', 'XMVIII', 'VMIII'], + 999 => ['LMVLIV', 'XMIX', 'VMIV', 'IM'], + 1045 => ['MVL'], + 1046 => ['MVLI'], + 1047 => ['MVLII'], + 1048 => ['MVLIII'], + 1049 => ['MVLIV', 'MIL'], + 1095 => ['MVC'], + 1096 => ['MVCI'], + 1097 => ['MVCII'], + 1098 => ['MVCIII'], + 1099 => ['MVCIV', 'MIC'], + 1145 => ['MCVL'], + 1146 => ['MCVLI'], + 1147 => ['MCVLII'], + 1148 => ['MCVLIII'], + 1149 => ['MCVLIV', 'MCIL'], + 1195 => ['MCVC'], + 1196 => ['MCVCI'], + 1197 => ['MCVCII'], + 1198 => ['MCVCIII'], + 1199 => ['MCVCIV', 'MCIC'], + 1245 => ['MCCVL'], + 1246 => ['MCCVLI'], + 1247 => ['MCCVLII'], + 1248 => ['MCCVLIII'], + 1249 => ['MCCVLIV', 'MCCIL'], + 1295 => ['MCCVC'], + 1296 => ['MCCVCI'], + 1297 => ['MCCVCII'], + 1298 => ['MCCVCIII'], + 1299 => ['MCCVCIV', 'MCCIC'], + 1345 => ['MCCCVL'], + 1346 => ['MCCCVLI'], + 1347 => ['MCCCVLII'], + 1348 => ['MCCCVLIII'], + 1349 => ['MCCCVLIV', 'MCCCIL'], + 1395 => ['MCCCVC'], + 1396 => ['MCCCVCI'], + 1397 => ['MCCCVCII'], + 1398 => ['MCCCVCIII'], + 1399 => ['MCCCVCIV', 'MCCCIC'], + 1445 => ['MCDVL'], + 1446 => ['MCDVLI'], + 1447 => ['MCDVLII'], + 1448 => ['MCDVLIII'], + 1449 => ['MCDVLIV', 'MCDIL'], + 1450 => ['MLD'], + 1451 => ['MLDI'], + 1452 => ['MLDII'], + 1453 => ['MLDIII'], + 1454 => ['MLDIV'], + 1455 => ['MLDV'], + 1456 => ['MLDVI'], + 1457 => ['MLDVII'], + 1458 => ['MLDVIII'], + 1459 => ['MLDIX'], + 1460 => ['MLDX'], + 1461 => ['MLDXI'], + 1462 => ['MLDXII'], + 1463 => ['MLDXIII'], + 1464 => ['MLDXIV'], + 1465 => ['MLDXV'], + 1466 => ['MLDXVI'], + 1467 => ['MLDXVII'], + 1468 => ['MLDXVIII'], + 1469 => ['MLDXIX'], + 1470 => ['MLDXX'], + 1471 => ['MLDXXI'], + 1472 => ['MLDXXII'], + 1473 => ['MLDXXIII'], + 1474 => ['MLDXXIV'], + 1475 => ['MLDXXV'], + 1476 => ['MLDXXVI'], + 1477 => ['MLDXXVII'], + 1478 => ['MLDXXVIII'], + 1479 => ['MLDXXIX'], + 1480 => ['MLDXXX'], + 1481 => ['MLDXXXI'], + 1482 => ['MLDXXXII'], + 1483 => ['MLDXXXIII'], + 1484 => ['MLDXXXIV'], + 1485 => ['MLDXXXV'], + 1486 => ['MLDXXXVI'], + 1487 => ['MLDXXXVII'], + 1488 => ['MLDXXXVIII'], + 1489 => ['MLDXXXIX'], + 1490 => ['MLDXL', 'MXD'], + 1491 => ['MLDXLI', 'MXDI'], + 1492 => ['MLDXLII', 'MXDII'], + 1493 => ['MLDXLIII', 'MXDIII'], + 1494 => ['MLDXLIV', 'MXDIV'], + 1495 => ['MLDVL', 'MXDV', 'MVD'], + 1496 => ['MLDVLI', 'MXDVI', 'MVDI'], + 1497 => ['MLDVLII', 'MXDVII', 'MVDII'], + 1498 => ['MLDVLIII', 'MXDVIII', 'MVDIII'], + 1499 => ['MLDVLIV', 'MXDIX', 'MVDIV', 'MID'], + 1545 => ['MDVL'], + 1546 => ['MDVLI'], + 1547 => ['MDVLII'], + 1548 => ['MDVLIII'], + 1549 => ['MDVLIV', 'MDIL'], + 1595 => ['MDVC'], + 1596 => ['MDVCI'], + 1597 => ['MDVCII'], + 1598 => ['MDVCIII'], + 1599 => ['MDVCIV', 'MDIC'], + 1645 => ['MDCVL'], + 1646 => ['MDCVLI'], + 1647 => ['MDCVLII'], + 1648 => ['MDCVLIII'], + 1649 => ['MDCVLIV', 'MDCIL'], + 1695 => ['MDCVC'], + 1696 => ['MDCVCI'], + 1697 => ['MDCVCII'], + 1698 => ['MDCVCIII'], + 1699 => ['MDCVCIV', 'MDCIC'], + 1745 => ['MDCCVL'], + 1746 => ['MDCCVLI'], + 1747 => ['MDCCVLII'], + 1748 => ['MDCCVLIII'], + 1749 => ['MDCCVLIV', 'MDCCIL'], + 1795 => ['MDCCVC'], + 1796 => ['MDCCVCI'], + 1797 => ['MDCCVCII'], + 1798 => ['MDCCVCIII'], + 1799 => ['MDCCVCIV', 'MDCCIC'], + 1845 => ['MDCCCVL'], + 1846 => ['MDCCCVLI'], + 1847 => ['MDCCCVLII'], + 1848 => ['MDCCCVLIII'], + 1849 => ['MDCCCVLIV', 'MDCCCIL'], + 1895 => ['MDCCCVC'], + 1896 => ['MDCCCVCI'], + 1897 => ['MDCCCVCII'], + 1898 => ['MDCCCVCIII'], + 1899 => ['MDCCCVCIV', 'MDCCCIC'], + 1945 => ['MCMVL'], + 1946 => ['MCMVLI'], + 1947 => ['MCMVLII'], + 1948 => ['MCMVLIII'], + 1949 => ['MCMVLIV', 'MCMIL'], + 1950 => ['MLM'], + 1951 => ['MLMI'], + 1952 => ['MLMII'], + 1953 => ['MLMIII'], + 1954 => ['MLMIV'], + 1955 => ['MLMV'], + 1956 => ['MLMVI'], + 1957 => ['MLMVII'], + 1958 => ['MLMVIII'], + 1959 => ['MLMIX'], + 1960 => ['MLMX'], + 1961 => ['MLMXI'], + 1962 => ['MLMXII'], + 1963 => ['MLMXIII'], + 1964 => ['MLMXIV'], + 1965 => ['MLMXV'], + 1966 => ['MLMXVI'], + 1967 => ['MLMXVII'], + 1968 => ['MLMXVIII'], + 1969 => ['MLMXIX'], + 1970 => ['MLMXX'], + 1971 => ['MLMXXI'], + 1972 => ['MLMXXII'], + 1973 => ['MLMXXIII'], + 1974 => ['MLMXXIV'], + 1975 => ['MLMXXV'], + 1976 => ['MLMXXVI'], + 1977 => ['MLMXXVII'], + 1978 => ['MLMXXVIII'], + 1979 => ['MLMXXIX'], + 1980 => ['MLMXXX'], + 1981 => ['MLMXXXI'], + 1982 => ['MLMXXXII'], + 1983 => ['MLMXXXIII'], + 1984 => ['MLMXXXIV'], + 1985 => ['MLMXXXV'], + 1986 => ['MLMXXXVI'], + 1987 => ['MLMXXXVII'], + 1988 => ['MLMXXXVIII'], + 1989 => ['MLMXXXIX'], + 1990 => ['MLMXL', 'MXM'], + 1991 => ['MLMXLI', 'MXMI'], + 1992 => ['MLMXLII', 'MXMII'], + 1993 => ['MLMXLIII', 'MXMIII'], + 1994 => ['MLMXLIV', 'MXMIV'], + 1995 => ['MLMVL', 'MXMV', 'MVM'], + 1996 => ['MLMVLI', 'MXMVI', 'MVMI'], + 1997 => ['MLMVLII', 'MXMVII', 'MVMII'], + 1998 => ['MLMVLIII', 'MXMVIII', 'MVMIII'], + 1999 => ['MLMVLIV', 'MXMIX', 'MVMIV', 'MIM'], + 2045 => ['MMVL'], + 2046 => ['MMVLI'], + 2047 => ['MMVLII'], + 2048 => ['MMVLIII'], + 2049 => ['MMVLIV', 'MMIL'], + 2095 => ['MMVC'], + 2096 => ['MMVCI'], + 2097 => ['MMVCII'], + 2098 => ['MMVCIII'], + 2099 => ['MMVCIV', 'MMIC'], + 2145 => ['MMCVL'], + 2146 => ['MMCVLI'], + 2147 => ['MMCVLII'], + 2148 => ['MMCVLIII'], + 2149 => ['MMCVLIV', 'MMCIL'], + 2195 => ['MMCVC'], + 2196 => ['MMCVCI'], + 2197 => ['MMCVCII'], + 2198 => ['MMCVCIII'], + 2199 => ['MMCVCIV', 'MMCIC'], + 2245 => ['MMCCVL'], + 2246 => ['MMCCVLI'], + 2247 => ['MMCCVLII'], + 2248 => ['MMCCVLIII'], + 2249 => ['MMCCVLIV', 'MMCCIL'], + 2295 => ['MMCCVC'], + 2296 => ['MMCCVCI'], + 2297 => ['MMCCVCII'], + 2298 => ['MMCCVCIII'], + 2299 => ['MMCCVCIV', 'MMCCIC'], + 2345 => ['MMCCCVL'], + 2346 => ['MMCCCVLI'], + 2347 => ['MMCCCVLII'], + 2348 => ['MMCCCVLIII'], + 2349 => ['MMCCCVLIV', 'MMCCCIL'], + 2395 => ['MMCCCVC'], + 2396 => ['MMCCCVCI'], + 2397 => ['MMCCCVCII'], + 2398 => ['MMCCCVCIII'], + 2399 => ['MMCCCVCIV', 'MMCCCIC'], + 2445 => ['MMCDVL'], + 2446 => ['MMCDVLI'], + 2447 => ['MMCDVLII'], + 2448 => ['MMCDVLIII'], + 2449 => ['MMCDVLIV', 'MMCDIL'], + 2450 => ['MMLD'], + 2451 => ['MMLDI'], + 2452 => ['MMLDII'], + 2453 => ['MMLDIII'], + 2454 => ['MMLDIV'], + 2455 => ['MMLDV'], + 2456 => ['MMLDVI'], + 2457 => ['MMLDVII'], + 2458 => ['MMLDVIII'], + 2459 => ['MMLDIX'], + 2460 => ['MMLDX'], + 2461 => ['MMLDXI'], + 2462 => ['MMLDXII'], + 2463 => ['MMLDXIII'], + 2464 => ['MMLDXIV'], + 2465 => ['MMLDXV'], + 2466 => ['MMLDXVI'], + 2467 => ['MMLDXVII'], + 2468 => ['MMLDXVIII'], + 2469 => ['MMLDXIX'], + 2470 => ['MMLDXX'], + 2471 => ['MMLDXXI'], + 2472 => ['MMLDXXII'], + 2473 => ['MMLDXXIII'], + 2474 => ['MMLDXXIV'], + 2475 => ['MMLDXXV'], + 2476 => ['MMLDXXVI'], + 2477 => ['MMLDXXVII'], + 2478 => ['MMLDXXVIII'], + 2479 => ['MMLDXXIX'], + 2480 => ['MMLDXXX'], + 2481 => ['MMLDXXXI'], + 2482 => ['MMLDXXXII'], + 2483 => ['MMLDXXXIII'], + 2484 => ['MMLDXXXIV'], + 2485 => ['MMLDXXXV'], + 2486 => ['MMLDXXXVI'], + 2487 => ['MMLDXXXVII'], + 2488 => ['MMLDXXXVIII'], + 2489 => ['MMLDXXXIX'], + 2490 => ['MMLDXL', 'MMXD'], + 2491 => ['MMLDXLI', 'MMXDI'], + 2492 => ['MMLDXLII', 'MMXDII'], + 2493 => ['MMLDXLIII', 'MMXDIII'], + 2494 => ['MMLDXLIV', 'MMXDIV'], + 2495 => ['MMLDVL', 'MMXDV', 'MMVD'], + 2496 => ['MMLDVLI', 'MMXDVI', 'MMVDI'], + 2497 => ['MMLDVLII', 'MMXDVII', 'MMVDII'], + 2498 => ['MMLDVLIII', 'MMXDVIII', 'MMVDIII'], + 2499 => ['MMLDVLIV', 'MMXDIX', 'MMVDIV', 'MMID'], + 2545 => ['MMDVL'], + 2546 => ['MMDVLI'], + 2547 => ['MMDVLII'], + 2548 => ['MMDVLIII'], + 2549 => ['MMDVLIV', 'MMDIL'], + 2595 => ['MMDVC'], + 2596 => ['MMDVCI'], + 2597 => ['MMDVCII'], + 2598 => ['MMDVCIII'], + 2599 => ['MMDVCIV', 'MMDIC'], + 2645 => ['MMDCVL'], + 2646 => ['MMDCVLI'], + 2647 => ['MMDCVLII'], + 2648 => ['MMDCVLIII'], + 2649 => ['MMDCVLIV', 'MMDCIL'], + 2695 => ['MMDCVC'], + 2696 => ['MMDCVCI'], + 2697 => ['MMDCVCII'], + 2698 => ['MMDCVCIII'], + 2699 => ['MMDCVCIV', 'MMDCIC'], + 2745 => ['MMDCCVL'], + 2746 => ['MMDCCVLI'], + 2747 => ['MMDCCVLII'], + 2748 => ['MMDCCVLIII'], + 2749 => ['MMDCCVLIV', 'MMDCCIL'], + 2795 => ['MMDCCVC'], + 2796 => ['MMDCCVCI'], + 2797 => ['MMDCCVCII'], + 2798 => ['MMDCCVCIII'], + 2799 => ['MMDCCVCIV', 'MMDCCIC'], + 2845 => ['MMDCCCVL'], + 2846 => ['MMDCCCVLI'], + 2847 => ['MMDCCCVLII'], + 2848 => ['MMDCCCVLIII'], + 2849 => ['MMDCCCVLIV', 'MMDCCCIL'], + 2895 => ['MMDCCCVC'], + 2896 => ['MMDCCCVCI'], + 2897 => ['MMDCCCVCII'], + 2898 => ['MMDCCCVCIII'], + 2899 => ['MMDCCCVCIV', 'MMDCCCIC'], + 2945 => ['MMCMVL'], + 2946 => ['MMCMVLI'], + 2947 => ['MMCMVLII'], + 2948 => ['MMCMVLIII'], + 2949 => ['MMCMVLIV', 'MMCMIL'], + 2950 => ['MMLM'], + 2951 => ['MMLMI'], + 2952 => ['MMLMII'], + 2953 => ['MMLMIII'], + 2954 => ['MMLMIV'], + 2955 => ['MMLMV'], + 2956 => ['MMLMVI'], + 2957 => ['MMLMVII'], + 2958 => ['MMLMVIII'], + 2959 => ['MMLMIX'], + 2960 => ['MMLMX'], + 2961 => ['MMLMXI'], + 2962 => ['MMLMXII'], + 2963 => ['MMLMXIII'], + 2964 => ['MMLMXIV'], + 2965 => ['MMLMXV'], + 2966 => ['MMLMXVI'], + 2967 => ['MMLMXVII'], + 2968 => ['MMLMXVIII'], + 2969 => ['MMLMXIX'], + 2970 => ['MMLMXX'], + 2971 => ['MMLMXXI'], + 2972 => ['MMLMXXII'], + 2973 => ['MMLMXXIII'], + 2974 => ['MMLMXXIV'], + 2975 => ['MMLMXXV'], + 2976 => ['MMLMXXVI'], + 2977 => ['MMLMXXVII'], + 2978 => ['MMLMXXVIII'], + 2979 => ['MMLMXXIX'], + 2980 => ['MMLMXXX'], + 2981 => ['MMLMXXXI'], + 2982 => ['MMLMXXXII'], + 2983 => ['MMLMXXXIII'], + 2984 => ['MMLMXXXIV'], + 2985 => ['MMLMXXXV'], + 2986 => ['MMLMXXXVI'], + 2987 => ['MMLMXXXVII'], + 2988 => ['MMLMXXXVIII'], + 2989 => ['MMLMXXXIX'], + 2990 => ['MMLMXL', 'MMXM'], + 2991 => ['MMLMXLI', 'MMXMI'], + 2992 => ['MMLMXLII', 'MMXMII'], + 2993 => ['MMLMXLIII', 'MMXMIII'], + 2994 => ['MMLMXLIV', 'MMXMIV'], + 2995 => ['MMLMVL', 'MMXMV', 'MMVM'], + 2996 => ['MMLMVLI', 'MMXMVI', 'MMVMI'], + 2997 => ['MMLMVLII', 'MMXMVII', 'MMVMII'], + 2998 => ['MMLMVLIII', 'MMXMVIII', 'MMVMIII'], + 2999 => ['MMLMVLIV', 'MMXMIX', 'MMVMIV', 'MMIM'], + 3045 => ['MMMVL'], + 3046 => ['MMMVLI'], + 3047 => ['MMMVLII'], + 3048 => ['MMMVLIII'], + 3049 => ['MMMVLIV', 'MMMIL'], + 3095 => ['MMMVC'], + 3096 => ['MMMVCI'], + 3097 => ['MMMVCII'], + 3098 => ['MMMVCIII'], + 3099 => ['MMMVCIV', 'MMMIC'], + 3145 => ['MMMCVL'], + 3146 => ['MMMCVLI'], + 3147 => ['MMMCVLII'], + 3148 => ['MMMCVLIII'], + 3149 => ['MMMCVLIV', 'MMMCIL'], + 3195 => ['MMMCVC'], + 3196 => ['MMMCVCI'], + 3197 => ['MMMCVCII'], + 3198 => ['MMMCVCIII'], + 3199 => ['MMMCVCIV', 'MMMCIC'], + 3245 => ['MMMCCVL'], + 3246 => ['MMMCCVLI'], + 3247 => ['MMMCCVLII'], + 3248 => ['MMMCCVLIII'], + 3249 => ['MMMCCVLIV', 'MMMCCIL'], + 3295 => ['MMMCCVC'], + 3296 => ['MMMCCVCI'], + 3297 => ['MMMCCVCII'], + 3298 => ['MMMCCVCIII'], + 3299 => ['MMMCCVCIV', 'MMMCCIC'], + 3345 => ['MMMCCCVL'], + 3346 => ['MMMCCCVLI'], + 3347 => ['MMMCCCVLII'], + 3348 => ['MMMCCCVLIII'], + 3349 => ['MMMCCCVLIV', 'MMMCCCIL'], + 3395 => ['MMMCCCVC'], + 3396 => ['MMMCCCVCI'], + 3397 => ['MMMCCCVCII'], + 3398 => ['MMMCCCVCIII'], + 3399 => ['MMMCCCVCIV', 'MMMCCCIC'], + 3445 => ['MMMCDVL'], + 3446 => ['MMMCDVLI'], + 3447 => ['MMMCDVLII'], + 3448 => ['MMMCDVLIII'], + 3449 => ['MMMCDVLIV', 'MMMCDIL'], + 3450 => ['MMMLD'], + 3451 => ['MMMLDI'], + 3452 => ['MMMLDII'], + 3453 => ['MMMLDIII'], + 3454 => ['MMMLDIV'], + 3455 => ['MMMLDV'], + 3456 => ['MMMLDVI'], + 3457 => ['MMMLDVII'], + 3458 => ['MMMLDVIII'], + 3459 => ['MMMLDIX'], + 3460 => ['MMMLDX'], + 3461 => ['MMMLDXI'], + 3462 => ['MMMLDXII'], + 3463 => ['MMMLDXIII'], + 3464 => ['MMMLDXIV'], + 3465 => ['MMMLDXV'], + 3466 => ['MMMLDXVI'], + 3467 => ['MMMLDXVII'], + 3468 => ['MMMLDXVIII'], + 3469 => ['MMMLDXIX'], + 3470 => ['MMMLDXX'], + 3471 => ['MMMLDXXI'], + 3472 => ['MMMLDXXII'], + 3473 => ['MMMLDXXIII'], + 3474 => ['MMMLDXXIV'], + 3475 => ['MMMLDXXV'], + 3476 => ['MMMLDXXVI'], + 3477 => ['MMMLDXXVII'], + 3478 => ['MMMLDXXVIII'], + 3479 => ['MMMLDXXIX'], + 3480 => ['MMMLDXXX'], + 3481 => ['MMMLDXXXI'], + 3482 => ['MMMLDXXXII'], + 3483 => ['MMMLDXXXIII'], + 3484 => ['MMMLDXXXIV'], + 3485 => ['MMMLDXXXV'], + 3486 => ['MMMLDXXXVI'], + 3487 => ['MMMLDXXXVII'], + 3488 => ['MMMLDXXXVIII'], + 3489 => ['MMMLDXXXIX'], + 3490 => ['MMMLDXL', 'MMMXD'], + 3491 => ['MMMLDXLI', 'MMMXDI'], + 3492 => ['MMMLDXLII', 'MMMXDII'], + 3493 => ['MMMLDXLIII', 'MMMXDIII'], + 3494 => ['MMMLDXLIV', 'MMMXDIV'], + 3495 => ['MMMLDVL', 'MMMXDV', 'MMMVD'], + 3496 => ['MMMLDVLI', 'MMMXDVI', 'MMMVDI'], + 3497 => ['MMMLDVLII', 'MMMXDVII', 'MMMVDII'], + 3498 => ['MMMLDVLIII', 'MMMXDVIII', 'MMMVDIII'], + 3499 => ['MMMLDVLIV', 'MMMXDIX', 'MMMVDIV', 'MMMID'], + 3545 => ['MMMDVL'], + 3546 => ['MMMDVLI'], + 3547 => ['MMMDVLII'], + 3548 => ['MMMDVLIII'], + 3549 => ['MMMDVLIV', 'MMMDIL'], + 3595 => ['MMMDVC'], + 3596 => ['MMMDVCI'], + 3597 => ['MMMDVCII'], + 3598 => ['MMMDVCIII'], + 3599 => ['MMMDVCIV', 'MMMDIC'], + 3645 => ['MMMDCVL'], + 3646 => ['MMMDCVLI'], + 3647 => ['MMMDCVLII'], + 3648 => ['MMMDCVLIII'], + 3649 => ['MMMDCVLIV', 'MMMDCIL'], + 3695 => ['MMMDCVC'], + 3696 => ['MMMDCVCI'], + 3697 => ['MMMDCVCII'], + 3698 => ['MMMDCVCIII'], + 3699 => ['MMMDCVCIV', 'MMMDCIC'], + 3745 => ['MMMDCCVL'], + 3746 => ['MMMDCCVLI'], + 3747 => ['MMMDCCVLII'], + 3748 => ['MMMDCCVLIII'], + 3749 => ['MMMDCCVLIV', 'MMMDCCIL'], + 3795 => ['MMMDCCVC'], + 3796 => ['MMMDCCVCI'], + 3797 => ['MMMDCCVCII'], + 3798 => ['MMMDCCVCIII'], + 3799 => ['MMMDCCVCIV', 'MMMDCCIC'], + 3845 => ['MMMDCCCVL'], + 3846 => ['MMMDCCCVLI'], + 3847 => ['MMMDCCCVLII'], + 3848 => ['MMMDCCCVLIII'], + 3849 => ['MMMDCCCVLIV', 'MMMDCCCIL'], + 3895 => ['MMMDCCCVC'], + 3896 => ['MMMDCCCVCI'], + 3897 => ['MMMDCCCVCII'], + 3898 => ['MMMDCCCVCIII'], + 3899 => ['MMMDCCCVCIV', 'MMMDCCCIC'], + 3945 => ['MMMCMVL'], + 3946 => ['MMMCMVLI'], + 3947 => ['MMMCMVLII'], + 3948 => ['MMMCMVLIII'], + 3949 => ['MMMCMVLIV', 'MMMCMIL'], + 3950 => ['MMMLM'], + 3951 => ['MMMLMI'], + 3952 => ['MMMLMII'], + 3953 => ['MMMLMIII'], + 3954 => ['MMMLMIV'], + 3955 => ['MMMLMV'], + 3956 => ['MMMLMVI'], + 3957 => ['MMMLMVII'], + 3958 => ['MMMLMVIII'], + 3959 => ['MMMLMIX'], + 3960 => ['MMMLMX'], + 3961 => ['MMMLMXI'], + 3962 => ['MMMLMXII'], + 3963 => ['MMMLMXIII'], + 3964 => ['MMMLMXIV'], + 3965 => ['MMMLMXV'], + 3966 => ['MMMLMXVI'], + 3967 => ['MMMLMXVII'], + 3968 => ['MMMLMXVIII'], + 3969 => ['MMMLMXIX'], + 3970 => ['MMMLMXX'], + 3971 => ['MMMLMXXI'], + 3972 => ['MMMLMXXII'], + 3973 => ['MMMLMXXIII'], + 3974 => ['MMMLMXXIV'], + 3975 => ['MMMLMXXV'], + 3976 => ['MMMLMXXVI'], + 3977 => ['MMMLMXXVII'], + 3978 => ['MMMLMXXVIII'], + 3979 => ['MMMLMXXIX'], + 3980 => ['MMMLMXXX'], + 3981 => ['MMMLMXXXI'], + 3982 => ['MMMLMXXXII'], + 3983 => ['MMMLMXXXIII'], + 3984 => ['MMMLMXXXIV'], + 3985 => ['MMMLMXXXV'], + 3986 => ['MMMLMXXXVI'], + 3987 => ['MMMLMXXXVII'], + 3988 => ['MMMLMXXXVIII'], + 3989 => ['MMMLMXXXIX'], + 3990 => ['MMMLMXL', 'MMMXM'], + 3991 => ['MMMLMXLI', 'MMMXMI'], + 3992 => ['MMMLMXLII', 'MMMXMII'], + 3993 => ['MMMLMXLIII', 'MMMXMIII'], + 3994 => ['MMMLMXLIV', 'MMMXMIV'], + 3995 => ['MMMLMVL', 'MMMXMV', 'MMMVM'], + 3996 => ['MMMLMVLI', 'MMMXMVI', 'MMMVMI'], + 3997 => ['MMMLMVLII', 'MMMXMVII', 'MMMVMII'], + 3998 => ['MMMLMVLIII', 'MMMXMVIII', 'MMMVMIII'], + 3999 => ['MMMLMVLIV', 'MMMXMIX', 'MMMVMIV', 'MMMIM'], + ]; + + private const THOUSANDS = ['', 'M', 'MM', 'MMM']; + private const HUNDREDS = ['', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM']; + private const TENS = ['', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC']; + private const ONES = ['', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX']; + const MAX_ROMAN_VALUE = 3999; + const MAX_ROMAN_STYLE = 4; + + private static function valueOk(int $aValue, int $style): string + { + $origValue = $aValue; + $m = \intdiv($aValue, 1000); + $aValue %= 1000; + $c = \intdiv($aValue, 100); + $aValue %= 100; + $t = \intdiv($aValue, 10); + $aValue %= 10; + $result = self::THOUSANDS[$m] . self::HUNDREDS[$c] . self::TENS[$t] . self::ONES[$aValue]; + if ($style > 0) { + if (array_key_exists($origValue, self::VALUES)) { + $arr = self::VALUES[$origValue]; + $idx = min($style, count($arr)) - 1; + $result = $arr[$idx]; + } + } + + return $result; + } + + private static function styleOk(int $aValue, int $style): string + { + return ($aValue < 0 || $aValue > self::MAX_ROMAN_VALUE) ? ExcelError::VALUE() : self::valueOk($aValue, $style); + } + + public static function calculateRoman(int $aValue, int $style): string + { + return ($style < 0 || $style > self::MAX_ROMAN_STYLE) ? ExcelError::VALUE() : self::styleOk($aValue, $style); + } + + /** + * ROMAN. + * + * Converts a number to Roman numeral + * + * @param mixed $aValue Number to convert + * Or can be an array of numbers + * @param mixed $style Number indicating one of five possible forms + * Or can be an array of styles + * + * @return array|string Roman numeral, or a string containing an error + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function evaluate($aValue, $style = 0) + { + if (is_array($aValue) || is_array($style)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $aValue, $style); + } + + try { + $aValue = Helpers::validateNumericNullBool($aValue); + if (is_bool($style)) { + $style = $style ? 0 : 4; + } + $style = Helpers::validateNumericNullSubstitution($style, null); + } catch (Exception $e) { + return $e->getMessage(); + } + + return self::calculateRoman((int) $aValue, (int) $style); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Round.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Round.php new file mode 100644 index 0000000..776f7eb --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Round.php @@ -0,0 +1,218 @@ +getMessage(); + } + + return round($number, (int) $precision); + } + + /** + * ROUNDUP. + * + * Rounds a number up to a specified number of decimal places + * + * @param array|float $number Number to round, or can be an array of numbers + * @param array|int $digits Number of digits to which you want to round $number, or can be an array of numbers + * + * @return array|float|string Rounded Number, or a string containing an error + * If an array of numbers is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function up($number, $digits) + { + if (is_array($number) || is_array($digits)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $digits); + } + + try { + $number = Helpers::validateNumericNullBool($number); + $digits = (int) Helpers::validateNumericNullSubstitution($digits, null); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($number == 0.0) { + return 0.0; + } + + if ($number < 0.0) { + return round($number - 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_DOWN); + } + + return round($number + 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_DOWN); + } + + /** + * ROUNDDOWN. + * + * Rounds a number down to a specified number of decimal places + * + * @param array|float $number Number to round, or can be an array of numbers + * @param array|int $digits Number of digits to which you want to round $number, or can be an array of numbers + * + * @return array|float|string Rounded Number, or a string containing an error + * If an array of numbers is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function down($number, $digits) + { + if (is_array($number) || is_array($digits)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $digits); + } + + try { + $number = Helpers::validateNumericNullBool($number); + $digits = (int) Helpers::validateNumericNullSubstitution($digits, null); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($number == 0.0) { + return 0.0; + } + + if ($number < 0.0) { + return round($number + 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_UP); + } + + return round($number - 0.5 * 0.1 ** $digits, $digits, PHP_ROUND_HALF_UP); + } + + /** + * MROUND. + * + * Rounds a number to the nearest multiple of a specified value + * + * @param mixed $number Expect float. Number to round, or can be an array of numbers + * @param mixed $multiple Expect int. Multiple to which you want to round, or can be an array of numbers. + * + * @return array|float|string Rounded Number, or a string containing an error + * If an array of numbers is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function multiple($number, $multiple) + { + if (is_array($number) || is_array($multiple)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $multiple); + } + + try { + $number = Helpers::validateNumericNullSubstitution($number, 0); + $multiple = Helpers::validateNumericNullSubstitution($multiple, null); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($number == 0 || $multiple == 0) { + return 0; + } + if ((Helpers::returnSign($number)) == (Helpers::returnSign($multiple))) { + $multiplier = 1 / $multiple; + + return round($number * $multiplier) / $multiplier; + } + + return ExcelError::NAN(); + } + + /** + * EVEN. + * + * Returns number rounded up to the nearest even integer. + * You can use this function for processing items that come in twos. For example, + * a packing crate accepts rows of one or two items. The crate is full when + * the number of items, rounded up to the nearest two, matches the crate's + * capacity. + * + * Excel Function: + * EVEN(number) + * + * @param array|float $number Number to round, or can be an array of numbers + * + * @return array|float|string Rounded Number, or a string containing an error + * If an array of numbers is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function even($number) + { + if (is_array($number)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number); + } + + try { + $number = Helpers::validateNumericNullBool($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + return Helpers::getEven($number); + } + + /** + * ODD. + * + * Returns number rounded up to the nearest odd integer. + * + * @param array|float $number Number to round, or can be an array of numbers + * + * @return array|float|string Rounded Number, or a string containing an error + * If an array of numbers is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function odd($number) + { + if (is_array($number)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number); + } + + try { + $number = Helpers::validateNumericNullBool($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + $significance = Helpers::returnSign($number); + if ($significance == 0) { + return 1; + } + + $result = ceil($number / $significance) * $significance; + if ($result == Helpers::getEven($result)) { + $result += $significance; + } + + return $result; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/SeriesSum.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/SeriesSum.php new file mode 100644 index 0000000..ecce359 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/SeriesSum.php @@ -0,0 +1,53 @@ +getMessage(); + } + + return $returnValue; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Sign.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Sign.php new file mode 100644 index 0000000..e40e1f6 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Sign.php @@ -0,0 +1,38 @@ +getMessage(); + } + + return Helpers::returnSign($number); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Sqrt.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Sqrt.php new file mode 100644 index 0000000..bb9f15f --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Sqrt.php @@ -0,0 +1,64 @@ +getMessage(); + } + + return Helpers::numberOrNan(sqrt($number)); + } + + /** + * SQRTPI. + * + * Returns the square root of (number * pi). + * + * @param array|float $number Number, or can be an array of numbers + * + * @return array|float|string Square Root of Number * Pi, or a string containing an error + * If an array of numbers is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function pi($number) + { + if (is_array($number)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number); + } + + try { + $number = Helpers::validateNumericNullSubstitution($number, 0); + Helpers::validateNotNegative($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + return sqrt($number * M_PI); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php new file mode 100644 index 0000000..6d8f472 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Subtotal.php @@ -0,0 +1,135 @@ +getWorksheet()->getRowDimension($row)->getVisible(); + }, + ARRAY_FILTER_USE_KEY + ); + } + + /** + * @param mixed $cellReference + * @param mixed $args + */ + protected static function filterFormulaArgs($cellReference, $args): array + { + return array_filter( + $args, + function ($index) use ($cellReference) { + $explodeArray = explode('.', $index); + $row = $explodeArray[1] ?? ''; + $column = $explodeArray[2] ?? ''; + $retVal = true; + if ($cellReference->getWorksheet()->cellExists($column . $row)) { + //take this cell out if it contains the SUBTOTAL or AGGREGATE functions in a formula + $isFormula = $cellReference->getWorksheet()->getCell($column . $row)->isFormula(); + $cellFormula = !preg_match( + '/^=.*\b(SUBTOTAL|AGGREGATE)\s*\(/i', + $cellReference->getWorksheet()->getCell($column . $row)->getValue() ?? '' + ); + + $retVal = !$isFormula || $cellFormula; + } + + return $retVal; + }, + ARRAY_FILTER_USE_KEY + ); + } + + private const CALL_FUNCTIONS = [ + 1 => [Statistical\Averages::class, 'average'], // 1 and 101 + [Statistical\Counts::class, 'COUNT'], // 2 and 102 + [Statistical\Counts::class, 'COUNTA'], // 3 and 103 + [Statistical\Maximum::class, 'max'], // 4 and 104 + [Statistical\Minimum::class, 'min'], // 5 and 105 + [Operations::class, 'product'], // 6 and 106 + [Statistical\StandardDeviations::class, 'STDEV'], // 7 and 107 + [Statistical\StandardDeviations::class, 'STDEVP'], // 8 and 108 + [Sum::class, 'sumIgnoringStrings'], // 9 and 109 + [Statistical\Variances::class, 'VAR'], // 10 and 110 + [Statistical\Variances::class, 'VARP'], // 111 and 111 + ]; + + /** + * SUBTOTAL. + * + * Returns a subtotal in a list or database. + * + * @param mixed $functionType + * A number 1 to 11 that specifies which function to + * use in calculating subtotals within a range + * list + * Numbers 101 to 111 shadow the functions of 1 to 11 + * but ignore any values in the range that are + * in hidden rows + * @param mixed[] $args A mixed data series of values + * + * @return float|string + */ + public static function evaluate($functionType, ...$args) + { + $cellReference = array_pop($args); + $bArgs = Functions::flattenArrayIndexed($args); + $aArgs = []; + // int keys must come before string keys for PHP 8.0+ + // Otherwise, PHP thinks positional args follow keyword + // in the subsequent call to call_user_func_array. + // Fortunately, order of args is unimportant to Subtotal. + foreach ($bArgs as $key => $value) { + if (is_int($key)) { + $aArgs[$key] = $value; + } + } + foreach ($bArgs as $key => $value) { + if (!is_int($key)) { + $aArgs[$key] = $value; + } + } + + try { + $subtotal = (int) Helpers::validateNumericNullBool($functionType); + } catch (Exception $e) { + return $e->getMessage(); + } + + // Calculate + if ($subtotal > 100) { + $aArgs = self::filterHiddenArgs($cellReference, $aArgs); + $subtotal -= 100; + } + + $aArgs = self::filterFormulaArgs($cellReference, $aArgs); + if (array_key_exists($subtotal, self::CALL_FUNCTIONS)) { + /** @var callable */ + $call = self::CALL_FUNCTIONS[$subtotal]; + + return call_user_func_array($call, $aArgs); + } + + return ExcelError::VALUE(); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Sum.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Sum.php new file mode 100644 index 0000000..1a797c8 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Sum.php @@ -0,0 +1,118 @@ + $arg) { + // Is it a numeric value? + if (is_numeric($arg) || empty($arg)) { + if (is_string($arg)) { + $arg = (int) $arg; + } + $returnValue += $arg; + } elseif (is_bool($arg)) { + $returnValue += (int) $arg; + } elseif (ErrorValue::isError($arg)) { + return $arg; + // ignore non-numerics from cell, but fail as literals (except null) + } elseif ($arg !== null && !Functions::isCellValue($k)) { + return ExcelError::VALUE(); + } + } + + return $returnValue; + } + + /** + * SUMPRODUCT. + * + * Excel Function: + * SUMPRODUCT(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float|string The result, or a string containing an error + */ + public static function product(...$args) + { + $arrayList = $args; + + $wrkArray = Functions::flattenArray(array_shift($arrayList)); + $wrkCellCount = count($wrkArray); + + for ($i = 0; $i < $wrkCellCount; ++$i) { + if ((!is_numeric($wrkArray[$i])) || (is_string($wrkArray[$i]))) { + $wrkArray[$i] = 0; + } + } + + foreach ($arrayList as $matrixData) { + $array2 = Functions::flattenArray($matrixData); + $count = count($array2); + if ($wrkCellCount != $count) { + return ExcelError::VALUE(); + } + + foreach ($array2 as $i => $val) { + if ((!is_numeric($val)) || (is_string($val))) { + $val = 0; + } + $wrkArray[$i] *= $val; + } + } + + return array_sum($wrkArray); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/SumSquares.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/SumSquares.php new file mode 100644 index 0000000..34b397c --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/SumSquares.php @@ -0,0 +1,143 @@ +getMessage(); + } + + return $returnValue; + } + + private static function getCount(array $array1, array $array2): int + { + $count = count($array1); + if ($count !== count($array2)) { + throw new Exception(ExcelError::NA()); + } + + return $count; + } + + /** + * These functions accept only numeric arguments, not even strings which are numeric. + * + * @param mixed $item + */ + private static function numericNotString($item): bool + { + return is_numeric($item) && !is_string($item); + } + + /** + * SUMX2MY2. + * + * @param mixed[] $matrixData1 Matrix #1 + * @param mixed[] $matrixData2 Matrix #2 + * + * @return float|string + */ + public static function sumXSquaredMinusYSquared($matrixData1, $matrixData2) + { + try { + $array1 = Functions::flattenArray($matrixData1); + $array2 = Functions::flattenArray($matrixData2); + $count = self::getCount($array1, $array2); + + $result = 0; + for ($i = 0; $i < $count; ++$i) { + if (self::numericNotString($array1[$i]) && self::numericNotString($array2[$i])) { + $result += ($array1[$i] * $array1[$i]) - ($array2[$i] * $array2[$i]); + } + } + } catch (Exception $e) { + return $e->getMessage(); + } + + return $result; + } + + /** + * SUMX2PY2. + * + * @param mixed[] $matrixData1 Matrix #1 + * @param mixed[] $matrixData2 Matrix #2 + * + * @return float|string + */ + public static function sumXSquaredPlusYSquared($matrixData1, $matrixData2) + { + try { + $array1 = Functions::flattenArray($matrixData1); + $array2 = Functions::flattenArray($matrixData2); + $count = self::getCount($array1, $array2); + + $result = 0; + for ($i = 0; $i < $count; ++$i) { + if (self::numericNotString($array1[$i]) && self::numericNotString($array2[$i])) { + $result += ($array1[$i] * $array1[$i]) + ($array2[$i] * $array2[$i]); + } + } + } catch (Exception $e) { + return $e->getMessage(); + } + + return $result; + } + + /** + * SUMXMY2. + * + * @param mixed[] $matrixData1 Matrix #1 + * @param mixed[] $matrixData2 Matrix #2 + * + * @return float|string + */ + public static function sumXMinusYSquared($matrixData1, $matrixData2) + { + try { + $array1 = Functions::flattenArray($matrixData1); + $array2 = Functions::flattenArray($matrixData2); + $count = self::getCount($array1, $array2); + + $result = 0; + for ($i = 0; $i < $count; ++$i) { + if (self::numericNotString($array1[$i]) && self::numericNotString($array2[$i])) { + $result += ($array1[$i] - $array2[$i]) * ($array1[$i] - $array2[$i]); + } + } + } catch (Exception $e) { + return $e->getMessage(); + } + + return $result; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Trig/Cosecant.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Trig/Cosecant.php new file mode 100644 index 0000000..845b6c1 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Trig/Cosecant.php @@ -0,0 +1,64 @@ +getMessage(); + } + + return Helpers::verySmallDenominator(1.0, sin($angle)); + } + + /** + * CSCH. + * + * Returns the hyperbolic cosecant of an angle. + * + * @param array|float $angle Number, or can be an array of numbers + * + * @return array|float|string The hyperbolic cosecant of the angle + * If an array of numbers is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function csch($angle) + { + if (is_array($angle)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $angle); + } + + try { + $angle = Helpers::validateNumericNullBool($angle); + } catch (Exception $e) { + return $e->getMessage(); + } + + return Helpers::verySmallDenominator(1.0, sinh($angle)); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Trig/Cosine.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Trig/Cosine.php new file mode 100644 index 0000000..c06f04d --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Trig/Cosine.php @@ -0,0 +1,116 @@ +getMessage(); + } + + return cos($number); + } + + /** + * COSH. + * + * Returns the result of builtin function cosh after validating args. + * + * @param mixed $number Should be numeric, or can be an array of numbers + * + * @return array|float|string hyperbolic cosine + * If an array of numbers is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function cosh($number) + { + if (is_array($number)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number); + } + + try { + $number = Helpers::validateNumericNullBool($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + return cosh($number); + } + + /** + * ACOS. + * + * Returns the arccosine of a number. + * + * @param array|float $number Number, or can be an array of numbers + * + * @return array|float|string The arccosine of the number + * If an array of numbers is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function acos($number) + { + if (is_array($number)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number); + } + + try { + $number = Helpers::validateNumericNullBool($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + return Helpers::numberOrNan(acos($number)); + } + + /** + * ACOSH. + * + * Returns the arc inverse hyperbolic cosine of a number. + * + * @param array|float $number Number, or can be an array of numbers + * + * @return array|float|string The inverse hyperbolic cosine of the number, or an error string + * If an array of numbers is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function acosh($number) + { + if (is_array($number)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number); + } + + try { + $number = Helpers::validateNumericNullBool($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + return Helpers::numberOrNan(acosh($number)); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Trig/Cotangent.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Trig/Cotangent.php new file mode 100644 index 0000000..eeedef9 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Trig/Cotangent.php @@ -0,0 +1,118 @@ +getMessage(); + } + + return Helpers::verySmallDenominator(cos($angle), sin($angle)); + } + + /** + * COTH. + * + * Returns the hyperbolic cotangent of an angle. + * + * @param array|float $angle Number, or can be an array of numbers + * + * @return array|float|string The hyperbolic cotangent of the angle + * If an array of numbers is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function coth($angle) + { + if (is_array($angle)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $angle); + } + + try { + $angle = Helpers::validateNumericNullBool($angle); + } catch (Exception $e) { + return $e->getMessage(); + } + + return Helpers::verySmallDenominator(1.0, tanh($angle)); + } + + /** + * ACOT. + * + * Returns the arccotangent of a number. + * + * @param array|float $number Number, or can be an array of numbers + * + * @return array|float|string The arccotangent of the number + * If an array of numbers is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function acot($number) + { + if (is_array($number)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number); + } + + try { + $number = Helpers::validateNumericNullBool($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + return (M_PI / 2) - atan($number); + } + + /** + * ACOTH. + * + * Returns the hyperbolic arccotangent of a number. + * + * @param array|float $number Number, or can be an array of numbers + * + * @return array|float|string The hyperbolic arccotangent of the number + * If an array of numbers is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function acoth($number) + { + if (is_array($number)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number); + } + + try { + $number = Helpers::validateNumericNullBool($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + $result = ($number === 1) ? NAN : (log(($number + 1) / ($number - 1)) / 2); + + return Helpers::numberOrNan($result); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Trig/Secant.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Trig/Secant.php new file mode 100644 index 0000000..2d26e5d --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Trig/Secant.php @@ -0,0 +1,64 @@ +getMessage(); + } + + return Helpers::verySmallDenominator(1.0, cos($angle)); + } + + /** + * SECH. + * + * Returns the hyperbolic secant of an angle. + * + * @param array|float $angle Number, or can be an array of numbers + * + * @return array|float|string The hyperbolic secant of the angle + * If an array of numbers is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function sech($angle) + { + if (is_array($angle)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $angle); + } + + try { + $angle = Helpers::validateNumericNullBool($angle); + } catch (Exception $e) { + return $e->getMessage(); + } + + return Helpers::verySmallDenominator(1.0, cosh($angle)); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Trig/Sine.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Trig/Sine.php new file mode 100644 index 0000000..6af568c --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Trig/Sine.php @@ -0,0 +1,116 @@ +getMessage(); + } + + return sin($angle); + } + + /** + * SINH. + * + * Returns the result of builtin function sinh after validating args. + * + * @param mixed $angle Should be numeric, or can be an array of numbers + * + * @return array|float|string hyperbolic sine + * If an array of numbers is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function sinh($angle) + { + if (is_array($angle)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $angle); + } + + try { + $angle = Helpers::validateNumericNullBool($angle); + } catch (Exception $e) { + return $e->getMessage(); + } + + return sinh($angle); + } + + /** + * ASIN. + * + * Returns the arcsine of a number. + * + * @param array|float $number Number, or can be an array of numbers + * + * @return array|float|string The arcsine of the number + * If an array of numbers is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function asin($number) + { + if (is_array($number)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number); + } + + try { + $number = Helpers::validateNumericNullBool($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + return Helpers::numberOrNan(asin($number)); + } + + /** + * ASINH. + * + * Returns the inverse hyperbolic sine of a number. + * + * @param array|float $number Number, or can be an array of numbers + * + * @return array|float|string The inverse hyperbolic sine of the number + * If an array of numbers is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function asinh($number) + { + if (is_array($number)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number); + } + + try { + $number = Helpers::validateNumericNullBool($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + return Helpers::numberOrNan(asinh($number)); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Trig/Tangent.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Trig/Tangent.php new file mode 100644 index 0000000..9e77021 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Trig/Tangent.php @@ -0,0 +1,161 @@ +getMessage(); + } + + return Helpers::verySmallDenominator(sin($angle), cos($angle)); + } + + /** + * TANH. + * + * Returns the result of builtin function sinh after validating args. + * + * @param mixed $angle Should be numeric, or can be an array of numbers + * + * @return array|float|string hyperbolic tangent + * If an array of numbers is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function tanh($angle) + { + if (is_array($angle)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $angle); + } + + try { + $angle = Helpers::validateNumericNullBool($angle); + } catch (Exception $e) { + return $e->getMessage(); + } + + return tanh($angle); + } + + /** + * ATAN. + * + * Returns the arctangent of a number. + * + * @param array|float $number Number, or can be an array of numbers + * + * @return array|float|string The arctangent of the number + * If an array of numbers is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function atan($number) + { + if (is_array($number)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number); + } + + try { + $number = Helpers::validateNumericNullBool($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + return Helpers::numberOrNan(atan($number)); + } + + /** + * ATANH. + * + * Returns the inverse hyperbolic tangent of a number. + * + * @param array|float $number Number, or can be an array of numbers + * + * @return array|float|string The inverse hyperbolic tangent of the number + * If an array of numbers is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function atanh($number) + { + if (is_array($number)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number); + } + + try { + $number = Helpers::validateNumericNullBool($number); + } catch (Exception $e) { + return $e->getMessage(); + } + + return Helpers::numberOrNan(atanh($number)); + } + + /** + * ATAN2. + * + * This function calculates the arc tangent of the two variables x and y. It is similar to + * calculating the arc tangent of y ÷ x, except that the signs of both arguments are used + * to determine the quadrant of the result. + * The arctangent is the angle from the x-axis to a line containing the origin (0, 0) and a + * point with coordinates (xCoordinate, yCoordinate). The angle is given in radians between + * -pi and pi, excluding -pi. + * + * Note that the Excel ATAN2() function accepts its arguments in the reverse order to the standard + * PHP atan2() function, so we need to reverse them here before calling the PHP atan() function. + * + * Excel Function: + * ATAN2(xCoordinate,yCoordinate) + * + * @param mixed $xCoordinate should be float, the x-coordinate of the point, or can be an array of numbers + * @param mixed $yCoordinate should be float, the y-coordinate of the point, or can be an array of numbers + * + * @return array|float|string + * The inverse tangent of the specified x- and y-coordinates, or a string containing an error + * If an array of numbers is passed as one of the arguments, then the returned result will also be an array + * with the same dimensions + */ + public static function atan2($xCoordinate, $yCoordinate) + { + if (is_array($xCoordinate) || is_array($yCoordinate)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $xCoordinate, $yCoordinate); + } + + try { + $xCoordinate = Helpers::validateNumericNullBool($xCoordinate); + $yCoordinate = Helpers::validateNumericNullBool($yCoordinate); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (($xCoordinate == 0) && ($yCoordinate == 0)) { + return ExcelError::DIV0(); + } + + return atan2($yCoordinate, $xCoordinate); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Trunc.php b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Trunc.php new file mode 100644 index 0000000..943e209 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/MathTrig/Trunc.php @@ -0,0 +1,50 @@ +getMessage(); + } + + $digits = floor($digits); + + // Truncate + $adjust = 10 ** $digits; + + if (($digits > 0) && (rtrim((string) (int) ((abs($value) - abs((int) $value)) * $adjust), '0') < $adjust / 10)) { + return $value; + } + + return ((int) ($value * $adjust)) / $adjust; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Statistical.php b/PhpOffice/PhpSpreadsheet/Calculation/Statistical.php old mode 100755 new mode 100644 index 1f9c766..6758f47 --- a/PhpOffice/PhpSpreadsheet/Calculation/Statistical.php +++ b/PhpOffice/PhpSpreadsheet/Calculation/Statistical.php @@ -2,560 +2,29 @@ namespace PhpOffice\PhpSpreadsheet\Calculation; -use PhpOffice\PhpSpreadsheet\Shared\Trend\Trend; +use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Averages; +use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Conditional; +use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Confidence; +use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Counts; +use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Maximum; +use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Minimum; +use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Permutations; +use PhpOffice\PhpSpreadsheet\Calculation\Statistical\StandardDeviations; +use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Trends; +use PhpOffice\PhpSpreadsheet\Calculation\Statistical\Variances; +/** + * @deprecated 1.18.0 + * + * @codeCoverageIgnore + */ class Statistical { const LOG_GAMMA_X_MAX_VALUE = 2.55e305; - const XMININ = 2.23e-308; const EPS = 2.22e-16; const MAX_VALUE = 1.2e308; - const MAX_ITERATIONS = 256; const SQRT2PI = 2.5066282746310005024157652848110452530069867406099; - private static function checkTrendArrays(&$array1, &$array2) - { - if (!is_array($array1)) { - $array1 = [$array1]; - } - if (!is_array($array2)) { - $array2 = [$array2]; - } - - $array1 = Functions::flattenArray($array1); - $array2 = Functions::flattenArray($array2); - foreach ($array1 as $key => $value) { - if ((is_bool($value)) || (is_string($value)) || ($value === null)) { - unset($array1[$key], $array2[$key]); - } - } - foreach ($array2 as $key => $value) { - if ((is_bool($value)) || (is_string($value)) || ($value === null)) { - unset($array1[$key], $array2[$key]); - } - } - $array1 = array_merge($array1); - $array2 = array_merge($array2); - - return true; - } - - /** - * Incomplete beta function. - * - * @author Jaco van Kooten - * @author Paul Meagher - * - * The computation is based on formulas from Numerical Recipes, Chapter 6.4 (W.H. Press et al, 1992). - * - * @param mixed $x require 0<=x<=1 - * @param mixed $p require p>0 - * @param mixed $q require q>0 - * - * @return float 0 if x<0, p<=0, q<=0 or p+q>2.55E305 and 1 if x>1 to avoid errors and over/underflow - */ - private static function incompleteBeta($x, $p, $q) - { - if ($x <= 0.0) { - return 0.0; - } elseif ($x >= 1.0) { - return 1.0; - } elseif (($p <= 0.0) || ($q <= 0.0) || (($p + $q) > self::LOG_GAMMA_X_MAX_VALUE)) { - return 0.0; - } - $beta_gam = exp((0 - self::logBeta($p, $q)) + $p * log($x) + $q * log(1.0 - $x)); - if ($x < ($p + 1.0) / ($p + $q + 2.0)) { - return $beta_gam * self::betaFraction($x, $p, $q) / $p; - } - - return 1.0 - ($beta_gam * self::betaFraction(1 - $x, $q, $p) / $q); - } - - // Function cache for logBeta function - private static $logBetaCacheP = 0.0; - - private static $logBetaCacheQ = 0.0; - - private static $logBetaCacheResult = 0.0; - - /** - * The natural logarithm of the beta function. - * - * @param mixed $p require p>0 - * @param mixed $q require q>0 - * - * @return float 0 if p<=0, q<=0 or p+q>2.55E305 to avoid errors and over/underflow - * - * @author Jaco van Kooten - */ - private static function logBeta($p, $q) - { - if ($p != self::$logBetaCacheP || $q != self::$logBetaCacheQ) { - self::$logBetaCacheP = $p; - self::$logBetaCacheQ = $q; - if (($p <= 0.0) || ($q <= 0.0) || (($p + $q) > self::LOG_GAMMA_X_MAX_VALUE)) { - self::$logBetaCacheResult = 0.0; - } else { - self::$logBetaCacheResult = self::logGamma($p) + self::logGamma($q) - self::logGamma($p + $q); - } - } - - return self::$logBetaCacheResult; - } - - /** - * Evaluates of continued fraction part of incomplete beta function. - * Based on an idea from Numerical Recipes (W.H. Press et al, 1992). - * - * @author Jaco van Kooten - * - * @param mixed $x - * @param mixed $p - * @param mixed $q - * - * @return float - */ - private static function betaFraction($x, $p, $q) - { - $c = 1.0; - $sum_pq = $p + $q; - $p_plus = $p + 1.0; - $p_minus = $p - 1.0; - $h = 1.0 - $sum_pq * $x / $p_plus; - if (abs($h) < self::XMININ) { - $h = self::XMININ; - } - $h = 1.0 / $h; - $frac = $h; - $m = 1; - $delta = 0.0; - while ($m <= self::MAX_ITERATIONS && abs($delta - 1.0) > Functions::PRECISION) { - $m2 = 2 * $m; - // even index for d - $d = $m * ($q - $m) * $x / (($p_minus + $m2) * ($p + $m2)); - $h = 1.0 + $d * $h; - if (abs($h) < self::XMININ) { - $h = self::XMININ; - } - $h = 1.0 / $h; - $c = 1.0 + $d / $c; - if (abs($c) < self::XMININ) { - $c = self::XMININ; - } - $frac *= $h * $c; - // odd index for d - $d = -($p + $m) * ($sum_pq + $m) * $x / (($p + $m2) * ($p_plus + $m2)); - $h = 1.0 + $d * $h; - if (abs($h) < self::XMININ) { - $h = self::XMININ; - } - $h = 1.0 / $h; - $c = 1.0 + $d / $c; - if (abs($c) < self::XMININ) { - $c = self::XMININ; - } - $delta = $h * $c; - $frac *= $delta; - ++$m; - } - - return $frac; - } - - /** - * logGamma function. - * - * @version 1.1 - * - * @author Jaco van Kooten - * - * Original author was Jaco van Kooten. Ported to PHP by Paul Meagher. - * - * The natural logarithm of the gamma function.
- * Based on public domain NETLIB (Fortran) code by W. J. Cody and L. Stoltz
- * Applied Mathematics Division
- * Argonne National Laboratory
- * Argonne, IL 60439
- *

- * References: - *

    - *
  1. W. J. Cody and K. E. Hillstrom, 'Chebyshev Approximations for the Natural - * Logarithm of the Gamma Function,' Math. Comp. 21, 1967, pp. 198-203.
  2. - *
  3. K. E. Hillstrom, ANL/AMD Program ANLC366S, DGAMMA/DLGAMA, May, 1969.
  4. - *
  5. Hart, Et. Al., Computer Approximations, Wiley and sons, New York, 1968.
  6. - *
- *

- *

- * From the original documentation: - *

- *

- * This routine calculates the LOG(GAMMA) function for a positive real argument X. - * Computation is based on an algorithm outlined in references 1 and 2. - * The program uses rational functions that theoretically approximate LOG(GAMMA) - * to at least 18 significant decimal digits. The approximation for X > 12 is from - * reference 3, while approximations for X < 12.0 are similar to those in reference - * 1, but are unpublished. The accuracy achieved depends on the arithmetic system, - * the compiler, the intrinsic functions, and proper selection of the - * machine-dependent constants. - *

- *

- * Error returns:
- * The program returns the value XINF for X .LE. 0.0 or when overflow would occur. - * The computation is believed to be free of underflow and overflow. - *

- * - * @return float MAX_VALUE for x < 0.0 or when overflow would occur, i.e. x > 2.55E305 - */ - - // Function cache for logGamma - private static $logGammaCacheResult = 0.0; - - private static $logGammaCacheX = 0.0; - - private static function logGamma($x) - { - // Log Gamma related constants - static $lg_d1 = -0.5772156649015328605195174; - static $lg_d2 = 0.4227843350984671393993777; - static $lg_d4 = 1.791759469228055000094023; - - static $lg_p1 = [ - 4.945235359296727046734888, - 201.8112620856775083915565, - 2290.838373831346393026739, - 11319.67205903380828685045, - 28557.24635671635335736389, - 38484.96228443793359990269, - 26377.48787624195437963534, - 7225.813979700288197698961, - ]; - static $lg_p2 = [ - 4.974607845568932035012064, - 542.4138599891070494101986, - 15506.93864978364947665077, - 184793.2904445632425417223, - 1088204.76946882876749847, - 3338152.967987029735917223, - 5106661.678927352456275255, - 3074109.054850539556250927, - ]; - static $lg_p4 = [ - 14745.02166059939948905062, - 2426813.369486704502836312, - 121475557.4045093227939592, - 2663432449.630976949898078, - 29403789566.34553899906876, - 170266573776.5398868392998, - 492612579337.743088758812, - 560625185622.3951465078242, - ]; - static $lg_q1 = [ - 67.48212550303777196073036, - 1113.332393857199323513008, - 7738.757056935398733233834, - 27639.87074403340708898585, - 54993.10206226157329794414, - 61611.22180066002127833352, - 36351.27591501940507276287, - 8785.536302431013170870835, - ]; - static $lg_q2 = [ - 183.0328399370592604055942, - 7765.049321445005871323047, - 133190.3827966074194402448, - 1136705.821321969608938755, - 5267964.117437946917577538, - 13467014.54311101692290052, - 17827365.30353274213975932, - 9533095.591844353613395747, - ]; - static $lg_q4 = [ - 2690.530175870899333379843, - 639388.5654300092398984238, - 41355999.30241388052042842, - 1120872109.61614794137657, - 14886137286.78813811542398, - 101680358627.2438228077304, - 341747634550.7377132798597, - 446315818741.9713286462081, - ]; - static $lg_c = [ - -0.001910444077728, - 8.4171387781295e-4, - -5.952379913043012e-4, - 7.93650793500350248e-4, - -0.002777777777777681622553, - 0.08333333333333333331554247, - 0.0057083835261, - ]; - - // Rough estimate of the fourth root of logGamma_xBig - static $lg_frtbig = 2.25e76; - static $pnt68 = 0.6796875; - - if ($x == self::$logGammaCacheX) { - return self::$logGammaCacheResult; - } - $y = $x; - if ($y > 0.0 && $y <= self::LOG_GAMMA_X_MAX_VALUE) { - if ($y <= self::EPS) { - $res = -log($y); - } elseif ($y <= 1.5) { - // --------------------- - // EPS .LT. X .LE. 1.5 - // --------------------- - if ($y < $pnt68) { - $corr = -log($y); - $xm1 = $y; - } else { - $corr = 0.0; - $xm1 = $y - 1.0; - } - if ($y <= 0.5 || $y >= $pnt68) { - $xden = 1.0; - $xnum = 0.0; - for ($i = 0; $i < 8; ++$i) { - $xnum = $xnum * $xm1 + $lg_p1[$i]; - $xden = $xden * $xm1 + $lg_q1[$i]; - } - $res = $corr + $xm1 * ($lg_d1 + $xm1 * ($xnum / $xden)); - } else { - $xm2 = $y - 1.0; - $xden = 1.0; - $xnum = 0.0; - for ($i = 0; $i < 8; ++$i) { - $xnum = $xnum * $xm2 + $lg_p2[$i]; - $xden = $xden * $xm2 + $lg_q2[$i]; - } - $res = $corr + $xm2 * ($lg_d2 + $xm2 * ($xnum / $xden)); - } - } elseif ($y <= 4.0) { - // --------------------- - // 1.5 .LT. X .LE. 4.0 - // --------------------- - $xm2 = $y - 2.0; - $xden = 1.0; - $xnum = 0.0; - for ($i = 0; $i < 8; ++$i) { - $xnum = $xnum * $xm2 + $lg_p2[$i]; - $xden = $xden * $xm2 + $lg_q2[$i]; - } - $res = $xm2 * ($lg_d2 + $xm2 * ($xnum / $xden)); - } elseif ($y <= 12.0) { - // ---------------------- - // 4.0 .LT. X .LE. 12.0 - // ---------------------- - $xm4 = $y - 4.0; - $xden = -1.0; - $xnum = 0.0; - for ($i = 0; $i < 8; ++$i) { - $xnum = $xnum * $xm4 + $lg_p4[$i]; - $xden = $xden * $xm4 + $lg_q4[$i]; - } - $res = $lg_d4 + $xm4 * ($xnum / $xden); - } else { - // --------------------------------- - // Evaluate for argument .GE. 12.0 - // --------------------------------- - $res = 0.0; - if ($y <= $lg_frtbig) { - $res = $lg_c[6]; - $ysq = $y * $y; - for ($i = 0; $i < 6; ++$i) { - $res = $res / $ysq + $lg_c[$i]; - } - $res /= $y; - $corr = log($y); - $res = $res + log(self::SQRT2PI) - 0.5 * $corr; - $res += $y * ($corr - 1.0); - } - } - } else { - // -------------------------- - // Return for bad arguments - // -------------------------- - $res = self::MAX_VALUE; - } - // ------------------------------ - // Final adjustments and return - // ------------------------------ - self::$logGammaCacheX = $x; - self::$logGammaCacheResult = $res; - - return $res; - } - - // - // Private implementation of the incomplete Gamma function - // - private static function incompleteGamma($a, $x) - { - static $max = 32; - $summer = 0; - for ($n = 0; $n <= $max; ++$n) { - $divisor = $a; - for ($i = 1; $i <= $n; ++$i) { - $divisor *= ($a + $i); - } - $summer += (pow($x, $n) / $divisor); - } - - return pow($x, $a) * exp(0 - $x) * $summer; - } - - // - // Private implementation of the Gamma function - // - private static function gamma($data) - { - if ($data == 0.0) { - return 0; - } - - static $p0 = 1.000000000190015; - static $p = [ - 1 => 76.18009172947146, - 2 => -86.50532032941677, - 3 => 24.01409824083091, - 4 => -1.231739572450155, - 5 => 1.208650973866179e-3, - 6 => -5.395239384953e-6, - ]; - - $y = $x = $data; - $tmp = $x + 5.5; - $tmp -= ($x + 0.5) * log($tmp); - - $summer = $p0; - for ($j = 1; $j <= 6; ++$j) { - $summer += ($p[$j] / ++$y); - } - - return exp(0 - $tmp + log(self::SQRT2PI * $summer / $x)); - } - - /* - * inverse_ncdf.php - * ------------------- - * begin : Friday, January 16, 2004 - * copyright : (C) 2004 Michael Nickerson - * email : nickersonm@yahoo.com - * - */ - private static function inverseNcdf($p) - { - // Inverse ncdf approximation by Peter J. Acklam, implementation adapted to - // PHP by Michael Nickerson, using Dr. Thomas Ziegler's C implementation as - // a guide. http://home.online.no/~pjacklam/notes/invnorm/index.html - // I have not checked the accuracy of this implementation. Be aware that PHP - // will truncate the coeficcients to 14 digits. - - // You have permission to use and distribute this function freely for - // whatever purpose you want, but please show common courtesy and give credit - // where credit is due. - - // Input paramater is $p - probability - where 0 < p < 1. - - // Coefficients in rational approximations - static $a = [ - 1 => -3.969683028665376e+01, - 2 => 2.209460984245205e+02, - 3 => -2.759285104469687e+02, - 4 => 1.383577518672690e+02, - 5 => -3.066479806614716e+01, - 6 => 2.506628277459239e+00, - ]; - - static $b = [ - 1 => -5.447609879822406e+01, - 2 => 1.615858368580409e+02, - 3 => -1.556989798598866e+02, - 4 => 6.680131188771972e+01, - 5 => -1.328068155288572e+01, - ]; - - static $c = [ - 1 => -7.784894002430293e-03, - 2 => -3.223964580411365e-01, - 3 => -2.400758277161838e+00, - 4 => -2.549732539343734e+00, - 5 => 4.374664141464968e+00, - 6 => 2.938163982698783e+00, - ]; - - static $d = [ - 1 => 7.784695709041462e-03, - 2 => 3.224671290700398e-01, - 3 => 2.445134137142996e+00, - 4 => 3.754408661907416e+00, - ]; - - // Define lower and upper region break-points. - $p_low = 0.02425; //Use lower region approx. below this - $p_high = 1 - $p_low; //Use upper region approx. above this - - if (0 < $p && $p < $p_low) { - // Rational approximation for lower region. - $q = sqrt(-2 * 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); - } elseif ($p_low <= $p && $p <= $p_high) { - // Rational approximation for central region. - $q = $p - 0.5; - $r = $q * $q; - - return ((((($a[1] * $r + $a[2]) * $r + $a[3]) * $r + $a[4]) * $r + $a[5]) * $r + $a[6]) * $q / - ((((($b[1] * $r + $b[2]) * $r + $b[3]) * $r + $b[4]) * $r + $b[5]) * $r + 1); - } elseif ($p_high < $p && $p < 1) { - // Rational approximation for upper region. - $q = sqrt(-2 * 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); - } - // If 0 < p < 1, return a null value - return Functions::NULL(); - } - - /** - * MS Excel does not count Booleans if passed as cell values, but they are counted if passed as literals. - * OpenOffice Calc always counts Booleans. - * Gnumeric never counts Booleans. - * - * @param mixed $arg - * @param mixed $k - * - * @return int|mixed - */ - private static function testAcceptedBoolean($arg, $k) - { - if ((is_bool($arg)) && - ((!Functions::isCellValue($k) && (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_EXCEL)) || - (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE))) { - $arg = (int) $arg; - } - - return $arg; - } - - /** - * @param mixed $arg - * @param mixed $k - * - * @return bool - */ - private static function isAcceptedCountable($arg, $k) - { - if (((is_numeric($arg)) && (!is_string($arg))) || - ((is_numeric($arg)) && (!Functions::isCellValue($k)) && - (Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_GNUMERIC))) { - return true; - } - - return false; - } - /** * AVEDEV. * @@ -565,7 +34,9 @@ class Statistical * Excel Function: * AVEDEV(value1[,value2[, ...]]) * - * @category Statistical Functions + * @deprecated 1.17.0 + * Use the averageDeviations() method in the Statistical\Averages class instead + * @see Statistical\Averages::averageDeviations() * * @param mixed ...$args Data values * @@ -573,39 +44,7 @@ class Statistical */ public static function AVEDEV(...$args) { - $aArgs = Functions::flattenArrayIndexed($args); - - // Return value - $returnValue = 0; - - $aMean = self::AVERAGE(...$args); - if ($aMean === Functions::DIV0()) { - return Functions::NAN(); - } elseif ($aMean === Functions::VALUE()) { - return Functions::VALUE(); - } - - $aCount = 0; - foreach ($aArgs as $k => $arg) { - $arg = self::testAcceptedBoolean($arg, $k); - // Is it a numeric value? - // Strings containing numeric values are only counted if they are string literals (not cell values) - // and then only in MS Excel and in Open Office, not in Gnumeric - if ((is_string($arg)) && (!is_numeric($arg)) && (!Functions::isCellValue($k))) { - return Functions::VALUE(); - } - if (self::isAcceptedCountable($arg, $k)) { - $returnValue += abs($arg - $aMean); - ++$aCount; - } - } - - // Return - if ($aCount === 0) { - return Functions::DIV0(); - } - - return $returnValue / $aCount; + return Averages::averageDeviations(...$args); } /** @@ -616,7 +55,9 @@ class Statistical * Excel Function: * AVERAGE(value1[,value2[, ...]]) * - * @category Statistical Functions + * @deprecated 1.17.0 + * Use the average() method in the Statistical\Averages class instead + * @see Statistical\Averages::average() * * @param mixed ...$args Data values * @@ -624,29 +65,7 @@ class Statistical */ public static function AVERAGE(...$args) { - $returnValue = $aCount = 0; - - // Loop through arguments - foreach (Functions::flattenArrayIndexed($args) as $k => $arg) { - $arg = self::testAcceptedBoolean($arg, $k); - // Is it a numeric value? - // Strings containing numeric values are only counted if they are string literals (not cell values) - // and then only in MS Excel and in Open Office, not in Gnumeric - if ((is_string($arg)) && (!is_numeric($arg)) && (!Functions::isCellValue($k))) { - return Functions::VALUE(); - } - if (self::isAcceptedCountable($arg, $k)) { - $returnValue += $arg; - ++$aCount; - } - } - - // Return - if ($aCount > 0) { - return $returnValue / $aCount; - } - - return Functions::DIV0(); + return Averages::average(...$args); } /** @@ -657,7 +76,9 @@ class Statistical * Excel Function: * AVERAGEA(value1[,value2[, ...]]) * - * @category Statistical Functions + * @deprecated 1.17.0 + * Use the averageA() method in the Statistical\Averages class instead + * @see Statistical\Averages::averageA() * * @param mixed ...$args Data values * @@ -665,31 +86,7 @@ class Statistical */ public static function AVERAGEA(...$args) { - $returnValue = null; - - $aCount = 0; - // Loop through arguments - foreach (Functions::flattenArrayIndexed($args) as $k => $arg) { - if ((is_bool($arg)) && - (!Functions::isMatrixValue($k))) { - } else { - if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) { - if (is_bool($arg)) { - $arg = (int) $arg; - } elseif (is_string($arg)) { - $arg = 0; - } - $returnValue += $arg; - ++$aCount; - } - } - } - - if ($aCount > 0) { - return $returnValue / $aCount; - } - - return Functions::DIV0(); + return Averages::averageA(...$args); } /** @@ -700,49 +97,19 @@ class Statistical * Excel Function: * AVERAGEIF(value1[,value2[, ...]],condition) * - * @category Mathematical and Trigonometric Functions + * @deprecated 1.17.0 + * Use the AVERAGEIF() method in the Statistical\Conditional class instead + * @see Statistical\Conditional::AVERAGEIF() * - * @param mixed $aArgs Data values + * @param mixed $range Data values * @param string $condition the criteria that defines which cells will be checked - * @param mixed[] $averageArgs Data values + * @param mixed[] $averageRange Data values * - * @return float|string + * @return null|float|string */ - public static function AVERAGEIF($aArgs, $condition, $averageArgs = []) + public static function AVERAGEIF($range, $condition, $averageRange = []) { - $returnValue = 0; - - $aArgs = Functions::flattenArray($aArgs); - $averageArgs = Functions::flattenArray($averageArgs); - if (empty($averageArgs)) { - $averageArgs = $aArgs; - } - $condition = Functions::ifCondition($condition); - $conditionIsNumeric = strpos($condition, '"') === false; - - // Loop through arguments - $aCount = 0; - foreach ($aArgs as $key => $arg) { - if (!is_numeric($arg)) { - if ($conditionIsNumeric) { - continue; - } - $arg = Calculation::wrapResult(strtoupper($arg)); - } elseif (!$conditionIsNumeric) { - continue; - } - $testCondition = '=' . $arg . $condition; - if (Calculation::getInstance()->_calculateFormulaValue($testCondition)) { - $returnValue += $averageArgs[$key]; - ++$aCount; - } - } - - if ($aCount > 0) { - return $returnValue / $aCount; - } - - return Functions::DIV0(); + return Conditional::AVERAGEIF($range, $condition, $averageRange); } /** @@ -750,44 +117,31 @@ class Statistical * * Returns the beta distribution. * + * @deprecated 1.18.0 + * Use the distribution() method in the Statistical\Distributions\Beta class instead + * @see Statistical\Distributions\Beta::distribution() + * * @param float $value Value at which you want to evaluate the distribution * @param float $alpha Parameter to the distribution * @param float $beta Parameter to the distribution * @param mixed $rMin * @param mixed $rMax * - * @return float|string + * @return array|float|string */ public static function BETADIST($value, $alpha, $beta, $rMin = 0, $rMax = 1) { - $value = Functions::flattenSingleValue($value); - $alpha = Functions::flattenSingleValue($alpha); - $beta = Functions::flattenSingleValue($beta); - $rMin = Functions::flattenSingleValue($rMin); - $rMax = Functions::flattenSingleValue($rMax); - - if ((is_numeric($value)) && (is_numeric($alpha)) && (is_numeric($beta)) && (is_numeric($rMin)) && (is_numeric($rMax))) { - if (($value < $rMin) || ($value > $rMax) || ($alpha <= 0) || ($beta <= 0) || ($rMin == $rMax)) { - return Functions::NAN(); - } - if ($rMin > $rMax) { - $tmp = $rMin; - $rMin = $rMax; - $rMax = $tmp; - } - $value -= $rMin; - $value /= ($rMax - $rMin); - - return self::incompleteBeta($value, $alpha, $beta); - } - - return Functions::VALUE(); + return Statistical\Distributions\Beta::distribution($value, $alpha, $beta, $rMin, $rMax); } /** * BETAINV. * - * Returns the inverse of the beta distribution. + * Returns the inverse of the Beta distribution. + * + * @deprecated 1.18.0 + * Use the inverse() method in the Statistical\Distributions\Beta class instead + * @see Statistical\Distributions\Beta::inverse() * * @param float $probability Probability at which you want to evaluate the distribution * @param float $alpha Parameter to the distribution @@ -795,48 +149,11 @@ class Statistical * @param float $rMin Minimum value * @param float $rMax Maximum value * - * @return float|string + * @return array|float|string */ public static function BETAINV($probability, $alpha, $beta, $rMin = 0, $rMax = 1) { - $probability = Functions::flattenSingleValue($probability); - $alpha = Functions::flattenSingleValue($alpha); - $beta = Functions::flattenSingleValue($beta); - $rMin = Functions::flattenSingleValue($rMin); - $rMax = Functions::flattenSingleValue($rMax); - - if ((is_numeric($probability)) && (is_numeric($alpha)) && (is_numeric($beta)) && (is_numeric($rMin)) && (is_numeric($rMax))) { - if (($alpha <= 0) || ($beta <= 0) || ($rMin == $rMax) || ($probability <= 0) || ($probability > 1)) { - return Functions::NAN(); - } - if ($rMin > $rMax) { - $tmp = $rMin; - $rMin = $rMax; - $rMax = $tmp; - } - $a = 0; - $b = 2; - - $i = 0; - while ((($b - $a) > Functions::PRECISION) && ($i++ < self::MAX_ITERATIONS)) { - $guess = ($a + $b) / 2; - $result = self::BETADIST($guess, $alpha, $beta); - if (($result == $probability) || ($result == 0)) { - $b = $a; - } elseif ($result > $probability) { - $b = $guess; - } else { - $a = $guess; - } - } - if ($i == self::MAX_ITERATIONS) { - return Functions::NA(); - } - - return round($rMin + $guess * ($rMax - $rMin), 12); - } - - return Functions::VALUE(); + return Statistical\Distributions\Beta::inverse($probability, $alpha, $beta, $rMin, $rMax); } /** @@ -848,43 +165,20 @@ class Statistical * experiment. For example, BINOMDIST can calculate the probability that two of the next three * babies born are male. * - * @param float $value Number of successes in trials - * @param float $trials Number of trials - * @param float $probability Probability of success on each trial - * @param bool $cumulative + * @deprecated 1.18.0 + * Use the distribution() method in the Statistical\Distributions\Binomial class instead + * @see Statistical\Distributions\Binomial::distribution() * - * @return float|string + * @param mixed $value Number of successes in trials + * @param mixed $trials Number of trials + * @param mixed $probability Probability of success on each trial + * @param mixed $cumulative + * + * @return array|float|string */ public static function BINOMDIST($value, $trials, $probability, $cumulative) { - $value = Functions::flattenSingleValue($value); - $trials = Functions::flattenSingleValue($trials); - $probability = Functions::flattenSingleValue($probability); - - if ((is_numeric($value)) && (is_numeric($trials)) && (is_numeric($probability))) { - $value = floor($value); - $trials = floor($trials); - if (($value < 0) || ($value > $trials)) { - return Functions::NAN(); - } - if (($probability < 0) || ($probability > 1)) { - return Functions::NAN(); - } - if ((is_numeric($cumulative)) || (is_bool($cumulative))) { - if ($cumulative) { - $summer = 0; - for ($i = 0; $i <= $value; ++$i) { - $summer += MathTrig::COMBIN($trials, $i) * pow($probability, $i) * pow(1 - $probability, $trials - $i); - } - - return $summer; - } - - return MathTrig::COMBIN($trials, $value) * pow($probability, $value) * pow(1 - $probability, $trials - $value); - } - } - - return Functions::VALUE(); + return Statistical\Distributions\Binomial::distribution($value, $trials, $probability, $cumulative); } /** @@ -892,33 +186,18 @@ class Statistical * * Returns the one-tailed probability of the chi-squared distribution. * + * @deprecated 1.18.0 + * Use the distributionRightTail() method in the Statistical\Distributions\ChiSquared class instead + * @see Statistical\Distributions\ChiSquared::distributionRightTail() + * * @param float $value Value for the function * @param float $degrees degrees of freedom * - * @return float|string + * @return array|float|string */ public static function CHIDIST($value, $degrees) { - $value = Functions::flattenSingleValue($value); - $degrees = Functions::flattenSingleValue($degrees); - - if ((is_numeric($value)) && (is_numeric($degrees))) { - $degrees = floor($degrees); - if ($degrees < 1) { - return Functions::NAN(); - } - if ($value < 0) { - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { - return 1; - } - - return Functions::NAN(); - } - - return 1 - (self::incompleteGamma($degrees / 2, $value / 2) / self::gamma($degrees / 2)); - } - - return Functions::VALUE(); + return Statistical\Distributions\ChiSquared::distributionRightTail($value, $degrees); } /** @@ -926,59 +205,18 @@ class Statistical * * Returns the one-tailed probability of the chi-squared distribution. * + * @deprecated 1.18.0 + * Use the inverseRightTail() method in the Statistical\Distributions\ChiSquared class instead + * @see Statistical\Distributions\ChiSquared::inverseRightTail() + * * @param float $probability Probability for the function * @param float $degrees degrees of freedom * - * @return float|string + * @return array|float|string */ public static function CHIINV($probability, $degrees) { - $probability = Functions::flattenSingleValue($probability); - $degrees = Functions::flattenSingleValue($degrees); - - if ((is_numeric($probability)) && (is_numeric($degrees))) { - $degrees = floor($degrees); - - $xLo = 100; - $xHi = 0; - - $x = $xNew = 1; - $dx = 1; - $i = 0; - - while ((abs($dx) > Functions::PRECISION) && ($i++ < self::MAX_ITERATIONS)) { - // Apply Newton-Raphson step - $result = 1 - (self::incompleteGamma($degrees / 2, $x / 2) / self::gamma($degrees / 2)); - $error = $result - $probability; - if ($error == 0.0) { - $dx = 0; - } elseif ($error < 0.0) { - $xLo = $x; - } else { - $xHi = $x; - } - // Avoid division by zero - if ($result != 0.0) { - $dx = $error / $result; - $xNew = $x - $dx; - } - // If the NR fails to converge (which for example may be the - // case if the initial guess is too rough) we apply a bisection - // step to determine a more narrow interval around the root. - if (($xNew < $xLo) || ($xNew > $xHi) || ($result == 0.0)) { - $xNew = ($xLo + $xHi) / 2; - $dx = $xNew - $x; - } - $x = $xNew; - } - if ($i == self::MAX_ITERATIONS) { - return Functions::NA(); - } - - return round($x, 12); - } - - return Functions::VALUE(); + return Statistical\Distributions\ChiSquared::inverseRightTail($probability, $degrees); } /** @@ -986,31 +224,19 @@ class Statistical * * Returns the confidence interval for a population mean * + * @deprecated 1.18.0 + * Use the CONFIDENCE() method in the Statistical\Confidence class instead + * @see Statistical\Confidence::CONFIDENCE() + * * @param float $alpha * @param float $stdDev Standard Deviation * @param float $size * - * @return float|string + * @return array|float|string */ public static function CONFIDENCE($alpha, $stdDev, $size) { - $alpha = Functions::flattenSingleValue($alpha); - $stdDev = Functions::flattenSingleValue($stdDev); - $size = Functions::flattenSingleValue($size); - - if ((is_numeric($alpha)) && (is_numeric($stdDev)) && (is_numeric($size))) { - $size = floor($size); - if (($alpha <= 0) || ($alpha >= 1)) { - return Functions::NAN(); - } - if (($stdDev <= 0) || ($size < 1)) { - return Functions::NAN(); - } - - return self::NORMSINV(1 - $alpha / 2) * $stdDev / sqrt($size); - } - - return Functions::VALUE(); + return Confidence::CONFIDENCE($alpha, $stdDev, $size); } /** @@ -1018,6 +244,10 @@ class Statistical * * Returns covariance, the average of the products of deviations for each data point pair. * + * @deprecated 1.18.0 + * Use the CORREL() method in the Statistical\Trends class instead + * @see Statistical\Trends::CORREL() + * * @param mixed $yValues array of mixed Data Series Y * @param null|mixed $xValues array of mixed Data Series X * @@ -1025,24 +255,7 @@ class Statistical */ public static function CORREL($yValues, $xValues = null) { - if (($xValues === null) || (!is_array($yValues)) || (!is_array($xValues))) { - return Functions::VALUE(); - } - if (!self::checkTrendArrays($yValues, $xValues)) { - return Functions::VALUE(); - } - $yValueCount = count($yValues); - $xValueCount = count($xValues); - - if (($yValueCount == 0) || ($yValueCount != $xValueCount)) { - return Functions::NA(); - } elseif ($yValueCount == 1) { - return Functions::DIV0(); - } - - $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); - - return $bestFitLinear->getCorrelation(); + return Trends::CORREL($xValues, $yValues); } /** @@ -1053,7 +266,9 @@ class Statistical * Excel Function: * COUNT(value1[,value2[, ...]]) * - * @category Statistical Functions + * @deprecated 1.17.0 + * Use the COUNT() method in the Statistical\Counts class instead + * @see Statistical\Counts::COUNT() * * @param mixed ...$args Data values * @@ -1061,21 +276,7 @@ class Statistical */ public static function COUNT(...$args) { - $returnValue = 0; - - // Loop through arguments - $aArgs = Functions::flattenArrayIndexed($args); - foreach ($aArgs as $k => $arg) { - $arg = self::testAcceptedBoolean($arg, $k); - // Is it a numeric value? - // Strings containing numeric values are only counted if they are string literals (not cell values) - // and then only in MS Excel and in Open Office, not in Gnumeric - if (self::isAcceptedCountable($arg, $k)) { - ++$returnValue; - } - } - - return $returnValue; + return Counts::COUNT(...$args); } /** @@ -1086,7 +287,9 @@ class Statistical * Excel Function: * COUNTA(value1[,value2[, ...]]) * - * @category Statistical Functions + * @deprecated 1.17.0 + * Use the COUNTA() method in the Statistical\Counts class instead + * @see Statistical\Counts::COUNTA() * * @param mixed ...$args Data values * @@ -1094,18 +297,7 @@ class Statistical */ public static function COUNTA(...$args) { - $returnValue = 0; - - // Loop through arguments - $aArgs = Functions::flattenArrayIndexed($args); - foreach ($aArgs as $k => $arg) { - // Nulls are counted if literals, but not if cell values - if ($arg !== null || (!Functions::isCellValue($k))) { - ++$returnValue; - } - } - - return $returnValue; + return Counts::COUNTA(...$args); } /** @@ -1116,26 +308,17 @@ class Statistical * Excel Function: * COUNTBLANK(value1[,value2[, ...]]) * - * @category Statistical Functions + * @deprecated 1.17.0 + * Use the COUNTBLANK() method in the Statistical\Counts class instead + * @see Statistical\Counts::COUNTBLANK() * - * @param mixed ...$args Data values + * @param mixed $range Data values * * @return int */ - public static function COUNTBLANK(...$args) + public static function COUNTBLANK($range) { - $returnValue = 0; - - // Loop through arguments - $aArgs = Functions::flattenArray($args); - foreach ($aArgs as $arg) { - // Is it a blank cell? - if (($arg === null) || ((is_string($arg)) && ($arg == ''))) { - ++$returnValue; - } - } - - return $returnValue; + return Counts::COUNTBLANK($range); } /** @@ -1144,40 +327,20 @@ class Statistical * Counts the number of cells that contain numbers within the list of arguments * * Excel Function: - * COUNTIF(value1[,value2[, ...]],condition) + * COUNTIF(range,condition) * - * @category Statistical Functions + * @deprecated 1.17.0 + * Use the COUNTIF() method in the Statistical\Conditional class instead + * @see Statistical\Conditional::COUNTIF() * - * @param mixed $aArgs Data values + * @param mixed $range Data values * @param string $condition the criteria that defines which cells will be counted * - * @return int + * @return int|string */ - public static function COUNTIF($aArgs, $condition) + public static function COUNTIF($range, $condition) { - $returnValue = 0; - - $aArgs = Functions::flattenArray($aArgs); - $condition = Functions::ifCondition($condition); - $conditionIsNumeric = strpos($condition, '"') === false; - // Loop through arguments - foreach ($aArgs as $arg) { - if (!is_numeric($arg)) { - if ($conditionIsNumeric) { - continue; - } - $arg = Calculation::wrapResult(strtoupper($arg)); - } elseif (!$conditionIsNumeric) { - continue; - } - $testCondition = '=' . $arg . $condition; - if (Calculation::getInstance()->_calculateFormulaValue($testCondition)) { - // Is it a value within our criteria - ++$returnValue; - } - } - - return $returnValue; + return Conditional::COUNTIF($range, $condition); } /** @@ -1188,68 +351,17 @@ class Statistical * Excel Function: * COUNTIFS(criteria_range1, criteria1, [criteria_range2, criteria2]…) * - * @category Statistical Functions + * @deprecated 1.17.0 + * Use the COUNTIFS() method in the Statistical\Conditional class instead + * @see Statistical\Conditional::COUNTIFS() * - * @param mixed $args Criterias + * @param mixed $args Pairs of Ranges and Criteria * - * @return int + * @return int|string */ public static function COUNTIFS(...$args) { - $arrayList = $args; - - // Return value - $returnValue = 0; - - if (empty($arrayList)) { - return $returnValue; - } - - $aArgsArray = []; - $conditions = []; - - while (count($arrayList) > 0) { - $aArgsArray[] = Functions::flattenArray(array_shift($arrayList)); - $conditions[] = Functions::ifCondition(array_shift($arrayList)); - } - - // Loop through each arg and see if arguments and conditions are true - foreach (array_keys($aArgsArray[0]) as $index) { - $valid = true; - - foreach ($conditions as $cidx => $condition) { - $conditionIsNumeric = strpos($condition, '"') === false; - $arg = $aArgsArray[$cidx][$index]; - - // Loop through arguments - if (!is_numeric($arg)) { - if ($conditionIsNumeric) { - $valid = false; - - break; // if false found, don't need to check other conditions - } - $arg = Calculation::wrapResult(strtoupper($arg)); - } elseif (!$conditionIsNumeric) { - $valid = false; - - break; // if false found, don't need to check other conditions - } - $testCondition = '=' . $arg . $condition; - if (!Calculation::getInstance()->_calculateFormulaValue($testCondition)) { - // Is not a value within our criteria - $valid = false; - - break; // if false found, don't need to check other conditions - } - } - - if ($valid) { - ++$returnValue; - } - } - - // Return - return $returnValue; + return Conditional::COUNTIFS(...$args); } /** @@ -1257,6 +369,10 @@ class Statistical * * Returns covariance, the average of the products of deviations for each data point pair. * + * @deprecated 1.18.0 + * Use the COVAR() method in the Statistical\Trends class instead + * @see Statistical\Trends::COVAR() + * * @param mixed $yValues array of mixed Data Series Y * @param mixed $xValues array of mixed Data Series X * @@ -1264,21 +380,7 @@ class Statistical */ public static function COVAR($yValues, $xValues) { - if (!self::checkTrendArrays($yValues, $xValues)) { - return Functions::VALUE(); - } - $yValueCount = count($yValues); - $xValueCount = count($xValues); - - if (($yValueCount == 0) || ($yValueCount != $xValueCount)) { - return Functions::NA(); - } elseif ($yValueCount == 1) { - return Functions::DIV0(); - } - - $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); - - return $bestFitLinear->getCovariance(); + return Trends::COVAR($yValues, $xValues); } /** @@ -1289,123 +391,19 @@ class Statistical * * See https://support.microsoft.com/en-us/help/828117/ for details of the algorithm used * + * @deprecated 1.18.0 + * Use the inverse() method in the Statistical\Distributions\Binomial class instead + * @see Statistical\Distributions\Binomial::inverse() + * * @param float $trials number of Bernoulli trials * @param float $probability probability of a success on each trial * @param float $alpha criterion value * - * @return int|string - * - * @todo Warning. This implementation differs from the algorithm detailed on the MS - * web site in that $CumPGuessMinus1 = $CumPGuess - 1 rather than $CumPGuess - $PGuess - * This eliminates a potential endless loop error, but may have an adverse affect on the - * accuracy of the function (although all my tests have so far returned correct results). + * @return array|int|string */ public static function CRITBINOM($trials, $probability, $alpha) { - $trials = floor(Functions::flattenSingleValue($trials)); - $probability = Functions::flattenSingleValue($probability); - $alpha = Functions::flattenSingleValue($alpha); - - if ((is_numeric($trials)) && (is_numeric($probability)) && (is_numeric($alpha))) { - $trials = (int) $trials; - if ($trials < 0) { - return Functions::NAN(); - } elseif (($probability < 0.0) || ($probability > 1.0)) { - return Functions::NAN(); - } elseif (($alpha < 0.0) || ($alpha > 1.0)) { - return Functions::NAN(); - } - - if ($alpha <= 0.5) { - $t = sqrt(log(1 / ($alpha * $alpha))); - $trialsApprox = 0 - ($t + (2.515517 + 0.802853 * $t + 0.010328 * $t * $t) / (1 + 1.432788 * $t + 0.189269 * $t * $t + 0.001308 * $t * $t * $t)); - } else { - $t = sqrt(log(1 / pow(1 - $alpha, 2))); - $trialsApprox = $t - (2.515517 + 0.802853 * $t + 0.010328 * $t * $t) / (1 + 1.432788 * $t + 0.189269 * $t * $t + 0.001308 * $t * $t * $t); - } - - $Guess = floor($trials * $probability + $trialsApprox * sqrt($trials * $probability * (1 - $probability))); - if ($Guess < 0) { - $Guess = 0; - } elseif ($Guess > $trials) { - $Guess = $trials; - } - - $TotalUnscaledProbability = $UnscaledPGuess = $UnscaledCumPGuess = 0.0; - $EssentiallyZero = 10e-12; - - $m = floor($trials * $probability); - ++$TotalUnscaledProbability; - if ($m == $Guess) { - ++$UnscaledPGuess; - } - if ($m <= $Guess) { - ++$UnscaledCumPGuess; - } - - $PreviousValue = 1; - $Done = false; - $k = $m + 1; - while ((!$Done) && ($k <= $trials)) { - $CurrentValue = $PreviousValue * ($trials - $k + 1) * $probability / ($k * (1 - $probability)); - $TotalUnscaledProbability += $CurrentValue; - if ($k == $Guess) { - $UnscaledPGuess += $CurrentValue; - } - if ($k <= $Guess) { - $UnscaledCumPGuess += $CurrentValue; - } - if ($CurrentValue <= $EssentiallyZero) { - $Done = true; - } - $PreviousValue = $CurrentValue; - ++$k; - } - - $PreviousValue = 1; - $Done = false; - $k = $m - 1; - while ((!$Done) && ($k >= 0)) { - $CurrentValue = $PreviousValue * $k + 1 * (1 - $probability) / (($trials - $k) * $probability); - $TotalUnscaledProbability += $CurrentValue; - if ($k == $Guess) { - $UnscaledPGuess += $CurrentValue; - } - if ($k <= $Guess) { - $UnscaledCumPGuess += $CurrentValue; - } - if ($CurrentValue <= $EssentiallyZero) { - $Done = true; - } - $PreviousValue = $CurrentValue; - --$k; - } - - $PGuess = $UnscaledPGuess / $TotalUnscaledProbability; - $CumPGuess = $UnscaledCumPGuess / $TotalUnscaledProbability; - - $CumPGuessMinus1 = $CumPGuess - 1; - - while (true) { - if (($CumPGuessMinus1 < $alpha) && ($CumPGuess >= $alpha)) { - return $Guess; - } elseif (($CumPGuessMinus1 < $alpha) && ($CumPGuess < $alpha)) { - $PGuessPlus1 = $PGuess * ($trials - $Guess) * $probability / $Guess / (1 - $probability); - $CumPGuessMinus1 = $CumPGuess; - $CumPGuess = $CumPGuess + $PGuessPlus1; - $PGuess = $PGuessPlus1; - ++$Guess; - } elseif (($CumPGuessMinus1 >= $alpha) && ($CumPGuess >= $alpha)) { - $PGuessMinus1 = $PGuess * $Guess * (1 - $probability) / ($trials - $Guess + 1) / $probability; - $CumPGuess = $CumPGuessMinus1; - $CumPGuessMinus1 = $CumPGuessMinus1 - $PGuess; - $PGuess = $PGuessMinus1; - --$Guess; - } - } - } - - return Functions::VALUE(); + return Statistical\Distributions\Binomial::inverse($trials, $probability, $alpha); } /** @@ -1416,7 +414,9 @@ class Statistical * Excel Function: * DEVSQ(value1[,value2[, ...]]) * - * @category Statistical Functions + * @deprecated 1.18.0 + * Use the sumSquares() method in the Statistical\Deviations class instead + * @see Statistical\Deviations::sumSquares() * * @param mixed ...$args Data values * @@ -1424,40 +424,7 @@ class Statistical */ public static function DEVSQ(...$args) { - $aArgs = Functions::flattenArrayIndexed($args); - - // Return value - $returnValue = null; - - $aMean = self::AVERAGE($aArgs); - if ($aMean != Functions::DIV0()) { - $aCount = -1; - foreach ($aArgs as $k => $arg) { - // Is it a numeric value? - if ((is_bool($arg)) && - ((!Functions::isCellValue($k)) || - (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE))) { - $arg = (int) $arg; - } - if ((is_numeric($arg)) && (!is_string($arg))) { - if ($returnValue === null) { - $returnValue = pow(($arg - $aMean), 2); - } else { - $returnValue += pow(($arg - $aMean), 2); - } - ++$aCount; - } - } - - // Return - if ($returnValue === null) { - return Functions::NAN(); - } - - return $returnValue; - } - - return self::NA(); + return Statistical\Deviations::sumSquares(...$args); } /** @@ -1467,32 +434,44 @@ class Statistical * such as how long an automated bank teller takes to deliver cash. For example, you can * use EXPONDIST to determine the probability that the process takes at most 1 minute. * + * @deprecated 1.18.0 + * Use the distribution() method in the Statistical\Distributions\Exponential class instead + * @see Statistical\Distributions\Exponential::distribution() + * * @param float $value Value of the function * @param float $lambda The parameter value * @param bool $cumulative * - * @return float|string + * @return array|float|string */ public static function EXPONDIST($value, $lambda, $cumulative) { - $value = Functions::flattenSingleValue($value); - $lambda = Functions::flattenSingleValue($lambda); - $cumulative = Functions::flattenSingleValue($cumulative); + return Statistical\Distributions\Exponential::distribution($value, $lambda, $cumulative); + } - if ((is_numeric($value)) && (is_numeric($lambda))) { - if (($value < 0) || ($lambda < 0)) { - return Functions::NAN(); - } - if ((is_numeric($cumulative)) || (is_bool($cumulative))) { - if ($cumulative) { - return 1 - exp(0 - $value * $lambda); - } - - return $lambda * exp(0 - $value * $lambda); - } - } - - return Functions::VALUE(); + /** + * F.DIST. + * + * Returns the F probability distribution. + * You can use this function to determine whether two data sets have different degrees of diversity. + * For example, you can examine the test scores of men and women entering high school, and determine + * if the variability in the females is different from that found in the males. + * + * @deprecated 1.18.0 + * Use the distribution() method in the Statistical\Distributions\F class instead + * @see Statistical\Distributions\F::distribution() + * + * @param float $value Value of the function + * @param int $u The numerator degrees of freedom + * @param int $v The denominator degrees of freedom + * @param bool $cumulative If cumulative is TRUE, F.DIST returns the cumulative distribution function; + * if FALSE, it returns the probability density function. + * + * @return array|float|string + */ + public static function FDIST2($value, $u, $v, $cumulative) + { + return Statistical\Distributions\F::distribution($value, $u, $v, $cumulative); } /** @@ -1502,23 +481,17 @@ class Statistical * is normally distributed rather than skewed. Use this function to perform hypothesis * testing on the correlation coefficient. * + * @deprecated 1.18.0 + * Use the distribution() method in the Statistical\Distributions\Fisher class instead + * @see Statistical\Distributions\Fisher::distribution() + * * @param float $value * - * @return float|string + * @return array|float|string */ public static function FISHER($value) { - $value = Functions::flattenSingleValue($value); - - if (is_numeric($value)) { - if (($value <= -1) || ($value >= 1)) { - return Functions::NAN(); - } - - return 0.5 * log((1 + $value) / (1 - $value)); - } - - return Functions::VALUE(); + return Statistical\Distributions\Fisher::distribution($value); } /** @@ -1528,19 +501,17 @@ class Statistical * analyzing correlations between ranges or arrays of data. If y = FISHER(x), then * FISHERINV(y) = x. * + * @deprecated 1.18.0 + * Use the inverse() method in the Statistical\Distributions\Fisher class instead + * @see Statistical\Distributions\Fisher::inverse() + * * @param float $value * - * @return float|string + * @return array|float|string */ public static function FISHERINV($value) { - $value = Functions::flattenSingleValue($value); - - if (is_numeric($value)) { - return (exp(2 * $value) - 1) / (exp(2 * $value) + 1); - } - - return Functions::VALUE(); + return Statistical\Distributions\Fisher::inverse($value); } /** @@ -1548,32 +519,37 @@ class Statistical * * Calculates, or predicts, a future value by using existing values. The predicted value is a y-value for a given x-value. * + * @deprecated 1.18.0 + * Use the FORECAST() method in the Statistical\Trends class instead + * @see Statistical\Trends::FORECAST() + * * @param float $xValue Value of X for which we want to find Y * @param mixed $yValues array of mixed Data Series Y * @param mixed $xValues of mixed Data Series X * - * @return bool|float|string + * @return array|bool|float|string */ public static function FORECAST($xValue, $yValues, $xValues) { - $xValue = Functions::flattenSingleValue($xValue); - if (!is_numeric($xValue)) { - return Functions::VALUE(); - } elseif (!self::checkTrendArrays($yValues, $xValues)) { - return Functions::VALUE(); - } - $yValueCount = count($yValues); - $xValueCount = count($xValues); + return Trends::FORECAST($xValue, $yValues, $xValues); + } - if (($yValueCount == 0) || ($yValueCount != $xValueCount)) { - return Functions::NA(); - } elseif ($yValueCount == 1) { - return Functions::DIV0(); - } - - $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); - - return $bestFitLinear->getValueOfYForX($xValue); + /** + * GAMMA. + * + * Returns the gamma function value. + * + * @deprecated 1.18.0 + * Use the gamma() method in the Statistical\Distributions\Gamma class instead + * @see Statistical\Distributions\Gamma::gamma() + * + * @param float $value + * + * @return array|float|string The result, or a string containing an error + */ + public static function GAMMAFunction($value) + { + return Statistical\Distributions\Gamma::gamma($value); } /** @@ -1581,96 +557,40 @@ class Statistical * * Returns the gamma distribution. * + * @deprecated 1.18.0 + * Use the distribution() method in the Statistical\Distributions\Gamma class instead + * @see Statistical\Distributions\Gamma::distribution() + * * @param float $value Value at which you want to evaluate the distribution * @param float $a Parameter to the distribution * @param float $b Parameter to the distribution * @param bool $cumulative * - * @return float|string + * @return array|float|string */ public static function GAMMADIST($value, $a, $b, $cumulative) { - $value = Functions::flattenSingleValue($value); - $a = Functions::flattenSingleValue($a); - $b = Functions::flattenSingleValue($b); - - if ((is_numeric($value)) && (is_numeric($a)) && (is_numeric($b))) { - if (($value < 0) || ($a <= 0) || ($b <= 0)) { - return Functions::NAN(); - } - if ((is_numeric($cumulative)) || (is_bool($cumulative))) { - if ($cumulative) { - return self::incompleteGamma($a, $value / $b) / self::gamma($a); - } - - return (1 / (pow($b, $a) * self::gamma($a))) * pow($value, $a - 1) * exp(0 - ($value / $b)); - } - } - - return Functions::VALUE(); + return Statistical\Distributions\Gamma::distribution($value, $a, $b, $cumulative); } /** * GAMMAINV. * - * Returns the inverse of the beta distribution. + * Returns the inverse of the Gamma distribution. + * + * @deprecated 1.18.0 + * Use the inverse() method in the Statistical\Distributions\Gamma class instead + * @see Statistical\Distributions\Gamma::inverse() * * @param float $probability Probability at which you want to evaluate the distribution * @param float $alpha Parameter to the distribution * @param float $beta Parameter to the distribution * - * @return float|string + * @return array|float|string */ public static function GAMMAINV($probability, $alpha, $beta) { - $probability = Functions::flattenSingleValue($probability); - $alpha = Functions::flattenSingleValue($alpha); - $beta = Functions::flattenSingleValue($beta); - - if ((is_numeric($probability)) && (is_numeric($alpha)) && (is_numeric($beta))) { - if (($alpha <= 0) || ($beta <= 0) || ($probability < 0) || ($probability > 1)) { - return Functions::NAN(); - } - - $xLo = 0; - $xHi = $alpha * $beta * 5; - - $x = $xNew = 1; - $error = $pdf = 0; - $dx = 1024; - $i = 0; - - while ((abs($dx) > Functions::PRECISION) && ($i++ < self::MAX_ITERATIONS)) { - // Apply Newton-Raphson step - $error = self::GAMMADIST($x, $alpha, $beta, true) - $probability; - if ($error < 0.0) { - $xLo = $x; - } else { - $xHi = $x; - } - $pdf = self::GAMMADIST($x, $alpha, $beta, false); - // Avoid division by zero - if ($pdf != 0.0) { - $dx = $error / $pdf; - $xNew = $x - $dx; - } - // If the NR fails to converge (which for example may be the - // case if the initial guess is too rough) we apply a bisection - // step to determine a more narrow interval around the root. - if (($xNew < $xLo) || ($xNew > $xHi) || ($pdf == 0.0)) { - $xNew = ($xLo + $xHi) / 2; - $dx = $xNew - $x; - } - $x = $xNew; - } - if ($i == self::MAX_ITERATIONS) { - return Functions::NA(); - } - - return $x; - } - - return Functions::VALUE(); + return Statistical\Distributions\Gamma::inverse($probability, $alpha, $beta); } /** @@ -1678,23 +598,36 @@ class Statistical * * Returns the natural logarithm of the gamma function. * + * @deprecated 1.18.0 + * Use the ln() method in the Statistical\Distributions\Gamma class instead + * @see Statistical\Distributions\Gamma::ln() + * * @param float $value * - * @return float|string + * @return array|float|string */ public static function GAMMALN($value) { - $value = Functions::flattenSingleValue($value); + return Statistical\Distributions\Gamma::ln($value); + } - if (is_numeric($value)) { - if ($value <= 0) { - return Functions::NAN(); - } - - return log(self::gamma($value)); - } - - return Functions::VALUE(); + /** + * GAUSS. + * + * Calculates the probability that a member of a standard normal population will fall between + * the mean and z standard deviations from the mean. + * + * @deprecated 1.18.0 + * Use the gauss() method in the Statistical\Distributions\StandardNormal class instead + * @see Statistical\Distributions\StandardNormal::gauss() + * + * @param float $value + * + * @return array|float|string The result, or a string containing an error + */ + public static function GAUSS($value) + { + return Statistical\Distributions\StandardNormal::gauss($value); } /** @@ -1707,7 +640,9 @@ class Statistical * Excel Function: * GEOMEAN(value1[,value2[, ...]]) * - * @category Statistical Functions + * @deprecated 1.18.0 + * Use the geometric() method in the Statistical\Averages\Mean class instead + * @see Statistical\Averages\Mean::geometric() * * @param mixed ...$args Data values * @@ -1715,17 +650,7 @@ class Statistical */ public static function GEOMEAN(...$args) { - $aArgs = Functions::flattenArray($args); - - $aMean = MathTrig::PRODUCT($aArgs); - if (is_numeric($aMean) && ($aMean > 0)) { - $aCount = self::COUNT($aArgs); - if (self::MIN($aArgs) > 0) { - return pow($aMean, (1 / $aCount)); - } - } - - return Functions::NAN(); + return Statistical\Averages\Mean::geometric(...$args); } /** @@ -1733,31 +658,20 @@ class Statistical * * Returns values along a predicted exponential Trend * + * @deprecated 1.18.0 + * Use the GROWTH() method in the Statistical\Trends class instead + * @see Statistical\Trends::GROWTH() + * * @param mixed[] $yValues Data Series Y * @param mixed[] $xValues Data Series X * @param mixed[] $newValues Values of X for which we want to find Y * @param bool $const a logical value specifying whether to force the intersect to equal 0 * - * @return array of float + * @return float[] */ public static function GROWTH($yValues, $xValues = [], $newValues = [], $const = true) { - $yValues = Functions::flattenArray($yValues); - $xValues = Functions::flattenArray($xValues); - $newValues = Functions::flattenArray($newValues); - $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const); - - $bestFitExponential = Trend::calculate(Trend::TREND_EXPONENTIAL, $yValues, $xValues, $const); - if (empty($newValues)) { - $newValues = $bestFitExponential->getXValues(); - } - - $returnArray = []; - foreach ($newValues as $xValue) { - $returnArray[0][] = $bestFitExponential->getValueOfYForX($xValue); - } - - return $returnArray; + return Trends::GROWTH($yValues, $xValues, $newValues, $const); } /** @@ -1769,7 +683,9 @@ class Statistical * Excel Function: * HARMEAN(value1[,value2[, ...]]) * - * @category Statistical Functions + * @deprecated 1.18.0 + * Use the harmonic() method in the Statistical\Averages\Mean class instead + * @see Statistical\Averages\Mean::harmonic() * * @param mixed ...$args Data values * @@ -1777,32 +693,7 @@ class Statistical */ public static function HARMEAN(...$args) { - // Return value - $returnValue = 0; - - // Loop through arguments - $aArgs = Functions::flattenArray($args); - if (self::MIN($aArgs) < 0) { - return Functions::NAN(); - } - $aCount = 0; - foreach ($aArgs as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - if ($arg <= 0) { - return Functions::NAN(); - } - $returnValue += (1 / $arg); - ++$aCount; - } - } - - // Return - if ($aCount > 0) { - return 1 / ($returnValue / $aCount); - } - - return Functions::NA(); + return Statistical\Averages\Mean::harmonic(...$args); } /** @@ -1811,37 +702,25 @@ class Statistical * Returns the hypergeometric distribution. HYPGEOMDIST returns the probability of a given number of * sample successes, given the sample size, population successes, and population size. * - * @param float $sampleSuccesses Number of successes in the sample - * @param float $sampleNumber Size of the sample - * @param float $populationSuccesses Number of successes in the population - * @param float $populationNumber Population size + * @deprecated 1.18.0 + * Use the distribution() method in the Statistical\Distributions\HyperGeometric class instead + * @see Statistical\Distributions\HyperGeometric::distribution() * - * @return float|string + * @param mixed $sampleSuccesses Number of successes in the sample + * @param mixed $sampleNumber Size of the sample + * @param mixed $populationSuccesses Number of successes in the population + * @param mixed $populationNumber Population size + * + * @return array|float|string */ public static function HYPGEOMDIST($sampleSuccesses, $sampleNumber, $populationSuccesses, $populationNumber) { - $sampleSuccesses = floor(Functions::flattenSingleValue($sampleSuccesses)); - $sampleNumber = floor(Functions::flattenSingleValue($sampleNumber)); - $populationSuccesses = floor(Functions::flattenSingleValue($populationSuccesses)); - $populationNumber = floor(Functions::flattenSingleValue($populationNumber)); - - if ((is_numeric($sampleSuccesses)) && (is_numeric($sampleNumber)) && (is_numeric($populationSuccesses)) && (is_numeric($populationNumber))) { - if (($sampleSuccesses < 0) || ($sampleSuccesses > $sampleNumber) || ($sampleSuccesses > $populationSuccesses)) { - return Functions::NAN(); - } - if (($sampleNumber <= 0) || ($sampleNumber > $populationNumber)) { - return Functions::NAN(); - } - if (($populationSuccesses <= 0) || ($populationSuccesses > $populationNumber)) { - return Functions::NAN(); - } - - return MathTrig::COMBIN($populationSuccesses, $sampleSuccesses) * - MathTrig::COMBIN($populationNumber - $populationSuccesses, $sampleNumber - $sampleSuccesses) / - MathTrig::COMBIN($populationNumber, $sampleNumber); - } - - return Functions::VALUE(); + return Statistical\Distributions\HyperGeometric::distribution( + $sampleSuccesses, + $sampleNumber, + $populationSuccesses, + $populationNumber + ); } /** @@ -1849,6 +728,10 @@ class Statistical * * Calculates the point at which a line will intersect the y-axis by using existing x-values and y-values. * + * @deprecated 1.18.0 + * Use the INTERCEPT() method in the Statistical\Trends class instead + * @see Statistical\Trends::INTERCEPT() + * * @param mixed[] $yValues Data Series Y * @param mixed[] $xValues Data Series X * @@ -1856,21 +739,7 @@ class Statistical */ public static function INTERCEPT($yValues, $xValues) { - if (!self::checkTrendArrays($yValues, $xValues)) { - return Functions::VALUE(); - } - $yValueCount = count($yValues); - $xValueCount = count($xValues); - - if (($yValueCount == 0) || ($yValueCount != $xValueCount)) { - return Functions::NA(); - } elseif ($yValueCount == 1) { - return Functions::DIV0(); - } - - $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); - - return $bestFitLinear->getIntersect(); + return Trends::INTERCEPT($yValues, $xValues); } /** @@ -1881,38 +750,17 @@ class Statistical * kurtosis indicates a relatively peaked distribution. Negative kurtosis indicates a * relatively flat distribution. * + * @deprecated 1.18.0 + * Use the kurtosis() method in the Statistical\Deviations class instead + * @see Statistical\Deviations::kurtosis() + * * @param array ...$args Data Series * * @return float|string */ public static function KURT(...$args) { - $aArgs = Functions::flattenArrayIndexed($args); - $mean = self::AVERAGE($aArgs); - $stdDev = self::STDEV($aArgs); - - if ($stdDev > 0) { - $count = $summer = 0; - // Loop through arguments - foreach ($aArgs as $k => $arg) { - if ((is_bool($arg)) && - (!Functions::isMatrixValue($k))) { - } else { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - $summer += pow((($arg - $mean) / $stdDev), 4); - ++$count; - } - } - } - - // Return - if ($count > 3) { - return $summer * ($count * ($count + 1) / (($count - 1) * ($count - 2) * ($count - 3))) - (3 * pow($count - 1, 2) / (($count - 2) * ($count - 3))); - } - } - - return Functions::DIV0(); + return Statistical\Deviations::kurtosis(...$args); } /** @@ -1924,39 +772,17 @@ class Statistical * Excel Function: * LARGE(value1[,value2[, ...]],entry) * - * @category Statistical Functions + * @deprecated 1.18.0 + * Use the large() method in the Statistical\Size class instead + * @see Statistical\Size::large() * * @param mixed $args Data values - * @param int $entry Position (ordered from the largest) in the array or range of data to return * - * @return float + * @return float|string The result, or a string containing an error */ public static function LARGE(...$args) { - $aArgs = Functions::flattenArray($args); - - // Calculate - $entry = floor(array_pop($aArgs)); - - if ((is_numeric($entry)) && (!is_string($entry))) { - $mArgs = []; - foreach ($aArgs as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - $mArgs[] = $arg; - } - } - $count = self::COUNT($mArgs); - $entry = floor(--$entry); - if (($entry < 0) || ($entry >= $count) || ($count == 0)) { - return Functions::NAN(); - } - rsort($mArgs); - - return $mArgs[$entry]; - } - - return Functions::VALUE(); + return Statistical\Size::large(...$args); } /** @@ -1965,57 +791,20 @@ class Statistical * Calculates the statistics for a line by using the "least squares" method to calculate a straight line that best fits your data, * and then returns an array that describes the line. * + * @deprecated 1.18.0 + * Use the LINEST() method in the Statistical\Trends class instead + * @see Statistical\Trends::LINEST() + * * @param mixed[] $yValues Data Series Y * @param null|mixed[] $xValues Data Series X * @param bool $const a logical value specifying whether to force the intersect to equal 0 * @param bool $stats a logical value specifying whether to return additional regression statistics * - * @return array + * @return array|int|string The result, or a string containing an error */ public static function LINEST($yValues, $xValues = null, $const = true, $stats = false) { - $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const); - $stats = ($stats === null) ? false : (bool) Functions::flattenSingleValue($stats); - if ($xValues === null) { - $xValues = range(1, count(Functions::flattenArray($yValues))); - } - - if (!self::checkTrendArrays($yValues, $xValues)) { - return Functions::VALUE(); - } - $yValueCount = count($yValues); - $xValueCount = count($xValues); - - if (($yValueCount == 0) || ($yValueCount != $xValueCount)) { - return Functions::NA(); - } elseif ($yValueCount == 1) { - return 0; - } - - $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues, $const); - if ($stats) { - return [ - [ - $bestFitLinear->getSlope(), - $bestFitLinear->getSlopeSE(), - $bestFitLinear->getGoodnessOfFit(), - $bestFitLinear->getF(), - $bestFitLinear->getSSRegression(), - ], - [ - $bestFitLinear->getIntersect(), - $bestFitLinear->getIntersectSE(), - $bestFitLinear->getStdevOfResiduals(), - $bestFitLinear->getDFResiduals(), - $bestFitLinear->getSSResiduals(), - ], - ]; - } - - return [ - $bestFitLinear->getSlope(), - $bestFitLinear->getIntersect(), - ]; + return Trends::LINEST($yValues, $xValues, $const, $stats); } /** @@ -2024,63 +813,20 @@ class Statistical * Calculates an exponential curve that best fits the X and Y data series, * and then returns an array that describes the line. * + * @deprecated 1.18.0 + * Use the LOGEST() method in the Statistical\Trends class instead + * @see Statistical\Trends::LOGEST() + * * @param mixed[] $yValues Data Series Y * @param null|mixed[] $xValues Data Series X * @param bool $const a logical value specifying whether to force the intersect to equal 0 * @param bool $stats a logical value specifying whether to return additional regression statistics * - * @return array + * @return array|int|string The result, or a string containing an error */ public static function LOGEST($yValues, $xValues = null, $const = true, $stats = false) { - $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const); - $stats = ($stats === null) ? false : (bool) Functions::flattenSingleValue($stats); - if ($xValues === null) { - $xValues = range(1, count(Functions::flattenArray($yValues))); - } - - if (!self::checkTrendArrays($yValues, $xValues)) { - return Functions::VALUE(); - } - $yValueCount = count($yValues); - $xValueCount = count($xValues); - - foreach ($yValues as $value) { - if ($value <= 0.0) { - return Functions::NAN(); - } - } - - if (($yValueCount == 0) || ($yValueCount != $xValueCount)) { - return Functions::NA(); - } elseif ($yValueCount == 1) { - return 1; - } - - $bestFitExponential = Trend::calculate(Trend::TREND_EXPONENTIAL, $yValues, $xValues, $const); - if ($stats) { - return [ - [ - $bestFitExponential->getSlope(), - $bestFitExponential->getSlopeSE(), - $bestFitExponential->getGoodnessOfFit(), - $bestFitExponential->getF(), - $bestFitExponential->getSSRegression(), - ], - [ - $bestFitExponential->getIntersect(), - $bestFitExponential->getIntersectSE(), - $bestFitExponential->getStdevOfResiduals(), - $bestFitExponential->getDFResiduals(), - $bestFitExponential->getSSResiduals(), - ], - ]; - } - - return [ - $bestFitExponential->getSlope(), - $bestFitExponential->getIntersect(), - ]; + return Trends::LOGEST($yValues, $xValues, $const, $stats); } /** @@ -2088,31 +834,23 @@ class Statistical * * Returns the inverse of the normal cumulative distribution * + * @deprecated 1.18.0 + * Use the inverse() method in the Statistical\Distributions\LogNormal class instead + * @see Statistical\Distributions\LogNormal::inverse() + * * @param float $probability * @param float $mean * @param float $stdDev * - * @return float + * @return array|float|string The result, or a string containing an error * - * @todo Try implementing P J Acklam's refinement algorithm for greater + * @TODO Try implementing P J Acklam's refinement algorithm for greater * accuracy if I can get my head round the mathematics * (as described at) http://home.online.no/~pjacklam/notes/invnorm/ */ public static function LOGINV($probability, $mean, $stdDev) { - $probability = Functions::flattenSingleValue($probability); - $mean = Functions::flattenSingleValue($mean); - $stdDev = Functions::flattenSingleValue($stdDev); - - if ((is_numeric($probability)) && (is_numeric($mean)) && (is_numeric($stdDev))) { - if (($probability < 0) || ($probability > 1) || ($stdDev <= 0)) { - return Functions::NAN(); - } - - return exp($mean + $stdDev * self::NORMSINV($probability)); - } - - return Functions::VALUE(); + return Statistical\Distributions\LogNormal::inverse($probability, $mean, $stdDev); } /** @@ -2121,27 +859,41 @@ class Statistical * Returns the cumulative lognormal distribution of x, where ln(x) is normally distributed * with parameters mean and standard_dev. * + * @deprecated 1.18.0 + * Use the cumulative() method in the Statistical\Distributions\LogNormal class instead + * @see Statistical\Distributions\LogNormal::cumulative() + * * @param float $value * @param float $mean * @param float $stdDev * - * @return float + * @return array|float|string The result, or a string containing an error */ public static function LOGNORMDIST($value, $mean, $stdDev) { - $value = Functions::flattenSingleValue($value); - $mean = Functions::flattenSingleValue($mean); - $stdDev = Functions::flattenSingleValue($stdDev); + return Statistical\Distributions\LogNormal::cumulative($value, $mean, $stdDev); + } - if ((is_numeric($value)) && (is_numeric($mean)) && (is_numeric($stdDev))) { - if (($value <= 0) || ($stdDev <= 0)) { - return Functions::NAN(); - } - - return self::NORMSDIST((log($value) - $mean) / $stdDev); - } - - return Functions::VALUE(); + /** + * LOGNORM.DIST. + * + * Returns the lognormal distribution of x, where ln(x) is normally distributed + * with parameters mean and standard_dev. + * + * @deprecated 1.18.0 + * Use the distribution() method in the Statistical\Distributions\LogNormal class instead + * @see Statistical\Distributions\LogNormal::distribution() + * + * @param float $value + * @param float $mean + * @param float $stdDev + * @param bool $cumulative + * + * @return array|float|string The result, or a string containing an error + */ + public static function LOGNORMDIST2($value, $mean, $stdDev, $cumulative = false) + { + return Statistical\Distributions\LogNormal::distribution($value, $mean, $stdDev, $cumulative); } /** @@ -2151,9 +903,11 @@ class Statistical * with negative numbers considered smaller than positive numbers. * * Excel Function: - * MAX(value1[,value2[, ...]]) + * max(value1[,value2[, ...]]) * - * @category Statistical Functions + * @deprecated 1.17.0 + * Use the MAX() method in the Statistical\Maximum class instead + * @see Statistical\Maximum::max() * * @param mixed ...$args Data values * @@ -2161,24 +915,7 @@ class Statistical */ public static function MAX(...$args) { - $returnValue = null; - - // Loop through arguments - $aArgs = Functions::flattenArray($args); - foreach ($aArgs as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - if (($returnValue === null) || ($arg > $returnValue)) { - $returnValue = $arg; - } - } - } - - if ($returnValue === null) { - return 0; - } - - return $returnValue; + return Maximum::max(...$args); } /** @@ -2187,9 +924,11 @@ class Statistical * Returns the greatest value in a list of arguments, including numbers, text, and logical values * * Excel Function: - * MAXA(value1[,value2[, ...]]) + * maxA(value1[,value2[, ...]]) * - * @category Statistical Functions + * @deprecated 1.17.0 + * Use the MAXA() method in the Statistical\Maximum class instead + * @see Statistical\Maximum::maxA() * * @param mixed ...$args Data values * @@ -2197,29 +936,7 @@ class Statistical */ public static function MAXA(...$args) { - $returnValue = null; - - // Loop through arguments - $aArgs = Functions::flattenArray($args); - foreach ($aArgs as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) { - if (is_bool($arg)) { - $arg = (int) $arg; - } elseif (is_string($arg)) { - $arg = 0; - } - if (($returnValue === null) || ($arg > $returnValue)) { - $returnValue = $arg; - } - } - } - - if ($returnValue === null) { - return 0; - } - - return $returnValue; + return Maximum::maxA(...$args); } /** @@ -2230,7 +947,9 @@ class Statistical * Excel Function: * MAXIFS(max_range, criteria_range1, criteria1, [criteria_range2, criteria2], ...) * - * @category Statistical Functions + * @deprecated 1.17.0 + * Use the MAXIFS() method in the Statistical\Conditional class instead + * @see Statistical\Conditional::MAXIFS() * * @param mixed $args Data range and criterias * @@ -2238,47 +957,7 @@ class Statistical */ public static function MAXIFS(...$args) { - $arrayList = $args; - - // Return value - $returnValue = null; - - $maxArgs = Functions::flattenArray(array_shift($arrayList)); - $aArgsArray = []; - $conditions = []; - - while (count($arrayList) > 0) { - $aArgsArray[] = Functions::flattenArray(array_shift($arrayList)); - $conditions[] = Functions::ifCondition(array_shift($arrayList)); - } - - // Loop through each arg and see if arguments and conditions are true - foreach ($maxArgs as $index => $value) { - $valid = true; - - foreach ($conditions as $cidx => $condition) { - $arg = $aArgsArray[$cidx][$index]; - - // Loop through arguments - if (!is_numeric($arg)) { - $arg = Calculation::wrapResult(strtoupper($arg)); - } - $testCondition = '=' . $arg . $condition; - if (!Calculation::getInstance()->_calculateFormulaValue($testCondition)) { - // Is not a value within our criteria - $valid = false; - - break; // if false found, don't need to check other conditions - } - } - - if ($valid) { - $returnValue = $returnValue === null ? $value : max($value, $returnValue); - } - } - - // Return - return $returnValue; + return Conditional::MAXIFS(...$args); } /** @@ -2289,39 +968,17 @@ class Statistical * Excel Function: * MEDIAN(value1[,value2[, ...]]) * - * @category Statistical Functions + * @deprecated 1.18.0 + * Use the median() method in the Statistical\Averages class instead + * @see Statistical\Averages::median() * * @param mixed ...$args Data values * - * @return float + * @return float|string The result, or a string containing an error */ public static function MEDIAN(...$args) { - $returnValue = Functions::NAN(); - - $mArgs = []; - // Loop through arguments - $aArgs = Functions::flattenArray($args); - foreach ($aArgs as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - $mArgs[] = $arg; - } - } - - $mValueCount = count($mArgs); - if ($mValueCount > 0) { - sort($mArgs, SORT_NUMERIC); - $mValueCount = $mValueCount / 2; - if ($mValueCount == floor($mValueCount)) { - $returnValue = ($mArgs[$mValueCount--] + $mArgs[$mValueCount]) / 2; - } else { - $mValueCount = floor($mValueCount); - $returnValue = $mArgs[$mValueCount]; - } - } - - return $returnValue; + return Statistical\Averages::median(...$args); } /** @@ -2333,7 +990,9 @@ class Statistical * Excel Function: * MIN(value1[,value2[, ...]]) * - * @category Statistical Functions + * @deprecated 1.17.0 + * Use the min() method in the Statistical\Minimum class instead + * @see Statistical\Minimum::min() * * @param mixed ...$args Data values * @@ -2341,24 +1000,7 @@ class Statistical */ public static function MIN(...$args) { - $returnValue = null; - - // Loop through arguments - $aArgs = Functions::flattenArray($args); - foreach ($aArgs as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - if (($returnValue === null) || ($arg < $returnValue)) { - $returnValue = $arg; - } - } - } - - if ($returnValue === null) { - return 0; - } - - return $returnValue; + return Minimum::min(...$args); } /** @@ -2369,7 +1011,9 @@ class Statistical * Excel Function: * MINA(value1[,value2[, ...]]) * - * @category Statistical Functions + * @deprecated 1.17.0 + * Use the minA() method in the Statistical\Minimum class instead + * @see Statistical\Minimum::minA() * * @param mixed ...$args Data values * @@ -2377,29 +1021,7 @@ class Statistical */ public static function MINA(...$args) { - $returnValue = null; - - // Loop through arguments - $aArgs = Functions::flattenArray($args); - foreach ($aArgs as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) { - if (is_bool($arg)) { - $arg = (int) $arg; - } elseif (is_string($arg)) { - $arg = 0; - } - if (($returnValue === null) || ($arg < $returnValue)) { - $returnValue = $arg; - } - } - } - - if ($returnValue === null) { - return 0; - } - - return $returnValue; + return Minimum::minA(...$args); } /** @@ -2410,7 +1032,9 @@ class Statistical * Excel Function: * MINIFS(min_range, criteria_range1, criteria1, [criteria_range2, criteria2], ...) * - * @category Statistical Functions + * @deprecated 1.17.0 + * Use the MINIFS() method in the Statistical\Conditional class instead + * @see Statistical\Conditional::MINIFS() * * @param mixed $args Data range and criterias * @@ -2418,85 +1042,7 @@ class Statistical */ public static function MINIFS(...$args) { - $arrayList = $args; - - // Return value - $returnValue = null; - - $minArgs = Functions::flattenArray(array_shift($arrayList)); - $aArgsArray = []; - $conditions = []; - - while (count($arrayList) > 0) { - $aArgsArray[] = Functions::flattenArray(array_shift($arrayList)); - $conditions[] = Functions::ifCondition(array_shift($arrayList)); - } - - // Loop through each arg and see if arguments and conditions are true - foreach ($minArgs as $index => $value) { - $valid = true; - - foreach ($conditions as $cidx => $condition) { - $arg = $aArgsArray[$cidx][$index]; - - // Loop through arguments - if (!is_numeric($arg)) { - $arg = Calculation::wrapResult(strtoupper($arg)); - } - $testCondition = '=' . $arg . $condition; - if (!Calculation::getInstance()->_calculateFormulaValue($testCondition)) { - // Is not a value within our criteria - $valid = false; - - break; // if false found, don't need to check other conditions - } - } - - if ($valid) { - $returnValue = $returnValue === null ? $value : min($value, $returnValue); - } - } - - // Return - return $returnValue; - } - - // - // Special variant of array_count_values that isn't limited to strings and integers, - // but can work with floating point numbers as values - // - private static function modeCalc($data) - { - $frequencyArray = []; - foreach ($data as $datum) { - $found = false; - foreach ($frequencyArray as $key => $value) { - if ((string) $value['value'] == (string) $datum) { - ++$frequencyArray[$key]['frequency']; - $found = true; - - break; - } - } - if (!$found) { - $frequencyArray[] = [ - 'value' => $datum, - 'frequency' => 1, - ]; - } - } - - foreach ($frequencyArray as $key => $value) { - $frequencyList[$key] = $value['frequency']; - $valueList[$key] = $value['value']; - } - array_multisort($frequencyList, SORT_DESC, $valueList, SORT_ASC, SORT_NUMERIC, $frequencyArray); - - if ($frequencyArray[0]['frequency'] == 1) { - return Functions::NA(); - } - - return $frequencyArray[0]['value']; + return Conditional::MINIFS(...$args); } /** @@ -2507,32 +1053,17 @@ class Statistical * Excel Function: * MODE(value1[,value2[, ...]]) * - * @category Statistical Functions + * @deprecated 1.18.0 + * Use the mode() method in the Statistical\Averages class instead + * @see Statistical\Averages::mode() * * @param mixed ...$args Data values * - * @return float + * @return float|string The result, or a string containing an error */ public static function MODE(...$args) { - $returnValue = Functions::NA(); - - // Loop through arguments - $aArgs = Functions::flattenArray($args); - - $mArgs = []; - foreach ($aArgs as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - $mArgs[] = $arg; - } - } - - if (!empty($mArgs)) { - return self::modeCalc($mArgs); - } - - return $returnValue; + return Statistical\Averages::mode(...$args); } /** @@ -2544,34 +1075,19 @@ class Statistical * distribution, except that the number of successes is fixed, and the number of trials is * variable. Like the binomial, trials are assumed to be independent. * - * @param float $failures Number of Failures - * @param float $successes Threshold number of Successes - * @param float $probability Probability of success on each trial + * @deprecated 1.18.0 + * Use the negative() method in the Statistical\Distributions\Binomial class instead + * @see Statistical\Distributions\Binomial::negative() * - * @return float + * @param mixed $failures Number of Failures + * @param mixed $successes Threshold number of Successes + * @param mixed $probability Probability of success on each trial + * + * @return array|float|string The result, or a string containing an error */ public static function NEGBINOMDIST($failures, $successes, $probability) { - $failures = floor(Functions::flattenSingleValue($failures)); - $successes = floor(Functions::flattenSingleValue($successes)); - $probability = Functions::flattenSingleValue($probability); - - if ((is_numeric($failures)) && (is_numeric($successes)) && (is_numeric($probability))) { - if (($failures < 0) || ($successes < 1)) { - return Functions::NAN(); - } elseif (($probability < 0) || ($probability > 1)) { - return Functions::NAN(); - } - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { - if (($failures + $successes - 1) <= 0) { - return Functions::NAN(); - } - } - - return (MathTrig::COMBIN($failures + $successes - 1, $successes - 1)) * (pow($probability, $successes)) * (pow(1 - $probability, $failures)); - } - - return Functions::VALUE(); + return Statistical\Distributions\Binomial::negative($failures, $successes, $probability); } /** @@ -2581,33 +1097,20 @@ class Statistical * function has a very wide range of applications in statistics, including hypothesis * testing. * - * @param float $value - * @param float $mean Mean Value - * @param float $stdDev Standard Deviation - * @param bool $cumulative + * @deprecated 1.18.0 + * Use the distribution() method in the Statistical\Distributions\Normal class instead + * @see Statistical\Distributions\Normal::distribution() * - * @return float + * @param mixed $value + * @param mixed $mean Mean Value + * @param mixed $stdDev Standard Deviation + * @param mixed $cumulative + * + * @return array|float|string The result, or a string containing an error */ public static function NORMDIST($value, $mean, $stdDev, $cumulative) { - $value = Functions::flattenSingleValue($value); - $mean = Functions::flattenSingleValue($mean); - $stdDev = Functions::flattenSingleValue($stdDev); - - if ((is_numeric($value)) && (is_numeric($mean)) && (is_numeric($stdDev))) { - if ($stdDev < 0) { - return Functions::NAN(); - } - if ((is_numeric($cumulative)) || (is_bool($cumulative))) { - if ($cumulative) { - return 0.5 * (1 + Engineering::erfVal(($value - $mean) / ($stdDev * sqrt(2)))); - } - - return (1 / (self::SQRT2PI * $stdDev)) * exp(0 - (pow($value - $mean, 2) / (2 * ($stdDev * $stdDev)))); - } - } - - return Functions::VALUE(); + return Statistical\Distributions\Normal::distribution($value, $mean, $stdDev, $cumulative); } /** @@ -2615,30 +1118,19 @@ class Statistical * * Returns the inverse of the normal cumulative distribution for the specified mean and standard deviation. * - * @param float $probability - * @param float $mean Mean Value - * @param float $stdDev Standard Deviation + * @deprecated 1.18.0 + * Use the inverse() method in the Statistical\Distributions\Normal class instead + * @see Statistical\Distributions\Normal::inverse() * - * @return float + * @param mixed $probability + * @param mixed $mean Mean Value + * @param mixed $stdDev Standard Deviation + * + * @return array|float|string The result, or a string containing an error */ public static function NORMINV($probability, $mean, $stdDev) { - $probability = Functions::flattenSingleValue($probability); - $mean = Functions::flattenSingleValue($mean); - $stdDev = Functions::flattenSingleValue($stdDev); - - if ((is_numeric($probability)) && (is_numeric($mean)) && (is_numeric($stdDev))) { - if (($probability < 0) || ($probability > 1)) { - return Functions::NAN(); - } - if ($stdDev < 0) { - return Functions::NAN(); - } - - return (self::inverseNcdf($probability) * $stdDev) + $mean; - } - - return Functions::VALUE(); + return Statistical\Distributions\Normal::inverse($probability, $mean, $stdDev); } /** @@ -2648,15 +1140,38 @@ class Statistical * a mean of 0 (zero) and a standard deviation of one. Use this function in place of a * table of standard normal curve areas. * - * @param float $value + * @deprecated 1.18.0 + * Use the cumulative() method in the Statistical\Distributions\StandardNormal class instead + * @see Statistical\Distributions\StandardNormal::cumulative() * - * @return float + * @param mixed $value + * + * @return array|float|string The result, or a string containing an error */ public static function NORMSDIST($value) { - $value = Functions::flattenSingleValue($value); + return Statistical\Distributions\StandardNormal::cumulative($value); + } - return self::NORMDIST($value, 0, 1, true); + /** + * NORM.S.DIST. + * + * Returns the standard normal cumulative distribution function. The distribution has + * a mean of 0 (zero) and a standard deviation of one. Use this function in place of a + * table of standard normal curve areas. + * + * @deprecated 1.18.0 + * Use the distribution() method in the Statistical\Distributions\StandardNormal class instead + * @see Statistical\Distributions\StandardNormal::distribution() + * + * @param mixed $value + * @param mixed $cumulative + * + * @return array|float|string The result, or a string containing an error + */ + public static function NORMSDIST2($value, $cumulative) + { + return Statistical\Distributions\StandardNormal::distribution($value, $cumulative); } /** @@ -2664,13 +1179,17 @@ class Statistical * * Returns the inverse of the standard normal cumulative distribution * - * @param float $value + * @deprecated 1.18.0 + * Use the inverse() method in the Statistical\Distributions\StandardNormal class instead + * @see Statistical\Distributions\StandardNormal::inverse() * - * @return float + * @param mixed $value + * + * @return array|float|string The result, or a string containing an error */ public static function NORMSINV($value) { - return self::NORMINV($value, 0, 1); + return Statistical\Distributions\StandardNormal::inverse($value); } /** @@ -2681,95 +1200,40 @@ class Statistical * Excel Function: * PERCENTILE(value1[,value2[, ...]],entry) * - * @category Statistical Functions + * @deprecated 1.18.0 + * Use the PERCENTILE() method in the Statistical\Percentiles class instead + * @see Statistical\Percentiles::PERCENTILE() * * @param mixed $args Data values - * @param float $entry Percentile value in the range 0..1, inclusive. * - * @return float + * @return float|string The result, or a string containing an error */ public static function PERCENTILE(...$args) { - $aArgs = Functions::flattenArray($args); - - // Calculate - $entry = array_pop($aArgs); - - if ((is_numeric($entry)) && (!is_string($entry))) { - if (($entry < 0) || ($entry > 1)) { - return Functions::NAN(); - } - $mArgs = []; - foreach ($aArgs as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - $mArgs[] = $arg; - } - } - $mValueCount = count($mArgs); - if ($mValueCount > 0) { - sort($mArgs); - $count = self::COUNT($mArgs); - $index = $entry * ($count - 1); - $iBase = floor($index); - if ($index == $iBase) { - return $mArgs[$index]; - } - $iNext = $iBase + 1; - $iProportion = $index - $iBase; - - return $mArgs[$iBase] + (($mArgs[$iNext] - $mArgs[$iBase]) * $iProportion); - } - } - - return Functions::VALUE(); + return Statistical\Percentiles::PERCENTILE(...$args); } /** * PERCENTRANK. * * Returns the rank of a value in a data set as a percentage of the data set. + * Note that the returned rank is simply rounded to the appropriate significant digits, + * rather than floored (as MS Excel), so value 3 for a value set of 1, 2, 3, 4 will return + * 0.667 rather than 0.666 * - * @param float[] $valueSet An array of, or a reference to, a list of numbers - * @param int $value the number whose rank you want to find - * @param int $significance the number of significant digits for the returned percentage value + * @deprecated 1.18.0 + * Use the PERCENTRANK() method in the Statistical\Percentiles class instead + * @see Statistical\Percentiles::PERCENTRANK() * - * @return float + * @param mixed $valueSet An array of, or a reference to, a list of numbers + * @param mixed $value the number whose rank you want to find + * @param mixed $significance the number of significant digits for the returned percentage value + * + * @return float|string (string if result is an error) */ public static function PERCENTRANK($valueSet, $value, $significance = 3) { - $valueSet = Functions::flattenArray($valueSet); - $value = Functions::flattenSingleValue($value); - $significance = ($significance === null) ? 3 : (int) Functions::flattenSingleValue($significance); - - foreach ($valueSet as $key => $valueEntry) { - if (!is_numeric($valueEntry)) { - unset($valueSet[$key]); - } - } - sort($valueSet, SORT_NUMERIC); - $valueCount = count($valueSet); - if ($valueCount == 0) { - return Functions::NAN(); - } - - $valueAdjustor = $valueCount - 1; - if (($value < $valueSet[0]) || ($value > $valueSet[$valueAdjustor])) { - return Functions::NA(); - } - - $pos = array_search($value, $valueSet); - if ($pos === false) { - $pos = 0; - $testValue = $valueSet[0]; - while ($testValue < $value) { - $testValue = $valueSet[++$pos]; - } - --$pos; - $pos += (($value - $valueSet[$pos]) / ($testValue - $valueSet[$pos])); - } - - return round($pos / $valueAdjustor, $significance); + return Statistical\Percentiles::PERCENTRANK($valueSet, $value, $significance); } /** @@ -2781,26 +1245,18 @@ class Statistical * combinations, for which the internal order is not significant. Use this function * for lottery-style probability calculations. * + * @deprecated 1.17.0 + * Use the PERMUT() method in the Statistical\Permutations class instead + * @see Statistical\Permutations::PERMUT() + * * @param int $numObjs Number of different objects * @param int $numInSet Number of objects in each permutation * - * @return int|string Number of permutations + * @return array|float|int|string Number of permutations, or a string containing an error */ public static function PERMUT($numObjs, $numInSet) { - $numObjs = Functions::flattenSingleValue($numObjs); - $numInSet = Functions::flattenSingleValue($numInSet); - - if ((is_numeric($numObjs)) && (is_numeric($numInSet))) { - $numInSet = floor($numInSet); - if ($numObjs < $numInSet) { - return Functions::NAN(); - } - - return round(MathTrig::FACT($numObjs) / MathTrig::FACT($numObjs - $numInSet)); - } - - return Functions::VALUE(); + return Permutations::PERMUT($numObjs, $numInSet); } /** @@ -2810,37 +1266,19 @@ class Statistical * is predicting the number of events over a specific time, such as the number of * cars arriving at a toll plaza in 1 minute. * - * @param float $value - * @param float $mean Mean Value - * @param bool $cumulative + * @deprecated 1.18.0 + * Use the distribution() method in the Statistical\Distributions\Poisson class instead + * @see Statistical\Distributions\Poisson::distribution() * - * @return float + * @param mixed $value + * @param mixed $mean Mean Value + * @param mixed $cumulative + * + * @return array|float|string The result, or a string containing an error */ public static function POISSON($value, $mean, $cumulative) { - $value = Functions::flattenSingleValue($value); - $mean = Functions::flattenSingleValue($mean); - - if ((is_numeric($value)) && (is_numeric($mean))) { - if (($value < 0) || ($mean <= 0)) { - return Functions::NAN(); - } - if ((is_numeric($cumulative)) || (is_bool($cumulative))) { - if ($cumulative) { - $summer = 0; - $floor = floor($value); - for ($i = 0; $i <= $floor; ++$i) { - $summer += pow($mean, $i) / MathTrig::FACT($i); - } - - return exp(0 - $mean) * $summer; - } - - return (exp(0 - $mean) * pow($mean, $value)) / MathTrig::FACT($value); - } - } - - return Functions::VALUE(); + return Statistical\Distributions\Poisson::distribution($value, $mean, $cumulative); } /** @@ -2851,30 +1289,17 @@ class Statistical * Excel Function: * QUARTILE(value1[,value2[, ...]],entry) * - * @category Statistical Functions + * @deprecated 1.18.0 + * Use the QUARTILE() method in the Statistical\Percentiles class instead + * @see Statistical\Percentiles::QUARTILE() * * @param mixed $args Data values - * @param int $entry Quartile value in the range 1..3, inclusive. * - * @return float + * @return float|string The result, or a string containing an error */ public static function QUARTILE(...$args) { - $aArgs = Functions::flattenArray($args); - - // Calculate - $entry = floor(array_pop($aArgs)); - - if ((is_numeric($entry)) && (!is_string($entry))) { - $entry /= 4; - if (($entry < 0) || ($entry > 1)) { - return Functions::NAN(); - } - - return self::PERCENTILE($aArgs, $entry); - } - - return Functions::VALUE(); + return Statistical\Percentiles::QUARTILE(...$args); } /** @@ -2882,35 +1307,19 @@ class Statistical * * Returns the rank of a number in a list of numbers. * - * @param int $value the number whose rank you want to find - * @param float[] $valueSet An array of, or a reference to, a list of numbers - * @param int $order Order to sort the values in the value set + * @deprecated 1.18.0 + * Use the RANK() method in the Statistical\Percentiles class instead + * @see Statistical\Percentiles::RANK() * - * @return float + * @param mixed $value the number whose rank you want to find + * @param mixed $valueSet An array of, or a reference to, a list of numbers + * @param mixed $order Order to sort the values in the value set + * + * @return float|string The result, or a string containing an error */ public static function RANK($value, $valueSet, $order = 0) { - $value = Functions::flattenSingleValue($value); - $valueSet = Functions::flattenArray($valueSet); - $order = ($order === null) ? 0 : (int) Functions::flattenSingleValue($order); - - foreach ($valueSet as $key => $valueEntry) { - if (!is_numeric($valueEntry)) { - unset($valueSet[$key]); - } - } - - if ($order == 0) { - rsort($valueSet, SORT_NUMERIC); - } else { - sort($valueSet, SORT_NUMERIC); - } - $pos = array_search($value, $valueSet); - if ($pos === false) { - return Functions::NA(); - } - - return ++$pos; + return Statistical\Percentiles::RANK($value, $valueSet, $order); } /** @@ -2918,28 +1327,18 @@ class Statistical * * Returns the square of the Pearson product moment correlation coefficient through data points in known_y's and known_x's. * + * @deprecated 1.18.0 + * Use the RSQ() method in the Statistical\Trends class instead + * @see Statistical\Trends::RSQ() + * * @param mixed[] $yValues Data Series Y * @param mixed[] $xValues Data Series X * - * @return float|string + * @return float|string The result, or a string containing an error */ public static function RSQ($yValues, $xValues) { - if (!self::checkTrendArrays($yValues, $xValues)) { - return Functions::VALUE(); - } - $yValueCount = count($yValues); - $xValueCount = count($xValues); - - if (($yValueCount == 0) || ($yValueCount != $xValueCount)) { - return Functions::NA(); - } elseif ($yValueCount == 1) { - return Functions::DIV0(); - } - - $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); - - return $bestFitLinear->getGoodnessOfFit(); + return Trends::RSQ($yValues, $xValues); } /** @@ -2950,35 +1349,17 @@ class Statistical * asymmetric tail extending toward more positive values. Negative skewness indicates a * distribution with an asymmetric tail extending toward more negative values. * + * @deprecated 1.18.0 + * Use the skew() method in the Statistical\Deviations class instead + * @see Statistical\Deviations::skew() + * * @param array ...$args Data Series * - * @return float|string + * @return float|string The result, or a string containing an error */ public static function SKEW(...$args) { - $aArgs = Functions::flattenArrayIndexed($args); - $mean = self::AVERAGE($aArgs); - $stdDev = self::STDEV($aArgs); - - $count = $summer = 0; - // Loop through arguments - foreach ($aArgs as $k => $arg) { - if ((is_bool($arg)) && - (!Functions::isMatrixValue($k))) { - } else { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - $summer += pow((($arg - $mean) / $stdDev), 3); - ++$count; - } - } - } - - if ($count > 2) { - return $summer * ($count / (($count - 1) * ($count - 2))); - } - - return Functions::DIV0(); + return Statistical\Deviations::skew(...$args); } /** @@ -2986,28 +1367,18 @@ class Statistical * * Returns the slope of the linear regression line through data points in known_y's and known_x's. * + * @deprecated 1.18.0 + * Use the SLOPE() method in the Statistical\Trends class instead + * @see Statistical\Trends::SLOPE() + * * @param mixed[] $yValues Data Series Y * @param mixed[] $xValues Data Series X * - * @return float|string + * @return float|string The result, or a string containing an error */ public static function SLOPE($yValues, $xValues) { - if (!self::checkTrendArrays($yValues, $xValues)) { - return Functions::VALUE(); - } - $yValueCount = count($yValues); - $xValueCount = count($xValues); - - if (($yValueCount == 0) || ($yValueCount != $xValueCount)) { - return Functions::NA(); - } elseif ($yValueCount == 1) { - return Functions::DIV0(); - } - - $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); - - return $bestFitLinear->getSlope(); + return Trends::SLOPE($yValues, $xValues); } /** @@ -3019,39 +1390,17 @@ class Statistical * Excel Function: * SMALL(value1[,value2[, ...]],entry) * - * @category Statistical Functions + * @deprecated 1.18.0 + * Use the small() method in the Statistical\Size class instead + * @see Statistical\Size::small() * * @param mixed $args Data values - * @param int $entry Position (ordered from the smallest) in the array or range of data to return * - * @return float + * @return float|string The result, or a string containing an error */ public static function SMALL(...$args) { - $aArgs = Functions::flattenArray($args); - - // Calculate - $entry = array_pop($aArgs); - - if ((is_numeric($entry)) && (!is_string($entry))) { - $mArgs = []; - foreach ($aArgs as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - $mArgs[] = $arg; - } - } - $count = self::COUNT($mArgs); - $entry = floor(--$entry); - if (($entry < 0) || ($entry >= $count) || ($count == 0)) { - return Functions::NAN(); - } - sort($mArgs); - - return $mArgs[$entry]; - } - - return Functions::VALUE(); + return Statistical\Size::small(...$args); } /** @@ -3059,27 +1408,19 @@ class Statistical * * Returns a normalized value from a distribution characterized by mean and standard_dev. * + * @deprecated 1.18.0 + * Use the execute() method in the Statistical\Standardize class instead + * @see Statistical\Standardize::execute() + * * @param float $value Value to normalize * @param float $mean Mean Value * @param float $stdDev Standard Deviation * - * @return float Standardized value + * @return array|float|string Standardized value, or a string containing an error */ public static function STANDARDIZE($value, $mean, $stdDev) { - $value = Functions::flattenSingleValue($value); - $mean = Functions::flattenSingleValue($mean); - $stdDev = Functions::flattenSingleValue($stdDev); - - if ((is_numeric($value)) && (is_numeric($mean)) && (is_numeric($stdDev))) { - if ($stdDev <= 0) { - return Functions::NAN(); - } - - return ($value - $mean) / $stdDev; - } - - return Functions::VALUE(); + return Statistical\Standardize::execute($value, $mean, $stdDev); } /** @@ -3091,45 +1432,17 @@ class Statistical * Excel Function: * STDEV(value1[,value2[, ...]]) * - * @category Statistical Functions + * @deprecated 1.17.0 + * Use the STDEV() method in the Statistical\StandardDeviations class instead + * @see Statistical\StandardDeviations::STDEV() * * @param mixed ...$args Data values * - * @return float|string + * @return float|string The result, or a string containing an error */ public static function STDEV(...$args) { - $aArgs = Functions::flattenArrayIndexed($args); - - // Return value - $returnValue = null; - - $aMean = self::AVERAGE($aArgs); - if ($aMean !== null) { - $aCount = -1; - foreach ($aArgs as $k => $arg) { - if ((is_bool($arg)) && - ((!Functions::isCellValue($k)) || (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE))) { - $arg = (int) $arg; - } - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - if ($returnValue === null) { - $returnValue = pow(($arg - $aMean), 2); - } else { - $returnValue += pow(($arg - $aMean), 2); - } - ++$aCount; - } - } - - // Return - if (($aCount > 0) && ($returnValue >= 0)) { - return sqrt($returnValue / $aCount); - } - } - - return Functions::DIV0(); + return StandardDeviations::STDEV(...$args); } /** @@ -3140,7 +1453,9 @@ class Statistical * Excel Function: * STDEVA(value1[,value2[, ...]]) * - * @category Statistical Functions + * @deprecated 1.17.0 + * Use the STDEVA() method in the Statistical\StandardDeviations class instead + * @see Statistical\StandardDeviations::STDEVA() * * @param mixed ...$args Data values * @@ -3148,40 +1463,7 @@ class Statistical */ public static function STDEVA(...$args) { - $aArgs = Functions::flattenArrayIndexed($args); - - $returnValue = null; - - $aMean = self::AVERAGEA($aArgs); - if ($aMean !== null) { - $aCount = -1; - foreach ($aArgs as $k => $arg) { - if ((is_bool($arg)) && - (!Functions::isMatrixValue($k))) { - } else { - // Is it a numeric value? - if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) & ($arg != '')))) { - if (is_bool($arg)) { - $arg = (int) $arg; - } elseif (is_string($arg)) { - $arg = 0; - } - if ($returnValue === null) { - $returnValue = pow(($arg - $aMean), 2); - } else { - $returnValue += pow(($arg - $aMean), 2); - } - ++$aCount; - } - } - } - - if (($aCount > 0) && ($returnValue >= 0)) { - return sqrt($returnValue / $aCount); - } - } - - return Functions::DIV0(); + return StandardDeviations::STDEVA(...$args); } /** @@ -3192,7 +1474,9 @@ class Statistical * Excel Function: * STDEVP(value1[,value2[, ...]]) * - * @category Statistical Functions + * @deprecated 1.17.0 + * Use the STDEVP() method in the Statistical\StandardDeviations class instead + * @see Statistical\StandardDeviations::STDEVP() * * @param mixed ...$args Data values * @@ -3200,35 +1484,7 @@ class Statistical */ public static function STDEVP(...$args) { - $aArgs = Functions::flattenArrayIndexed($args); - - $returnValue = null; - - $aMean = self::AVERAGE($aArgs); - if ($aMean !== null) { - $aCount = 0; - foreach ($aArgs as $k => $arg) { - if ((is_bool($arg)) && - ((!Functions::isCellValue($k)) || (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE))) { - $arg = (int) $arg; - } - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - if ($returnValue === null) { - $returnValue = pow(($arg - $aMean), 2); - } else { - $returnValue += pow(($arg - $aMean), 2); - } - ++$aCount; - } - } - - if (($aCount > 0) && ($returnValue >= 0)) { - return sqrt($returnValue / $aCount); - } - } - - return Functions::DIV0(); + return StandardDeviations::STDEVP(...$args); } /** @@ -3239,7 +1495,9 @@ class Statistical * Excel Function: * STDEVPA(value1[,value2[, ...]]) * - * @category Statistical Functions + * @deprecated 1.17.0 + * Use the STDEVPA() method in the Statistical\StandardDeviations class instead + * @see Statistical\StandardDeviations::STDEVPA() * * @param mixed ...$args Data values * @@ -3247,45 +1505,16 @@ class Statistical */ public static function STDEVPA(...$args) { - $aArgs = Functions::flattenArrayIndexed($args); - - $returnValue = null; - - $aMean = self::AVERAGEA($aArgs); - if ($aMean !== null) { - $aCount = 0; - foreach ($aArgs as $k => $arg) { - if ((is_bool($arg)) && - (!Functions::isMatrixValue($k))) { - } else { - // Is it a numeric value? - if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) & ($arg != '')))) { - if (is_bool($arg)) { - $arg = (int) $arg; - } elseif (is_string($arg)) { - $arg = 0; - } - if ($returnValue === null) { - $returnValue = pow(($arg - $aMean), 2); - } else { - $returnValue += pow(($arg - $aMean), 2); - } - ++$aCount; - } - } - } - - if (($aCount > 0) && ($returnValue >= 0)) { - return sqrt($returnValue / $aCount); - } - } - - return Functions::DIV0(); + return StandardDeviations::STDEVPA(...$args); } /** * STEYX. * + * @deprecated 1.18.0 + * Use the STEYX() method in the Statistical\Trends class instead + * @see Statistical\Trends::STEYX() + * * Returns the standard error of the predicted y-value for each x in the regression. * * @param mixed[] $yValues Data Series Y @@ -3295,21 +1524,7 @@ class Statistical */ public static function STEYX($yValues, $xValues) { - if (!self::checkTrendArrays($yValues, $xValues)) { - return Functions::VALUE(); - } - $yValueCount = count($yValues); - $xValueCount = count($xValues); - - if (($yValueCount == 0) || ($yValueCount != $xValueCount)) { - return Functions::NA(); - } elseif ($yValueCount == 1) { - return Functions::DIV0(); - } - - $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); - - return $bestFitLinear->getStdevOfResiduals(); + return Trends::STEYX($yValues, $xValues); } /** @@ -3317,122 +1532,38 @@ class Statistical * * Returns the probability of Student's T distribution. * + * @deprecated 1.18.0 + * Use the distribution() method in the Statistical\Distributions\StudentT class instead + * @see Statistical\Distributions\StudentT::distribution() + * * @param float $value Value for the function * @param float $degrees degrees of freedom * @param float $tails number of tails (1 or 2) * - * @return float + * @return array|float|string The result, or a string containing an error */ public static function TDIST($value, $degrees, $tails) { - $value = Functions::flattenSingleValue($value); - $degrees = floor(Functions::flattenSingleValue($degrees)); - $tails = floor(Functions::flattenSingleValue($tails)); - - if ((is_numeric($value)) && (is_numeric($degrees)) && (is_numeric($tails))) { - if (($value < 0) || ($degrees < 1) || ($tails < 1) || ($tails > 2)) { - return Functions::NAN(); - } - // tdist, which finds the probability that corresponds to a given value - // of t with k degrees of freedom. This algorithm is translated from a - // pascal function on p81 of "Statistical Computing in Pascal" by D - // Cooke, A H Craven & G M Clark (1985: Edward Arnold (Pubs.) Ltd: - // London). The above Pascal algorithm is itself a translation of the - // fortran algoritm "AS 3" by B E Cooper of the Atlas Computer - // Laboratory as reported in (among other places) "Applied Statistics - // Algorithms", editied by P Griffiths and I D Hill (1985; Ellis - // Horwood Ltd.; W. Sussex, England). - $tterm = $degrees; - $ttheta = atan2($value, sqrt($tterm)); - $tc = cos($ttheta); - $ts = sin($ttheta); - $tsum = 0; - - if (($degrees % 2) == 1) { - $ti = 3; - $tterm = $tc; - } else { - $ti = 2; - $tterm = 1; - } - - $tsum = $tterm; - while ($ti < $degrees) { - $tterm *= $tc * $tc * ($ti - 1) / $ti; - $tsum += $tterm; - $ti += 2; - } - $tsum *= $ts; - if (($degrees % 2) == 1) { - $tsum = Functions::M_2DIVPI * ($tsum + $ttheta); - } - $tValue = 0.5 * (1 + $tsum); - if ($tails == 1) { - return 1 - abs($tValue); - } - - return 1 - abs((1 - $tValue) - $tValue); - } - - return Functions::VALUE(); + return Statistical\Distributions\StudentT::distribution($value, $degrees, $tails); } /** * TINV. * - * Returns the one-tailed probability of the chi-squared distribution. + * Returns the one-tailed probability of the Student-T distribution. + * + * @deprecated 1.18.0 + * Use the inverse() method in the Statistical\Distributions\StudentT class instead + * @see Statistical\Distributions\StudentT::inverse() * * @param float $probability Probability for the function * @param float $degrees degrees of freedom * - * @return float + * @return array|float|string The result, or a string containing an error */ public static function TINV($probability, $degrees) { - $probability = Functions::flattenSingleValue($probability); - $degrees = floor(Functions::flattenSingleValue($degrees)); - - if ((is_numeric($probability)) && (is_numeric($degrees))) { - $xLo = 100; - $xHi = 0; - - $x = $xNew = 1; - $dx = 1; - $i = 0; - - while ((abs($dx) > Functions::PRECISION) && ($i++ < self::MAX_ITERATIONS)) { - // Apply Newton-Raphson step - $result = self::TDIST($x, $degrees, 2); - $error = $result - $probability; - if ($error == 0.0) { - $dx = 0; - } elseif ($error < 0.0) { - $xLo = $x; - } else { - $xHi = $x; - } - // Avoid division by zero - if ($result != 0.0) { - $dx = $error / $result; - $xNew = $x - $dx; - } - // If the NR fails to converge (which for example may be the - // case if the initial guess is too rough) we apply a bisection - // step to determine a more narrow interval around the root. - if (($xNew < $xLo) || ($xNew > $xHi) || ($result == 0.0)) { - $xNew = ($xLo + $xHi) / 2; - $dx = $xNew - $x; - } - $x = $xNew; - } - if ($i == self::MAX_ITERATIONS) { - return Functions::NA(); - } - - return round($x, 12); - } - - return Functions::VALUE(); + return Statistical\Distributions\StudentT::inverse($probability, $degrees); } /** @@ -3440,31 +1571,20 @@ class Statistical * * Returns values along a linear Trend * + * @deprecated 1.18.0 + * Use the TREND() method in the Statistical\Trends class instead + * @see Statistical\Trends::TREND() + * * @param mixed[] $yValues Data Series Y * @param mixed[] $xValues Data Series X * @param mixed[] $newValues Values of X for which we want to find Y * @param bool $const a logical value specifying whether to force the intersect to equal 0 * - * @return array of float + * @return float[] */ public static function TREND($yValues, $xValues = [], $newValues = [], $const = true) { - $yValues = Functions::flattenArray($yValues); - $xValues = Functions::flattenArray($xValues); - $newValues = Functions::flattenArray($newValues); - $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const); - - $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues, $const); - if (empty($newValues)) { - $newValues = $bestFitLinear->getXValues(); - } - - $returnArray = []; - foreach ($newValues as $xValue) { - $returnArray[0][] = $bestFitLinear->getValueOfYForX($xValue); - } - - return $returnArray; + return Trends::TREND($yValues, $xValues, $newValues, $const); } /** @@ -3477,42 +1597,17 @@ class Statistical * Excel Function: * TRIMEAN(value1[,value2[, ...]], $discard) * - * @category Statistical Functions + * @deprecated 1.18.0 + * Use the trim() method in the Statistical\Averages\Mean class instead + * @see Statistical\Averages\Mean::trim() * * @param mixed $args Data values - * @param float $discard Percentage to discard * * @return float|string */ public static function TRIMMEAN(...$args) { - $aArgs = Functions::flattenArray($args); - - // Calculate - $percent = array_pop($aArgs); - - if ((is_numeric($percent)) && (!is_string($percent))) { - if (($percent < 0) || ($percent > 1)) { - return Functions::NAN(); - } - $mArgs = []; - foreach ($aArgs as $arg) { - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - $mArgs[] = $arg; - } - } - $discard = floor(self::COUNT($mArgs) * $percent / 2); - sort($mArgs); - for ($i = 0; $i < $discard; ++$i) { - array_pop($mArgs); - array_shift($mArgs); - } - - return self::AVERAGE($mArgs); - } - - return Functions::VALUE(); + return Statistical\Averages\Mean::trim(...$args); } /** @@ -3523,40 +1618,17 @@ class Statistical * Excel Function: * VAR(value1[,value2[, ...]]) * - * @category Statistical Functions + * @deprecated 1.17.0 + * Use the VAR() method in the Statistical\Variances class instead + * @see Statistical\Variances::VAR() * * @param mixed ...$args Data values * - * @return float + * @return float|string (string if result is an error) */ public static function VARFunc(...$args) { - $returnValue = Functions::DIV0(); - - $summerA = $summerB = 0; - - // Loop through arguments - $aArgs = Functions::flattenArray($args); - $aCount = 0; - foreach ($aArgs as $arg) { - if (is_bool($arg)) { - $arg = (int) $arg; - } - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - $summerA += ($arg * $arg); - $summerB += $arg; - ++$aCount; - } - } - - if ($aCount > 1) { - $summerA *= $aCount; - $summerB *= $summerB; - $returnValue = ($summerA - $summerB) / ($aCount * ($aCount - 1)); - } - - return $returnValue; + return Variances::VAR(...$args); } /** @@ -3567,49 +1639,17 @@ class Statistical * Excel Function: * VARA(value1[,value2[, ...]]) * - * @category Statistical Functions + * @deprecated 1.17.0 + * Use the VARA() method in the Statistical\Variances class instead + * @see Statistical\Variances::VARA() * * @param mixed ...$args Data values * - * @return float + * @return float|string (string if result is an error) */ public static function VARA(...$args) { - $returnValue = Functions::DIV0(); - - $summerA = $summerB = 0; - - // Loop through arguments - $aArgs = Functions::flattenArrayIndexed($args); - $aCount = 0; - foreach ($aArgs as $k => $arg) { - if ((is_string($arg)) && - (Functions::isValue($k))) { - return Functions::VALUE(); - } elseif ((is_string($arg)) && - (!Functions::isMatrixValue($k))) { - } else { - // Is it a numeric value? - if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) & ($arg != '')))) { - if (is_bool($arg)) { - $arg = (int) $arg; - } elseif (is_string($arg)) { - $arg = 0; - } - $summerA += ($arg * $arg); - $summerB += $arg; - ++$aCount; - } - } - } - - if ($aCount > 1) { - $summerA *= $aCount; - $summerB *= $summerB; - $returnValue = ($summerA - $summerB) / ($aCount * ($aCount - 1)); - } - - return $returnValue; + return Variances::VARA(...$args); } /** @@ -3620,41 +1660,17 @@ class Statistical * Excel Function: * VARP(value1[,value2[, ...]]) * - * @category Statistical Functions + * @deprecated 1.17.0 + * Use the VARP() method in the Statistical\Variances class instead + * @see Statistical\Variances::VARP() * * @param mixed ...$args Data values * - * @return float + * @return float|string (string if result is an error) */ public static function VARP(...$args) { - // Return value - $returnValue = Functions::DIV0(); - - $summerA = $summerB = 0; - - // Loop through arguments - $aArgs = Functions::flattenArray($args); - $aCount = 0; - foreach ($aArgs as $arg) { - if (is_bool($arg)) { - $arg = (int) $arg; - } - // Is it a numeric value? - if ((is_numeric($arg)) && (!is_string($arg))) { - $summerA += ($arg * $arg); - $summerB += $arg; - ++$aCount; - } - } - - if ($aCount > 0) { - $summerA *= $aCount; - $summerB *= $summerB; - $returnValue = ($summerA - $summerB) / ($aCount * $aCount); - } - - return $returnValue; + return Variances::VARP(...$args); } /** @@ -3665,49 +1681,17 @@ class Statistical * Excel Function: * VARPA(value1[,value2[, ...]]) * - * @category Statistical Functions + * @deprecated 1.17.0 + * Use the VARPA() method in the Statistical\Variances class instead + * @see Statistical\Variances::VARPA() * * @param mixed ...$args Data values * - * @return float + * @return float|string (string if result is an error) */ public static function VARPA(...$args) { - $returnValue = Functions::DIV0(); - - $summerA = $summerB = 0; - - // Loop through arguments - $aArgs = Functions::flattenArrayIndexed($args); - $aCount = 0; - foreach ($aArgs as $k => $arg) { - if ((is_string($arg)) && - (Functions::isValue($k))) { - return Functions::VALUE(); - } elseif ((is_string($arg)) && - (!Functions::isMatrixValue($k))) { - } else { - // Is it a numeric value? - if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) & ($arg != '')))) { - if (is_bool($arg)) { - $arg = (int) $arg; - } elseif (is_string($arg)) { - $arg = 0; - } - $summerA += ($arg * $arg); - $summerB += $arg; - ++$aCount; - } - } - } - - if ($aCount > 0) { - $summerA *= $aCount; - $summerB *= $summerB; - $returnValue = ($summerA - $summerB) / ($aCount * $aCount); - } - - return $returnValue; + return Variances::VARPA(...$args); } /** @@ -3716,58 +1700,42 @@ class Statistical * Returns the Weibull distribution. Use this distribution in reliability * analysis, such as calculating a device's mean time to failure. * + * @deprecated 1.18.0 + * Use the distribution() method in the Statistical\Distributions\Weibull class instead + * @see Statistical\Distributions\Weibull::distribution() + * * @param float $value * @param float $alpha Alpha Parameter * @param float $beta Beta Parameter * @param bool $cumulative * - * @return float + * @return array|float|string (string if result is an error) */ public static function WEIBULL($value, $alpha, $beta, $cumulative) { - $value = Functions::flattenSingleValue($value); - $alpha = Functions::flattenSingleValue($alpha); - $beta = Functions::flattenSingleValue($beta); - - if ((is_numeric($value)) && (is_numeric($alpha)) && (is_numeric($beta))) { - if (($value < 0) || ($alpha <= 0) || ($beta <= 0)) { - return Functions::NAN(); - } - if ((is_numeric($cumulative)) || (is_bool($cumulative))) { - if ($cumulative) { - return 1 - exp(0 - pow($value / $beta, $alpha)); - } - - return ($alpha / pow($beta, $alpha)) * pow($value, $alpha - 1) * exp(0 - pow($value / $beta, $alpha)); - } - } - - return Functions::VALUE(); + return Statistical\Distributions\Weibull::distribution($value, $alpha, $beta, $cumulative); } /** * ZTEST. * - * Returns the Weibull distribution. Use this distribution in reliability - * analysis, such as calculating a device's mean time to failure. + * Returns the one-tailed P-value of a z-test. + * + * For a given hypothesized population mean, x, Z.TEST returns the probability that the sample mean would be + * greater than the average of observations in the data set (array) — that is, the observed sample mean. + * + * @deprecated 1.18.0 + * Use the zTest() method in the Statistical\Distributions\StandardNormal class instead + * @see Statistical\Distributions\StandardNormal::zTest() * * @param float $dataSet * @param float $m0 Alpha Parameter * @param float $sigma Beta Parameter * - * @return float|string + * @return array|float|string (string if result is an error) */ public static function ZTEST($dataSet, $m0, $sigma = null) { - $dataSet = Functions::flattenArrayIndexed($dataSet); - $m0 = Functions::flattenSingleValue($m0); - $sigma = Functions::flattenSingleValue($sigma); - - if ($sigma === null) { - $sigma = self::STDEV($dataSet); - } - $n = count($dataSet); - - return 1 - self::NORMSDIST((self::AVERAGE($dataSet) - $m0) / ($sigma / sqrt($n))); + return Statistical\Distributions\StandardNormal::zTest($dataSet, $m0, $sigma); } } diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Statistical/AggregateBase.php b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/AggregateBase.php new file mode 100644 index 0000000..10933f4 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/AggregateBase.php @@ -0,0 +1,70 @@ + $arg) { + $arg = self::testAcceptedBoolean($arg, $k); + // Is it a numeric value? + // Strings containing numeric values are only counted if they are string literals (not cell values) + // and then only in MS Excel and in Open Office, not in Gnumeric + if ((is_string($arg)) && (!is_numeric($arg)) && (!Functions::isCellValue($k))) { + return ExcelError::VALUE(); + } + if (self::isAcceptedCountable($arg, $k)) { + $returnValue += abs($arg - $aMean); + ++$aCount; + } + } + + // Return + if ($aCount === 0) { + return ExcelError::DIV0(); + } + + return $returnValue / $aCount; + } + + /** + * AVERAGE. + * + * Returns the average (arithmetic mean) of the arguments + * + * Excel Function: + * AVERAGE(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float|string (string if result is an error) + */ + public static function average(...$args) + { + $returnValue = $aCount = 0; + + // Loop through arguments + foreach (Functions::flattenArrayIndexed($args) as $k => $arg) { + $arg = self::testAcceptedBoolean($arg, $k); + // Is it a numeric value? + // Strings containing numeric values are only counted if they are string literals (not cell values) + // and then only in MS Excel and in Open Office, not in Gnumeric + if ((is_string($arg)) && (!is_numeric($arg)) && (!Functions::isCellValue($k))) { + return ExcelError::VALUE(); + } + if (self::isAcceptedCountable($arg, $k)) { + $returnValue += $arg; + ++$aCount; + } + } + + // Return + if ($aCount > 0) { + return $returnValue / $aCount; + } + + return ExcelError::DIV0(); + } + + /** + * AVERAGEA. + * + * Returns the average of its arguments, including numbers, text, and logical values + * + * Excel Function: + * AVERAGEA(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float|string (string if result is an error) + */ + public static function averageA(...$args) + { + $returnValue = null; + + $aCount = 0; + // Loop through arguments + foreach (Functions::flattenArrayIndexed($args) as $k => $arg) { + if (is_numeric($arg)) { + // do nothing + } elseif (is_bool($arg)) { + $arg = (int) $arg; + } elseif (!Functions::isMatrixValue($k)) { + $arg = 0; + } else { + return ExcelError::VALUE(); + } + $returnValue += $arg; + ++$aCount; + } + + if ($aCount > 0) { + return $returnValue / $aCount; + } + + return ExcelError::DIV0(); + } + + /** + * MEDIAN. + * + * Returns the median of the given numbers. The median is the number in the middle of a set of numbers. + * + * Excel Function: + * MEDIAN(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float|string The result, or a string containing an error + */ + public static function median(...$args) + { + $aArgs = Functions::flattenArray($args); + + $returnValue = ExcelError::NAN(); + + $aArgs = self::filterArguments($aArgs); + $valueCount = count($aArgs); + if ($valueCount > 0) { + sort($aArgs, SORT_NUMERIC); + $valueCount = $valueCount / 2; + if ($valueCount == floor($valueCount)) { + $returnValue = ($aArgs[$valueCount--] + $aArgs[$valueCount]) / 2; + } else { + $valueCount = floor($valueCount); + $returnValue = $aArgs[$valueCount]; + } + } + + return $returnValue; + } + + /** + * MODE. + * + * Returns the most frequently occurring, or repetitive, value in an array or range of data + * + * Excel Function: + * MODE(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float|string The result, or a string containing an error + */ + public static function mode(...$args) + { + $returnValue = ExcelError::NA(); + + // Loop through arguments + $aArgs = Functions::flattenArray($args); + $aArgs = self::filterArguments($aArgs); + + if (!empty($aArgs)) { + return self::modeCalc($aArgs); + } + + return $returnValue; + } + + protected static function filterArguments($args) + { + return array_filter( + $args, + function ($value) { + // Is it a numeric value? + return is_numeric($value) && (!is_string($value)); + } + ); + } + + // + // Special variant of array_count_values that isn't limited to strings and integers, + // but can work with floating point numbers as values + // + private static function modeCalc($data) + { + $frequencyArray = []; + $index = 0; + $maxfreq = 0; + $maxfreqkey = ''; + $maxfreqdatum = ''; + foreach ($data as $datum) { + $found = false; + ++$index; + foreach ($frequencyArray as $key => $value) { + if ((string) $value['value'] == (string) $datum) { + ++$frequencyArray[$key]['frequency']; + $freq = $frequencyArray[$key]['frequency']; + if ($freq > $maxfreq) { + $maxfreq = $freq; + $maxfreqkey = $key; + $maxfreqdatum = $datum; + } elseif ($freq == $maxfreq) { + if ($frequencyArray[$key]['index'] < $frequencyArray[$maxfreqkey]['index']) { + $maxfreqkey = $key; + $maxfreqdatum = $datum; + } + } + $found = true; + + break; + } + } + + if ($found === false) { + $frequencyArray[] = [ + 'value' => $datum, + 'frequency' => 1, + 'index' => $index, + ]; + } + } + + if ($maxfreq <= 1) { + return ExcelError::NA(); + } + + return $maxfreqdatum; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Averages/Mean.php b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Averages/Mean.php new file mode 100644 index 0000000..001c91e --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Averages/Mean.php @@ -0,0 +1,132 @@ + 0)) { + $aCount = Counts::COUNT($aArgs); + if (Minimum::min($aArgs) > 0) { + return $aMean ** (1 / $aCount); + } + } + + return ExcelError::NAN(); + } + + /** + * HARMEAN. + * + * Returns the harmonic mean of a data set. The harmonic mean is the reciprocal of the + * arithmetic mean of reciprocals. + * + * Excel Function: + * HARMEAN(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float|string + */ + public static function harmonic(...$args) + { + // Loop through arguments + $aArgs = Functions::flattenArray($args); + if (Minimum::min($aArgs) < 0) { + return ExcelError::NAN(); + } + + $returnValue = 0; + $aCount = 0; + foreach ($aArgs as $arg) { + // Is it a numeric value? + if ((is_numeric($arg)) && (!is_string($arg))) { + if ($arg <= 0) { + return ExcelError::NAN(); + } + $returnValue += (1 / $arg); + ++$aCount; + } + } + + // Return + if ($aCount > 0) { + return 1 / ($returnValue / $aCount); + } + + return ExcelError::NA(); + } + + /** + * TRIMMEAN. + * + * Returns the mean of the interior of a data set. TRIMMEAN calculates the mean + * taken by excluding a percentage of data points from the top and bottom tails + * of a data set. + * + * Excel Function: + * TRIMEAN(value1[,value2[, ...]], $discard) + * + * @param mixed $args Data values + * + * @return float|string + */ + public static function trim(...$args) + { + $aArgs = Functions::flattenArray($args); + + // Calculate + $percent = array_pop($aArgs); + + if ((is_numeric($percent)) && (!is_string($percent))) { + if (($percent < 0) || ($percent > 1)) { + return ExcelError::NAN(); + } + + $mArgs = []; + foreach ($aArgs as $arg) { + // Is it a numeric value? + if ((is_numeric($arg)) && (!is_string($arg))) { + $mArgs[] = $arg; + } + } + + $discard = floor(Counts::COUNT($mArgs) * $percent / 2); + sort($mArgs); + + for ($i = 0; $i < $discard; ++$i) { + array_pop($mArgs); + array_shift($mArgs); + } + + return Averages::average($mArgs); + } + + return ExcelError::VALUE(); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Conditional.php b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Conditional.php new file mode 100644 index 0000000..5d40943 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Conditional.php @@ -0,0 +1,310 @@ +getMessage(); + } + + if (($alpha <= 0) || ($alpha >= 1) || ($stdDev <= 0) || ($size < 1)) { + return ExcelError::NAN(); + } + /** @var float */ + $temp = Distributions\StandardNormal::inverse(1 - $alpha / 2); + + return Functions::scalar($temp * $stdDev / sqrt($size)); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Counts.php b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Counts.php new file mode 100644 index 0000000..6792730 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Counts.php @@ -0,0 +1,102 @@ + $arg) { + $arg = self::testAcceptedBoolean($arg, $k); + // Is it a numeric value? + // Strings containing numeric values are only counted if they are string literals (not cell values) + // and then only in MS Excel and in Open Office, not in Gnumeric + if (self::isAcceptedCountable($arg, $k, true)) { + ++$returnValue; + } + } + + return $returnValue; + } + + /** + * COUNTA. + * + * Counts the number of cells that are not empty within the list of arguments + * + * Excel Function: + * COUNTA(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return int + */ + public static function COUNTA(...$args) + { + $returnValue = 0; + + // Loop through arguments + $aArgs = Functions::flattenArrayIndexed($args); + foreach ($aArgs as $k => $arg) { + // Nulls are counted if literals, but not if cell values + if ($arg !== null || (!Functions::isCellValue($k))) { + ++$returnValue; + } + } + + return $returnValue; + } + + /** + * COUNTBLANK. + * + * Counts the number of empty cells within the list of arguments + * + * Excel Function: + * COUNTBLANK(value1[,value2[, ...]]) + * + * @param mixed $range Data values + * + * @return int + */ + public static function COUNTBLANK($range) + { + if ($range === null) { + return 1; + } + if (!is_array($range) || array_key_exists(0, $range)) { + throw new CalcException('Must specify range of cells, not any kind of literal'); + } + $returnValue = 0; + + // Loop through arguments + $aArgs = Functions::flattenArray($range); + foreach ($aArgs as $arg) { + // Is it a blank cell? + if (($arg === null) || ((is_string($arg)) && ($arg == ''))) { + ++$returnValue; + } + } + + return $returnValue; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Deviations.php b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Deviations.php new file mode 100644 index 0000000..6b1db3a --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Deviations.php @@ -0,0 +1,142 @@ + $arg) { + // Is it a numeric value? + if ( + (is_bool($arg)) && + ((!Functions::isCellValue($k)) || + (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE)) + ) { + $arg = (int) $arg; + } + if ((is_numeric($arg)) && (!is_string($arg))) { + $returnValue += ($arg - $aMean) ** 2; + ++$aCount; + } + } + + return $aCount === 0 ? ExcelError::VALUE() : $returnValue; + } + + /** + * KURT. + * + * Returns the kurtosis of a data set. Kurtosis characterizes the relative peakedness + * or flatness of a distribution compared with the normal distribution. Positive + * kurtosis indicates a relatively peaked distribution. Negative kurtosis indicates a + * relatively flat distribution. + * + * @param array ...$args Data Series + * + * @return float|string + */ + public static function kurtosis(...$args) + { + $aArgs = Functions::flattenArrayIndexed($args); + $mean = Averages::average($aArgs); + if (!is_numeric($mean)) { + return ExcelError::DIV0(); + } + $stdDev = StandardDeviations::STDEV($aArgs); + + if ($stdDev > 0) { + $count = $summer = 0; + + foreach ($aArgs as $k => $arg) { + if ((is_bool($arg)) && (!Functions::isMatrixValue($k))) { + } else { + // Is it a numeric value? + if ((is_numeric($arg)) && (!is_string($arg))) { + $summer += (($arg - $mean) / $stdDev) ** 4; + ++$count; + } + } + } + + if ($count > 3) { + return $summer * ($count * ($count + 1) / + (($count - 1) * ($count - 2) * ($count - 3))) - (3 * ($count - 1) ** 2 / + (($count - 2) * ($count - 3))); + } + } + + return ExcelError::DIV0(); + } + + /** + * SKEW. + * + * Returns the skewness of a distribution. Skewness characterizes the degree of asymmetry + * of a distribution around its mean. Positive skewness indicates a distribution with an + * asymmetric tail extending toward more positive values. Negative skewness indicates a + * distribution with an asymmetric tail extending toward more negative values. + * + * @param array ...$args Data Series + * + * @return float|int|string The result, or a string containing an error + */ + public static function skew(...$args) + { + $aArgs = Functions::flattenArrayIndexed($args); + $mean = Averages::average($aArgs); + if (!is_numeric($mean)) { + return ExcelError::DIV0(); + } + $stdDev = StandardDeviations::STDEV($aArgs); + if ($stdDev === 0.0 || is_string($stdDev)) { + return ExcelError::DIV0(); + } + + $count = $summer = 0; + // Loop through arguments + foreach ($aArgs as $k => $arg) { + if ((is_bool($arg)) && (!Functions::isMatrixValue($k))) { + } elseif (!is_numeric($arg)) { + return ExcelError::VALUE(); + } else { + // Is it a numeric value? + if ((is_numeric($arg)) && (!is_string($arg))) { + $summer += (($arg - $mean) / $stdDev) ** 3; + ++$count; + } + } + } + + if ($count > 2) { + return $summer * ($count / (($count - 1) * ($count - 2))); + } + + return ExcelError::DIV0(); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Beta.php b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Beta.php new file mode 100644 index 0000000..8b41ac8 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Beta.php @@ -0,0 +1,283 @@ +getMessage(); + } + + if ($rMin > $rMax) { + $tmp = $rMin; + $rMin = $rMax; + $rMax = $tmp; + } + if (($value < $rMin) || ($value > $rMax) || ($alpha <= 0) || ($beta <= 0) || ($rMin == $rMax)) { + return ExcelError::NAN(); + } + + $value -= $rMin; + $value /= ($rMax - $rMin); + + return self::incompleteBeta($value, $alpha, $beta); + } + + /** + * BETAINV. + * + * Returns the inverse of the Beta distribution. + * + * @param mixed $probability Float probability at which you want to evaluate the distribution + * Or can be an array of values + * @param mixed $alpha Parameter to the distribution as a float + * Or can be an array of values + * @param mixed $beta Parameter to the distribution as a float + * Or can be an array of values + * @param mixed $rMin Minimum value as a float + * Or can be an array of values + * @param mixed $rMax Maximum value as a float + * Or can be an array of values + * + * @return array|float|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function inverse($probability, $alpha, $beta, $rMin = 0.0, $rMax = 1.0) + { + if (is_array($probability) || is_array($alpha) || is_array($beta) || is_array($rMin) || is_array($rMax)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $alpha, $beta, $rMin, $rMax); + } + + $rMin = $rMin ?? 0.0; + $rMax = $rMax ?? 1.0; + + try { + $probability = DistributionValidations::validateProbability($probability); + $alpha = DistributionValidations::validateFloat($alpha); + $beta = DistributionValidations::validateFloat($beta); + $rMax = DistributionValidations::validateFloat($rMax); + $rMin = DistributionValidations::validateFloat($rMin); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($rMin > $rMax) { + $tmp = $rMin; + $rMin = $rMax; + $rMax = $tmp; + } + if (($alpha <= 0) || ($beta <= 0) || ($rMin == $rMax) || ($probability <= 0.0)) { + return ExcelError::NAN(); + } + + return self::calculateInverse($probability, $alpha, $beta, $rMin, $rMax); + } + + /** + * @return float|string + */ + private static function calculateInverse(float $probability, float $alpha, float $beta, float $rMin, float $rMax) + { + $a = 0; + $b = 2; + $guess = ($a + $b) / 2; + + $i = 0; + while ((($b - $a) > Functions::PRECISION) && (++$i <= self::MAX_ITERATIONS)) { + $guess = ($a + $b) / 2; + $result = self::distribution($guess, $alpha, $beta); + if (($result === $probability) || ($result === 0.0)) { + $b = $a; + } elseif ($result > $probability) { + $b = $guess; + } else { + $a = $guess; + } + } + + if ($i === self::MAX_ITERATIONS) { + return ExcelError::NA(); + } + + return round($rMin + $guess * ($rMax - $rMin), 12); + } + + /** + * Incomplete beta function. + * + * @author Jaco van Kooten + * @author Paul Meagher + * + * The computation is based on formulas from Numerical Recipes, Chapter 6.4 (W.H. Press et al, 1992). + * + * @param float $x require 0<=x<=1 + * @param float $p require p>0 + * @param float $q require q>0 + * + * @return float 0 if x<0, p<=0, q<=0 or p+q>2.55E305 and 1 if x>1 to avoid errors and over/underflow + */ + public static function incompleteBeta(float $x, float $p, float $q): float + { + if ($x <= 0.0) { + return 0.0; + } elseif ($x >= 1.0) { + return 1.0; + } elseif (($p <= 0.0) || ($q <= 0.0) || (($p + $q) > self::LOG_GAMMA_X_MAX_VALUE)) { + return 0.0; + } + + $beta_gam = exp((0 - self::logBeta($p, $q)) + $p * log($x) + $q * log(1.0 - $x)); + if ($x < ($p + 1.0) / ($p + $q + 2.0)) { + return $beta_gam * self::betaFraction($x, $p, $q) / $p; + } + + return 1.0 - ($beta_gam * self::betaFraction(1 - $x, $q, $p) / $q); + } + + // Function cache for logBeta function + private static $logBetaCacheP = 0.0; + + private static $logBetaCacheQ = 0.0; + + private static $logBetaCacheResult = 0.0; + + /** + * The natural logarithm of the beta function. + * + * @param float $p require p>0 + * @param float $q require q>0 + * + * @return float 0 if p<=0, q<=0 or p+q>2.55E305 to avoid errors and over/underflow + * + * @author Jaco van Kooten + */ + private static function logBeta(float $p, float $q): float + { + if ($p != self::$logBetaCacheP || $q != self::$logBetaCacheQ) { + self::$logBetaCacheP = $p; + self::$logBetaCacheQ = $q; + if (($p <= 0.0) || ($q <= 0.0) || (($p + $q) > self::LOG_GAMMA_X_MAX_VALUE)) { + self::$logBetaCacheResult = 0.0; + } else { + self::$logBetaCacheResult = Gamma::logGamma($p) + Gamma::logGamma($q) - Gamma::logGamma($p + $q); + } + } + + return self::$logBetaCacheResult; + } + + /** + * Evaluates of continued fraction part of incomplete beta function. + * Based on an idea from Numerical Recipes (W.H. Press et al, 1992). + * + * @author Jaco van Kooten + */ + private static function betaFraction(float $x, float $p, float $q): float + { + $c = 1.0; + $sum_pq = $p + $q; + $p_plus = $p + 1.0; + $p_minus = $p - 1.0; + $h = 1.0 - $sum_pq * $x / $p_plus; + if (abs($h) < self::XMININ) { + $h = self::XMININ; + } + $h = 1.0 / $h; + $frac = $h; + $m = 1; + $delta = 0.0; + while ($m <= self::MAX_ITERATIONS && abs($delta - 1.0) > Functions::PRECISION) { + $m2 = 2 * $m; + // even index for d + $d = $m * ($q - $m) * $x / (($p_minus + $m2) * ($p + $m2)); + $h = 1.0 + $d * $h; + if (abs($h) < self::XMININ) { + $h = self::XMININ; + } + $h = 1.0 / $h; + $c = 1.0 + $d / $c; + if (abs($c) < self::XMININ) { + $c = self::XMININ; + } + $frac *= $h * $c; + // odd index for d + $d = -($p + $m) * ($sum_pq + $m) * $x / (($p + $m2) * ($p_plus + $m2)); + $h = 1.0 + $d * $h; + if (abs($h) < self::XMININ) { + $h = self::XMININ; + } + $h = 1.0 / $h; + $c = 1.0 + $d / $c; + if (abs($c) < self::XMININ) { + $c = self::XMININ; + } + $delta = $h * $c; + $frac *= $delta; + ++$m; + } + + return $frac; + } + + /* + private static function betaValue(float $a, float $b): float + { + return (Gamma::gammaValue($a) * Gamma::gammaValue($b)) / + Gamma::gammaValue($a + $b); + } + + private static function regularizedIncompleteBeta(float $value, float $a, float $b): float + { + return self::incompleteBeta($value, $a, $b) / self::betaValue($a, $b); + } + */ +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Binomial.php b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Binomial.php new file mode 100644 index 0000000..02b53e8 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Binomial.php @@ -0,0 +1,237 @@ +getMessage(); + } + + if (($value < 0) || ($value > $trials)) { + return ExcelError::NAN(); + } + + if ($cumulative) { + return self::calculateCumulativeBinomial($value, $trials, $probability); + } + /** @var float */ + $comb = Combinations::withoutRepetition($trials, $value); + + return $comb * $probability ** $value + * (1 - $probability) ** ($trials - $value); + } + + /** + * BINOM.DIST.RANGE. + * + * Returns returns the Binomial Distribution probability for the number of successes from a specified number + * of trials falling into a specified range. + * + * @param mixed $trials Integer number of trials + * Or can be an array of values + * @param mixed $probability Probability of success on each trial as a float + * Or can be an array of values + * @param mixed $successes The integer number of successes in trials + * Or can be an array of values + * @param mixed $limit Upper limit for successes in trials as null, or an integer + * If null, then this will indicate the same as the number of Successes + * Or can be an array of values + * + * @return array|float|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function range($trials, $probability, $successes, $limit = null) + { + if (is_array($trials) || is_array($probability) || is_array($successes) || is_array($limit)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $trials, $probability, $successes, $limit); + } + + $limit = $limit ?? $successes; + + try { + $trials = DistributionValidations::validateInt($trials); + $probability = DistributionValidations::validateProbability($probability); + $successes = DistributionValidations::validateInt($successes); + $limit = DistributionValidations::validateInt($limit); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (($successes < 0) || ($successes > $trials)) { + return ExcelError::NAN(); + } + if (($limit < 0) || ($limit > $trials) || $limit < $successes) { + return ExcelError::NAN(); + } + + $summer = 0; + for ($i = $successes; $i <= $limit; ++$i) { + /** @var float */ + $comb = Combinations::withoutRepetition($trials, $i); + $summer += $comb * $probability ** $i + * (1 - $probability) ** ($trials - $i); + } + + return $summer; + } + + /** + * NEGBINOMDIST. + * + * Returns the negative binomial distribution. NEGBINOMDIST returns the probability that + * there will be number_f failures before the number_s-th success, when the constant + * probability of a success is probability_s. This function is similar to the binomial + * distribution, except that the number of successes is fixed, and the number of trials is + * variable. Like the binomial, trials are assumed to be independent. + * + * @param mixed $failures Number of Failures as an integer + * Or can be an array of values + * @param mixed $successes Threshold number of Successes as an integer + * Or can be an array of values + * @param mixed $probability Probability of success on each trial as a float + * Or can be an array of values + * + * @return array|float|string The result, or a string containing an error + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + * + * TODO Add support for the cumulative flag not present for NEGBINOMDIST, but introduced for NEGBINOM.DIST + * The cumulative default should be false to reflect the behaviour of NEGBINOMDIST + */ + public static function negative($failures, $successes, $probability) + { + if (is_array($failures) || is_array($successes) || is_array($probability)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $failures, $successes, $probability); + } + + try { + $failures = DistributionValidations::validateInt($failures); + $successes = DistributionValidations::validateInt($successes); + $probability = DistributionValidations::validateProbability($probability); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (($failures < 0) || ($successes < 1)) { + return ExcelError::NAN(); + } + if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { + if (($failures + $successes - 1) <= 0) { + return ExcelError::NAN(); + } + } + /** @var float */ + $comb = Combinations::withoutRepetition($failures + $successes - 1, $successes - 1); + + return $comb + * ($probability ** $successes) * ((1 - $probability) ** $failures); + } + + /** + * BINOM.INV. + * + * Returns the smallest value for which the cumulative binomial distribution is greater + * than or equal to a criterion value + * + * @param mixed $trials number of Bernoulli trials as an integer + * Or can be an array of values + * @param mixed $probability probability of a success on each trial as a float + * Or can be an array of values + * @param mixed $alpha criterion value as a float + * Or can be an array of values + * + * @return array|int|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function inverse($trials, $probability, $alpha) + { + if (is_array($trials) || is_array($probability) || is_array($alpha)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $trials, $probability, $alpha); + } + + try { + $trials = DistributionValidations::validateInt($trials); + $probability = DistributionValidations::validateProbability($probability); + $alpha = DistributionValidations::validateFloat($alpha); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($trials < 0) { + return ExcelError::NAN(); + } elseif (($alpha < 0.0) || ($alpha > 1.0)) { + return ExcelError::NAN(); + } + + $successes = 0; + while ($successes <= $trials) { + $result = self::calculateCumulativeBinomial($successes, $trials, $probability); + if ($result >= $alpha) { + break; + } + ++$successes; + } + + return $successes; + } + + /** + * @return float|int + */ + private static function calculateCumulativeBinomial(int $value, int $trials, float $probability) + { + $summer = 0; + for ($i = 0; $i <= $value; ++$i) { + /** @var float */ + $comb = Combinations::withoutRepetition($trials, $i); + $summer += $comb * $probability ** $i + * (1 - $probability) ** ($trials - $i); + } + + return $summer; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php new file mode 100644 index 0000000..c874336 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/ChiSquared.php @@ -0,0 +1,337 @@ +getMessage(); + } + + if ($degrees < 1) { + return ExcelError::NAN(); + } + if ($value < 0) { + if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { + return 1; + } + + return ExcelError::NAN(); + } + + return 1 - (Gamma::incompleteGamma($degrees / 2, $value / 2) / Gamma::gammaValue($degrees / 2)); + } + + /** + * CHIDIST. + * + * Returns the one-tailed probability of the chi-squared distribution. + * + * @param mixed $value Float value for which we want the probability + * Or can be an array of values + * @param mixed $degrees Integer degrees of freedom + * Or can be an array of values + * @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false) + * Or can be an array of values + * + * @return array|float|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function distributionLeftTail($value, $degrees, $cumulative) + { + if (is_array($value) || is_array($degrees) || is_array($cumulative)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $degrees, $cumulative); + } + + try { + $value = DistributionValidations::validateFloat($value); + $degrees = DistributionValidations::validateInt($degrees); + $cumulative = DistributionValidations::validateBool($cumulative); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($degrees < 1) { + return ExcelError::NAN(); + } + if ($value < 0) { + if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) { + return 1; + } + + return ExcelError::NAN(); + } + + if ($cumulative === true) { + return 1 - self::distributionRightTail($value, $degrees); + } + + return ($value ** (($degrees / 2) - 1) * exp(-$value / 2)) / + ((2 ** ($degrees / 2)) * Gamma::gammaValue($degrees / 2)); + } + + /** + * CHIINV. + * + * Returns the inverse of the right-tailed probability of the chi-squared distribution. + * + * @param mixed $probability Float probability at which you want to evaluate the distribution + * Or can be an array of values + * @param mixed $degrees Integer degrees of freedom + * Or can be an array of values + * + * @return array|float|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function inverseRightTail($probability, $degrees) + { + if (is_array($probability) || is_array($degrees)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $degrees); + } + + try { + $probability = DistributionValidations::validateProbability($probability); + $degrees = DistributionValidations::validateInt($degrees); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($degrees < 1) { + return ExcelError::NAN(); + } + + $callback = function ($value) use ($degrees) { + return 1 - (Gamma::incompleteGamma($degrees / 2, $value / 2) + / Gamma::gammaValue($degrees / 2)); + }; + + $newtonRaphson = new NewtonRaphson($callback); + + return $newtonRaphson->execute($probability); + } + + /** + * CHIINV. + * + * Returns the inverse of the left-tailed probability of the chi-squared distribution. + * + * @param mixed $probability Float probability at which you want to evaluate the distribution + * Or can be an array of values + * @param mixed $degrees Integer degrees of freedom + * Or can be an array of values + * + * @return array|float|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function inverseLeftTail($probability, $degrees) + { + if (is_array($probability) || is_array($degrees)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $degrees); + } + + try { + $probability = DistributionValidations::validateProbability($probability); + $degrees = DistributionValidations::validateInt($degrees); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($degrees < 1) { + return ExcelError::NAN(); + } + + return self::inverseLeftTailCalculation($probability, $degrees); + } + + /** + * CHITEST. + * + * 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. + * + * @param mixed $actual an array of observed frequencies + * @param mixed $expected an array of expected frequencies + * + * @return float|string + */ + public static function test($actual, $expected) + { + $rows = count($actual); + $actual = Functions::flattenArray($actual); + $expected = Functions::flattenArray($expected); + $columns = count($actual) / $rows; + + $countActuals = count($actual); + $countExpected = count($expected); + if ($countActuals !== $countExpected || $countActuals === 1) { + return ExcelError::NAN(); + } + + $result = 0.0; + for ($i = 0; $i < $countActuals; ++$i) { + if ($expected[$i] == 0.0) { + return ExcelError::DIV0(); + } elseif ($expected[$i] < 0.0) { + return ExcelError::NAN(); + } + $result += (($actual[$i] - $expected[$i]) ** 2) / $expected[$i]; + } + + $degrees = self::degrees($rows, $columns); + + $result = Functions::scalar(self::distributionRightTail($result, $degrees)); + + return $result; + } + + protected static function degrees(int $rows, int $columns): int + { + if ($rows === 1) { + return $columns - 1; + } elseif ($columns === 1) { + return $rows - 1; + } + + return ($columns - 1) * ($rows - 1); + } + + private static function inverseLeftTailCalculation(float $probability, int $degrees): float + { + // bracket the root + $min = 0; + $sd = sqrt(2.0 * $degrees); + $max = 2 * $sd; + $s = -1; + + while ($s * self::pchisq($max, $degrees) > $probability * $s) { + $min = $max; + $max += 2 * $sd; + } + + // Find root using bisection + $chi2 = 0.5 * ($min + $max); + + while (($max - $min) > self::EPS * $chi2) { + if ($s * self::pchisq($chi2, $degrees) > $probability * $s) { + $min = $chi2; + } else { + $max = $chi2; + } + $chi2 = 0.5 * ($min + $max); + } + + return $chi2; + } + + private static function pchisq($chi2, $degrees) + { + return self::gammp($degrees, 0.5 * $chi2); + } + + private static function gammp($n, $x) + { + if ($x < 0.5 * $n + 1) { + return self::gser($n, $x); + } + + return 1 - self::gcf($n, $x); + } + + // Return the incomplete gamma function P(n/2,x) evaluated by + // series representation. Algorithm from numerical recipe. + // Assume that n is a positive integer and x>0, won't check arguments. + // Relative error controlled by the eps parameter + private static function gser($n, $x) + { + /** @var float */ + $gln = Gamma::ln($n / 2); + $a = 0.5 * $n; + $ap = $a; + $sum = 1.0 / $a; + $del = $sum; + for ($i = 1; $i < 101; ++$i) { + ++$ap; + $del = $del * $x / $ap; + $sum += $del; + if ($del < $sum * self::EPS) { + break; + } + } + + return $sum * exp(-$x + $a * log($x) - $gln); + } + + // Return the incomplete gamma function Q(n/2,x) evaluated by + // its continued fraction representation. Algorithm from numerical recipe. + // Assume that n is a postive integer and x>0, won't check arguments. + // Relative error controlled by the eps parameter + private static function gcf($n, $x) + { + /** @var float */ + $gln = Gamma::ln($n / 2); + $a = 0.5 * $n; + $b = $x + 1 - $a; + $fpmin = 1.e-300; + $c = 1 / $fpmin; + $d = 1 / $b; + $h = $d; + for ($i = 1; $i < 101; ++$i) { + $an = -$i * ($i - $a); + $b += 2; + $d = $an * $d + $b; + if (abs($d) < $fpmin) { + $d = $fpmin; + } + $c = $b + $an / $c; + if (abs($c) < $fpmin) { + $c = $fpmin; + } + $d = 1 / $d; + $del = $d * $c; + $h = $h * $del; + if (abs($del - 1) < self::EPS) { + break; + } + } + + return $h * exp(-$x + $a * log($x) - $gln); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/DistributionValidations.php b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/DistributionValidations.php new file mode 100644 index 0000000..bd45a22 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/DistributionValidations.php @@ -0,0 +1,24 @@ + 1.0) { + throw new Exception(ExcelError::NAN()); + } + + return $probability; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Exponential.php b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Exponential.php new file mode 100644 index 0000000..a03671c --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Exponential.php @@ -0,0 +1,55 @@ +getMessage(); + } + + if (($value < 0) || ($lambda < 0)) { + return ExcelError::NAN(); + } + + if ($cumulative === true) { + return 1 - exp(0 - $value * $lambda); + } + + return $lambda * exp(0 - $value * $lambda); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/F.php b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/F.php new file mode 100644 index 0000000..ff413b6 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/F.php @@ -0,0 +1,64 @@ +getMessage(); + } + + if ($value < 0 || $u < 1 || $v < 1) { + return ExcelError::NAN(); + } + + if ($cumulative) { + $adjustedValue = ($u * $value) / ($u * $value + $v); + + return Beta::incompleteBeta($adjustedValue, $u / 2, $v / 2); + } + + return (Gamma::gammaValue(($v + $u) / 2) / + (Gamma::gammaValue($u / 2) * Gamma::gammaValue($v / 2))) * + (($u / $v) ** ($u / 2)) * + (($value ** (($u - 2) / 2)) / ((1 + ($u / $v) * $value) ** (($u + $v) / 2))); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Fisher.php b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Fisher.php new file mode 100644 index 0000000..df9906e --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Fisher.php @@ -0,0 +1,74 @@ +getMessage(); + } + + if (($value <= -1) || ($value >= 1)) { + return ExcelError::NAN(); + } + + return 0.5 * log((1 + $value) / (1 - $value)); + } + + /** + * FISHERINV. + * + * Returns the inverse of the Fisher transformation. Use this transformation when + * analyzing correlations between ranges or arrays of data. If y = FISHER(x), then + * FISHERINV(y) = x. + * + * @param mixed $probability Float probability at which you want to evaluate the distribution + * Or can be an array of values + * + * @return array|float|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function inverse($probability) + { + if (is_array($probability)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $probability); + } + + try { + DistributionValidations::validateFloat($probability); + } catch (Exception $e) { + return $e->getMessage(); + } + + return (exp(2 * $probability) - 1) / (exp(2 * $probability) + 1); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Gamma.php b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Gamma.php new file mode 100644 index 0000000..216f234 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Gamma.php @@ -0,0 +1,151 @@ +getMessage(); + } + + if ((((int) $value) == ((float) $value)) && $value <= 0.0) { + return ExcelError::NAN(); + } + + return self::gammaValue($value); + } + + /** + * GAMMADIST. + * + * Returns the gamma distribution. + * + * @param mixed $value Float Value at which you want to evaluate the distribution + * Or can be an array of values + * @param mixed $a Parameter to the distribution as a float + * Or can be an array of values + * @param mixed $b Parameter to the distribution as a float + * Or can be an array of values + * @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false) + * Or can be an array of values + * + * @return array|float|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function distribution($value, $a, $b, $cumulative) + { + if (is_array($value) || is_array($a) || is_array($b) || is_array($cumulative)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $a, $b, $cumulative); + } + + try { + $value = DistributionValidations::validateFloat($value); + $a = DistributionValidations::validateFloat($a); + $b = DistributionValidations::validateFloat($b); + $cumulative = DistributionValidations::validateBool($cumulative); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (($value < 0) || ($a <= 0) || ($b <= 0)) { + return ExcelError::NAN(); + } + + return self::calculateDistribution($value, $a, $b, $cumulative); + } + + /** + * GAMMAINV. + * + * Returns the inverse of the Gamma distribution. + * + * @param mixed $probability Float probability at which you want to evaluate the distribution + * Or can be an array of values + * @param mixed $alpha Parameter to the distribution as a float + * Or can be an array of values + * @param mixed $beta Parameter to the distribution as a float + * Or can be an array of values + * + * @return array|float|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function inverse($probability, $alpha, $beta) + { + if (is_array($probability) || is_array($alpha) || is_array($beta)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $alpha, $beta); + } + + try { + $probability = DistributionValidations::validateProbability($probability); + $alpha = DistributionValidations::validateFloat($alpha); + $beta = DistributionValidations::validateFloat($beta); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (($alpha <= 0.0) || ($beta <= 0.0)) { + return ExcelError::NAN(); + } + + return self::calculateInverse($probability, $alpha, $beta); + } + + /** + * GAMMALN. + * + * Returns the natural logarithm of the gamma function. + * + * @param mixed $value Float Value at which you want to evaluate the distribution + * Or can be an array of values + * + * @return array|float|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function ln($value) + { + if (is_array($value)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value); + } + + try { + $value = DistributionValidations::validateFloat($value); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($value <= 0) { + return ExcelError::NAN(); + } + + return log(self::gammaValue($value)); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php new file mode 100644 index 0000000..8e6386e --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php @@ -0,0 +1,380 @@ + Functions::PRECISION) && (++$i <= self::MAX_ITERATIONS)) { + // Apply Newton-Raphson step + $result = self::calculateDistribution($x, $alpha, $beta, true); + $error = $result - $probability; + + if ($error == 0.0) { + $dx = 0; + } elseif ($error < 0.0) { + $xLo = $x; + } else { + $xHi = $x; + } + + $pdf = self::calculateDistribution($x, $alpha, $beta, false); + // Avoid division by zero + if ($pdf !== 0.0) { + $dx = $error / $pdf; + $xNew = $x - $dx; + } + + // If the NR fails to converge (which for example may be the + // case if the initial guess is too rough) we apply a bisection + // step to determine a more narrow interval around the root. + if (($xNew < $xLo) || ($xNew > $xHi) || ($pdf == 0.0)) { + $xNew = ($xLo + $xHi) / 2; + $dx = $xNew - $x; + } + $x = $xNew; + } + + if ($i === self::MAX_ITERATIONS) { + return ExcelError::NA(); + } + + return $x; + } + + // + // Implementation of the incomplete Gamma function + // + public static function incompleteGamma(float $a, float $x): float + { + static $max = 32; + $summer = 0; + for ($n = 0; $n <= $max; ++$n) { + $divisor = $a; + for ($i = 1; $i <= $n; ++$i) { + $divisor *= ($a + $i); + } + $summer += ($x ** $n / $divisor); + } + + return $x ** $a * exp(0 - $x) * $summer; + } + + // + // Implementation of the Gamma function + // + public static function gammaValue(float $value): float + { + if ($value == 0.0) { + return 0; + } + + static $p0 = 1.000000000190015; + static $p = [ + 1 => 76.18009172947146, + 2 => -86.50532032941677, + 3 => 24.01409824083091, + 4 => -1.231739572450155, + 5 => 1.208650973866179e-3, + 6 => -5.395239384953e-6, + ]; + + $y = $x = $value; + $tmp = $x + 5.5; + $tmp -= ($x + 0.5) * log($tmp); + + $summer = $p0; + for ($j = 1; $j <= 6; ++$j) { + $summer += ($p[$j] / ++$y); + } + + return exp(0 - $tmp + log(self::SQRT2PI * $summer / $x)); + } + + private const LG_D1 = -0.5772156649015328605195174; + + private const LG_D2 = 0.4227843350984671393993777; + + private const LG_D4 = 1.791759469228055000094023; + + private const LG_P1 = [ + 4.945235359296727046734888, + 201.8112620856775083915565, + 2290.838373831346393026739, + 11319.67205903380828685045, + 28557.24635671635335736389, + 38484.96228443793359990269, + 26377.48787624195437963534, + 7225.813979700288197698961, + ]; + + private const LG_P2 = [ + 4.974607845568932035012064, + 542.4138599891070494101986, + 15506.93864978364947665077, + 184793.2904445632425417223, + 1088204.76946882876749847, + 3338152.967987029735917223, + 5106661.678927352456275255, + 3074109.054850539556250927, + ]; + + private const LG_P4 = [ + 14745.02166059939948905062, + 2426813.369486704502836312, + 121475557.4045093227939592, + 2663432449.630976949898078, + 29403789566.34553899906876, + 170266573776.5398868392998, + 492612579337.743088758812, + 560625185622.3951465078242, + ]; + + private const LG_Q1 = [ + 67.48212550303777196073036, + 1113.332393857199323513008, + 7738.757056935398733233834, + 27639.87074403340708898585, + 54993.10206226157329794414, + 61611.22180066002127833352, + 36351.27591501940507276287, + 8785.536302431013170870835, + ]; + + private const LG_Q2 = [ + 183.0328399370592604055942, + 7765.049321445005871323047, + 133190.3827966074194402448, + 1136705.821321969608938755, + 5267964.117437946917577538, + 13467014.54311101692290052, + 17827365.30353274213975932, + 9533095.591844353613395747, + ]; + + private const LG_Q4 = [ + 2690.530175870899333379843, + 639388.5654300092398984238, + 41355999.30241388052042842, + 1120872109.61614794137657, + 14886137286.78813811542398, + 101680358627.2438228077304, + 341747634550.7377132798597, + 446315818741.9713286462081, + ]; + + private const LG_C = [ + -0.001910444077728, + 8.4171387781295e-4, + -5.952379913043012e-4, + 7.93650793500350248e-4, + -0.002777777777777681622553, + 0.08333333333333333331554247, + 0.0057083835261, + ]; + + // Rough estimate of the fourth root of logGamma_xBig + private const LG_FRTBIG = 2.25e76; + + private const PNT68 = 0.6796875; + + // Function cache for logGamma + private static $logGammaCacheResult = 0.0; + + private static $logGammaCacheX = 0.0; + + /** + * logGamma function. + * + * Original author was Jaco van Kooten. Ported to PHP by Paul Meagher. + * + * The natural logarithm of the gamma function.
+ * Based on public domain NETLIB (Fortran) code by W. J. Cody and L. Stoltz
+ * Applied Mathematics Division
+ * Argonne National Laboratory
+ * Argonne, IL 60439
+ *

+ * References: + *

    + *
  1. W. J. Cody and K. E. Hillstrom, 'Chebyshev Approximations for the Natural + * Logarithm of the Gamma Function,' Math. Comp. 21, 1967, pp. 198-203.
  2. + *
  3. K. E. Hillstrom, ANL/AMD Program ANLC366S, DGAMMA/DLGAMA, May, 1969.
  4. + *
  5. Hart, Et. Al., Computer Approximations, Wiley and sons, New York, 1968.
  6. + *
+ *

+ *

+ * From the original documentation: + *

+ *

+ * This routine calculates the LOG(GAMMA) function for a positive real argument X. + * Computation is based on an algorithm outlined in references 1 and 2. + * The program uses rational functions that theoretically approximate LOG(GAMMA) + * to at least 18 significant decimal digits. The approximation for X > 12 is from + * reference 3, while approximations for X < 12.0 are similar to those in reference + * 1, but are unpublished. The accuracy achieved depends on the arithmetic system, + * the compiler, the intrinsic functions, and proper selection of the + * machine-dependent constants. + *

+ *

+ * Error returns:
+ * The program returns the value XINF for X .LE. 0.0 or when overflow would occur. + * The computation is believed to be free of underflow and overflow. + *

+ * + * @version 1.1 + * + * @author Jaco van Kooten + * + * @return float MAX_VALUE for x < 0.0 or when overflow would occur, i.e. x > 2.55E305 + */ + public static function logGamma(float $x): float + { + if ($x == self::$logGammaCacheX) { + return self::$logGammaCacheResult; + } + + $y = $x; + if ($y > 0.0 && $y <= self::LOG_GAMMA_X_MAX_VALUE) { + if ($y <= self::EPS) { + $res = -log($y); + } elseif ($y <= 1.5) { + $res = self::logGamma1($y); + } elseif ($y <= 4.0) { + $res = self::logGamma2($y); + } elseif ($y <= 12.0) { + $res = self::logGamma3($y); + } else { + $res = self::logGamma4($y); + } + } else { + // -------------------------- + // Return for bad arguments + // -------------------------- + $res = self::MAX_VALUE; + } + + // ------------------------------ + // Final adjustments and return + // ------------------------------ + self::$logGammaCacheX = $x; + self::$logGammaCacheResult = $res; + + return $res; + } + + private static function logGamma1(float $y) + { + // --------------------- + // EPS .LT. X .LE. 1.5 + // --------------------- + if ($y < self::PNT68) { + $corr = -log($y); + $xm1 = $y; + } else { + $corr = 0.0; + $xm1 = $y - 1.0; + } + + $xden = 1.0; + $xnum = 0.0; + if ($y <= 0.5 || $y >= self::PNT68) { + for ($i = 0; $i < 8; ++$i) { + $xnum = $xnum * $xm1 + self::LG_P1[$i]; + $xden = $xden * $xm1 + self::LG_Q1[$i]; + } + + return $corr + $xm1 * (self::LG_D1 + $xm1 * ($xnum / $xden)); + } + + $xm2 = $y - 1.0; + for ($i = 0; $i < 8; ++$i) { + $xnum = $xnum * $xm2 + self::LG_P2[$i]; + $xden = $xden * $xm2 + self::LG_Q2[$i]; + } + + return $corr + $xm2 * (self::LG_D2 + $xm2 * ($xnum / $xden)); + } + + private static function logGamma2(float $y) + { + // --------------------- + // 1.5 .LT. X .LE. 4.0 + // --------------------- + $xm2 = $y - 2.0; + $xden = 1.0; + $xnum = 0.0; + for ($i = 0; $i < 8; ++$i) { + $xnum = $xnum * $xm2 + self::LG_P2[$i]; + $xden = $xden * $xm2 + self::LG_Q2[$i]; + } + + return $xm2 * (self::LG_D2 + $xm2 * ($xnum / $xden)); + } + + protected static function logGamma3(float $y) + { + // ---------------------- + // 4.0 .LT. X .LE. 12.0 + // ---------------------- + $xm4 = $y - 4.0; + $xden = -1.0; + $xnum = 0.0; + for ($i = 0; $i < 8; ++$i) { + $xnum = $xnum * $xm4 + self::LG_P4[$i]; + $xden = $xden * $xm4 + self::LG_Q4[$i]; + } + + return self::LG_D4 + $xm4 * ($xnum / $xden); + } + + protected static function logGamma4(float $y) + { + // --------------------------------- + // Evaluate for argument .GE. 12.0 + // --------------------------------- + $res = 0.0; + if ($y <= self::LG_FRTBIG) { + $res = self::LG_C[6]; + $ysq = $y * $y; + for ($i = 0; $i < 6; ++$i) { + $res = $res / $ysq + self::LG_C[$i]; + } + $res /= $y; + $corr = log($y); + $res = $res + log(self::SQRT2PI) - 0.5 * $corr; + $res += $y * ($corr - 1.0); + } + + return $res; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/HyperGeometric.php b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/HyperGeometric.php new file mode 100644 index 0000000..b3ad69d --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/HyperGeometric.php @@ -0,0 +1,76 @@ +getMessage(); + } + + if (($sampleSuccesses < 0) || ($sampleSuccesses > $sampleNumber) || ($sampleSuccesses > $populationSuccesses)) { + return ExcelError::NAN(); + } + if (($sampleNumber <= 0) || ($sampleNumber > $populationNumber)) { + return ExcelError::NAN(); + } + if (($populationSuccesses <= 0) || ($populationSuccesses > $populationNumber)) { + return ExcelError::NAN(); + } + + $successesPopulationAndSample = (float) Combinations::withoutRepetition($populationSuccesses, $sampleSuccesses); + $numbersPopulationAndSample = (float) Combinations::withoutRepetition($populationNumber, $sampleNumber); + $adjustedPopulationAndSample = (float) Combinations::withoutRepetition( + $populationNumber - $populationSuccesses, + $sampleNumber - $sampleSuccesses + ); + + return $successesPopulationAndSample * $adjustedPopulationAndSample / $numbersPopulationAndSample; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/LogNormal.php b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/LogNormal.php new file mode 100644 index 0000000..d572d23 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/LogNormal.php @@ -0,0 +1,139 @@ +getMessage(); + } + + if (($value <= 0) || ($stdDev <= 0)) { + return ExcelError::NAN(); + } + + return StandardNormal::cumulative((log($value) - $mean) / $stdDev); + } + + /** + * LOGNORM.DIST. + * + * Returns the lognormal distribution of x, where ln(x) is normally distributed + * with parameters mean and standard_dev. + * + * @param mixed $value Float value for which we want the probability + * Or can be an array of values + * @param mixed $mean Mean value as a float + * Or can be an array of values + * @param mixed $stdDev Standard Deviation as a float + * Or can be an array of values + * @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false) + * Or can be an array of values + * + * @return array|float|string The result, or a string containing an error + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function distribution($value, $mean, $stdDev, $cumulative = false) + { + if (is_array($value) || is_array($mean) || is_array($stdDev) || is_array($cumulative)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $mean, $stdDev, $cumulative); + } + + try { + $value = DistributionValidations::validateFloat($value); + $mean = DistributionValidations::validateFloat($mean); + $stdDev = DistributionValidations::validateFloat($stdDev); + $cumulative = DistributionValidations::validateBool($cumulative); + } catch (Exception $e) { + return $e->getMessage(); + } + + if (($value <= 0) || ($stdDev <= 0)) { + return ExcelError::NAN(); + } + + if ($cumulative === true) { + return StandardNormal::distribution((log($value) - $mean) / $stdDev, true); + } + + return (1 / (sqrt(2 * M_PI) * $stdDev * $value)) * + exp(0 - ((log($value) - $mean) ** 2 / (2 * $stdDev ** 2))); + } + + /** + * LOGINV. + * + * Returns the inverse of the lognormal cumulative distribution + * + * @param mixed $probability Float probability for which we want the value + * Or can be an array of values + * @param mixed $mean Mean Value as a float + * Or can be an array of values + * @param mixed $stdDev Standard Deviation as a float + * Or can be an array of values + * + * @return array|float|string The result, or a string containing an error + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + * + * @TODO Try implementing P J Acklam's refinement algorithm for greater + * accuracy if I can get my head round the mathematics + * (as described at) http://home.online.no/~pjacklam/notes/invnorm/ + */ + public static function inverse($probability, $mean, $stdDev) + { + if (is_array($probability) || is_array($mean) || is_array($stdDev)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $mean, $stdDev); + } + + try { + $probability = DistributionValidations::validateProbability($probability); + $mean = DistributionValidations::validateFloat($mean); + $stdDev = DistributionValidations::validateFloat($stdDev); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($stdDev <= 0) { + return ExcelError::NAN(); + } + /** @var float */ + $inverse = StandardNormal::inverse($probability); + + return exp($mean + $stdDev * $inverse); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/NewtonRaphson.php b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/NewtonRaphson.php new file mode 100644 index 0000000..b994864 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/NewtonRaphson.php @@ -0,0 +1,63 @@ +callback = $callback; + } + + public function execute(float $probability) + { + $xLo = 100; + $xHi = 0; + + $dx = 1; + $x = $xNew = 1; + $i = 0; + + while ((abs($dx) > Functions::PRECISION) && ($i++ < self::MAX_ITERATIONS)) { + // Apply Newton-Raphson step + $result = call_user_func($this->callback, $x); + $error = $result - $probability; + + if ($error == 0.0) { + $dx = 0; + } elseif ($error < 0.0) { + $xLo = $x; + } else { + $xHi = $x; + } + + // Avoid division by zero + if ($result != 0.0) { + $dx = $error / $result; + $xNew = $x - $dx; + } + + // If the NR fails to converge (which for example may be the + // case if the initial guess is too rough) we apply a bisection + // step to determine a more narrow interval around the root. + if (($xNew < $xLo) || ($xNew > $xHi) || ($result == 0.0)) { + $xNew = ($xLo + $xHi) / 2; + $dx = $xNew - $x; + } + $x = $xNew; + } + + if ($i == self::MAX_ITERATIONS) { + return ExcelError::NA(); + } + + return $x; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Normal.php b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Normal.php new file mode 100644 index 0000000..67533c4 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Normal.php @@ -0,0 +1,180 @@ +getMessage(); + } + + if ($stdDev < 0) { + return ExcelError::NAN(); + } + + if ($cumulative) { + return 0.5 * (1 + Engineering\Erf::erfValue(($value - $mean) / ($stdDev * sqrt(2)))); + } + + return (1 / (self::SQRT2PI * $stdDev)) * exp(0 - (($value - $mean) ** 2 / (2 * ($stdDev * $stdDev)))); + } + + /** + * NORMINV. + * + * Returns the inverse of the normal cumulative distribution for the specified mean and standard deviation. + * + * @param mixed $probability Float probability for which we want the value + * Or can be an array of values + * @param mixed $mean Mean Value as a float + * Or can be an array of values + * @param mixed $stdDev Standard Deviation as a float + * Or can be an array of values + * + * @return array|float|string The result, or a string containing an error + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function inverse($probability, $mean, $stdDev) + { + if (is_array($probability) || is_array($mean) || is_array($stdDev)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $mean, $stdDev); + } + + try { + $probability = DistributionValidations::validateProbability($probability); + $mean = DistributionValidations::validateFloat($mean); + $stdDev = DistributionValidations::validateFloat($stdDev); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($stdDev < 0) { + return ExcelError::NAN(); + } + + return (self::inverseNcdf($probability) * $stdDev) + $mean; + } + + /* + * inverse_ncdf.php + * ------------------- + * begin : Friday, January 16, 2004 + * copyright : (C) 2004 Michael Nickerson + * email : nickersonm@yahoo.com + * + */ + private static function inverseNcdf($p) + { + // Inverse ncdf approximation by Peter J. Acklam, implementation adapted to + // PHP by Michael Nickerson, using Dr. Thomas Ziegler's C implementation as + // a guide. http://home.online.no/~pjacklam/notes/invnorm/index.html + // I have not checked the accuracy of this implementation. Be aware that PHP + // will truncate the coeficcients to 14 digits. + + // You have permission to use and distribute this function freely for + // whatever purpose you want, but please show common courtesy and give credit + // where credit is due. + + // Input paramater is $p - probability - where 0 < p < 1. + + // Coefficients in rational approximations + static $a = [ + 1 => -3.969683028665376e+01, + 2 => 2.209460984245205e+02, + 3 => -2.759285104469687e+02, + 4 => 1.383577518672690e+02, + 5 => -3.066479806614716e+01, + 6 => 2.506628277459239e+00, + ]; + + static $b = [ + 1 => -5.447609879822406e+01, + 2 => 1.615858368580409e+02, + 3 => -1.556989798598866e+02, + 4 => 6.680131188771972e+01, + 5 => -1.328068155288572e+01, + ]; + + static $c = [ + 1 => -7.784894002430293e-03, + 2 => -3.223964580411365e-01, + 3 => -2.400758277161838e+00, + 4 => -2.549732539343734e+00, + 5 => 4.374664141464968e+00, + 6 => 2.938163982698783e+00, + ]; + + static $d = [ + 1 => 7.784695709041462e-03, + 2 => 3.224671290700398e-01, + 3 => 2.445134137142996e+00, + 4 => 3.754408661907416e+00, + ]; + + // Define lower and upper region break-points. + $p_low = 0.02425; //Use lower region approx. below this + $p_high = 1 - $p_low; //Use upper region approx. above this + + if (0 < $p && $p < $p_low) { + // Rational approximation for lower region. + $q = sqrt(-2 * 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); + } elseif ($p_high < $p && $p < 1) { + // Rational approximation for upper region. + $q = sqrt(-2 * 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); + } + + // Rational approximation for central region. + $q = $p - 0.5; + $r = $q * $q; + + return ((((($a[1] * $r + $a[2]) * $r + $a[3]) * $r + $a[4]) * $r + $a[5]) * $r + $a[6]) * $q / + ((((($b[1] * $r + $b[2]) * $r + $b[3]) * $r + $b[4]) * $r + $b[5]) * $r + 1); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Poisson.php b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Poisson.php new file mode 100644 index 0000000..041c34a --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Poisson.php @@ -0,0 +1,66 @@ +getMessage(); + } + + if (($value < 0) || ($mean < 0)) { + return ExcelError::NAN(); + } + + if ($cumulative) { + $summer = 0; + $floor = floor($value); + for ($i = 0; $i <= $floor; ++$i) { + /** @var float */ + $fact = MathTrig\Factorial::fact($i); + $summer += $mean ** $i / $fact; + } + + return exp(0 - $mean) * $summer; + } + /** @var float */ + $fact = MathTrig\Factorial::fact($value); + + return (exp(0 - $mean) * $mean ** $value) / $fact; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/StandardNormal.php b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/StandardNormal.php new file mode 100644 index 0000000..a655fa7 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/StandardNormal.php @@ -0,0 +1,151 @@ +getMessage(); + } + + if (($value < 0) || ($degrees < 1) || ($tails < 1) || ($tails > 2)) { + return ExcelError::NAN(); + } + + return self::calculateDistribution($value, $degrees, $tails); + } + + /** + * TINV. + * + * Returns the one-tailed probability of the chi-squared distribution. + * + * @param mixed $probability Float probability for the function + * Or can be an array of values + * @param mixed $degrees Integer value for degrees of freedom + * Or can be an array of values + * + * @return array|float|string The result, or a string containing an error + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function inverse($probability, $degrees) + { + if (is_array($probability) || is_array($degrees)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $degrees); + } + + try { + $probability = DistributionValidations::validateProbability($probability); + $degrees = DistributionValidations::validateInt($degrees); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($degrees <= 0) { + return ExcelError::NAN(); + } + + $callback = function ($value) use ($degrees) { + return self::distribution($value, $degrees, 2); + }; + + $newtonRaphson = new NewtonRaphson($callback); + + return $newtonRaphson->execute($probability); + } + + /** + * @return float + */ + private static function calculateDistribution(float $value, int $degrees, int $tails) + { + // tdist, which finds the probability that corresponds to a given value + // of t with k degrees of freedom. This algorithm is translated from a + // pascal function on p81 of "Statistical Computing in Pascal" by D + // Cooke, A H Craven & G M Clark (1985: Edward Arnold (Pubs.) Ltd: + // London). The above Pascal algorithm is itself a translation of the + // fortran algoritm "AS 3" by B E Cooper of the Atlas Computer + // Laboratory as reported in (among other places) "Applied Statistics + // Algorithms", editied by P Griffiths and I D Hill (1985; Ellis + // Horwood Ltd.; W. Sussex, England). + $tterm = $degrees; + $ttheta = atan2($value, sqrt($tterm)); + $tc = cos($ttheta); + $ts = sin($ttheta); + + if (($degrees % 2) === 1) { + $ti = 3; + $tterm = $tc; + } else { + $ti = 2; + $tterm = 1; + } + + $tsum = $tterm; + while ($ti < $degrees) { + $tterm *= $tc * $tc * ($ti - 1) / $ti; + $tsum += $tterm; + $ti += 2; + } + + $tsum *= $ts; + if (($degrees % 2) == 1) { + $tsum = Functions::M_2DIVPI * ($tsum + $ttheta); + } + + $tValue = 0.5 * (1 + $tsum); + if ($tails == 1) { + return 1 - abs($tValue); + } + + return 1 - abs((1 - $tValue) - $tValue); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Weibull.php b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Weibull.php new file mode 100644 index 0000000..51392c4 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Distributions/Weibull.php @@ -0,0 +1,57 @@ +getMessage(); + } + + if (($value < 0) || ($alpha <= 0) || ($beta <= 0)) { + return ExcelError::NAN(); + } + + if ($cumulative) { + return 1 - exp(0 - ($value / $beta) ** $alpha); + } + + return ($alpha / $beta ** $alpha) * $value ** ($alpha - 1) * exp(0 - ($value / $beta) ** $alpha); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Statistical/MaxMinBase.php b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/MaxMinBase.php new file mode 100644 index 0000000..bd17b06 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/MaxMinBase.php @@ -0,0 +1,17 @@ + $returnValue)) { + $returnValue = $arg; + } + } + } + + if ($returnValue === null) { + return 0; + } + + return $returnValue; + } + + /** + * MAXA. + * + * Returns the greatest value in a list of arguments, including numbers, text, and logical values + * + * Excel Function: + * MAXA(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float + */ + public static function maxA(...$args) + { + $returnValue = null; + + // Loop through arguments + $aArgs = Functions::flattenArray($args); + foreach ($aArgs as $arg) { + if (ErrorValue::isError($arg)) { + $returnValue = $arg; + + break; + } + // Is it a numeric value? + if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) { + $arg = self::datatypeAdjustmentAllowStrings($arg); + if (($returnValue === null) || ($arg > $returnValue)) { + $returnValue = $arg; + } + } + } + + if ($returnValue === null) { + return 0; + } + + return $returnValue; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Minimum.php b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Minimum.php new file mode 100644 index 0000000..0dbbf3a --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Minimum.php @@ -0,0 +1,89 @@ +getMessage(); + } + + if (($entry < 0) || ($entry > 1)) { + return ExcelError::NAN(); + } + + $mArgs = self::percentileFilterValues($aArgs); + $mValueCount = count($mArgs); + if ($mValueCount > 0) { + sort($mArgs); + $count = Counts::COUNT($mArgs); + $index = $entry * ($count - 1); + $iBase = floor($index); + if ($index == $iBase) { + return $mArgs[$index]; + } + $iNext = $iBase + 1; + $iProportion = $index - $iBase; + + return $mArgs[$iBase] + (($mArgs[$iNext] - $mArgs[$iBase]) * $iProportion); + } + + return ExcelError::NAN(); + } + + /** + * PERCENTRANK. + * + * Returns the rank of a value in a data set as a percentage of the data set. + * Note that the returned rank is simply rounded to the appropriate significant digits, + * rather than floored (as MS Excel), so value 3 for a value set of 1, 2, 3, 4 will return + * 0.667 rather than 0.666 + * + * @param mixed $valueSet An array of (float) values, or a reference to, a list of numbers + * @param mixed $value The number whose rank you want to find + * @param mixed $significance The (integer) number of significant digits for the returned percentage value + * + * @return float|string (string if result is an error) + */ + public static function PERCENTRANK($valueSet, $value, $significance = 3) + { + $valueSet = Functions::flattenArray($valueSet); + $value = Functions::flattenSingleValue($value); + $significance = ($significance === null) ? 3 : Functions::flattenSingleValue($significance); + + try { + $value = StatisticalValidations::validateFloat($value); + $significance = StatisticalValidations::validateInt($significance); + } catch (Exception $e) { + return $e->getMessage(); + } + + $valueSet = self::rankFilterValues($valueSet); + $valueCount = count($valueSet); + if ($valueCount == 0) { + return ExcelError::NA(); + } + sort($valueSet, SORT_NUMERIC); + + $valueAdjustor = $valueCount - 1; + if (($value < $valueSet[0]) || ($value > $valueSet[$valueAdjustor])) { + return ExcelError::NA(); + } + + $pos = array_search($value, $valueSet); + if ($pos === false) { + $pos = 0; + $testValue = $valueSet[0]; + while ($testValue < $value) { + $testValue = $valueSet[++$pos]; + } + --$pos; + $pos += (($value - $valueSet[$pos]) / ($testValue - $valueSet[$pos])); + } + + return round($pos / $valueAdjustor, $significance); + } + + /** + * QUARTILE. + * + * Returns the quartile of a data set. + * + * Excel Function: + * QUARTILE(value1[,value2[, ...]],entry) + * + * @param mixed $args Data values + * + * @return float|string The result, or a string containing an error + */ + public static function QUARTILE(...$args) + { + $aArgs = Functions::flattenArray($args); + $entry = array_pop($aArgs); + + try { + $entry = StatisticalValidations::validateFloat($entry); + } catch (Exception $e) { + return $e->getMessage(); + } + + $entry = floor($entry); + $entry /= 4; + if (($entry < 0) || ($entry > 1)) { + return ExcelError::NAN(); + } + + return self::PERCENTILE($aArgs, $entry); + } + + /** + * RANK. + * + * Returns the rank of a number in a list of numbers. + * + * @param mixed $value The number whose rank you want to find + * @param mixed $valueSet An array of float values, or a reference to, a list of numbers + * @param mixed $order Order to sort the values in the value set + * + * @return float|string The result, or a string containing an error (0 = Descending, 1 = Ascending) + */ + public static function RANK($value, $valueSet, $order = self::RANK_SORT_DESCENDING) + { + $value = Functions::flattenSingleValue($value); + $valueSet = Functions::flattenArray($valueSet); + $order = ($order === null) ? self::RANK_SORT_DESCENDING : Functions::flattenSingleValue($order); + + try { + $value = StatisticalValidations::validateFloat($value); + $order = StatisticalValidations::validateInt($order); + } catch (Exception $e) { + return $e->getMessage(); + } + + $valueSet = self::rankFilterValues($valueSet); + if ($order === self::RANK_SORT_DESCENDING) { + rsort($valueSet, SORT_NUMERIC); + } else { + sort($valueSet, SORT_NUMERIC); + } + + $pos = array_search($value, $valueSet); + if ($pos === false) { + return ExcelError::NA(); + } + + return ++$pos; + } + + protected static function percentileFilterValues(array $dataSet) + { + return array_filter( + $dataSet, + function ($value): bool { + return is_numeric($value) && !is_string($value); + } + ); + } + + protected static function rankFilterValues(array $dataSet) + { + return array_filter( + $dataSet, + function ($value): bool { + return is_numeric($value); + } + ); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Permutations.php b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Permutations.php new file mode 100644 index 0000000..5d9d304 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Permutations.php @@ -0,0 +1,90 @@ +getMessage(); + } + + if ($numObjs < $numInSet) { + return ExcelError::NAN(); + } + $result = round(MathTrig\Factorial::fact($numObjs) / MathTrig\Factorial::fact($numObjs - $numInSet)); + + return IntOrFloat::evaluate($result); + } + + /** + * PERMUTATIONA. + * + * Returns the number of permutations for a given number of objects (with repetitions) + * that can be selected from the total objects. + * + * @param mixed $numObjs Integer number of different objects + * Or can be an array of values + * @param mixed $numInSet Integer number of objects in each permutation + * Or can be an array of values + * + * @return array|float|int|string Number of permutations, or a string containing an error + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function PERMUTATIONA($numObjs, $numInSet) + { + if (is_array($numObjs) || is_array($numInSet)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $numObjs, $numInSet); + } + + try { + $numObjs = StatisticalValidations::validateInt($numObjs); + $numInSet = StatisticalValidations::validateInt($numInSet); + } catch (Exception $e) { + return $e->getMessage(); + } + + if ($numObjs < 0 || $numInSet < 0) { + return ExcelError::NAN(); + } + + $result = $numObjs ** $numInSet; + + return IntOrFloat::evaluate($result); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Size.php b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Size.php new file mode 100644 index 0000000..2eef5fc --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/Size.php @@ -0,0 +1,97 @@ += $count) { + return ExcelError::NAN(); + } + rsort($mArgs); + + return $mArgs[$entry]; + } + + return ExcelError::VALUE(); + } + + /** + * SMALL. + * + * Returns the nth smallest value in a data set. You can use this function to + * select a value based on its relative standing. + * + * Excel Function: + * SMALL(value1[,value2[, ...]],entry) + * + * @param mixed $args Data values + * + * @return float|string The result, or a string containing an error + */ + public static function small(...$args) + { + $aArgs = Functions::flattenArray($args); + + $entry = array_pop($aArgs); + + if ((is_numeric($entry)) && (!is_string($entry))) { + $entry = (int) floor($entry); + + $mArgs = self::filter($aArgs); + $count = Counts::COUNT($mArgs); + --$entry; + if ($count === 0 || $entry < 0 || $entry >= $count) { + return ExcelError::NAN(); + } + sort($mArgs); + + return $mArgs[$entry]; + } + + return ExcelError::VALUE(); + } + + /** + * @param mixed[] $args Data values + */ + protected static function filter(array $args): array + { + $mArgs = []; + + foreach ($args as $arg) { + // Is it a numeric value? + if ((is_numeric($arg)) && (!is_string($arg))) { + $mArgs[] = $arg; + } + } + + return $mArgs; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Statistical/StandardDeviations.php b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/StandardDeviations.php new file mode 100644 index 0000000..af27120 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/StandardDeviations.php @@ -0,0 +1,95 @@ +getMessage(); + } + + if ($stdDev <= 0) { + return ExcelError::NAN(); + } + + return ($value - $mean) / $stdDev; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Statistical/StatisticalValidations.php b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/StatisticalValidations.php new file mode 100644 index 0000000..e23a52c --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/StatisticalValidations.php @@ -0,0 +1,45 @@ + $value) { + if ((is_bool($value)) || (is_string($value)) || ($value === null)) { + unset($array1[$key], $array2[$key]); + } + } + } + + private static function checkTrendArrays(&$array1, &$array2): void + { + if (!is_array($array1)) { + $array1 = [$array1]; + } + if (!is_array($array2)) { + $array2 = [$array2]; + } + + $array1 = Functions::flattenArray($array1); + $array2 = Functions::flattenArray($array2); + + self::filterTrendValues($array1, $array2); + self::filterTrendValues($array2, $array1); + + // Reset the array indexes + $array1 = array_merge($array1); + $array2 = array_merge($array2); + } + + protected static function validateTrendArrays(array $yValues, array $xValues): void + { + $yValueCount = count($yValues); + $xValueCount = count($xValues); + + if (($yValueCount === 0) || ($yValueCount !== $xValueCount)) { + throw new Exception(ExcelError::NA()); + } elseif ($yValueCount === 1) { + throw new Exception(ExcelError::DIV0()); + } + } + + /** + * CORREL. + * + * Returns covariance, the average of the products of deviations for each data point pair. + * + * @param mixed $yValues array of mixed Data Series Y + * @param null|mixed $xValues array of mixed Data Series X + * + * @return float|string + */ + public static function CORREL($yValues, $xValues = null) + { + if (($xValues === null) || (!is_array($yValues)) || (!is_array($xValues))) { + return ExcelError::VALUE(); + } + + try { + self::checkTrendArrays($yValues, $xValues); + self::validateTrendArrays($yValues, $xValues); + } catch (Exception $e) { + return $e->getMessage(); + } + + $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); + + return $bestFitLinear->getCorrelation(); + } + + /** + * COVAR. + * + * Returns covariance, the average of the products of deviations for each data point pair. + * + * @param mixed $yValues array of mixed Data Series Y + * @param mixed $xValues array of mixed Data Series X + * + * @return float|string + */ + public static function COVAR($yValues, $xValues) + { + try { + self::checkTrendArrays($yValues, $xValues); + self::validateTrendArrays($yValues, $xValues); + } catch (Exception $e) { + return $e->getMessage(); + } + + $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); + + return $bestFitLinear->getCovariance(); + } + + /** + * FORECAST. + * + * Calculates, or predicts, a future value by using existing values. + * The predicted value is a y-value for a given x-value. + * + * @param mixed $xValue Float value of X for which we want to find Y + * Or can be an array of values + * @param mixed $yValues array of mixed Data Series Y + * @param mixed $xValues of mixed Data Series X + * + * @return array|bool|float|string + * If an array of numbers is passed as an argument, then the returned result will also be an array + * with the same dimensions + */ + public static function FORECAST($xValue, $yValues, $xValues) + { + if (is_array($xValue)) { + return self::evaluateArrayArgumentsSubset([self::class, __FUNCTION__], 1, $xValue, $yValues, $xValues); + } + + try { + $xValue = StatisticalValidations::validateFloat($xValue); + self::checkTrendArrays($yValues, $xValues); + self::validateTrendArrays($yValues, $xValues); + } catch (Exception $e) { + return $e->getMessage(); + } + + $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); + + return $bestFitLinear->getValueOfYForX($xValue); + } + + /** + * GROWTH. + * + * Returns values along a predicted exponential Trend + * + * @param mixed[] $yValues Data Series Y + * @param mixed[] $xValues Data Series X + * @param mixed[] $newValues Values of X for which we want to find Y + * @param mixed $const A logical (boolean) value specifying whether to force the intersect to equal 0 or not + * + * @return float[] + */ + public static function GROWTH($yValues, $xValues = [], $newValues = [], $const = true) + { + $yValues = Functions::flattenArray($yValues); + $xValues = Functions::flattenArray($xValues); + $newValues = Functions::flattenArray($newValues); + $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const); + + $bestFitExponential = Trend::calculate(Trend::TREND_EXPONENTIAL, $yValues, $xValues, $const); + if (empty($newValues)) { + $newValues = $bestFitExponential->getXValues(); + } + + $returnArray = []; + foreach ($newValues as $xValue) { + $returnArray[0][] = [$bestFitExponential->getValueOfYForX($xValue)]; + } + + return $returnArray; + } + + /** + * INTERCEPT. + * + * Calculates the point at which a line will intersect the y-axis by using existing x-values and y-values. + * + * @param mixed[] $yValues Data Series Y + * @param mixed[] $xValues Data Series X + * + * @return float|string + */ + public static function INTERCEPT($yValues, $xValues) + { + try { + self::checkTrendArrays($yValues, $xValues); + self::validateTrendArrays($yValues, $xValues); + } catch (Exception $e) { + return $e->getMessage(); + } + + $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); + + return $bestFitLinear->getIntersect(); + } + + /** + * LINEST. + * + * Calculates the statistics for a line by using the "least squares" method to calculate a straight line + * that best fits your data, and then returns an array that describes the line. + * + * @param mixed[] $yValues Data Series Y + * @param null|mixed[] $xValues Data Series X + * @param mixed $const A logical (boolean) value specifying whether to force the intersect to equal 0 or not + * @param mixed $stats A logical (boolean) value specifying whether to return additional regression statistics + * + * @return array|int|string The result, or a string containing an error + */ + public static function LINEST($yValues, $xValues = null, $const = true, $stats = false) + { + $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const); + $stats = ($stats === null) ? false : (bool) Functions::flattenSingleValue($stats); + if ($xValues === null) { + $xValues = $yValues; + } + + try { + self::checkTrendArrays($yValues, $xValues); + self::validateTrendArrays($yValues, $xValues); + } catch (Exception $e) { + return $e->getMessage(); + } + + $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues, $const); + + if ($stats === true) { + return [ + [ + $bestFitLinear->getSlope(), + $bestFitLinear->getIntersect(), + ], + [ + $bestFitLinear->getSlopeSE(), + ($const === false) ? ExcelError::NA() : $bestFitLinear->getIntersectSE(), + ], + [ + $bestFitLinear->getGoodnessOfFit(), + $bestFitLinear->getStdevOfResiduals(), + ], + [ + $bestFitLinear->getF(), + $bestFitLinear->getDFResiduals(), + ], + [ + $bestFitLinear->getSSRegression(), + $bestFitLinear->getSSResiduals(), + ], + ]; + } + + return [ + $bestFitLinear->getSlope(), + $bestFitLinear->getIntersect(), + ]; + } + + /** + * LOGEST. + * + * Calculates an exponential curve that best fits the X and Y data series, + * and then returns an array that describes the line. + * + * @param mixed[] $yValues Data Series Y + * @param null|mixed[] $xValues Data Series X + * @param mixed $const A logical (boolean) value specifying whether to force the intersect to equal 0 or not + * @param mixed $stats A logical (boolean) value specifying whether to return additional regression statistics + * + * @return array|int|string The result, or a string containing an error + */ + public static function LOGEST($yValues, $xValues = null, $const = true, $stats = false) + { + $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const); + $stats = ($stats === null) ? false : (bool) Functions::flattenSingleValue($stats); + if ($xValues === null) { + $xValues = $yValues; + } + + try { + self::checkTrendArrays($yValues, $xValues); + self::validateTrendArrays($yValues, $xValues); + } catch (Exception $e) { + return $e->getMessage(); + } + + foreach ($yValues as $value) { + if ($value < 0.0) { + return ExcelError::NAN(); + } + } + + $bestFitExponential = Trend::calculate(Trend::TREND_EXPONENTIAL, $yValues, $xValues, $const); + + if ($stats === true) { + return [ + [ + $bestFitExponential->getSlope(), + $bestFitExponential->getIntersect(), + ], + [ + $bestFitExponential->getSlopeSE(), + ($const === false) ? ExcelError::NA() : $bestFitExponential->getIntersectSE(), + ], + [ + $bestFitExponential->getGoodnessOfFit(), + $bestFitExponential->getStdevOfResiduals(), + ], + [ + $bestFitExponential->getF(), + $bestFitExponential->getDFResiduals(), + ], + [ + $bestFitExponential->getSSRegression(), + $bestFitExponential->getSSResiduals(), + ], + ]; + } + + return [ + $bestFitExponential->getSlope(), + $bestFitExponential->getIntersect(), + ]; + } + + /** + * RSQ. + * + * Returns the square of the Pearson product moment correlation coefficient through data points + * in known_y's and known_x's. + * + * @param mixed[] $yValues Data Series Y + * @param mixed[] $xValues Data Series X + * + * @return float|string The result, or a string containing an error + */ + public static function RSQ($yValues, $xValues) + { + try { + self::checkTrendArrays($yValues, $xValues); + self::validateTrendArrays($yValues, $xValues); + } catch (Exception $e) { + return $e->getMessage(); + } + + $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); + + return $bestFitLinear->getGoodnessOfFit(); + } + + /** + * SLOPE. + * + * Returns the slope of the linear regression line through data points in known_y's and known_x's. + * + * @param mixed[] $yValues Data Series Y + * @param mixed[] $xValues Data Series X + * + * @return float|string The result, or a string containing an error + */ + public static function SLOPE($yValues, $xValues) + { + try { + self::checkTrendArrays($yValues, $xValues); + self::validateTrendArrays($yValues, $xValues); + } catch (Exception $e) { + return $e->getMessage(); + } + + $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); + + return $bestFitLinear->getSlope(); + } + + /** + * STEYX. + * + * Returns the standard error of the predicted y-value for each x in the regression. + * + * @param mixed[] $yValues Data Series Y + * @param mixed[] $xValues Data Series X + * + * @return float|string + */ + public static function STEYX($yValues, $xValues) + { + try { + self::checkTrendArrays($yValues, $xValues); + self::validateTrendArrays($yValues, $xValues); + } catch (Exception $e) { + return $e->getMessage(); + } + + $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues); + + return $bestFitLinear->getStdevOfResiduals(); + } + + /** + * TREND. + * + * Returns values along a linear Trend + * + * @param mixed[] $yValues Data Series Y + * @param mixed[] $xValues Data Series X + * @param mixed[] $newValues Values of X for which we want to find Y + * @param mixed $const A logical (boolean) value specifying whether to force the intersect to equal 0 or not + * + * @return float[] + */ + public static function TREND($yValues, $xValues = [], $newValues = [], $const = true) + { + $yValues = Functions::flattenArray($yValues); + $xValues = Functions::flattenArray($xValues); + $newValues = Functions::flattenArray($newValues); + $const = ($const === null) ? true : (bool) Functions::flattenSingleValue($const); + + $bestFitLinear = Trend::calculate(Trend::TREND_LINEAR, $yValues, $xValues, $const); + if (empty($newValues)) { + $newValues = $bestFitLinear->getXValues(); + } + + $returnArray = []; + foreach ($newValues as $xValue) { + $returnArray[0][] = [$bestFitLinear->getValueOfYForX($xValue)]; + } + + return $returnArray; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Statistical/VarianceBase.php b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/VarianceBase.php new file mode 100644 index 0000000..e533467 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Statistical/VarianceBase.php @@ -0,0 +1,28 @@ + 1) { + $summerA *= $aCount; + $summerB *= $summerB; + + return ($summerA - $summerB) / ($aCount * ($aCount - 1)); + } + + return $returnValue; + } + + /** + * VARA. + * + * Estimates variance based on a sample, including numbers, text, and logical values + * + * Excel Function: + * VARA(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float|string (string if result is an error) + */ + public static function VARA(...$args) + { + $returnValue = ExcelError::DIV0(); + + $summerA = $summerB = 0.0; + + // Loop through arguments + $aArgs = Functions::flattenArrayIndexed($args); + $aCount = 0; + foreach ($aArgs as $k => $arg) { + if ((is_string($arg)) && (Functions::isValue($k))) { + return ExcelError::VALUE(); + } elseif ((is_string($arg)) && (!Functions::isMatrixValue($k))) { + } else { + // Is it a numeric value? + if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) { + $arg = self::datatypeAdjustmentAllowStrings($arg); + $summerA += ($arg * $arg); + $summerB += $arg; + ++$aCount; + } + } + } + + if ($aCount > 1) { + $summerA *= $aCount; + $summerB *= $summerB; + + return ($summerA - $summerB) / ($aCount * ($aCount - 1)); + } + + return $returnValue; + } + + /** + * VARP. + * + * Calculates variance based on the entire population + * + * Excel Function: + * VARP(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float|string (string if result is an error) + */ + public static function VARP(...$args) + { + // Return value + $returnValue = ExcelError::DIV0(); + + $summerA = $summerB = 0.0; + + // Loop through arguments + $aArgs = Functions::flattenArray($args); + $aCount = 0; + foreach ($aArgs as $arg) { + $arg = self::datatypeAdjustmentBooleans($arg); + + // Is it a numeric value? + if ((is_numeric($arg)) && (!is_string($arg))) { + $summerA += ($arg * $arg); + $summerB += $arg; + ++$aCount; + } + } + + if ($aCount > 0) { + $summerA *= $aCount; + $summerB *= $summerB; + + return ($summerA - $summerB) / ($aCount * $aCount); + } + + return $returnValue; + } + + /** + * VARPA. + * + * Calculates variance based on the entire population, including numbers, text, and logical values + * + * Excel Function: + * VARPA(value1[,value2[, ...]]) + * + * @param mixed ...$args Data values + * + * @return float|string (string if result is an error) + */ + public static function VARPA(...$args) + { + $returnValue = ExcelError::DIV0(); + + $summerA = $summerB = 0.0; + + // Loop through arguments + $aArgs = Functions::flattenArrayIndexed($args); + $aCount = 0; + foreach ($aArgs as $k => $arg) { + if ((is_string($arg)) && (Functions::isValue($k))) { + return ExcelError::VALUE(); + } elseif ((is_string($arg)) && (!Functions::isMatrixValue($k))) { + } else { + // Is it a numeric value? + if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) { + $arg = self::datatypeAdjustmentAllowStrings($arg); + $summerA += ($arg * $arg); + $summerB += $arg; + ++$aCount; + } + } + } + + if ($aCount > 0) { + $summerA *= $aCount; + $summerB *= $summerB; + + return ($summerA - $summerB) / ($aCount * $aCount); + } + + return $returnValue; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/TextData.php b/PhpOffice/PhpSpreadsheet/Calculation/TextData.php old mode 100755 new mode 100644 index e30b2ff..97f4629 --- a/PhpOffice/PhpSpreadsheet/Calculation/TextData.php +++ b/PhpOffice/PhpSpreadsheet/Calculation/TextData.php @@ -2,140 +2,89 @@ namespace PhpOffice\PhpSpreadsheet\Calculation; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PhpOffice\PhpSpreadsheet\Shared\StringHelper; -use PhpOffice\PhpSpreadsheet\Style\NumberFormat; +use DateTimeInterface; +/** + * @deprecated 1.18.0 + */ class TextData { - private static $invalidChars; - - private static function unicodeToOrd($character) - { - return unpack('V', iconv('UTF-8', 'UCS-4LE', $character))[1]; - } - /** * CHARACTER. * + * @deprecated 1.18.0 + * Use the character() method in the TextData\CharacterConvert class instead + * @see TextData\CharacterConvert::character() + * * @param string $character Value * - * @return string + * @return array|string */ public static function CHARACTER($character) { - $character = Functions::flattenSingleValue($character); - - if ((!is_numeric($character)) || ($character < 0)) { - return Functions::VALUE(); - } - - if (function_exists('iconv')) { - return iconv('UCS-4LE', 'UTF-8', pack('V', $character)); - } - - return mb_convert_encoding('&#' . (int) $character . ';', 'UTF-8', 'HTML-ENTITIES'); + return TextData\CharacterConvert::character($character); } /** * TRIMNONPRINTABLE. * + * @deprecated 1.18.0 + * Use the nonPrintable() method in the TextData\Trim class instead + * @see TextData\Trim::nonPrintable() + * * @param mixed $stringValue Value to check * - * @return string + * @return null|array|string */ public static function TRIMNONPRINTABLE($stringValue = '') { - $stringValue = Functions::flattenSingleValue($stringValue); - - if (is_bool($stringValue)) { - return ($stringValue) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - - if (self::$invalidChars == null) { - self::$invalidChars = range(chr(0), chr(31)); - } - - if (is_string($stringValue) || is_numeric($stringValue)) { - return str_replace(self::$invalidChars, '', trim($stringValue, "\x00..\x1F")); - } - - return null; + return TextData\Trim::nonPrintable($stringValue); } /** * TRIMSPACES. * + * @deprecated 1.18.0 + * Use the spaces() method in the TextData\Trim class instead + * @see TextData\Trim::spaces() + * * @param mixed $stringValue Value to check * - * @return string + * @return array|string */ public static function TRIMSPACES($stringValue = '') { - $stringValue = Functions::flattenSingleValue($stringValue); - if (is_bool($stringValue)) { - return ($stringValue) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - - if (is_string($stringValue) || is_numeric($stringValue)) { - return trim(preg_replace('/ +/', ' ', trim($stringValue, ' ')), ' '); - } - - return null; + return TextData\Trim::spaces($stringValue); } /** * ASCIICODE. * - * @param string $characters Value + * @deprecated 1.18.0 + * Use the code() method in the TextData\CharacterConvert class instead + * @see TextData\CharacterConvert::code() * - * @return int + * @param array|string $characters Value + * + * @return array|int|string A string if arguments are invalid */ public static function ASCIICODE($characters) { - if (($characters === null) || ($characters === '')) { - return Functions::VALUE(); - } - $characters = Functions::flattenSingleValue($characters); - if (is_bool($characters)) { - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) { - $characters = (int) $characters; - } else { - $characters = ($characters) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - } - - $character = $characters; - if (mb_strlen($characters, 'UTF-8') > 1) { - $character = mb_substr($characters, 0, 1, 'UTF-8'); - } - - return self::unicodeToOrd($character); + return TextData\CharacterConvert::code($characters); } /** * CONCATENATE. * + * @deprecated 1.18.0 + * Use the CONCATENATE() method in the TextData\Concatenate class instead + * @see TextData\Concatenate::CONCATENATE() + * * @return string */ public static function CONCATENATE(...$args) { - $returnValue = ''; - - // Loop through arguments - $aArgs = Functions::flattenArray($args); - foreach ($aArgs as $arg) { - if (is_bool($arg)) { - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) { - $arg = (int) $arg; - } else { - $arg = ($arg) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - } - $returnValue .= $arg; - } - - return $returnValue; + return TextData\Concatenate::CONCATENATE(...$args); } /** @@ -144,254 +93,160 @@ class TextData * This function converts a number to text using currency format, with the decimals rounded to the specified place. * The format used is $#,##0.00_);($#,##0.00).. * + * @deprecated 1.18.0 + * Use the DOLLAR() method in the TextData\Format class instead + * @see TextData\Format::DOLLAR() + * * @param float $value The value to format * @param int $decimals The number of digits to display to the right of the decimal point. * If decimals is negative, number is rounded to the left of the decimal point. * If you omit decimals, it is assumed to be 2 * - * @return string + * @return array|string */ public static function DOLLAR($value = 0, $decimals = 2) { - $value = Functions::flattenSingleValue($value); - $decimals = $decimals === null ? 0 : Functions::flattenSingleValue($decimals); - - // Validate parameters - if (!is_numeric($value) || !is_numeric($decimals)) { - return Functions::NAN(); - } - $decimals = floor($decimals); - - $mask = '$#,##0'; - if ($decimals > 0) { - $mask .= '.' . str_repeat('0', $decimals); - } else { - $round = pow(10, abs($decimals)); - if ($value < 0) { - $round = 0 - $round; - } - $value = MathTrig::MROUND($value, $round); - } - - return NumberFormat::toFormattedString($value, $mask); + return TextData\Format::DOLLAR($value, $decimals); } /** - * SEARCHSENSITIVE. + * FIND. * - * @param string $needle The string to look for - * @param string $haystack The string in which to look - * @param int $offset Offset within $haystack + * @deprecated 1.18.0 + * Use the sensitive() method in the TextData\Search class instead + * @see TextData\Search::sensitive() * - * @return string + * @param array|string $needle The string to look for + * @param array|string $haystack The string in which to look + * @param array|int $offset Offset within $haystack + * + * @return array|int|string */ public static function SEARCHSENSITIVE($needle, $haystack, $offset = 1) { - $needle = Functions::flattenSingleValue($needle); - $haystack = Functions::flattenSingleValue($haystack); - $offset = Functions::flattenSingleValue($offset); - - if (!is_bool($needle)) { - if (is_bool($haystack)) { - $haystack = ($haystack) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - - if (($offset > 0) && (StringHelper::countCharacters($haystack) > $offset)) { - if (StringHelper::countCharacters($needle) == 0) { - return $offset; - } - - $pos = mb_strpos($haystack, $needle, --$offset, 'UTF-8'); - if ($pos !== false) { - return ++$pos; - } - } - } - - return Functions::VALUE(); + return TextData\Search::sensitive($needle, $haystack, $offset); } /** - * SEARCHINSENSITIVE. + * SEARCH. * - * @param string $needle The string to look for - * @param string $haystack The string in which to look - * @param int $offset Offset within $haystack + * @deprecated 1.18.0 + * Use the insensitive() method in the TextData\Search class instead + * @see TextData\Search::insensitive() * - * @return string + * @param array|string $needle The string to look for + * @param array|string $haystack The string in which to look + * @param array|int $offset Offset within $haystack + * + * @return array|int|string */ public static function SEARCHINSENSITIVE($needle, $haystack, $offset = 1) { - $needle = Functions::flattenSingleValue($needle); - $haystack = Functions::flattenSingleValue($haystack); - $offset = Functions::flattenSingleValue($offset); - - if (!is_bool($needle)) { - if (is_bool($haystack)) { - $haystack = ($haystack) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - - if (($offset > 0) && (StringHelper::countCharacters($haystack) > $offset)) { - if (StringHelper::countCharacters($needle) == 0) { - return $offset; - } - - $pos = mb_stripos($haystack, $needle, --$offset, 'UTF-8'); - if ($pos !== false) { - return ++$pos; - } - } - } - - return Functions::VALUE(); + return TextData\Search::insensitive($needle, $haystack, $offset); } /** * FIXEDFORMAT. * + * @deprecated 1.18.0 + * Use the FIXEDFORMAT() method in the TextData\Format class instead + * @see TextData\Format::FIXEDFORMAT() + * * @param mixed $value Value to check * @param int $decimals * @param bool $no_commas * - * @return string + * @return array|string */ public static function FIXEDFORMAT($value, $decimals = 2, $no_commas = false) { - $value = Functions::flattenSingleValue($value); - $decimals = Functions::flattenSingleValue($decimals); - $no_commas = Functions::flattenSingleValue($no_commas); - - // Validate parameters - if (!is_numeric($value) || !is_numeric($decimals)) { - return Functions::NAN(); - } - $decimals = floor($decimals); - - $valueResult = round($value, $decimals); - if ($decimals < 0) { - $decimals = 0; - } - if (!$no_commas) { - $valueResult = number_format($valueResult, $decimals); - } - - return (string) $valueResult; + return TextData\Format::FIXEDFORMAT($value, $decimals, $no_commas); } /** * LEFT. * - * @param string $value Value - * @param int $chars Number of characters + * @deprecated 1.18.0 + * Use the left() method in the TextData\Extract class instead + * @see TextData\Extract::left() * - * @return string + * @param array|string $value Value + * @param array|int $chars Number of characters + * + * @return array|string */ public static function LEFT($value = '', $chars = 1) { - $value = Functions::flattenSingleValue($value); - $chars = Functions::flattenSingleValue($chars); - - if ($chars < 0) { - return Functions::VALUE(); - } - - if (is_bool($value)) { - $value = ($value) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - - return mb_substr($value, 0, $chars, 'UTF-8'); + return TextData\Extract::left($value, $chars); } /** * MID. * - * @param string $value Value - * @param int $start Start character - * @param int $chars Number of characters + * @deprecated 1.18.0 + * Use the mid() method in the TextData\Extract class instead + * @see TextData\Extract::mid() * - * @return string + * @param array|string $value Value + * @param array|int $start Start character + * @param array|int $chars Number of characters + * + * @return array|string */ public static function MID($value = '', $start = 1, $chars = null) { - $value = Functions::flattenSingleValue($value); - $start = Functions::flattenSingleValue($start); - $chars = Functions::flattenSingleValue($chars); - - if (($start < 1) || ($chars < 0)) { - return Functions::VALUE(); - } - - if (is_bool($value)) { - $value = ($value) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - - if (empty($chars)) { - return ''; - } - - return mb_substr($value, --$start, $chars, 'UTF-8'); + return TextData\Extract::mid($value, $start, $chars); } /** * RIGHT. * - * @param string $value Value - * @param int $chars Number of characters + * @deprecated 1.18.0 + * Use the right() method in the TextData\Extract class instead + * @see TextData\Extract::right() * - * @return string + * @param array|string $value Value + * @param array|int $chars Number of characters + * + * @return array|string */ public static function RIGHT($value = '', $chars = 1) { - $value = Functions::flattenSingleValue($value); - $chars = Functions::flattenSingleValue($chars); - - if ($chars < 0) { - return Functions::VALUE(); - } - - if (is_bool($value)) { - $value = ($value) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - - return mb_substr($value, mb_strlen($value, 'UTF-8') - $chars, $chars, 'UTF-8'); + return TextData\Extract::right($value, $chars); } /** * STRINGLENGTH. * + * @deprecated 1.18.0 + * Use the length() method in the TextData\Text class instead + * @see TextData\Text::length() + * * @param string $value Value * - * @return int + * @return array|int */ public static function STRINGLENGTH($value = '') { - $value = Functions::flattenSingleValue($value); - - if (is_bool($value)) { - $value = ($value) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - - return mb_strlen($value, 'UTF-8'); + return TextData\Text::length($value); } /** * LOWERCASE. * - * Converts a string value to upper case. + * Converts a string value to lower case. * - * @param string $mixedCaseString + * @deprecated 1.18.0 + * Use the lower() method in the TextData\CaseConvert class instead + * @see TextData\CaseConvert::lower() * - * @return string + * @param array|string $mixedCaseString + * + * @return array|string */ public static function LOWERCASE($mixedCaseString) { - $mixedCaseString = Functions::flattenSingleValue($mixedCaseString); - - if (is_bool($mixedCaseString)) { - $mixedCaseString = ($mixedCaseString) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - - return StringHelper::strToLower($mixedCaseString); + return TextData\CaseConvert::lower($mixedCaseString); } /** @@ -399,229 +254,140 @@ class TextData * * Converts a string value to upper case. * + * @deprecated 1.18.0 + * Use the upper() method in the TextData\CaseConvert class instead + * @see TextData\CaseConvert::upper() + * * @param string $mixedCaseString * - * @return string + * @return array|string */ public static function UPPERCASE($mixedCaseString) { - $mixedCaseString = Functions::flattenSingleValue($mixedCaseString); - - if (is_bool($mixedCaseString)) { - $mixedCaseString = ($mixedCaseString) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - - return StringHelper::strToUpper($mixedCaseString); + return TextData\CaseConvert::upper($mixedCaseString); } /** * PROPERCASE. * - * Converts a string value to upper case. + * Converts a string value to proper/title case. * - * @param string $mixedCaseString + * @deprecated 1.18.0 + * Use the proper() method in the TextData\CaseConvert class instead + * @see TextData\CaseConvert::proper() * - * @return string + * @param array|string $mixedCaseString + * + * @return array|string */ public static function PROPERCASE($mixedCaseString) { - $mixedCaseString = Functions::flattenSingleValue($mixedCaseString); - - if (is_bool($mixedCaseString)) { - $mixedCaseString = ($mixedCaseString) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - - return StringHelper::strToTitle($mixedCaseString); + return TextData\CaseConvert::proper($mixedCaseString); } /** * REPLACE. * + * @deprecated 1.18.0 + * Use the replace() method in the TextData\Replace class instead + * @see TextData\Replace::replace() + * * @param string $oldText String to modify * @param int $start Start character * @param int $chars Number of characters * @param string $newText String to replace in defined position * - * @return string + * @return array|string */ public static function REPLACE($oldText, $start, $chars, $newText) { - $oldText = Functions::flattenSingleValue($oldText); - $start = Functions::flattenSingleValue($start); - $chars = Functions::flattenSingleValue($chars); - $newText = Functions::flattenSingleValue($newText); - - $left = self::LEFT($oldText, $start - 1); - $right = self::RIGHT($oldText, self::STRINGLENGTH($oldText) - ($start + $chars) + 1); - - return $left . $newText . $right; + return TextData\Replace::replace($oldText, $start, $chars, $newText); } /** * SUBSTITUTE. * + * @deprecated 1.18.0 + * Use the substitute() method in the TextData\Replace class instead + * @see TextData\Replace::substitute() + * * @param string $text Value * @param string $fromText From Value * @param string $toText To Value * @param int $instance Instance Number * - * @return string + * @return array|string */ public static function SUBSTITUTE($text = '', $fromText = '', $toText = '', $instance = 0) { - $text = Functions::flattenSingleValue($text); - $fromText = Functions::flattenSingleValue($fromText); - $toText = Functions::flattenSingleValue($toText); - $instance = floor(Functions::flattenSingleValue($instance)); - - if ($instance == 0) { - return str_replace($fromText, $toText, $text); - } - - $pos = -1; - while ($instance > 0) { - $pos = mb_strpos($text, $fromText, $pos + 1, 'UTF-8'); - if ($pos === false) { - break; - } - --$instance; - } - - if ($pos !== false) { - return self::REPLACE($text, ++$pos, mb_strlen($fromText, 'UTF-8'), $toText); - } - - return $text; + return TextData\Replace::substitute($text, $fromText, $toText, $instance); } /** * RETURNSTRING. * + * @deprecated 1.18.0 + * Use the test() method in the TextData\Text class instead + * @see TextData\Text::test() + * * @param mixed $testValue Value to check * - * @return null|string + * @return null|array|string */ public static function RETURNSTRING($testValue = '') { - $testValue = Functions::flattenSingleValue($testValue); - - if (is_string($testValue)) { - return $testValue; - } - - return null; + return TextData\Text::test($testValue); } /** * TEXTFORMAT. * + * @deprecated 1.18.0 + * Use the TEXTFORMAT() method in the TextData\Format class instead + * @see TextData\Format::TEXTFORMAT() + * * @param mixed $value Value to check * @param string $format Format mask to use * - * @return string + * @return array|string */ public static function TEXTFORMAT($value, $format) { - $value = Functions::flattenSingleValue($value); - $format = Functions::flattenSingleValue($format); - - if ((is_string($value)) && (!is_numeric($value)) && Date::isDateTimeFormatCode($format)) { - $value = DateTime::DATEVALUE($value); - } - - return (string) NumberFormat::toFormattedString($value, $format); + return TextData\Format::TEXTFORMAT($value, $format); } /** * VALUE. * + * @deprecated 1.18.0 + * Use the VALUE() method in the TextData\Format class instead + * @see TextData\Format::VALUE() + * * @param mixed $value Value to check * - * @return bool + * @return array|DateTimeInterface|float|int|string A string if arguments are invalid */ public static function VALUE($value = '') { - $value = Functions::flattenSingleValue($value); - - if (!is_numeric($value)) { - $numberValue = str_replace( - StringHelper::getThousandsSeparator(), - '', - trim($value, " \t\n\r\0\x0B" . StringHelper::getCurrencyCode()) - ); - if (is_numeric($numberValue)) { - return (float) $numberValue; - } - - $dateSetting = Functions::getReturnDateType(); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - - if (strpos($value, ':') !== false) { - $timeValue = DateTime::TIMEVALUE($value); - if ($timeValue !== Functions::VALUE()) { - Functions::setReturnDateType($dateSetting); - - return $timeValue; - } - } - $dateValue = DateTime::DATEVALUE($value); - if ($dateValue !== Functions::VALUE()) { - Functions::setReturnDateType($dateSetting); - - return $dateValue; - } - Functions::setReturnDateType($dateSetting); - - return Functions::VALUE(); - } - - return (float) $value; + return TextData\Format::VALUE($value); } /** * NUMBERVALUE. * + * @deprecated 1.18.0 + * Use the NUMBERVALUE() method in the TextData\Format class instead + * @see TextData\Format::NUMBERVALUE() + * * @param mixed $value Value to check * @param string $decimalSeparator decimal separator, defaults to locale defined value * @param string $groupSeparator group/thosands separator, defaults to locale defined value * - * @return float|string + * @return array|float|string */ public static function NUMBERVALUE($value = '', $decimalSeparator = null, $groupSeparator = null) { - $value = Functions::flattenSingleValue($value); - $decimalSeparator = Functions::flattenSingleValue($decimalSeparator); - $groupSeparator = Functions::flattenSingleValue($groupSeparator); - - if (!is_numeric($value)) { - $decimalSeparator = empty($decimalSeparator) ? StringHelper::getDecimalSeparator() : $decimalSeparator; - $groupSeparator = empty($groupSeparator) ? StringHelper::getThousandsSeparator() : $groupSeparator; - - $decimalPositions = preg_match_all('/' . preg_quote($decimalSeparator) . '/', $value, $matches, PREG_OFFSET_CAPTURE); - if ($decimalPositions > 1) { - return Functions::VALUE(); - } - $decimalOffset = array_pop($matches[0])[1]; - if (strpos($value, $groupSeparator, $decimalOffset) !== false) { - return Functions::VALUE(); - } - - $value = str_replace([$groupSeparator, $decimalSeparator], ['', '.'], $value); - - // Handle the special case of trailing % signs - $percentageString = rtrim($value, '%'); - if (!is_numeric($percentageString)) { - return Functions::VALUE(); - } - - $percentageAdjustment = strlen($value) - strlen($percentageString); - if ($percentageAdjustment) { - $value = (float) $percentageString; - $value /= pow(10, $percentageAdjustment * 2); - } - } - - return (float) $value; + return TextData\Format::NUMBERVALUE($value, $decimalSeparator, $groupSeparator); } /** @@ -629,44 +395,54 @@ class TextData * EXACT is case-sensitive but ignores formatting differences. * Use EXACT to test text being entered into a document. * - * @param $value1 - * @param $value2 + * @deprecated 1.18.0 + * Use the exact() method in the TextData\Text class instead + * @see TextData\Text::exact() * - * @return bool + * @param mixed $value1 + * @param mixed $value2 + * + * @return array|bool */ public static function EXACT($value1, $value2) { - $value1 = Functions::flattenSingleValue($value1); - $value2 = Functions::flattenSingleValue($value2); - - return (string) $value2 === (string) $value1; + return TextData\Text::exact($value1, $value2); } /** * TEXTJOIN. * + * @deprecated 1.18.0 + * Use the TEXTJOIN() method in the TextData\Concatenate class instead + * @see TextData\Concatenate::TEXTJOIN() + * * @param mixed $delimiter * @param mixed $ignoreEmpty * @param mixed $args * - * @return string + * @return array|string */ public static function TEXTJOIN($delimiter, $ignoreEmpty, ...$args) { - // Loop through arguments - $aArgs = Functions::flattenArray($args); - foreach ($aArgs as $key => &$arg) { - if ($ignoreEmpty && trim($arg) == '') { - unset($aArgs[$key]); - } elseif (is_bool($arg)) { - if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE) { - $arg = (int) $arg; - } else { - $arg = ($arg) ? Calculation::getTRUE() : Calculation::getFALSE(); - } - } - } + return TextData\Concatenate::TEXTJOIN($delimiter, $ignoreEmpty, ...$args); + } - return implode($delimiter, $aArgs); + /** + * REPT. + * + * Returns the result of builtin function repeat after validating args. + * + * @deprecated 1.18.0 + * Use the builtinREPT() method in the TextData\Concatenate class instead + * @see TextData\Concatenate::builtinREPT() + * + * @param array|string $str Should be numeric + * @param mixed $number Should be int + * + * @return array|string + */ + public static function builtinREPT($str, $number) + { + return TextData\Concatenate::builtinREPT($str, $number); } } diff --git a/PhpOffice/PhpSpreadsheet/Calculation/TextData/CaseConvert.php b/PhpOffice/PhpSpreadsheet/Calculation/TextData/CaseConvert.php new file mode 100644 index 0000000..f1aea16 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/TextData/CaseConvert.php @@ -0,0 +1,80 @@ + 255) { + return ExcelError::VALUE(); + } + $result = iconv('UCS-4LE', 'UTF-8', pack('V', $character)); + + return ($result === false) ? '' : $result; + } + + /** + * CODE. + * + * @param mixed $characters String character to convert to its ASCII value + * Or can be an array of values + * + * @return array|int|string A string if arguments are invalid + * If an array of values is passed as the argument, then the returned result will also be an array + * with the same dimensions + */ + public static function code($characters) + { + if (is_array($characters)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $characters); + } + + $characters = Helpers::extractString($characters); + if ($characters === '') { + return ExcelError::VALUE(); + } + + $character = $characters; + if (mb_strlen($characters, 'UTF-8') > 1) { + $character = mb_substr($characters, 0, 1, 'UTF-8'); + } + + return self::unicodeToOrd($character); + } + + private static function unicodeToOrd(string $character): int + { + $retVal = 0; + $iconv = iconv('UTF-8', 'UCS-4LE', $character); + if ($iconv !== false) { + $result = unpack('V', $iconv); + if (is_array($result) && isset($result[1])) { + $retVal = $result[1]; + } + } + + return $retVal; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/TextData/Concatenate.php b/PhpOffice/PhpSpreadsheet/Calculation/TextData/Concatenate.php new file mode 100644 index 0000000..37c135d --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/TextData/Concatenate.php @@ -0,0 +1,137 @@ + DataType::MAX_STRING_LENGTH) { + $returnValue = ExcelError::CALC(); + + break; + } + } + + return $returnValue; + } + + /** + * TEXTJOIN. + * + * @param mixed $delimiter The delimter to use between the joined arguments + * Or can be an array of values + * @param mixed $ignoreEmpty true/false Flag indicating whether empty arguments should be skipped + * Or can be an array of values + * @param mixed $args The values to join + * + * @return array|string The joined string + * If an array of values is passed for the $delimiter or $ignoreEmpty arguments, then the returned result + * will also be an array with matching dimensions + */ + public static function TEXTJOIN($delimiter = '', $ignoreEmpty = true, ...$args) + { + if (is_array($delimiter) || is_array($ignoreEmpty)) { + return self::evaluateArrayArgumentsSubset( + [self::class, __FUNCTION__], + 2, + $delimiter, + $ignoreEmpty, + ...$args + ); + } + + $delimiter ??= ''; + $ignoreEmpty ??= true; + $aArgs = Functions::flattenArray($args); + $returnValue = self::evaluateTextJoinArray($ignoreEmpty, $aArgs); + + $returnValue ??= implode($delimiter, $aArgs); + if (StringHelper::countCharacters($returnValue) > DataType::MAX_STRING_LENGTH) { + $returnValue = ExcelError::CALC(); + } + + return $returnValue; + } + + private static function evaluateTextJoinArray(bool $ignoreEmpty, array &$aArgs): ?string + { + foreach ($aArgs as $key => &$arg) { + $value = Helpers::extractString($arg); + if (ErrorValue::isError($value)) { + return $value; + } + + if ($ignoreEmpty === true && ((is_string($arg) && trim($arg) === '') || $arg === null)) { + unset($aArgs[$key]); + } elseif (is_bool($arg)) { + $arg = Helpers::convertBooleanValue($arg); + } + } + + return null; + } + + /** + * REPT. + * + * Returns the result of builtin function round after validating args. + * + * @param mixed $stringValue The value to repeat + * Or can be an array of values + * @param mixed $repeatCount The number of times the string value should be repeated + * Or can be an array of values + * + * @return array|string The repeated string + * If an array of values is passed for the $stringValue or $repeatCount arguments, then the returned result + * will also be an array with matching dimensions + */ + public static function builtinREPT($stringValue, $repeatCount) + { + if (is_array($stringValue) || is_array($repeatCount)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $stringValue, $repeatCount); + } + + $stringValue = Helpers::extractString($stringValue); + + if (!is_numeric($repeatCount) || $repeatCount < 0) { + $returnValue = ExcelError::VALUE(); + } elseif (ErrorValue::isError($stringValue)) { + $returnValue = $stringValue; + } else { + $returnValue = str_repeat($stringValue, (int) $repeatCount); + if (StringHelper::countCharacters($returnValue) > DataType::MAX_STRING_LENGTH) { + $returnValue = ExcelError::VALUE(); // note VALUE not CALC + } + } + + return $returnValue; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/TextData/Extract.php b/PhpOffice/PhpSpreadsheet/Calculation/TextData/Extract.php new file mode 100644 index 0000000..d3668f8 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/TextData/Extract.php @@ -0,0 +1,280 @@ +getMessage(); + } + + return mb_substr($value ?? '', 0, $chars, 'UTF-8'); + } + + /** + * MID. + * + * @param mixed $value String value from which to extract characters + * Or can be an array of values + * @param mixed $start Integer offset of the first character that we want to extract + * Or can be an array of values + * @param mixed $chars The number of characters to extract (as an integer) + * Or can be an array of values + * + * @return array|string The joined string + * If an array of values is passed for the $value, $start or $chars arguments, then the returned result + * will also be an array with matching dimensions + */ + public static function mid($value, $start, $chars) + { + if (is_array($value) || is_array($start) || is_array($chars)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $start, $chars); + } + + try { + $value = Helpers::extractString($value); + $start = Helpers::extractInt($start, 1); + $chars = Helpers::extractInt($chars, 0); + } catch (CalcExp $e) { + return $e->getMessage(); + } + + return mb_substr($value ?? '', --$start, $chars, 'UTF-8'); + } + + /** + * RIGHT. + * + * @param mixed $value String value from which to extract characters + * Or can be an array of values + * @param mixed $chars The number of characters to extract (as an integer) + * Or can be an array of values + * + * @return array|string The joined string + * If an array of values is passed for the $value or $chars arguments, then the returned result + * will also be an array with matching dimensions + */ + public static function right($value, $chars = 1) + { + if (is_array($value) || is_array($chars)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $chars); + } + + try { + $value = Helpers::extractString($value); + $chars = Helpers::extractInt($chars, 0, 1); + } catch (CalcExp $e) { + return $e->getMessage(); + } + + return mb_substr($value ?? '', mb_strlen($value ?? '', 'UTF-8') - $chars, $chars, 'UTF-8'); + } + + /** + * TEXTBEFORE. + * + * @param mixed $text the text that you're searching + * Or can be an array of values + * @param null|array|string $delimiter the text that marks the point before which you want to extract + * Multiple delimiters can be passed as an array of string values + * @param mixed $instance The instance of the delimiter after which you want to extract the text. + * By default, this is the first instance (1). + * A negative value means start searching from the end of the text string. + * Or can be an array of values + * @param mixed $matchMode Determines whether the match is case-sensitive or not. + * 0 - Case-sensitive + * 1 - Case-insensitive + * Or can be an array of values + * @param mixed $matchEnd Treats the end of text as a delimiter. + * 0 - Don't match the delimiter against the end of the text. + * 1 - Match the delimiter against the end of the text. + * Or can be an array of values + * @param mixed $ifNotFound value to return if no match is found + * The default is a #N/A Error + * Or can be an array of values + * + * @return mixed|mixed[] the string extracted from text before the delimiter; or the $ifNotFound value + * If an array of values is passed for any of the arguments, then the returned result + * will also be an array with matching dimensions + */ + public static function before($text, $delimiter, $instance = 1, $matchMode = 0, $matchEnd = 0, $ifNotFound = '#N/A') + { + if (is_array($text) || is_array($instance) || is_array($matchMode) || is_array($matchEnd) || is_array($ifNotFound)) { + return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound); + } + + $text = Helpers::extractString($text ?? ''); + $instance = (int) $instance; + $matchMode = (int) $matchMode; + $matchEnd = (int) $matchEnd; + + $split = self::validateTextBeforeAfter($text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound); + if (is_string($split)) { + return $split; + } + if (Helpers::extractString(Functions::flattenSingleValue($delimiter ?? '')) === '') { + return ($instance > 0) ? '' : $text; + } + + // Adjustment for a match as the first element of the split + $flags = self::matchFlags($matchMode); + $delimiter = self::buildDelimiter($delimiter); + $adjust = preg_match('/^' . $delimiter . "\$/{$flags}", $split[0]); + $oddReverseAdjustment = count($split) % 2; + + $split = ($instance < 0) + ? array_slice($split, 0, max(count($split) - (abs($instance) * 2 - 1) - $adjust - $oddReverseAdjustment, 0)) + : array_slice($split, 0, $instance * 2 - 1 - $adjust); + + return implode('', $split); + } + + /** + * TEXTAFTER. + * + * @param mixed $text the text that you're searching + * @param null|array|string $delimiter the text that marks the point before which you want to extract + * Multiple delimiters can be passed as an array of string values + * @param mixed $instance The instance of the delimiter after which you want to extract the text. + * By default, this is the first instance (1). + * A negative value means start searching from the end of the text string. + * Or can be an array of values + * @param mixed $matchMode Determines whether the match is case-sensitive or not. + * 0 - Case-sensitive + * 1 - Case-insensitive + * Or can be an array of values + * @param mixed $matchEnd Treats the end of text as a delimiter. + * 0 - Don't match the delimiter against the end of the text. + * 1 - Match the delimiter against the end of the text. + * Or can be an array of values + * @param mixed $ifNotFound value to return if no match is found + * The default is a #N/A Error + * Or can be an array of values + * + * @return mixed|mixed[] the string extracted from text before the delimiter; or the $ifNotFound value + * If an array of values is passed for any of the arguments, then the returned result + * will also be an array with matching dimensions + */ + public static function after($text, $delimiter, $instance = 1, $matchMode = 0, $matchEnd = 0, $ifNotFound = '#N/A') + { + if (is_array($text) || is_array($instance) || is_array($matchMode) || is_array($matchEnd) || is_array($ifNotFound)) { + return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound); + } + + $text = Helpers::extractString($text ?? ''); + $instance = (int) $instance; + $matchMode = (int) $matchMode; + $matchEnd = (int) $matchEnd; + + $split = self::validateTextBeforeAfter($text, $delimiter, $instance, $matchMode, $matchEnd, $ifNotFound); + if (is_string($split)) { + return $split; + } + if (Helpers::extractString(Functions::flattenSingleValue($delimiter ?? '')) === '') { + return ($instance < 0) ? '' : $text; + } + + // Adjustment for a match as the first element of the split + $flags = self::matchFlags($matchMode); + $delimiter = self::buildDelimiter($delimiter); + $adjust = preg_match('/^' . $delimiter . "\$/{$flags}", $split[0]); + $oddReverseAdjustment = count($split) % 2; + + $split = ($instance < 0) + ? array_slice($split, count($split) - ((int) abs($instance + 1) * 2) - $adjust - $oddReverseAdjustment) + : array_slice($split, $instance * 2 - $adjust); + + return implode('', $split); + } + + /** + * @param null|array|string $delimiter + * @param int $matchMode + * @param int $matchEnd + * @param mixed $ifNotFound + * + * @return array|string + */ + private static function validateTextBeforeAfter(string $text, $delimiter, int $instance, $matchMode, $matchEnd, $ifNotFound) + { + $flags = self::matchFlags($matchMode); + $delimiter = self::buildDelimiter($delimiter); + + if (preg_match('/' . $delimiter . "/{$flags}", $text) === 0 && $matchEnd === 0) { + return $ifNotFound; + } + + $split = preg_split('/' . $delimiter . "/{$flags}", $text, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); + if ($split === false) { + return ExcelError::NA(); + } + + if ($instance === 0 || abs($instance) > StringHelper::countCharacters($text)) { + return ExcelError::VALUE(); + } + + if ($matchEnd === 0 && (abs($instance) > floor(count($split) / 2))) { + return ExcelError::NA(); + } elseif ($matchEnd !== 0 && (abs($instance) - 1 > ceil(count($split) / 2))) { + return ExcelError::NA(); + } + + return $split; + } + + /** + * @param null|array|string $delimiter the text that marks the point before which you want to extract + * Multiple delimiters can be passed as an array of string values + */ + private static function buildDelimiter($delimiter): string + { + if (is_array($delimiter)) { + $delimiter = Functions::flattenArray($delimiter); + $quotedDelimiters = array_map( + function ($delimiter) { + return preg_quote($delimiter ?? ''); + }, + $delimiter + ); + $delimiters = implode('|', $quotedDelimiters); + + return '(' . $delimiters . ')'; + } + + return '(' . preg_quote($delimiter ?? '') . ')'; + } + + private static function matchFlags(int $matchMode): string + { + return ($matchMode === 0) ? 'mu' : 'miu'; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/TextData/Format.php b/PhpOffice/PhpSpreadsheet/Calculation/TextData/Format.php new file mode 100644 index 0000000..93e7282 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/TextData/Format.php @@ -0,0 +1,314 @@ +getMessage(); + } + + $mask = '$#,##0'; + if ($decimals > 0) { + $mask .= '.' . str_repeat('0', $decimals); + } else { + $round = 10 ** abs($decimals); + if ($value < 0) { + $round = 0 - $round; + } + $value = MathTrig\Round::multiple($value, $round); + } + $mask = "{$mask};-{$mask}"; + + return NumberFormat::toFormattedString($value, $mask); + } + + /** + * FIXED. + * + * @param mixed $value The value to format + * Or can be an array of values + * @param mixed $decimals Integer value for the number of decimal places that should be formatted + * Or can be an array of values + * @param mixed $noCommas Boolean value indicating whether the value should have thousands separators or not + * Or can be an array of values + * + * @return array|string + * If an array of values is passed for either of the arguments, then the returned result + * will also be an array with matching dimensions + */ + public static function FIXEDFORMAT($value, $decimals = 2, $noCommas = false) + { + if (is_array($value) || is_array($decimals) || is_array($noCommas)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $decimals, $noCommas); + } + + try { + $value = Helpers::extractFloat($value); + $decimals = Helpers::extractInt($decimals, -100, 0, true); + } catch (CalcExp $e) { + return $e->getMessage(); + } + + $valueResult = round($value, $decimals); + if ($decimals < 0) { + $decimals = 0; + } + if ($noCommas === false) { + $valueResult = number_format( + $valueResult, + $decimals, + StringHelper::getDecimalSeparator(), + StringHelper::getThousandsSeparator() + ); + } + + return (string) $valueResult; + } + + /** + * TEXT. + * + * @param mixed $value The value to format + * Or can be an array of values + * @param mixed $format A string with the Format mask that should be used + * Or can be an array of values + * + * @return array|string + * If an array of values is passed for either of the arguments, then the returned result + * will also be an array with matching dimensions + */ + public static function TEXTFORMAT($value, $format) + { + if (is_array($value) || is_array($format)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $format); + } + + $value = Helpers::extractString($value); + $format = Helpers::extractString($format); + + if (!is_numeric($value) && Date::isDateTimeFormatCode($format)) { + $value = DateTimeExcel\DateValue::fromString($value); + } + + return (string) NumberFormat::toFormattedString($value, $format); + } + + /** + * @param mixed $value Value to check + * + * @return mixed + */ + private static function convertValue($value) + { + $value = $value ?? 0; + if (is_bool($value)) { + if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) { + $value = (int) $value; + } else { + throw new CalcExp(ExcelError::VALUE()); + } + } + + return $value; + } + + /** + * VALUE. + * + * @param mixed $value Value to check + * Or can be an array of values + * + * @return array|DateTimeInterface|float|int|string A string if arguments are invalid + * If an array of values is passed for the argument, then the returned result + * will also be an array with matching dimensions + */ + public static function VALUE($value = '') + { + if (is_array($value)) { + return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value); + } + + try { + $value = self::convertValue($value); + } catch (CalcExp $e) { + return $e->getMessage(); + } + if (!is_numeric($value)) { + $numberValue = str_replace( + StringHelper::getThousandsSeparator(), + '', + trim($value, " \t\n\r\0\x0B" . StringHelper::getCurrencyCode()) + ); + if (is_numeric($numberValue)) { + return (float) $numberValue; + } + + $dateSetting = Functions::getReturnDateType(); + Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); + + if (strpos($value, ':') !== false) { + $timeValue = Functions::scalar(DateTimeExcel\TimeValue::fromString($value)); + if ($timeValue !== ExcelError::VALUE()) { + Functions::setReturnDateType($dateSetting); + + return $timeValue; + } + } + $dateValue = Functions::scalar(DateTimeExcel\DateValue::fromString($value)); + if ($dateValue !== ExcelError::VALUE()) { + Functions::setReturnDateType($dateSetting); + + return $dateValue; + } + Functions::setReturnDateType($dateSetting); + + return ExcelError::VALUE(); + } + + return (float) $value; + } + + /** + * TEXT. + * + * @param mixed $value The value to format + * Or can be an array of values + * @param mixed $format + * + * @return array|string + * If an array of values is passed for either of the arguments, then the returned result + * will also be an array with matching dimensions + */ + public static function valueToText($value, $format = false) + { + if (is_array($value) || is_array($format)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $format); + } + + $format = (bool) $format; + + if (is_object($value) && $value instanceof RichText) { + $value = $value->getPlainText(); + } + if (is_string($value)) { + $value = ($format === true) ? Calculation::wrapResult($value) : $value; + $value = str_replace("\n", '', $value); + } elseif (is_bool($value)) { + $value = Calculation::getLocaleBoolean($value ? 'TRUE' : 'FALSE'); + } + + return (string) $value; + } + + /** + * @param mixed $decimalSeparator + */ + private static function getDecimalSeparator($decimalSeparator): string + { + return empty($decimalSeparator) ? StringHelper::getDecimalSeparator() : (string) $decimalSeparator; + } + + /** + * @param mixed $groupSeparator + */ + private static function getGroupSeparator($groupSeparator): string + { + return empty($groupSeparator) ? StringHelper::getThousandsSeparator() : (string) $groupSeparator; + } + + /** + * NUMBERVALUE. + * + * @param mixed $value The value to format + * Or can be an array of values + * @param mixed $decimalSeparator A string with the decimal separator to use, defaults to locale defined value + * Or can be an array of values + * @param mixed $groupSeparator A string with the group/thousands separator to use, defaults to locale defined value + * Or can be an array of values + * + * @return array|float|string + */ + public static function NUMBERVALUE($value = '', $decimalSeparator = null, $groupSeparator = null) + { + if (is_array($value) || is_array($decimalSeparator) || is_array($groupSeparator)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $decimalSeparator, $groupSeparator); + } + + try { + $value = self::convertValue($value); + $decimalSeparator = self::getDecimalSeparator($decimalSeparator); + $groupSeparator = self::getGroupSeparator($groupSeparator); + } catch (CalcExp $e) { + return $e->getMessage(); + } + + if (!is_numeric($value)) { + $decimalPositions = preg_match_all('/' . preg_quote($decimalSeparator) . '/', $value, $matches, PREG_OFFSET_CAPTURE); + if ($decimalPositions > 1) { + return ExcelError::VALUE(); + } + $decimalOffset = array_pop($matches[0])[1]; // @phpstan-ignore-line + if (strpos($value, $groupSeparator, $decimalOffset) !== false) { + return ExcelError::VALUE(); + } + + $value = str_replace([$groupSeparator, $decimalSeparator], ['', '.'], $value); + + // Handle the special case of trailing % signs + $percentageString = rtrim($value, '%'); + if (!is_numeric($percentageString)) { + return ExcelError::VALUE(); + } + + $percentageAdjustment = strlen($value) - strlen($percentageString); + if ($percentageAdjustment) { + $value = (float) $percentageString; + $value /= 10 ** ($percentageAdjustment * 2); + } + } + + return is_array($value) ? ExcelError::VALUE() : (float) $value; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/TextData/Helpers.php b/PhpOffice/PhpSpreadsheet/Calculation/TextData/Helpers.php new file mode 100644 index 0000000..e7b67a3 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/TextData/Helpers.php @@ -0,0 +1,91 @@ +getMessage(); + } + $returnValue = $left . $newText . $right; + if (StringHelper::countCharacters($returnValue) > DataType::MAX_STRING_LENGTH) { + $returnValue = ExcelError::VALUE(); + } + + return $returnValue; + } + + /** + * SUBSTITUTE. + * + * @param mixed $text The text string value to modify + * Or can be an array of values + * @param mixed $fromText The string value that we want to replace in $text + * Or can be an array of values + * @param mixed $toText The string value that we want to replace with in $text + * Or can be an array of values + * @param mixed $instance Integer instance Number for the occurrence of frmText to change + * Or can be an array of values + * + * @return array|string + * If an array of values is passed for either of the arguments, then the returned result + * will also be an array with matching dimensions + */ + public static function substitute($text = '', $fromText = '', $toText = '', $instance = null) + { + if (is_array($text) || is_array($fromText) || is_array($toText) || is_array($instance)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $text, $fromText, $toText, $instance); + } + + try { + $text = Helpers::extractString($text, true); + $fromText = Helpers::extractString($fromText, true); + $toText = Helpers::extractString($toText, true); + if ($instance === null) { + $returnValue = str_replace($fromText, $toText, $text); + } else { + if (is_bool($instance)) { + if ($instance === false || Functions::getCompatibilityMode() !== Functions::COMPATIBILITY_OPENOFFICE) { + return ExcelError::Value(); + } + $instance = 1; + } + $instance = Helpers::extractInt($instance, 1, 0, true); + $returnValue = self::executeSubstitution($text, $fromText, $toText, $instance); + } + } catch (CalcExp $e) { + return $e->getMessage(); + } + if (StringHelper::countCharacters($returnValue) > DataType::MAX_STRING_LENGTH) { + $returnValue = ExcelError::VALUE(); + } + + return $returnValue; + } + + private static function executeSubstitution(string $text, string $fromText, string $toText, int $instance): string + { + $pos = -1; + while ($instance > 0) { + $pos = mb_strpos($text, $fromText, $pos + 1, 'UTF-8'); + if ($pos === false) { + return $text; + } + --$instance; + } + + return Functions::scalar(self::REPLACE($text, ++$pos, StringHelper::countCharacters($fromText), $toText)); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/TextData/Search.php b/PhpOffice/PhpSpreadsheet/Calculation/TextData/Search.php new file mode 100644 index 0000000..10b6a1a --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/TextData/Search.php @@ -0,0 +1,97 @@ +getMessage(); + } + + if (StringHelper::countCharacters($haystack) >= $offset) { + if (StringHelper::countCharacters($needle) === 0) { + return $offset; + } + + $pos = mb_strpos($haystack, $needle, --$offset, 'UTF-8'); + if ($pos !== false) { + return ++$pos; + } + } + + return ExcelError::VALUE(); + } + + /** + * SEARCH (case insensitive search). + * + * @param mixed $needle The string to look for + * Or can be an array of values + * @param mixed $haystack The string in which to look + * Or can be an array of values + * @param mixed $offset Integer offset within $haystack to start searching from + * Or can be an array of values + * + * @return array|int|string The offset where the first occurrence of needle was found in the haystack + * If an array of values is passed for the $value or $chars arguments, then the returned result + * will also be an array with matching dimensions + */ + public static function insensitive($needle, $haystack, $offset = 1) + { + if (is_array($needle) || is_array($haystack) || is_array($offset)) { + return self::evaluateArrayArguments([self::class, __FUNCTION__], $needle, $haystack, $offset); + } + + try { + $needle = Helpers::extractString($needle); + $haystack = Helpers::extractString($haystack); + $offset = Helpers::extractInt($offset, 1, 0, true); + } catch (CalcExp $e) { + return $e->getMessage(); + } + + if (StringHelper::countCharacters($haystack) >= $offset) { + if (StringHelper::countCharacters($needle) === 0) { + return $offset; + } + + $pos = mb_stripos($haystack, $needle, --$offset, 'UTF-8'); + if ($pos !== false) { + return ++$pos; + } + } + + return ExcelError::VALUE(); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/TextData/Text.php b/PhpOffice/PhpSpreadsheet/Calculation/TextData/Text.php new file mode 100644 index 0000000..7b97f91 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/TextData/Text.php @@ -0,0 +1,255 @@ + 1) { + $quotedDelimiters = array_map( + function ($delimiter) { + return preg_quote($delimiter ?? ''); + }, + $valueSet + ); + $delimiters = implode('|', $quotedDelimiters); + + return '(' . $delimiters . ')'; + } + + return '(' . preg_quote(/** @scrutinizer ignore-type */ Functions::flattenSingleValue($delimiter)) . ')'; + } + + private static function matchFlags(bool $matchMode): string + { + return ($matchMode === true) ? 'miu' : 'mu'; + } + + public static function fromArray(array $array, int $format = 0): string + { + $result = []; + foreach ($array as $row) { + $cells = []; + foreach ($row as $cellValue) { + $value = ($format === 1) ? self::formatValueMode1($cellValue) : self::formatValueMode0($cellValue); + $cells[] = $value; + } + $result[] = implode(($format === 1) ? ',' : ', ', $cells); + } + + $result = implode(($format === 1) ? ';' : ', ', $result); + + return ($format === 1) ? '{' . $result . '}' : $result; + } + + /** + * @param mixed $cellValue + */ + private static function formatValueMode0($cellValue): string + { + if (is_bool($cellValue)) { + return Calculation::getLocaleBoolean($cellValue ? 'TRUE' : 'FALSE'); + } + + return (string) $cellValue; + } + + /** + * @param mixed $cellValue + */ + private static function formatValueMode1($cellValue): string + { + if (is_string($cellValue) && ErrorValue::isError($cellValue) === false) { + return Calculation::FORMULA_STRING_QUOTE . $cellValue . Calculation::FORMULA_STRING_QUOTE; + } elseif (is_bool($cellValue)) { + return Calculation::getLocaleBoolean($cellValue ? 'TRUE' : 'FALSE'); + } + + return (string) $cellValue; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/TextData/Trim.php b/PhpOffice/PhpSpreadsheet/Calculation/TextData/Trim.php new file mode 100644 index 0000000..27eceb9 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/TextData/Trim.php @@ -0,0 +1,52 @@ +branchPruner = $branchPruner; + } + /** * Return the number of entries on the stack. - * - * @return int */ - public function count() + public function count(): int { return $this->count; } @@ -33,28 +42,14 @@ class Stack /** * Push a new entry onto the stack. * - * @param mixed $type * @param mixed $value - * @param mixed $reference - * @param null|string $storeKey will store the result under this alias - * @param null|string $onlyIf will only run computation if the matching - * store key is true - * @param null|string $onlyIfNot will only run computation if the matching - * store key is false */ - public function push( - $type, - $value, - $reference = null, - $storeKey = null, - $onlyIf = null, - $onlyIfNot = null - ) { - $stackItem = $this->getStackItem($type, $value, $reference, $storeKey, $onlyIf, $onlyIfNot); - + public function push(string $type, $value, ?string $reference = null): void + { + $stackItem = $this->getStackItem($type, $value, $reference); $this->stack[$this->count++] = $stackItem; - if ($type == 'Function') { + if ($type === 'Function') { $localeFunction = Calculation::localeFunc($value); if ($localeFunction != $value) { $this->stack[($this->count - 1)]['localeValue'] = $localeFunction; @@ -62,29 +57,37 @@ class Stack } } - public function getStackItem( - $type, - $value, - $reference = null, - $storeKey = null, - $onlyIf = null, - $onlyIfNot = null - ) { + public function pushStackItem(array $stackItem): void + { + $this->stack[$this->count++] = $stackItem; + } + + /** + * @param mixed $value + */ + public function getStackItem(string $type, $value, ?string $reference = null): array + { $stackItem = [ 'type' => $type, 'value' => $value, 'reference' => $reference, ]; - if (isset($storeKey)) { + // will store the result under this alias + $storeKey = $this->branchPruner->currentCondition(); + if (isset($storeKey) || $reference === 'NULL') { $stackItem['storeKey'] = $storeKey; } - if (isset($onlyIf)) { + // will only run computation if the matching store key is true + $onlyIf = $this->branchPruner->currentOnlyIf(); + if (isset($onlyIf) || $reference === 'NULL') { $stackItem['onlyIf'] = $onlyIf; } - if (isset($onlyIfNot)) { + // will only run computation if the matching store key is false + $onlyIfNot = $this->branchPruner->currentOnlyIfNot(); + if (isset($onlyIfNot) || $reference === 'NULL') { $stackItem['onlyIfNot'] = $onlyIfNot; } @@ -93,10 +96,8 @@ class Stack /** * Pop the last entry from the stack. - * - * @return mixed */ - public function pop() + public function pop(): ?array { if ($this->count > 0) { return $this->stack[--$this->count]; @@ -107,12 +108,8 @@ class Stack /** * Return an entry from the stack without removing it. - * - * @param int $n number indicating how far back in the stack we want to look - * - * @return mixed */ - public function last($n = 1) + public function last(int $n = 1): ?array { if ($this->count - $n < 0) { return null; @@ -124,26 +121,9 @@ class Stack /** * Clear the stack. */ - public function clear() + public function clear(): void { $this->stack = []; $this->count = 0; } - - public function __toString() - { - $str = 'Stack: '; - foreach ($this->stack as $index => $item) { - if ($index > $this->count - 1) { - break; - } - $value = $item['value'] ?? 'no value'; - while (is_array($value)) { - $value = array_pop($value); - } - $str .= $value . ' |> '; - } - - return $str; - } } diff --git a/PhpOffice/PhpSpreadsheet/Calculation/Web.php b/PhpOffice/PhpSpreadsheet/Calculation/Web.php new file mode 100644 index 0000000..f4dd40e --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/Web.php @@ -0,0 +1,30 @@ + 2048) { + return ExcelError::VALUE(); // Invalid URL length + } + + if (!preg_match('/^http[s]?:\/\//', $url)) { + return ExcelError::VALUE(); // Invalid protocol + } + + // Get results from the the webservice + $client = Settings::getHttpClient(); + $requestFactory = Settings::getRequestFactory(); + $request = $requestFactory->createRequest('GET', $url); + + try { + $response = $client->sendRequest($request); + } catch (ClientExceptionInterface $e) { + return ExcelError::VALUE(); // cURL error + } + + if ($response->getStatusCode() != 200) { + return ExcelError::VALUE(); // cURL error + } + + $output = $response->getBody()->getContents(); + if (strlen($output) > 32767) { + return ExcelError::VALUE(); // Output not a string or too long + } + + return $output; + } + + /** + * URLENCODE. + * + * Returns data from a web service on the Internet or Intranet. + * + * Excel Function: + * urlEncode(text) + * + * @param mixed $text + * + * @return string the url encoded output + */ + public static function urlEncode($text) + { + if (!is_string($text)) { + return ExcelError::VALUE(); + } + + return str_replace('+', '%20', urlencode($text)); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Calculation/functionlist.txt b/PhpOffice/PhpSpreadsheet/Calculation/functionlist.txt deleted file mode 100755 index 97a0cee..0000000 --- a/PhpOffice/PhpSpreadsheet/Calculation/functionlist.txt +++ /dev/null @@ -1,388 +0,0 @@ -ABS -ACCRINT -ACCRINTM -ACOS -ACOSH -ACOT -ACOTH -ADDRESS -AMORDEGRC -AMORLINC -AND -AREAS -ASC -ASIN -ASINH -ATAN -ATAN2 -ATANH -AVEDEV -AVERAGE -AVERAGEA -AVERAGEIF -AVERAGEIFS -BAHTTEXT -BESSELI -BESSELJ -BESSELK -BESSELY -BETADIST -BETAINV -BIN2DEC -BIN2HEX -BIN2OCT -BINOMDIST -BITAND -BITLSHIFT -BITOR -BITRSHIFT -BITXOR -CEILING -CELL -CHAR -CHIDIST -CHIINV -CHITEST -CHOOSE -CLEAN -CODE -COLUMN -COLUMNS -COMBIN -COMPLEX -CONCAT -CONCATENATE -CONFIDENCE -CONVERT -CORREL -COS -COSH -COT -COTH -COUNT -COUNTA -COUNTBLANK -COUNTIF -COUNTIFS -COUPDAYBS -COUPDAYBS -COUPDAYSNC -COUPNCD -COUPNUM -COUPPCD -COVAR -CRITBINOM -CSC -CSCH -CUBEKPIMEMBER -CUBEMEMBER -CUBEMEMBERPROPERTY -CUBERANKEDMEMBER -CUBESET -CUBESETCOUNT -CUBEVALUE -CUMIPMT -CUMPRINC -DATE -DATEDIF -DATEVALUE -DAVERAGE -DAY -DAYS -DAYS360 -DB -DCOUNT -DCOUNTA -DDB -DEC2BIN -DEC2HEX -DEC2OCT -DEGREES -DELTA -DEVSQ -DGET -DISC -DMAX -DMIN -DOLLAR -DOLLARDE -DOLLARFR -DPRODUCT -DSTDEV -DSTDEVP -DSUM -DURATION -DVAR -DVARP -EDATE -EFFECT -EOMONTH -ERF -ERF.PRECISE -ERFC -ERFC.PRECISE -ERROR.TYPE -EVEN -EXACT -EXP -EXPONDIST -FACT -FACTDOUBLE -FALSE -FDIST -FIND -FINDB -FINV -FISHER -FISHERINV -FIXED -FLOOR -FORECAST -FREQUENCY -FTEST -FV -FVSCHEDULE -GAMAMDIST -GAMMAINV -GAMMALN -GCD -GEOMEAN -GESTEP -GETPIVOTDATA -GROWTH -HARMEAN -HEX2BIN -HEX2OCT -HLOOKUP -HOUR -HYPERLINK -HYPGEOMDIST -IF -IFERROR -IMABS -IMAGINARY -IMARGUMENT -IMCONJUGATE -IMCOS -IMCOSH -IMCOT -IMCSC -IMCSCH -IMEXP -IMLN -IMLOG10 -IMLOG2 -IMPOWER -IMPRODUCT -IMREAL -IMSEC -IMSECH -IMSIN -IMSINH -IMSQRT -IMSUB -IMSUM -IMTAN -INDEX -INDIRECT -INFO -INT -INTERCEPT -INTRATE -IPMT -IRR -ISBLANK -ISERR -ISERROR -ISEVEN -ISLOGICAL -ISNA -ISNONTEXT -ISNUMBER -ISODD -ISOWEEKNUM -ISPMT -ISREF -ISTEXT -JIS -KURT -LARGE -LCM -LEFT -LEFTB -LEN -LENB -LINEST -LN -LOG -LOG10 -LOGEST -LOGINV -LOGNORMDIST -LOOKUP -LOWER -MATCH -MAX -MAXA -MAXIFS -MDETERM -MDURATION -MEDIAN -MID -MIDB -MIN -MINA -MINIFS -MINUTE -MINVERSE -MIRR -MMULT -MOD -MODE -MONTH -MROUND -MULTINOMIAL -N -NA -NEGBINOMDIST -NETWORKDAYS -NOMINAL -NORMDIST -NORMINV -NORMSDIST -NORMSINV -NOT -NOW -NPER -NPV -NUMBERVALUE -OCT2BIN -OCT2DEC -OCT2HEX -ODD -ODDFPRICE -ODDFYIELD -ODDLPRICE -ODDLYIELD -OFFSET -OR -PDURATION -PEARSON -PERCENTILE -PERCENTRANK -PERMUT -PHONETIC -PI -PMT -POISSON -POWER -PPMT -PRICE -PRICEDISC -PRICEMAT -PROB -PRODUCT -PROPER -PV -QUARTILE -QUOTIENT -RADIANS -RAND -RANDBETWEEN -RANK -RATE -RECEIVED -REPLACE -REPLACEB -REPT -RIGHT -RIGHTB -ROMAN -ROUND -ROUNDDOWN -ROUNDUP -ROW -ROWS -RRI -RSQ -RTD -SEARCH -SEARCHB -SEC -SECH -SECOND -SERIESSUM -SIGN -SIN -SINH -SKEW -SLN -SLOPE -SMALL -SQRT -SQRTPI -STANDARDIZE -STDEV -STDEV.A -STDEV.P -STDEVA -STDEVP -STDEVPA -STEYX -SUBSTITUTE -SUBTOTAL -SUM -SUMIF -SUMIFS -SUMPRODUCT -SUMSQ -SUMX2MY2 -SUMX2PY2 -SUMXMY2 -SWITCH -SYD -T -TAN -TANH -TBILLEQ -TBILLPRICE -TBILLYIELD -TDIST -TEXT -TEXTJOIN -TIME -TIMEVALUE -TINV -TODAY -TRANSPOSE -TREND -TRIM -TRIMMEAN -TRUE -TRUNC -TTEST -TYPE -UNICHAR -UNIORD -UPPER -USDOLLAR -VALUE -VAR -VARA -VARP -VARPA -VDB -VLOOKUP -WEEKDAY -WEEKNUM -WEIBULL -WORKDAY -XIRR -XNPV -XOR -YEAR -YEARFRAC -YIELD -YIELDDISC -YIELDMAT -ZTEST diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/Translations.xlsx b/PhpOffice/PhpSpreadsheet/Calculation/locale/Translations.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..c0cc1ce88a51e09576f373803194bd1603b66fa8 GIT binary patch literal 108747 zcmeEt^+Q!{w=E&v-6hi99Rh+%DP5aVy1PR{LInZo4r$nw^d>hT4bt7Y>2B`A_dEAH z=iYPwf^+#{5o@ja%o<~k=KhVQ9;kQM9a8GwI*eCMy*5@*egj9&TBi0+pw6o1+Lf?^> zg@5ZS>wgT&^KalMcLob3{N%&L(}+TXo8iS6<>qOD$7A{|WB5^?*@^TlNwYiV7Z1Bq zn`+t(QAa}Sd>m0vtK6?O%5pf?Gq<+UX;s5ROXt^ynt3#RC+mHFEFO~OB#Yqcx#zN5 zEr!7ZtZTCo4eel*+)@V|dWxcxl2DWz&pTDga2$f)H{OpLPch{D|B#+S~ezv+wCgALW+3Acu_vz^- zzr69=>6@u>#5$?NcY>GHw~x=PpEvHJv1i%YGQU|7WuAhi@f29u%p54K<#C7ml#jU3 zfQ*-9oDcoJNz&F`qIQMH@=C8n&<8|E4PfY2K`2C3Nkyz&a?dk~242Bh3=dnhS zjNz#JXf+ss2nYB0hzR%YzmNsd-O+b;fGk{ryBGjj7&)8Sx^QtmUH?Bx{10Y^{}g)Z z*q}-$HzvxF82%ydMoT^wjf~|2C#scDNH?Nq#GIzxD)X_ik55&*alHIX)1KN#QK`AU zkF}NQo?W;U&Oh0!4A5Oul-73DTG*)-Jk;cnEwbpte$JczsNUJ=S|<}*K2tXkkc`ym z>QXCDx`(K}2am`M{LWQ0VnrtwmiVmaHNr7o*zM(C(&2<9vOhuSqTa_Xq^-tYE17*@_Si}i{%ez z*(3c^cd-)dOS4r9iY55Jjp7|tS-|8$8Gv%3(#BL?K_{|1*kseObc04!HvFyjDE zc-nG#I5^vwIyl%oP2oH(JBL|O!UxHnhp^vEW6j^6g_Gxh#w}B8%1)T`e_8zX1Njo8 zlet)^^=!bBlZ+*cHUSKGx^nn zzS9wv^BNpQ%5R6hJ@YylE;z*^QDqy-#DpvS7~=eMcckh?uT{hb8Q2ObR)B;-tAa2( zp%kerd4NUJ%%q`~N{7LmXct$;2_4x8OqPwtr2K|$;uET#G16v#+C-HCTQ7?v1F@>I z9lqftT%{?JIu{2s?^}FBNiBnAO?Z0;s-NVkRN?QZicz|D~1=nWa1ifAw!+f zp8S~2v=M`@#6J0nDg=DB_n&sWNc_bW?@V^*q}s{wLPFN$H^jUxw^6;?Lg_~Z7BBF7 zuqhHCDaW0Qeo7Lp6y7tc`xW*f9$)R(*<#NAOfOcmuBDO7#dv^ zhWMdOXtH*JwA9`r*P6|;mZ65boS!erR*bC~!F6h~;pjV^j9+H`0OkJ8ac@mgo7{Gf zLlo8`rHg*#nNTOkvq%+HyYil>1W$9*VTj#ql^P}W9t;aBv z6O8{3l=x_q>omZ`g95-LheL%2K>5$m`_It)j{yh|sAS;T|JfhSas9vm094}ZyWkB8 zC;Tj0jRK00agHB26=}V)-)K&f6dp3(BVfh6Z7k9m`sKT|Jv88bY9)Gs4X2TbHu0=>ZeBdTy7_7 zc8CTiDNzyTucNnznpn?O$Kp|2B|LdfKhCM0RB(Vr=FT%jGcVjVC#*ePZw3RuDd>g2 zHjw_zI*!Xg)AxP=A7NuvSk__Vj3wr=Fxm0e3ff>-oZh~VoXY_xjx>xodFPGU58b|t;Ku;n%rHBsZ@)lXip=J;M6jizf7rq^z{=upfSedWJCQDZwIo%F?qw3)f@>%y_E zQMEiaZaXV0-GjY$ZF)Au}mWw7(!Rm&rZN~ zO+PH!#5kG~S>PuI?}*WB*H`?5%dhcwQT270T_@6l#z+i3w$&yjk~ms(P<7zW&jOfi z{!E0cZf^;<@YypT&iER6LN5@}@fq|vJ-a6ZyJ)*c1Ju`OljX)MI`QSR=r^u3Tudvx zY09yho>E>4`FK%x=b{lEM%(5wiCGfy@$@<|ISd(GiNbP=gE>7+8&b7(-A@JMC3R;`qhJP;fdkq&Gx99IRolz1FKht5~hbzTYo1`0*uy*>*v;^<^jd@s2N`LCOB-9Zw+f*qq z;r{}*kRFgg)atCCmGa!V?69t)wI`fmz_UUvMaFM>Hn)yz^Q;v4)4~jUWEt~O$*2V) zY?`1+8vk3Mn;g`MGUU@MMec#gENpImrcnm6yu4Q*Z=x08UrZtds!RJ&Fa$=Jp;W{K z@ha`A`@H&)i){8akaJg~8XKn zw!Y%*(Bd989Co|hGI4c1Vx9J|EQZ;nfXTX5x7a%8K~uS zQ4^X;>2%(nbXPVs#Vf1vY=AKGzB|o^H>q!~p{EieD~A}f=>@Hc#f0GQA$>}v^A$yH zIwhh`%n#bCY~1}b0$MI3;+I-P7yXGAMuGR!6i) za7|772dPHncF&9(tc8M__N}gP3)SZguR~fetd?cS8^6nQ=0#wzc+d@kUSp8_`4d{> zOjiba9g1OfC?i_`{ZGwjvlotMpPtz_FVyyX?C*ZLEEC#0+wZ#D$I_?Co6-=RZwiQB ziM`uUF`_R1Ji0oI@a9@LLo`LBGJ9l+uZ_KP?g)-s)wFFpId`tRr*4JCIPHyyahkkh zo3N=?pp$*6Lucw-1_>i&wv;`*>kKASOfLVg=KmaHMInJH0`*IDFiWl62H znq)J~QjH-|MCUi0>jM&z9Bkh-oKUA2u&unI*~*#1P%_*w zQQ6h+7IKugG*f#HGL%+QNEx*LB2G{T`FZkpDaTveUhU`MOR>U6%GMXH(N`o; zyhl4_|GoOf6cO@cG!a+HB$(__@mU@oa=GS5SJ&3G*P=^4oVF#>*>?J-EHq~GEV@(Z zm?E8rcy+9purhQ+dreYDX7QEN?4uvTgae;krumto(Dm>Y?$B2!?eT#haPM97$w5cuCcZC5v z^cGsC3d#?->)=`UR%x-hqj8ZS%)sB$OciFel&3sSpArvvPmsyrv|U|X`4mta;9}VDwfwus^`k=Z-)oiw0^%) zW~!D!3Le~bR+nt|cEf!k2t+Vrh8UbsbR-uHw|BZRf>xtaC6 zd?rTiMnZ-Ji_M>Ggt}(=)2u{Z`J}Ay8)yBeU#hSS2fO=svy@@Qt@o6^I?3=PZ89b#y;n!VG?| znAA^)`JPbxgU9nZt$+yL_+^E@GlB$gLf_{j6Afr=0cllrz2(O70u(zxeo4`%7BzrIJ?E$5)Z|M|UidzUv^4(z;fc zT6K%c+@0Ozc=j<&_x9gB3Kk5|FUePA{wJSiMp8s>w~%Mkp8q}?3C&XdvWdZV78`FC znea@6f9x(^q9c;9EMzjrdlmMr7DcnuA(XIH$L@UE?weYtDqdD&5l8NaAjb2t%_``l z{-nLB+*xWJhJ&M8;=vy_(>D2Z2q9SbDwd1O$ zml4U$+4)c;h6~z((@rXNvU#j`+Cuy}yzdbZCDS8Q#jvq)7wWz(H!Mp3sDBe1@#~!1Ahpj}ZJMEdfyZ+3F{Z^0Ghl_r2 zz}Qk`KvBR|e{^dz$uaoQ?Cv`3CQNGkuyqIgc%8W0BlWOl9PHw)U+dtGduW9z-{N{n$o)&dvQye;1uOAqEzNVyTuo=Q9Ok{jUmhTO_#TEnZ-kx4uN)?hXSrHdm_SrKQ79HzH@x3(r(%%&Q@;TAV zu;i@94jlilFV9E5kCVPo!*k8R>hSYS#_C|GA3|PXY8^!WRAD8Enz(GoSjaKbKW`EA z8Q-J(!=~vZlFH@Rmmof01B4HnRE2I`Gm@7Q!l5SxOOM}WbcveL>UdtSuhuO9Aq)7& zI58G}yleoLBqzu{^|g)cE{)6XjdqGGwgfLs*K&``}#8J&(L(^W7- zYfw;^oR!eL0IZ4N*Y&if+=d9jxEt4RyUFjj)OTh|$B;`~!TJZOuB6&Sr5?tAQq*h0?8_X=v8ocdrW?Vnu5X#7V~SH)lk%g_?#8jz4MN36emU z>{BpSN7n2P@G%R={L1stJyt$&hWnkna28$(r@M9TqbCKZ9eI3pTBs|U6cp$k=+Esn z3$v+K-7uHEHg*POcZ%r!3PUE$=7Y>nS+hA6TbkzZ~rS zu$mB=C=T+wWdLp+B$V_LH8tw9USc(t^lF?5Yn3I4FDX;M{c%?Tdr#0*&yQzLa#V6JXng&^%6ZCf9$S8>;xi>u08^N@lZ|N?g->E) zkA}Tsl*45ayQ^b74r4z{5Km&C42r}g%T%cgYa~^Qqq}Vn#b%1bIY+!t)Hfz6Qb^Q9 z$p-i-?W1Ipdy(TNNw&_3({bG@x}~4CByNJKqhgffNj^0NYofL>;4By>K$V-jlY=JJ z%QDwkU6g~SV05=#-VJdDS>6wxWytQKb-7@-rS&8fEr&mNKNNTBB5Q#ceo5nz??LT##S2QhRaGO}->TpHzF8cWchYc6`nIYu=ETV= zs#$GTSs*bie~eu}0GcpCeKBlso87bEnaW|)88d53*yG}G`aVqvc;0vAgCu(H*p{t^ zDTC<6A@bCg*!x#Zn{0ZO9O}9bh%3oyQwmZo>vtWCuID*vxQ>wgFjsyMg!v#YZYQNl z=6<+5>~_zzu{*EkEeI^kSf9Lj`pre=)auk{U+?0?V=9bSDLcP43Yp*JV*HG=K~&|3 zIG9MTfr`xgW!=5!)L~zuKZ{JcXXHExy%Um{pVw8Jx_$oAZM-u{%XEB9=9nZPg z66JT{mVGZY=F<|XV(Fih;oSF8j<<-#n=gXsQ@7}e` zd^Rc6sr6-VWwBk$#_-j&Z#%oWBQ4c1q)ZBuvGeh@fp0LP+ux}>5=NR#4u;WxmYUr; zynt@fNNYPTQIGq*1)CIm?>r3HpEQe(*|?BMKll(;up-g;)J_z{tsY*ToKNPX0rP>Y z1}YC{7I71@PLe&Y$51pXLK^3>0i%u7yB|_FgKSgy7fmqPBn`vDyHmF{Dkd^(Hb-8u zI56MbpXNWGg|_Wq(kMIV+%0Zk-9`fq3MtOrId3D|QGYnwYWC&P<#$Rz30rgW;%nFn z_v@p%H?J}f&hq#03+M7ZAg(?cPPDt!=+>;YiJvW3K4MUsybA9{!?>~Z);vOMy&Q8$ zdg1H{*%aFxbsdFT(MSaho(yB0Hzjdq2w$&xcOB0GUC+f&P%f#kZrGHdeRy!wW?LU- zl%As6|2GFErBkeKSLBQ^TV?ysHOnpU&QxQ55Ly9g>)!nt#f>n^luguCwBbYBH7st2 zT#9F+?>vb9`P=%A5_^T@TjryN@{8u&&1hPZRsczF03n;&(8RR&QuPtU=VK3X~Us6`6*56RZ0L?ym07OAp{S*bx;1_+p=Y2hIbmxU+XR{*CcUBgm!L#=@r1VFQbaE z&+lf{0E)pu`1&Rx`{ftCom?t|VVh&;oI7n)g_>GkW?I+MCG#KM-dm1ghXvP-e*Bgr zgR>zoK|(~;e3b%zrZCn;kna4y^IzWUA-ZwBxukS(Ethtp(CW3JhaG!Rjx+Gu=c=pd z`nPY3ee-EeA2WC$g4*(j?B;+X!0*H=+maXlSLU**^XDmCND5=21a{6ETxTkqvSGWR zJ;%`yo*{vyo77&3%+Kl%+d{4G94N0nrchK0CGTd(eGMdy9tBkhI0heky$5z7xSW-6 zm{Nn!tiB-;AX?<`^9@4(sJ?pQFysS^+@#qj4tv|aW}x9oA~~2qG-*C0@08@9+*FL{ za0;{4P{ptnjLcoO5{y(0s=H=3?;V!!sv8d17K8R28)(FuEKh?^Ky3ZxAMtC7CZt{6 zF>bbhdel%~=J)^J8vhOxFmjE?*plK%d)(_G*(>72=rIC;Ez9PW4nK1}p3WmnLwhbaZ@~918lszycf(^XH7vArZ zk~B9gVtR5fd5Ir{GtE7ku4qE~mVKii9*U>peQ%f4wwkEjHVNNt*^@sHELE=QS8q_VkncNIow_rDO={n#O(xqIf*iaEsK~3S z-(K1NBo0PBx?f>3w+{K2P`i_{u}b$}8?ftz^G^-q(V^WEMzEo=uvQ(v88{{w*W{)N zPbgI582aWHk{>@v1h*zwiA0rBq291M0DxA8@LHzb!puaF)O45r)bYf2#$=lOie!rwInb|DAF0WF4m8om;JCPj)Nv3e7Y$GRgrqpu| zpQqR#kDhrO%Z&A&6>pB6?RmFn?yc^_gae#FmX83@2(7Ib6zF2bbHzMm0(=N`#FLE&?g&@-ClpYO^ibLs2PD+w+Y2HdIc0*UpLY zs};!d!_%lRPrExC*a6Zpz5PUCf5LY3v_dj3cDxfY+0*l*G3SRzKE6paVb3VGBbfA? z^zw6stgmk`0;JfhJ7ts71&9POr{jVh--L7!$5k*$X$sO-q&vmdf(CRX+T3~p4!zS- zRUmz?Sd=;A4xkFA*H(7CFRg8U-Eihn)x+fYMSu1quWn_q&w}d{*!c*)aDZt%7a@{$B(X;`B+Nyex5rfYheXO zv(foJ!)Ggx{+}82*Qn)J65J{9L*0KBY*cNL0{q3&C~kSk(^%w zvQx5{%&T^5eOJM545WVQkZ5s-eaE9yZZMb%3<-SKnww~JntoK1l}WPe!6UKPSN*qN zOj4^tTfHXqq9PA4m;wN|eZm2&f2=5!2g5k-(NY$3rQDy%sm#=mXb||-xI&=?4}EM9eQ_p1pd>FCDCDbsoLcm^-pz8 zTMtX5zKHaEBFD3p@PN++<9J)09&^)-HCUMfO-Mjh^MdPzAT!)Mm&@LR9%s2b-jh5Z zpl;%ChVzRL*Xw(Mg0m0;Wkvj_tnq7kNq})x7ho<)o^&eW_`DJa9mYm2Slt|TO3AA4 zh7lau#TD{|h5$pbKPGtZnd2 za^1>vt)AfrVEgxz;|A+tLK3CP0ga0cRCQxDeOM0cbHuHilNzIPkIsM7{i?NhxW(k= zs{K6Wa9aWB8w>5bJAwPsQ<;0t{4Y#@E&DuKwy(A(p#d#D<)jRd0mj6WAF`cPTRlO} zPH6?2{+=Ip%IhC%^Am`A7=eWFgT$RWmWEr%w!?DWeHEp=uLY<|#)A9}3zxd)5)bw+ z%o?D7<=kxrPNY!u%rk(fRI8g{h^NEUGk-F{sR#UJ&Yh{_ss|lI(PG=}C|N^DZ^jY1 z{2z`syVsH@WF^GG?<7emk3En4b5&CDMAJjW+&FvlqhObL4^H=Y3PxsnLbVe%)4!Zh z($h~~Q%Ut@h<=mXy?P%to{S)qUQ(QWinaX~`0+PyPy4I$HTA8bwNX{^!nGTTq5uP_ z$5Cyser0PDcPE^;+mu34c8St*x1bTKJcg2?dFk9Jd72C``K`7O@Apza#if@|?TJU{+Z^?3 zV2+v9IC6x-ka|2w#ssdQ$AGSANm>HN(PT=qSG%PXMG_aA2l%edjS;6+SOAo_1?v`5 zEr|`J5HhV~XewF_oyh}6ocEhl_vmVzv;$X2zLPsYgnAk&9iZKI>&kB*Frp4Q-J3np zgR!R9m3V5`d{za$PJ<>)<87|1oG-hv2I>Q@y3kSuB!CW#X*wP+?^E@UR!PVSVp8uS zP}e0n8$ByVZIZs35i+Rv1mwS)`%`rvk$~}F_2plEqLS5Qc)W3E?mf(Ovz{1j{zY3w zjMDU$b*j_jGqt}{hnDj2)Hja|t)KFB=I4KGodinFmKqk1Hr(WP_%DFj+_;uk023Ij z&fQs!*dfg?3FtUI0(hKr*|gDf^^f@!o20`(!>7OVWwo|Sk>+~uMK_j_0iasY#Qu8T z9(Y>AD$poUooN201gnSZFDfxpwUaqB93N>6z}Fd1Zu8L5KW)HvC7jN>TBj@&cltAS9yvSu(eQKnh?2w2x|oU4*U4bm3S0RTEbW*7 z*_9G7Nj$B3<_cMjyD;}won{16Dy#zNVbeM&4;#J{2L`8d3NYMyx!bJter?IvL<}3O z99R<&!vie1a|228wrsRq1+u-OSfaqyx=DCv^-_v!{18ZVPQ$u)7t=m4G3%8XT}Y2N z<82yDHx9h7ZiYKu$&l*n|EZy!@_dQ{x%_Iz6(uqJ%;;|PaSf^R)aJ%YXTUUR@|VCp zPd`M=x}(v4oTkmpsxq-u*4a@ZBc*@5M%ViJPfCEn6ESMA;&|B?hJzphc)g#T0S+K9 zh)5;7Q@uE{T*5e(5T6#f=O-AuF!|8Z5Zm$jPt#wWNGW*;=tS9oq;KdpOItbQv)TnS zRUHZ?7P*%9hMVQPZ6u3;PK1g)MB3@h@D3()Y0m>faenb~rm$jR$yQ@J$4-!S=3`fp z(MD};tr>iref2J9NtCWzrX~)+)=-@lOK+l9+b_Gfnt9E~A zE>-RNWp_m*-Cq~GDpRpZ!72R`=l0_1|JDrhC@LLa8t(l*o-A1R6m4n4irK08&ep(VqtghV80XgKF6p;S^ z_Bgk?k78fV;m6?zef4w6-~2OAGi!asGgIQWT?0O(?lE`gq$+X@DC6Wu;{p$R^Qb3cP z2WIwnk5ke#oN;elth1^nFxG`nP!2?;Or$vQl@G+uv(|Oa>dP^5mQko|D8iX@-uG*O z0n$V*x%dPPcSAn_8Vv_Se~O>YaGt-q-kwfjO-MSfdeXmXqYD%HIF7+lYsB`SG~Ioj{#7O1E3%o^#&k^*9<^z?#b>&rY~UE>&b5O9Oth^&n<`Q z-64&b&;BN^5X`8;9p~xf@O^KfMWSsfDdtEzC8Qi}H{J5L8&RwhSU$?mUWF-Cx6hZp8< zjno&4QYnTwCgqGz{9_tn)dmQs&H8^#Q%h=!du@ztK!EMo4T;B8=T;`Qzg35!2XLtI zB74$GFP?t>XGCH1CD1Vz#pa?W+h6lxQ>z{RKx+9!K{%-|sjGj9nN6I^EO6$-T#5%A zoFLD8gvL7aSK_oezcxvTCAoJ?%+-Vy?ZzkBJT&eMjTUn}ZyYbfoq3&eu=gzr^VB8SeHn(z7mPzfV~rBb;iNpJx8#CvU?$e zOetXPARZ?Z&*$nVn$P^x2&u8nwW_xM5`MXTi0poaZKbu4%$jiY6`%-k*SX?T)J~1m zg8O3THXeP&TcM30C6YG-wt!hb=hJE+$7WHD5N8g8yTh4$2_?-S5z%o#*~LcfQfBMJFE>$0NrwK@34FP{?Z*hpOq6-_BOj38^DnM{Ycze<7%0IgQi<>trG z7RL^FtYz<OZhldY;SLfLzqv972rG0xb@7bBPy?qt%xx7o+Q8XztOf>5v0->MLw*U# zs%->}XxN_*QXp=|b_tBHWII2=v4yQC$I7l<{o>f!oKyZ>RtF2iUwfM10d#&UezH@k9;i-60dm> zj?snJRVdo6wIpci#h1vmpPHPPi~{O2F1E^cojsNAG*#ERzJ)L(AV}@DY_)jp{9sBHxiv5LW;`v9M7SH zO~6^>6r15FWG5)X3ch^m?qzg2N$>r+*~?>=rRM71-PC&hPc#{fVfQUOc`yOh06xDg z@oe|Bpwro30|bEM#8MJUB=6uZc5WDZdn*(`t$hc8o(x3b)&~x^wp0L!x%ovb_<;Q$ z&QRh~Q>t-*j4+C)pLAs^6RW0;o_CHts|1VBbd`ABq*6(}%XlZI0`=P0GY?(>;m}-o zC6zU?Dzj;gv(%gM)I;D1~01}txe0G2ZW%fR^ubpBj9Hhl#+_JR1^UeNGK0tbUkLs{$q zg5Tyds?Lnj>^UCG&CQ($6{!~Z!iIpmnD06LJyU)h$1|1q4UR4CU41$UndQM7vYF!L z+5^;6GoYT1Lo7PS0L$4=mRoMeuQ)}tTL=4r-~McFKzhj~fXB~M7`sQ$gM>O+0&c2% zb0Vsnt~`5 zRxJdPe!ayeRl;fV+3&p60osG&&-ek-8&33A`$CB@&hT#7r)VEnr^Uc_19U`(bf65bBypCcp(l1NhxTZIY!(- z`-SnrN%h{GdS=0fMzZ&W8`zJ$iG-y-^FXnV=UAMZ?Dk!>Jw4&%iuUqys1jvj>~1J0 zBXn1(EplrQJO5XHPu>2jyVCP6t;zE4fV&)%H2D~ejjLP~7VKChO2?PmI7<=foq}A4 zCnrgXmyG={Hpam)EYM^w42kJh<$4TgT*WjKz|D}%B_GBqn~aA9srzTih)!Tozj##> z%p~AE3_w%uRsXDrQi;KFyMfa~#$0oAfBy%=#Z>kr;ZvBmtm6H)cl*nwzupB!BzQg? zmGlFwz~@J6P4IS$cAl-k%ENXU<~^-{ZvwC|{X>=0&>vP=UvAa99J&MNl$YlloAVnu zG^ZzvAKRftMpG2CI{>m&F49j5q<}@X!?^e#cD;cPRiZ(i3z>DoJoq)H!D4l?pCxjA zUa|J8PWb*Pk)+2;&nygQb6abEaq#1>@TV;GG&c(YrP`f3|7?B(RNMi#;BIG_@e5<) z3efn-XXheY_QI+G)oUmng4ynrpMbM)^E-eYZ;Pb>)$iJAWJp zicoJY7c#khJB5*w`PU)_qu$EDi&Ch}oQ~)S46U!3Knk^i6q-h;T=jR0D3*LZYX5qK zp1$+9B$m!4nHYJ?eIk5Zwu<= zpd5I2M(uBVzcgC=A$33Vu~H64Jm89m_g@9Z##wW?Y)i&)1=^45c2C{z9WV}i| z!XZhcFv8Ro|iFRSYA?`U@NAJPanbHhuRK+@47 zF)HG0*^Z{q%I;xrDGdqAFTCp0dg=SK@hV3l3gM5&{n9Pxv+>fPtGB8=-=&T>H1pm^ zvDPZ>;~GtVu5gRLdW#4pfjB?2jfG*hXw7VxnfQyePSCYfHD2}YuCh&-F=T-d*s*Yp z&j#>DM%;;wwPapqY6P0WnA--tIE7Oh*a@>9`U!e<5s%}MG1pany|6@F6|nRfeZskf zhLf$hTK>TKDaIUJ!vqn)nh##ONoWIH=;+{5&gQDdqqpa^t7FQZKjAyRFp46O-;8wh zzh&M|LkHVZg=!aFBWwEW=<=epSe zgYmEQHVSP*Wm3Z0%0OL-eu0%Mk&N&OIJhP=e5e%~B*y%cKn!VrV-V5|G%KNPQv(H# z8W=H4-zPGqR3oplVUhmMygDIw_KYU4r9>@xtYQoqjD%{F)3$6^w%XGJ3sfbB&g{+S zsr$|_kqMc6B0?Z{A6RAcK|YS(UHxJx1_Uv&3It(U5ec>>f+p(DA}I)YOX|VzsO33O zB_RQofKx<7MqqAhj&uqH5Q3>7(?cX%Iotxejn z{)S3NQabP_e9HDZxg-z`|1uB;l-MMp&v3+@$Ye$J zyn^4D_S5r$64J1%jF=$`-4I>r>8aNtGdOsHGv4O@EVQ+K(OeiV^9>YrxTk~N4d`#f zHxNUTkTiTzG?)Nqcy3NmEw}A3c-XSDDA=G?WrU{Y0VI^GsFk+x-mgtp5wHVMfk^O~ zJMNx2eLqB&aB#x|A{nN~OF%Bf$$j4Vzb>1KmgJiGj)uHl^HX zvKgiT1paZUh?yIF!bF!gfD8M@DgHwsQ;)<( z8Eq<=TE0NRTLzwU33uQb%^-{iGz1Tmqk@4f`FHfI;-XyJubv8$7!u>Uzri=O3OvgB z21KBu-7wT0fb&>!+(7VKOA(O(Bq*tkz=`w~wQ!Y`-6%AeWQ5m&Ve zd4B;0XNxuuDDLFf<4ItOX8kn_GJDwR5>PMlW3y z6PUr>pSutC8?+s2pb#gkWwk)mYJApN9UmNKT_iknK|fg>gOG-3)m*I*GYXcD`JSz- z%^-w7(pF{WRDVYGXl)*O!-EjZVYcfred z8&dwaTT73PkGE0}H`UaUH=P0TgTt+NJ4+8fmoSIcyBneVva7_Ku-3b)jmY~F;(ULx zqcyy~jY+A&SG{K43-oV4n1FtF;Fn~g5mdUEqy^*FxtKUnTMM%1)ea>P3*zARxiD>5 zv*AgQ5DQ*pLYmt@)enV@?%=N-=k1~&a}({lm?-b5TXc3{cG_CiSjjyaSox-pNpO=~ zSofDz-EoHov=TV~#xUojZy+@wOl6sO)16{W0aZ{V1qB7=@O*=sbLGK8w>b z0|?|p=x96`%))MT{{XLunDPifBCY4#?lHS`(+LvvPv9q1bRO_?B`G>{&AlsGR2~<< z&Ww-1yB&;Pz*X>#1qxKsc14xW-2DZKMKMYL?sN7MjEYiDfZLCFfxlx<`+ijQ6jBGk zUMukvuM&5PlF!Q!tKrHVv0TlDX&b8=w|RTCWwVbYO`{y9vz4BHSZ}yv4D<86I+S|c z_Y1!=E3H2Y37LvGf3b0EdHSLLcXsG>->xZJ$(OQH&RR8)JNl)TjC4C1MrFK2@Y9`s zw-)81ttvRP5Tx2F3tW9&kZtnJoD)x#Hz%J@r@xsg;7sNDTNHg7!=r*}UW!-@p$Uhd z-g74zcm~&Sqh|0#Ot~S6bL|5u1V+Roj-0Rk5|IegDsYVe7UVir!w@Axz(iW^Wol!W zwzq);sN-sz(AXcD^fSUh@wAg)6&zoa(4H}48$0dZB})gpaoqGll+L*cY5;QOn&GyNRSIL6Ap93$kARSrAhhM5Ac*S zhIsF)(S>kGukO-3k(+6ovm>drIhG`({i$Y;q@|}3&LRSpSDO(laTKo9!;=YmyEJ45 z?&cGOhV5CT!`tz&lk13Sp)#8(DEw)7qBkrx=9I*raQkc3!rSpTgG7ki%5}mU7zS8T zdFn!rNr_AiyNl3deTQy%1JeMz1s$@g@Ib#$8{_ffr)dT6%L3f$W>Zc*T+@V%g@3KIiME)Q zfNz?yJV>~V2*3vDV%4KfOO;Yo7k&ICB_Ebcbb5r7qHH8gkjVbAY50M!5uMiyoyvV`$z}ySrcomw2WQ*T}7vB2=w4$Y9PVXWq7oJS9bh7!L z3z%A_#P3GGk(h3FUn=oybD6_qK^%e1A}P4Yu+P1vOW2iRXRkg*5ZsHL_|lL$4AkrN z^eH&QF5&q)AT%XL=)gA|ib4R_sJCCyc>uyJr^k(BX+q|yj!8~p0q#0@ouXFy2I5u+ zM%p*y@(Hy#$Y7_U@Sk;rCdI#VFxgp+J`>MN2D9?vXZhWpe(rljF*AB z@TpL*XU4o1tq59vrbC6iV#yIM2m=P!2@=(C0DE#{6bi%W+Xmtf8#LqrmBpX-1J9WF z-I1L}2+(+K1N>95S#YEVm&>r*zw`1Z1Ewz?u8BMHW?}?4kTDH)Jo9qj=@{iFk))v; zuG_}wL`_xc4a&Lnxe5c*j14O}=(`U!Inx=-Nq0qLLQuAYwcDra)cJ2t8W0`j(2$no zRjJ=q#am1XjBH&*Fw;_H`TD)SaVymgO-ow1CbDZk9yyNhpaE9q%zBb+=3Dgh$PSWGgfy>^9b&`^PL$9T-;hCViS89 z)1n~mZN>T1;OSja-!a=SmuRwZd*TM*?Ey;NKwOO478FaN>OfLkzciV@a5aX7CFlCW zYo}#x!YXdKUCysbiA)STj~I6Z_JoJ z!%jmBouAmMiN$uHUMnI-n8!Jw28$SqQBBg%6cDZ<3mF<-p}CmphZC_D)>Ay) zl`T6|D0(sj?AH~osQ}C?N~(dYGOrf`bbX-w!v?tk#MUr3VfiG-9W z7nD6~r6egM4ruF3BGVZybe^$aH|(_L2|l84uP49gN)LLN0#u0U8}-C*lzXX*PI(N_ zfmj@$<|oQYmB4tSoH!7>JT(TFU7rIIiF3`F+ev6j02 z7hCTg)4<{j^3WVO9QYA#yn-P<)K+$^z0~wpc4PyA zLk536s2yvhSX2a>J*zpPesB-_MBEzqPI5jw|9zQKun_CzhJ1Ekbu#%T{PD-ONaDKL zmm{}intp(F+qxT`YBrDik&f&*L#i*^LXe3nz;}Aa$Y0@a>P($*BQXMZDi-+`Z2iS9 z6eZq?X*)xDre>-#mAr$Qx1pc{(g8Bs++4ED#x-*s`1N*XUXX)6IsE9`sZVEa*B)s$ z9{v&${xvJ&CBiA;K)xl{#D1wZGNg3J_JL>div|CiPd>LrVDa17XAIE+w0gA6K9VK$ z(&vbl!^jDLea!ea*?uwbEl7#oyv?lVaMP_{L$t&>*IjQ$xTS;V!CkU%ZP z{|JG^1EZdrA#$py!6 zB}|sVO#8|g8almhlhs>fzTno|irt?RI9Ejxo@&hJlt1O)qMw5FG|hBmCkC{Wtcbx5 z3(Bakg*zOwMF*eLrShY3%NwxHTS2;Jxm1_6Yv#GR$>7p~)Qab`CV5T6iCO*q`H>h% z2Xoe?&Z`Hn@*Af+bb|Wz0uLw}nEk)`>hK{gTo!w8(-B7w77K|Tl~wEY;;vt=qph>^ zuI*I-T((4udnU+cY=Supo+IZrY@yU-t<$2Wh$&nD?@y^VlEqa0%Co!m-|rJQptEN_ zH(`Bqrt$CPd64EVe|r0v&4<{ZeS4|G@FzjE(kgr+HJ-DE%;MEI@!4qty}4b9YZ?Fj z!20fZ+M9b@mAF`Z=DVr=>w&f$d^MBv*_6{ARFk$yjLRQSPLaCNo}8ic4M2)1mVE1C zxYG9hH8}^6@*v11_O|3Zf%lmgy6nlT^KqHIp;M5C=czbH>5@ZT6fO|tVAyAm=oafc zW7o<>qx~hQ9>@jKSu*S49wUMIJ(K7yK25XBMYMKnuv}>PNpEA?;7CVwE2R*+Im2^D zO6BA3;Q|e#-8m*gO{3lOat2Z5nVFrPP|36cN95&nxj@AlClK@5c$+q%Kxe^0^L>QCsS)R9~tJ{_MOiALm2BP_2U1}O#x5pwqRWhH_EjCZiM|$&e_uvxg0g9@> z^smbw?Y6i0I?sO`_)8z$YUUxz}6B!ZY8UzJP}F}}HAWuly;1NV3Z+ChE;PgSsX zugp80rq`1X3N*d>m`FH)ZYe8z)8O=3PXcpb0@q7kVJ_jtMdB@34t zkDdXuf3?=A4v@OsT?V3@LD}jVPd$LUcy@0ySmBo3(?78gedHCgCL5xU%$r`{I4C#> z$#`v2u%zW-n{&Q{4$G^Go=#be0(>yFVI*C z6i+{eH2oCWebs%$h(&K~XTzmLpXE$UH04ymwy3G+)O18SY_FM;tLoG;L`5uw?B?yK zU(%27>{m+q5lj&GnN>w81>L?$9BD~am#2U-v|K&8GHO%HQDimMmcM#)LYV=Je${Ng z4aIxylooLCBcdZEi>&k;Y@jj4z7$rL?ePYmRg5EQu7Y{Phi{l3qX!S)Q1?8uW$JjK zGQ)D@!7FHLA$~sU!`HQXNR(3G9rfkZueBfN&74l6dY>g1uL;4B9Qk!#YAW1OAMk61 zoys?Hew-wKB}5S=Ecge=L97;*D@)1n$c!M)OHmQUYeSHz{|MmlOVo$bS|(Sp6D^Xw%mO;hSaiTk z-jtfMND|(Z@tO$+q_*osp%s$Kb)pxF?6{aR1(s=*ObwNH(AIbzsqHzjB?ZP1bT)%k zOXtaztFwp6M^S}=cU!DXuuncC!8Lj-c~R9tnQ>|tz?UOda>q4j9iuT9A1dSEd$t<6 zcBlmdxnl?dqCH<7_~Y%_5zoc5X(oDzqbS4zJml*q`Chw`R}?lPlmhB-=thZe7Ul8# z6~imp6jU=coq=je{b7(Icjz1MtTYjq=#pSs>AQ7|lausNk?ff1C zG^%Y#e8l5gomYX}kqCOU5sOwha`}xRT0~-@MZXa7Hz@88lTITMH&7Wtw`J8(x#*Td zaOVtgyw~oEQW!Xan-bVoi6s|0%Cei=p}9fg8&9Q04ojgCTjnZt%e%)Jt) zIxK>E_`xyeQV2NK_MP+ZmOE#dd&FErnIiUUJmD3kNdRnLZx(wFEU7lQY+z zuJ?_@{cJ}ocPB;R84P#)+i)wgwfyHAhc7|~StpeG;JD)>g)U&<)Qs3>PhqkhD4M)| zY6F>a+XGFa+idP0p za{LumVu?Uw$8=5xIBy0~D2}Xjq#HDWch9dj3Be%1Oxi!Zp~l9-Afg|GDVb}}cnlkU zTe622`yQCM$5Du2BkXGrd|^209VM2Nl&~>|(6w(?Lp!fa-SDO`ONYe(0*$5APRFz_ zB~eTi5M#{8Y+jJ567}J;t9Y7B9t+VY&ijR;UmcE%@2Q0~8!<$+{d)P9)M~{n4BaFz z_?|cATP=qbQrlocl!2G|V~r4uH^mTDS#vpf!w*>BuRSaoan!>ap>Ca6Qv8n)2AnDi zbc6b8f!#{c?{-<=O_cyxsQ zVSr+bjj{P&{?%xx#lm*QP-y-x<$52XUQ|q%R;}j!q%=EuNO8s#JhPfpef}z?kMH<; zvLVWIFveo}XPq&7HTOH9rtKQCz{p2%Ur==}H`a0?+Gn_BncDDMl?TU$_i)I(-7+XD z)?79L$t<(dcY`N90<$p_irW?B&5ON;KCI#nRUH(@;yiTRDZ9*RLvi@}acs)Mi=nlqlOMR2@sR1szhbIZ7R&6@bz|qt{!EHYh!%7@f*ccF9&ny*9ti ztcHTmrgQM77_DIt6q4sEFGRJeZ@*xBDe=%aV34<;N)-h!G+=RPx>4^@JfLxbIjlLJ ztVvyB=qB_(LT=wdav+MVY-P=oAlZx5s!k|NUlmF#ljeeZvt{fgNbURqJ80C0fhD{k z7!~tc`K?Cq@txvqcC@9fY8Bi2nW|+u4(;XDo3)kCY^$|s+hc!qqbBeBd_7Ib3iO>R zg^x@=pp-Oq7oXrb8>u}xIXme-JIOx_JhM1E$vlJVA8YKjm7RGGn`<2y$gby1?8FCr z*{OKhwX16x7+KxPZZ1GFbni;dJ<~fE*V?b1?z`1Whe*Mq*6t@6R3`qTS7R(I`vrOO z%5>jjZ+g+ToeE>~23#IGPGnV(<|NU5`wj+sC}ZE=fUHc;V0 za?EVc+d8ZO-0=>seDp$$F(wvvxK&V;0yZ%6ql8wf(@H&h)K*@bm7redp!DGy4FSDM(Sb=43g9fzn(}I$ejp+Gt31*Y3*%$~bmD&M0bxZ_?gP>$}E^)1{ z$cC!Bhrk;1;Q+j}@Ct7Zv0hhygD{EjIAAJlpzSm;D0{w>U z9Jhg(IZy}4{UemtkK>{7iok4&!pz-Y&azsv}#dI{gg)DXE++J_Db6yhh zGEr}%4#P`F2E|;6`5?dCLSoIAy3E~3T(TZE!cXp9HwNOEt`1D1&|Xx6c^9Xs&)OmM zcH%`Sx=H+vZzDRjayIbqdbQHUTxW7!`9xls)|h`DbyYLojTeZn69ui{MQ z$)hj&@j@)=Y6qbaxOJb7u6yKQb6V+uui@}uY99Q~70hI9sa3%Mo+Cub^9gZp7HAZk zsR_SnUam-qppX49s4>ft7K#3Gfv69P%k{uQ6_;56Vu0J{!ylu|8eM*?0^Fc+NR)xV z|CIp%9FP$OWaLHsv+R#z*uNg^IX|jiC4W6bXHqI%2b$wQe|7n_mT>>_5u-#p%JIHC zMclFxm-3hJwy~2O#?G3K?q{AhNkWPG2vRXywkyD#5h1{wU1n>5ITvNpE>3G6{I%zP zqCL<4DTU7rWvVZyP#wQAT4pyX2=?b?POC~M({dHSYz3;JJVReklUlu?syf<_rKNYv z^EHPxy$f$!RgP*lOS$mF5(Vd^Fl6np!Q@*8ey5i}ZNIAPMb?qV4T>Nx7%WY)^Bs#VYMt!g{38wXh}H08Q^cnJQ`?;!NLYGCpDxaHHI zY%vkopwI}Aju34H=mrB9FsHYA5qN!7MI+`r*>EGP4)urRKBb-#DYxIn>|G9)RF{xp zR#p6%f+PSn^n_dU4U&55$Bb2-7)vq-yPQZIUa1Z;ZI|e$!T{I%k`{xiIvO*LwnTjo zQsEvZN)iKFI6LhLp3B*pR-yJigwU?_FbQI9yXQgfbM{k$+3=Iajqd$s@_Mx2o~=Du z%!l$dNg-geSNTL)!v(UbD20NnU0mpF?f$~m6ZxgQt~qn z9C}1W{6%3dQ9A8KU>?CHs#^6!g6D&5Q;+P(iR;G~0@jbN0sZP|wd?OR1u&k}pV3*% zdY&1^MC6**#s*Wb1&{2U5+}*L0ozeH@Ckz&JsC6I0?)Ypk+^;`-G9Mrv|NjN@;490 zNxo!pj~f)y*0v|Y@Jb6ifbskJLYl+{$(w#o`nu9s@d;Zff;71Uh;xN(U}TLZsdZu zZ}7>kdTijAvN&LI=@G5tE>+p*{V zJjn{YLE&``M_rS^+L*4`fG&&J$zB?8v(0~_)c}w)$FKxI&e|Fh%-@z*|2fY}RX_dL zfjG7Q^|`)8C3$23lmucud$nCSbPn%yNLjx+#{d@eSK2>^6*tHUzn4;OS%?T*+zG>P zdy`pf##nZ7S)!I34*PZ-ImqxbG?vbsbvDLEEzXSVtDhsFxc`8dX--+g^@mhh2*(*| zj5BB1@P53#3Y}X7kEzrhhq__Wy8djxcf~wgo)*8)wmrLDUV{)EM|X!_IFo$*bNEnh z!kR%{&n@moG79EBWgdalhtF*8dYA`(Jex;Z&gF@0F$wepCTDZq(W`0L6BEZD3D($G zU46>)Wx9E$BlA*S^}e_g#oDDK0Fmnp8Le85N0n}=QeRRcKW+|Li}eb&|`7Gy)o zEN%)?-tyf5HDY$8Ejw^QlQ_ykt@z{uF49_MWA>D=Xd}Fie&I6~1~^C%M@)zlw%m}3 z=>Y+N!_)m>;WQUip zpKJ1m#1u9#y5zPGw7eLg;e!&qc`|Ut47sc;H&{+!QZz;zjaR zdH_XUY21PY-S){@sM9wJrANR~GnNO_`KTlKIh7ZXNg|?;wB%yx1@phE zJ$T=HX*`28oo{lvSW1))5yRH8>6BD=iBNErBWz4o%{rGy$`KL(o-Pv}>wLyPDF&aN zNiQm)4M9jgsrSWnHz*&5`gN5~=r)fZ*BQT6yr;?-kpA!!`s0?y!=v+Od$rAjcKJC> zYlZ{^mdYyb{1+PuciHlv3Er-{Xh@(kpm|UwWO`M1y13k6`1kc|=5H>}MElGt%J125 zsu`QVS5#@0gPv-Tp5zA0-jFCR8k23$O^(ys5M47onU+)(A9Q>pkUzwQZMVPqXlS*y z);k@~d7f>_@@-vqse=s!> zV67T^54}@VdCuRo1oHzKBvQPK#@;KKU-b)>9J<9$+K{>|NCEuj2yRiF@W^>mV?+X8?r8qM6B@HvMJ(mTtIJX1Z=Zf~Ti12HV!y>A^RGdD-{26CRQAKLXt zw$1IVAgXH8n5Sc8#wT>B+`0ThuHg>HCQrT>A@fZ(PwnnSsr0l_WjOHZY91xP4$1R# zwQyyxXAp5s32nahRGgY@=0~=9>5urhDK}uHi1pJORe>wXdwWy)S+dj5g(PlEh86|E zt?9%C*AnHzV?%C}xyGXqIv=a@F4iwbl3C6O3~sMB#@>x=o|q5E)g&NgD0w@VzR)Ml zIPenpl?B;<+Jj^U%gqb7bdR`$Kt_uuQ;3sj<5CQ2`BSS1ha!mi zhvH3Uo5TJyXy!VOsqatelN!BhG$jrMYbxVxS6_lH*g8D;Ef^wVUUTcnG;BjKVi0e6 zuyEZx5S>rj?Q63>*KJXwOX|JzF3{^9HQTZwTlt$;Lm_3NrX5lvW<$UG&=2xhnz!zZ zDNiK3hka<-^3@{ItIn}lOC}7OXFYcK%8__e34w{7Dy~fB@m&huxQVb?%hH#bRa@mU zIan7v9)A&A8!oIYv#HXoOQ#~n-Y%o7C{KWn9L3P(eBM8 zD!}XlXpw0FPwqF1i(u8oziH~lEr-?)dP^U0^NKJ1F|aXgiMDu@>gY;8=+XT=R#2it z>00A0N{QS3r^$Co6|xiB-F91S&dPG8!*5>^QN2fXA-UesEI;uFVO;cUpzD53r~Q`t zTJ_IWi9ijbI5X&Mzz2CH+IMSh8F;Dz!;%y)|KY)a0uQ~-Ad`cN`Jl9J(wxuLzWRWR zIZ+x~4m>%MGIgjT+0){vps1#3IgT)JQrr-EuXmN;p!HgIR|itA->^Kz>Fn$hi)SGJ znvHd){zBsHYyk@=oW5@6;f?%$%(j?X6nkxkRV6t)_krUzi39Ejjw7O2^U;Q8l?y%B zp|+yZz`=?_hqFQNptVu2$TC?i0-2(lps3)#We%|{cqPsK7+3UanM4hJ@0K|}T*BlQ z4BlXKc-(&-K^$G`Pf1`9e{Y!lrLS0sCOuqskn% zaZ9Up&f2aw`gktwXmDHBvq^LC=hxaGK|i7T<)o@T5zJ9-j461AXR0CabrTA7$Iqk z5F5V9`+s02?|qZHErY((v%WZxBcb^Bc}z^cE0|}HklUDGZ=dQ{1=6zJwshuhdItWkX7K^vZXT zdAf07E69wDEj5ziae)W%?u1bRhsm~ESRasG^hWJoh9|nhiVEYpR_=0%yELdNQVJ_> zI;dRJtGPivLK{K~_Ddn7QDV~$4#52S!|unsm#eR-a1-}yf|T%^%fAr^pOMu&>!b|D z3s3!iPs>W>>9(QA-8!5(rA>IG-(KOvsJgW-CAluISk_qie87| zDbAva(yHLB90<>Ya0!q$CH~OjQKH1=p`0}+R6o)`XayM?jJHEW-;TqiC0Y^A8x0fe zeiVwzpG@O>9)aOp{lZsi@+&{gsZ&VdNG^z%W5z3`IaRxU@z{_znto34^{gpad%an@v2b z7{E+Ao|cO9=gwAlT1q-S4?}HV$W2RIE6a&deS(oY=BtV_6INGUVt$|%YP%+(IJfIV z(Rz05&PK;vFX&YU%e>~+SbkU*tl>L!-CF}fNX(z)!n z2&$2k#xjFCy*!pW<9i=LXjIozzCdoZk`2M1Muy{svz(YC0Kp=$yKaoM_Vd+X)fU&wSBZ&m)|NG4T|yAk zknf7%Yh@+XV>~ji%P$lZXRfYA;Ma9J<^w@0cCNTx59KuCAuJygk+pm`9FI&=I*>?- zf7d0F9mf(+Y6?Lr zu-6uso`XJ5?u%f|GuJ>!vDMmAh{9EkQZx@p*Gi zP4%4Mb&4Vz;0}!KsedNyz<5FM|8Z{2cKF!E z4pi3s`I1vm&cXSfy=G5B%xY{YZ5-T=%wPp>3mrcyRa2B}MJa3+woD;52g2ZP`ZuPx z46&%7;P~KBFs5Twch^lQ3evmJF19ud@3=>(_7g1gVjrV9B5~3OV+2*;!t>v$qhB4H z|9*lip@j4krgDj-TuwHMIm80uRm_5skRco#N8Z!_^9&|P8}L>X9=*c#$Nw&(-n(;B zrG@!NLRdp1b^U|D#fp}z6I&0q@e-yQ2YhHUam@jxIry7K-pvK8Djn+#%g~!s&WngD zUA|N$o9Q6R99Q2(F_lN)21YlPBPM_E1diw6%G+B^AHDr~b4$Q4TU{?MY&LmOJFA(d z{3gIvrlGV47L%$HoSR;7+BzG1!@>if&xa~$cW0kz%YbBn~_=}1TU zSe2fIJIUM8Rkylw=y%-PiIhiVQ+!$)zyEk3;{`5e>mTzk3Ec@{KUjWx$A3VIrSbh1 zFRbRv5%rl%aV-akO*G6yH(6Xi4@M>Z;_TifeUGO5Vdk&ax>u()=sqnS?<~uN$ikG= zo~XNHJ8Fv%4}(!r+BeMHJPbNQ40L18Z>T$yfa#nI;c!sTkBXnx9;NL z?bxO?bVs?{>+D?@oeN>p-uP~=#_nwC{S$)Qk z5`U;uGZS-uG<2Ym{vQ^Gd=d?unB;7j^ z6k=8lsj#|es;dzGt%Q;|D>wF5Fp=mW9({RMk~6TD0>$Tw1}O8Ud7G@I`khn+?XAn6t^J%^UqL zBkhGiM#dhE^@GcNGFb7*+@lq>sea zkb+{ydPQdd{7I1uE@B5_F68#}eZ#&kx@Qt8(NG3x_5oqAO2q90Ki z32)B+fmpU}ZLP>=psKkf%1vT|8ejqh7-ph+vZQ+O-l%6A>*O1iQl{&QkOVU2wW!nRX@}A z62$#>VCz6@%>h&jz~+}g_9^XywkRkd{mc-H6R|pDW(oe*XW~wSpu+FC^xkBjea6XH zl!8qJJ*J?BPIB{k#~dCDBNu(7DS}pV`Us7t*BwuzvRpA!A7ssWDz>1;hdD-fr2+V! z0cmQ$WP+q5#XFB1{mXspfE?r}Nr@N?N-R+2BEYd_9jY;wE!>t)Uap!~MHO8KkV!Gu z?@3Z@Z;_%8*mrI{l1>~?`L)~Fbg8P*r@LuF1t?h$_Zbu@jdcapfk`>ea0+U8+) zhPa^Qtc=w4TVZX!8z*hF2iH`7&t2mP{(|+`{~=DXuct+W4c3-$K_IRCM9E(DY5QH# z6?k-c&mGBe7Hnqb%Br28!)=d*v5E?5f57Y*d9iYcL#=1GTTTU)RdPYOHc6D@`;n+B znh~Umym>)1NM@CpxeFY;I^XWM9o>TFh%z}e8p;NKrRzbw2oqTpyq3t zO)6KPUNbg5%@J~+e0atU{M1*V)4+idJ@l{~rr7=7HyPc=9pOt~)(zu|Os9I~RM@`^ z%iS$@|MYN15BSwDvDXYaBEQJc7&>!)u`x(4?x}&`fY&o}=FA(GLl?W_9?oFE`FU=4 z<1kD#v3u9T3^~YhWoZ7_1Wn!S+o+ZImvm+HhSSx%A@V0wRz-ud{gjeH?SH z7z8YnO|g6H!%S&Le&WD%iZ&@@=758oe>iLeCHFE-%-)7 z4xopHg1+XJ8CT7b<%zmFQH_a~A#C4A*eW<4;fdv>}q-qr5Wauxr{hN`KpVfmM8<; zYF8FQL7e{&3~I6Lk&txEb?9u;9Z(#q^|D-^>gFZH4Q-A*sI=&}ykgqSU1(MMBcw88 zF>;B29dQ@G*FEm!PG6s`e zJ#`|XhrW|Zs!G5G0^ZF(l}rn4c^V zixos&ZftLDoIA6wBS$~8>fSKH(lI76Lf;fu0vUY9?$TxWI2g^>5;9%}Tk zQ_CIpYb>2|#I?V$YQ(x{li{s1jP$=kTX$ipTnOH`=2Q9i z*eWQ4{NXN~DZyDS?hRd_v7Xgy6oRpGKtj@ih zy{X9jQAY{l$T4Gcum6V`xlt`A$S?~N^(>tj>(Ff=^Rv6yv&y&1tn`Qsb zcL6WrHK-OVT;~`w0t1&u&i8CRvJa)@53qpSQodE#M}Cd}0I#OVjLyoqy`Y^`(-XFN zMV?ApaNm4UZ`0dMUH%6e7Lu_3Qe0m}Xr7+?!^!=4Hh1yO^dC#4^ic%4>bJohyo(4` z#;`)qv`}HEq98H(DG-~`(DQ8W{>qtm+;(q4Oyu+mZ+D+0!2BB%a_9swyU(z$7cDml z?7SU9`fS=4x*Qf%4C~siNwFr<^)*?5tbLr^jHB}bVpDqSCe>l8sM!@a)&OZ>@MVwqAi^q|c`l>K?wBSKzn1{N= z?;8|}1|gt4*&yB>FQg(pSwahQxlnTmbK?lxkRXNnbY@q^m7EJ;)3WlVY!_NnLumMUR{{Ac>#b`{ z0vS~w)voo83p|gYy)qK}@inFB3IN_Fl8Cn7Xdir^5t47Ykoo!Q|yj({A+Z1v5g@7tD%024M`GI;Hl?&0Z$mk4mJ(81uheJ_tSymiMc25Z{Jj|KsNWSI8m8q&Rup zfP}abAN>D_M;B*obMK$+>Gh@15<}bnFXr`Smwi&0D>rZaw#`fa#pd(FR4Sut$Tv`6 zO-2nQ6s3gqaYJlA%r15!dnmQN-OV}0j7&8KwK_mT^Q{CzL|)16w=}T1Cjpa`rA5oM zk>8Mc+6h?)&Z6L{IGMjo)Z4s-~lOMP( zLJ>}c`kOB; z&whf>vzF%+Tq=7sz~+ojU7Qsb2N-j4gOb=U5oHj=RXDr%Bgd?YhSb`b6Zp+@ly{Jh zR9IZp+G#K?=(OpKJBr3tg_Sa^f~ps};|v58h$E7(Z`45c0dunVb3OTr@KYwbOj+0J zFo2ua*=OeaRvG-j#lPwJO_g&?RPPp5bU=-QII9|B=e50tnnI2J>vT^;^NW^F5Ip;o z^@}u5bfwnn(?&pBEPr4GlX8FzHO8Yt+aFD-wJ00@!E9|AL*Ai~g4oELnWIJ{1uM%jl-y7I!o*HrBNy=#_06HDiW&J8};QG z83-Fb^vOl23HgnA)#sK3?od>fX=yEBoE%z)<)Vt+$)opIX|>e><>U)8GjnL&uv{$q zzb0Nr*mv+?7k4X0fJ`d#t0ax-tEdJeQ^*6QXVIv0Ll8Rh=B?O zW73jv<%RcXRRCoJ0mhrR91mz?8FtVkv{7_d@`-3bI9|ft%$=30u!i#|r8Qq!*|db9 z(EV%@*654NEJ`Xwh3G;M)ZIMp@B&V|qBY^<@O?s&<%wBO6ll$IC)PG{EMO+elxjCc znPu^hE>k{_u(sIM?2Rd=n83Y$@S2s@G0&A0zwxzSK8&+6KQ)3Kv z;qu~Rc_lrWKbe=71z*@5PX4)lBK z-2K`&_osZfE=1w3Jy9mZ#7Ey4pMY3OZC48(=s+wTQYQFNUvP?BoDDyJ_zIC&I$T$b zW@-TIgsX|OxZ_sw$`VP{QiIYCMc+H@awEZ)#PKqFj~;(gMTrHKg4Y4GCyrEKWvazk zey^#?v9b1*m~-Z3vk({jO>`3xTuLu#7e7%}%8J*aD-Kt!F(@u~A>Ib`uh8P}UWVAb zeE#PjW}IUH9~soG0l3K%Ls!w~v)Ou_tv?6$F2Q&~NXbJMWC*elckqP;0VwXWO6 ztIFIff7v$Av>tv9M?<1%w)cFXqo#z;MJVDY_Tx)O>CnGy9{ODWK%QYl(AFg%3L{_F zy4Siny~^0iYVASpnXP4S#X;T5fqYu0$Sgle+-pFhQehOA{#?=TbKg=X8KkK!aT0nD zz128C&E(Xv9`roxJ9Do$;7P&b)N>Q2u3zky zpHFFP1xs5mR)KeeCN^i2?LlL8V=3w)SUr^(QY)#zzJO3Pdren9nIv0{^2+tF^1x9c z#5s7-a5UVO6=LnMuMmo8-PHG!$yIRCeNH09R%34X9JA912cJsm;cTm8FO>bIk1lyJ z+%vWR8aOI>b)BT7*&`n=|$MijUtkZZ~bZL|2T(1DO_sbv(}v|{Gd)6!nW52x~spZX;cSZcAP-X!uvy`AOi&=;G6r zDT=3lK4}#`uIaYB6_5#w^ATCm>j!@WJLbdW@0ve}m!>L@Gu##KXNsr52$#DOo&!Of zz{1LJOqjL&eA#@<M~&B#<41AJPp^^*mW z`wB-`w?4uYG5H)0*Zmqw%r_s-{Pz&ZN6SKUN3PdBq=Mv~vOqxQ_%{P3Kmi}wpIug` zV3+Xb{A|AiANCoq zXSOgFn8Nk>>oycQ%}*`{+HMvaL{Ew?p)=ltuwjD4f&&_M$dtD3;JdtFq@~{ImArO* zT{+`$miAx+3+yp55FgaZuM{C`Z%ta#I9-*hbML!3WQ0#HontWW;&S?yBtKfo=-pJ3 z=9hYUr&3>v5h=~rTZyp2ex?Tswh7Fh=MjmQ(}69b&*cG`6jz;cv={04oH_({G)2A> z^U3n}Ik>s2Xkc68$NddxaV#ip@iK1&ZPAPiAym-;P(WFLV#UI%5jnqzlXAbO2_Fl^ zAb>hk@+305bP!N$Tbau)>rt4CeRH1f=LPnjb{*|nX+SBf=2pWVTo2si(~=%_hGz8< z)Ii0<2rwOz!4(Qq93`L@R2ceoml8*0cViok&dy;#YG1p32vHZ?J&b#_KvcWb`l1_+nXhIV{+Z#!^ta@Hx2LRKrHws%La-}A*kTU z+$~W^-q~9S3Z8gbiVqZ`u3Q6|Xth2v+Tzd#3S#w6VHloR8{)d`ecK_`-*I1lTH@|J zvy`_0L`ptlvoI8O%Zz85)JDqtiZrwUnV|!DPIN>Li~g0QF&{oJ6#<&aU|@@fHSx1) z0(i?Qft(ob91sMX#YOK($+QZA#N~}_EyA4j$JOZLOwVX_G)qvx>bo_mip@AV==syY zp_A(av?j2xw@uKU^t!>DnZxYCF4-1RS`(irW{oMBZ!3Yu5qavZN>^N-M)S$k%%ae7 z8babEY4RmJm*dX_eT)SY(x@eHRQ~?eGauJ3x}1fJnz-BW$>bX!w`f6Ie^q-$@!}to z0Sz-NcQQ(I{mAoJ+^#)9B9n{fG+3ANA=W3|5Ni{}8b!BU%z`H9(1pY#k47@)xnzMp zC{`6wUV_^nn!HC{mVX|7rXdjI`b}+7a28`ug1Rb)*(=uK z=V4hD?bf;+Ui({vop3yf%l|9r=lGTIB5Q}W!{~gk!~);zA~B$VWkmY7rK-rc+WH)! zA*0&=9R&14j3zgthbz0DgyZwF&EJFS=9~aGt9HNuj5vUuEAZtJpzLZ{9%DIhHXGZe zUP0T4CjOq~%m=A^H2s5kZAE6o!IKqZ;%y3p-gCL~S4NGD)X zRfoCSkOJwgv1}56cIMTfpv0-L7!Lwc1t~ZzmL&it`cl~NvC@t++prdD+_0IN@mGi2 zSZ8?O%zU}8qpHm;&~u)p_2>7!gA>s4(f61Q+;`jebAL4%VC#nb+KPJc?jUNMm(mJ& zeNu##Nrb{5o|(XBcfh-c051sqkVNE`5<&r?%ylOwob_~>uBoAG2MxOBidoTHyu=Q7kYs9GOmMM9orJV^IcfU*E-N`}pzT3Cr{GTz zY}2{Ab@dqfwOQkt-8&n>lM}PPMLUAI`GSexRUsb)tTlqQMDaYpE`Ff*I5p!g#A+y9 zVVdSYtyF#~++cKZkssc$x|*yxTLoY96=nrgYUH zh(j#|bj_PslxB+ODsf%*%=}%N9NrRZoKthW^LSI1wtYfbIAkyD z_~h{>tw|TVx?V`2UVRSPY$i`q$RoYe9kqi}ve^lqq%%Lg)2-lgoPyoln+El5G}Oph z9$h?(=}UQRXbLFWzhADThzqn*8#yB|$ui$ablm!kAjZz}_lsxU`cfLfG~$;rQ5NHt zbECa~hJH|u-&ZjV=qyx>m7(q%AEg1GKgvhQPEN&?@{W;-qx`7-p=;xs`-;s`E{Ark0Mj(6GmIaYv(Gt zoya{knOhVsA2B<76IFB$z2_b|gvBK9hL+De+)s6#fmVOn9c(z3zC`d7SmosRR~Tcq z-r~tDU_Z47{~`5DmD8iMn$wPnkb{m8iPF^5h3&|YlX;8kFDfS&YXv^W*_(bUE*<)D zW_Yw0azLWgE`D~hcXl{pF>$)0a&~C{a7v{j_he*Y`))^mMacca47ghQ8~qh!?X=S3 z_QAAad#PF-Li*8tXI;20Z-DNc4Rm~Z-e#OV)N+MwXvjge>PL6q+!c|v9Z#v+({tY4 zSH}yKr?^6N=h&284?U&C7uinVB+B28)L)lR%jT*&?(R2Io)0oIQYi^Al3D(d;M#B? z^ERZ7YL}k}vp0T{gPN!cespqk{RS-M7q{m${rr+)ersi3=!XYwKU>-DF7NO;Voqmo zSqq@AN(5`wS1H~(GYKo)I6Ghe`|kIAf4y}qyIenF%kMB~? zkWPffeA_OSoWK6OdHG7nnQ_h*`q_wnLMJ!5D(WFd(+n9sG`n+8>dHG(q^QNq9E^+_ zky37|T6hPKo*+T?a!yp{YP*#PfER@uf4^aGnpS(n;Es$=FY^n80&dbu!0-Yhw^oc4 z|H+c)5Y6Y4zEDe!zxiG`r}lN1ehpjDA$V!UW`$|oPJRsC_A81KlV|hrJw`@dqF-jp zIwkS5zB2z)%0JERFB?VV6pP?iX`l7GMuU28AUQC$l3o;Fpp* zA>o70eS7Dd&d0ywMEELb9GGYS5g9yo0p{r@^(dupgIEhhy$LIf^knVOq0Vvs89OVk@1xo(W1c~G&N}V0B{atNaZ&U(2usXH$`vE*?ck#0f5s!de~TIllD^Np3vh+i1e_UJp`IQPc?O)MZ~ zvFDQU!4q8P?PUbXR6%id0V3ziKq!b6SWp@9bGMU-`9OjJ=@({)#dF)*1LES0w3a2C zydD!vQ@3cyvxOI)=v2OD3y>3XawYiy%`bdENCC9=V1H??6}$)=y1@1~gLy%MFN>9s zq5RYG4+I3U`vY%8QZp(ktcM5*+nK<=ycr1RJKp{FAvXJkjFJPKE!)OqEvxwFddzj^ z>+WKlf4NIwFesSk0*SM~+Fqb28=QX`7t07g> zltR8?>r7#7Wcdxo@5S@pG)w3_!R+#qT7ZJIF~2Ix)st=ZB_pA|csQN?|%}D901hLky8Ms@>=)^r? ziAt1i$EVQiD5o(I&WBj;phyW)nvIWH|6MRBalx47nJcr60EKPE(+FCf?VTIYC3xMv z>{u2d@bNb~>6axy7Y}Dx0HrcP?V7BSBhgRKMy>4g0#foCbTB@@6RqQ;>ghX#`@~ zJRbl5nELW~DBtMqnK5=D2_Z{LWC|_TZ0*R_!pJsbEGY(!lw@BA z4Jwj--?H!f_MY+m{oeQeqx&9@Dwnwfn#tP}|gA zRbbcF^%4Hr4useldm-6|Z9nQ?rvYx;iw+LpqsO8DfmIYHU zm)SkUVT$fA|B{QM=5~1q=#qL)?1RJANg{TfyJC3sQyX{3nE(S?@`QxQv}!LGESBSa zFw2CegXw3u3}*@gA%|CeJbKemm@nOQ;a`Q|Jj|07`Cn1eVK$p;Q zQJ@@t=hhk;K*%@*Jtsq5Q=q@axw{NTk)L?Xrq9ISODq6O;$m96zXq}>Gk zxQZtVR!S^4-5e8=d#^3s4+M)n_r_K(im4>^IDP!@p*vu#_((Hc1pKdQ`jy<+pkK*R zU)46bC`U}}4|wVfTXK9{`8En=0MHsd_O+jwGB`w)yUk^DB$Lvcp!hdB0K2}-E$LAh z_Ttv8>J>Kpty$s$K(A)km%*4k<&NSjspe)GcS4mn_AlS$gg>ZubK%>lb04PzBsJlT zQ^R6#M$(7cOVT0RmIu9yOgCi5sv5~HH-aqT4<5asAdwp^FF=#=Y}1y7bd-H?8Ni+a zU*`7PvDsZ@g(9n2`=tn-vi{6XKa84h@MCg1iBmI5q=aEhGm7hz_e306$W(MqsvoEN-32-M@ZpS=)Dh)sa@DV!JwgZ*WnfOo&y#xu8b(@>W=% z#b1O?)d(#8`5b)f41SS*)Km$8Aj`?Mt%y~1#gy&DFaPY53VY0kjU1W zm%p+Bt-797jTn@v+q{094Mf7{VGisb-(NdF9~!V@EpfAvZav*liZ%E9c~e+dr);p2 zOF%cVCz8IGN%jV`_d<=y!m3)Rw+=FNZ=%L+a(4fGD$c!ClsXqJb$`oYpepu|T-2?N zpqXHF+WZDdM{dgE2=%V|c8CDGN87_+;VD0-UD2=r9YpLPX!D+htaI7m2_+n`8-Lou zJO689fbw`i4(O0)&o2;0=ZTu&(oE!OgxU(x(s#B?*3gunPAoukP1~~x<3M)`WSxGN z1Qtwap{Q@YGnvo4!`!9PCW z=A{^{Z@p=e^_%C3{Pu!5+j&;;r;R3%I~fo=Y7IRXxOL-to*kf%S1-VIPL%Fkzzg~X z+=UCMr(b}h@5pLJYge@xHS0E4E5R&=#zclaMDc8mVH>Ra$vZy-|A8q{ChekLKz6)- zadL|{!ZtBk{y)6ZiO3Bh@TkYdCvfkCS7Y_SJ*eZ{pf7$s+z(NB@ZZqX@+w(Yec0QY zMNv>aL_Hc)C15-$|C-|i+*vEv17I5Om!bdG*C!go199%X zqTq|UHT>@_OZlH$=0$-UcQ4urSal;kaL2|D5^VFDY^}uU9Bg`}U*oWcBq-w4mv~7s zK>WB1@#w1_B0E>Oyxj#co`?jA823M-rE_d< zSfae)#a{zS|&*Aa5&5^4rQJ`*!eOMepj=bk58*e>Hu zbD24k`1l-S@AWGyz(#5*;$z7mP`3{GtWthrtvJmldjgEjIBz&A-vsd!uYS~xTVtwg z9A=Yy0cH#SoC^d=mj@ZSKsj=$xlJ4W);=f7^`mdBy9XdsyRAc~L5TUB6{pE$&rY~$ zq`peyEoDpgoqx!f)mZCMvoEV%5&_4@Ix6D=4N4yLAx&=$s`{Y6%(2<_2@JX3xbdpo zSa5Z!nXG%k_pw(`k@j!5y6Td5yR3LB%`M%Ry~W6xd8m$iO2V=JwN^J#*tHcm{-3;c zVTuf31Q1aQMFr|CCB$Gu8>dQH9n&{N$*-;qMMm(Zzs5KDbrtE# z2k{e22}SRAWzMcOYY#0A{VS)S*m0NDecKk586!1(prm(J&GF41SnQrBc9!VeEbU^i zIPSJpwfr=2da%}sIHB87( zrhTD{C?Mt#U0(Qt3;eC*Az(#b!&TJ=WfM4hbs;OE@0o0|?pn5UpU3ix1$w~_fMVQr z`XYYWHwtB@0Z*|OYOjVSfv0KXkSBSmjIj2oDo^e9D#?%@1)C5Mc8heBVD z>EJOw-mo?&uOMC zS$7O~WqScSJFAAj~2Ly(@!^{-}1 zJ?No>hTX76t&?4`TE0mtCUL`OX#AqTBF_q&lj!xCW8m)AZET6CpT1dE4OooG3zDzl z){xd}3t%zuAClqLfY$2I^`QgfF^@}viFYIzDUDr=NBAiZkT!v2i{R7e-s9$SA11w2 zD83lI%!A=PQDIDs&H{WUWF6NF3u0M}!(w!hSjBD$wE)kdYHVsmHv|0p7aWRjMz z$a3K!U0W*zyA_?C8(k6{Iewj9p+z5I4ndL2^xmOJ4F*KE^SEO8QJC>nv5&?6Dj+M{ zUFf#J0h4GV>wyHL9{C)B$uxB54RLwDdWdHqh#ftv;Z5KA$5T4h^=?IFcytN zVqu%8xOAv8%yf+?P&;L!m3}mJ`!)>;;cI5DaA9M^vH^poBjc~;sIy@CEF6`czYb-= z35?AeI*X5{PJ_Y9Z+FSdHvvA$_Z6aN^^{@Rkxi#eyS`8S2;=+}{aL?s*V&>(#+xZ2 z)57O|v6c1TJmCPV(yT8+*0Ky?;yGiIDUu|EPMh2_!&WvCN6vI0Jm$3(lGSf!l>5GN zNHEsF+3KG^_D>d9q!{cmRXr7LZWVag|1*_wP(*~yUuifK1P=Y;!Vhlen8f#zurF`* zJD-4THRqkCKLSwd;>pTj)8}ke|+=9t5{0ErFNrJjIbA@_W3Oe(z-cv%kz9CBv})da5XUndgH$$KBQJ24HzA6cdUv z^-a}Z3(g7=z?bDLGTP-(WlyC9)pnQ`(gbnoD=S77UiS)D>CWs%%#d(GTHnf5I$lz0 z&uUyCZFMllXOC4EmFLKlIP+suoDj)|zRZ6t%9GyUv?5)m9;ywi>kVm_P#azWN~pUD zE91a?k9=cw#p8=K z$sm(?)4LymUQp1iH%>n21QB=4egD%=3OhP9#cFysPMto{SXyl}!FJZF>Xu;zmWIeT z;hy(DqO&6SUiJ&ul+U~#pRJS5d!UW9xfRC0?C2NzT`hDIHF@WQ|OBU#RUS^iNrL1Ej? z)tz=RLoRAKhX`>*T#uR@kgAeIb)$O#fU&xNKRP>?RqSunS*#f+ZgjTe zB|M##1Z!rTSD;FtD9!XrRCxPaFsdRX64#8|v`8SoI8x6Ke1qeU%|_t)7<>Gd#+P`;3hl3aIGE;2D|K(s|w zM8$6k#e>XJ4rzZ^_vhC~MTgFP{c;YOA@?o>kq7h4L~yRj!Kb0V7%8E141dPE^qou0Oy!0(Tv0|mcQwl$R<%!H?eS?RYU*;3Y zvs-0*OVb>Yo?nOP8_oaS6krdpFkW9yYL412v^qxNR9+pC-XMg_M7-25_UI$0TOv#+ z_ln6p^-c}f0(Z0_pN!eJ+MTwjwMSFNe947{(E*B3%^Z~-cJ`NwzgYe1ls9|w$^&F$ zq0q?^nLqV1B;%~emKWPV>qp67dzscfabw$6`G;CJ+E`pc)h^@9Oo9@V#jQTo9#pfhVf}i~;{r1|aNE+Q8hM$s z_!tsD+RJ5}&#gMxo0yvQG=ULe-B&Vgd+|Q2g3~Xgwad)n9AGz+BTvh)Dk}}&$nwf7 zE{x1?%{UNr`zupSmQ5Gfq8Kz=%*TE!W*PX{`U zuk3>ld*}qB;}rjqvp@Llkz^x`_NqE@P#eoU8W=-U0;UzAG6@3BnQH!v`8bU5N?LUL zP9SH~HxArFV?+LDzbm_D-61cT`h0*L)DhMC$ynGk_4uuWFK^8AE@dPOiShBR-{k_V zSI?Wo1ew8J@?(G4s=KME%El@W3@oy2O@3{OgTdh?;b0co%?oC7>ayJa?Mo&?QtEsJ?Jdfh{{QN zfhb+;VJ!v2nTO0G!a{Ji37^?3Lh-obpPAKOoqUC4WfX*)_lTO=i{g}%_MwB*Pm!%s z2s!(Ix}~wnD&~@mhdy(Z!UmMLw;V<#wn`XJf?`wsGM3AFR@d`bwX43L75x5soQ}rfgBMYzidTRmnS2wF*vdfwkI%=_?59 zs`_xr__q4WM~jN9@|mS_ql15$muHk|Bh7tDvOIMnV&99532Zo%clRXR&NSZj( z^P@QU(6z5ahHjbW*YLF>0{cdsoj3EfuE%tyCEMeg*?W(~KM1p!tBLWiz4a*KrEzh+ z(k~sE9xc1pF(FoS{7YF*IYsjWcTU1*wf3+Y52W>i9V^_KIR*Q5G9Jp>Q(t|BM3JVMrV02W*GRa^Sv_S^z5 zC`BXqnL{uWwXsoF=$~$uR};H{u5OR`zV~?gxypfkxHN#f9OcU@v`8mLENpgNWSc2J zNR~HUJ9IMEzwNAg=#XsR{?R|wE_dP3loTh>R%(=eQ_DLMFd|Exu?=4!^c^HJ?jkrU z6Kq#z{*?_q-U{pEU*5*Z1CC^h1 zx~D+gmw$lR<(GuULOXNH)h^F++vuPUlKQm&8 z_{gepy+R$0VQiDPC$XxwW%5S^4*9q%p40dX8YL5hUG8%oyv`3}C8g;u<{o0VZkEyp z!p29!50l_yFN`o%Sr7ASj0CdAB2NU2vs-uAemKm>wdWB}vD=JJm}wfo_dL8&l!B6% zVioafa=)$0p|vH0Y3o#6A2S7V+Ty)7m*X;|V>(Sya5Vi%B*k@UKg4GA#Mx7yRN+!= z>zoNQ$mF5ZOfJKUqAvqmA>9S3jy$3oJK@%Z84yD!h(8ZQHi_iCRV6Sy0a&g6k|vta zm+xdpS1JrmF>_Fj4a3}y=`;aiH|`6D3Tng!V>{7P;H~1koWSg?q0BG?*k_G1>FlVS z#FKRFJYEA%(v1ASC!rk%4E5^cY{2t)+$jYt0_-h5C)Vi-g_o(m+Abh?AuSXNqTmZ6 zYuP84_M-x_<)(fXqv?01b$>+QJ(8O@TTNrzw|iV*UepE|Ot*O5+C?2Zi{=n;)lfj^ zx>Pi@2jWN31AhJ-q@_z2`NAk94YA1knbL|8AdbL}9Lb3)JC2ULcvXyj-1q)*Cirl6 zHm41}EE(I-NI6$_wluxuTsUB7fsme?Tz3nCFfd{9qL7E|X-JCs&@*E`POE6b+>(=r zG&Rg?UAMJe|2{d}xmz)Zi%ccSd}ExT_D z2+?Yff7-;dD9|gWP@Ff9!be;#G82-?XD^0SB>Gozs_wva1m#WaWB*{X`?g;YN--J9pRf0c;T&Ur?iRedjN@F{FIKLeqdvfChS2^UmKzo3K# zxNGza^4@r^#7q#Eid%w%ES06|0ns*e>+ov*zi;(hW5PoV1l#QdUh~4mUqa2L%IS8P zJ29QVi|D~KcGHM~CgJ8PN(uVxus42))-Oj(<^2^t%9eNt>v+od8E%QZ-Ry|Dzm=yQ z+HbuWO!kHENVgX9Q-3<3Qk6QVu0Y|%nn9mz2Qn~QZcMh@{NJJkGW6-GUo+MbLx<>B z6lGkZ+5r%lyC(j!aY_4(k$=@QJ~7?2p5JH)?`|K4cyes{5v0(rzR-rqaLTRPL+zI3 zUaqLk@LdEC(h-1BMl}MPobIzhsJE6qw`jw5%vTu%Q3)NQPHvBD_wNT(Y&r?J6S!;! zHiFAq^PY07%I4B5JSM@Hc@GZ84#;Zimt6D~02_084Qx!ye)=1X8s33Cl)`QLin*{Y zJz=}fBnNl8%B<{H_zuK|ur?|ij5zry@Tt`W6#yw!U#SL^IH4Xw)+>Xr9I{OZkuZLH z%7urVR$*|~lDyAVcqA%fkp5y9gKeh>+rANSQKlTyCaH4KlY*L9&l!FvGy_m304C6c zAT#XZSr|EAoyD>|>f|qnO$pWs%hlpU4rHXf1sr~8xnGDr-sHkN3W;Odu}f+JSu_81 zAMAMuaYWV!oap5vyMqjfI6W^m`?lA}hk@rwvkG$Df7!(_=)=oK01Vdtz6tJcJm1H? z43GlLUaeJX1D0JNsaUD<%wayx&1O6r7#Cx1bg|ea92dZ|l=pQCQ>%IjZP2*p6P=Zf2$(`&B({$wq}V>q$ZSSSbwSw^dfU7y3j5 ztUTj9s}9`-{3D6d@H+=_@IV5r8bpvlL0nSb5sMU}?USYj-xZI^7^I(r ziQE7I?6v)JZ`q%rvOGz4R1c0)A1U%bnWm6!UMgi>KtMO7r{N^9Yx+N1L-D%r@)bGw zGT}FUOoqgne^vM4owbNhlvRiwrIsg^s63)`M>pf?rns6&2O4(H@|*$w2p$P6_{bpm z+xm<^rGUcZGha-oEdy)=79FuW;=Ulf4EguN!EdzywGfiwB(d?CCH{ZdZ90hw0Xs>6 z8gOuQIHZ3>cgb)ffa`!lfZ9Uw)XT$`BZR4~$c^Etf^|=~FcEg z^}qxU*D{_aJT*u$;?@K z{tPpSzqtTP|6D%uup#X;0`5deCc%A6`Lt!Vcb`nx1E)i1I(~h|`IBhyJKS6{BOOOe zL15Qb6`GJxTDHQbu|3!;iEb-)C-&!f!lq;T;zB9zuCS>unkw01|NVZ{!8@3J9o-tX|y*!aG<( zLAa(XzEb3kjJE~zrJYBT3v#k9^sn5(jLdj^Hphc3Q2qTKIg>u~L+Dl58N##pXFG5i-dEa z$wUN;b6}gfJ4ek><#Cp82a;t;d5imyY$x2+u~26wK`P z@NyI3_J(c!2^tcyxj87Hlkj+hsaopN#8?nZ7{nx7isPkY{m113LDsm%cWa>Oz1sGP z#O)25==LVbYI|t9WMfDp%P^Isve{>~z1m`NeA`t$+|E(kD=2&Bk8HOnFWsQQ@2BT{ z8^3tq{6ye!mO}RT4y{iHvz(SgrZ>Bocy;Rx(B0a)*}T8%B{2h_(#3AFBFN~9AF2r7 zVsphW--vMitax>C``_An$%fzd+EB^%mKAAz9AxfCte|LN<05lcNdoEfkV%(@@aFR) zo2et$96qH6ua7D3TkRdIuqwH{H0%{y(dLvG-S)$Gxv*}Ewr+20Z;$f!ELI?zizY^U z)h~>;RTIC;uB>{+vTn^4rc3@&pQq&n>X=rm%jAeJY_5i^CC7^Xq;*Da7D^PlagL8bMg&eb;a4ungmMEl%~@?J54SJ2iWE-!(l2SF$2{d`9Qv z?iiG;FHhm$0K<6@H!W8q5zBl-j_tUx+uGS}DVSEbYK{wZyF*`8K(kZ>16rUi?_NT}v&SE^X) z3}YBVp`GQB+kPM-9;kXnIfG?6)~ySiK<7_Fq3>`mcFNPnt>+K5a#;5T#&`AkL*0q| zcj-gPN6D4Ud)9f}b(RYQ} zkB9n8L_94%qdkG9IFpWb%f-9~6+_c}jm4TaW{?vT{2|Y-`PT|nQ?98}z)!Ck*;w&Q z_tAH?At)37)ZoF@t;@yX!I?<5C2`5|@fm4H*nV*U!k>qzKX-xGWm(hh%;agqITgr; z?XWlpgAjH`lU%Xh^EXDIUkHyWDA69A%c$$k!&I!`;?E-DYTxqYa$*7|%Jn4yp0pG;z zEEAIBIp0h$&S=g^-cfq#FbK>Xn}L1{2<2^p!Q*SVuo8iiyttPCFByG!djk4HSS4KD zGzINEGC;eI2$G(bw&U7+g{b>YB^nPJA(|Zf0rZuX&*<2$&)uqBRP*c*(PI&|7m^e) zzMGd2Hbxb-+NU(Zp^*mx!gpHCkH}zm>+b=+qWQjTPt6(UxdCR%rS$@pG(pHKsK{~y z9@D|VL|IBjP{1X>B?Xi&Zliuq2ugA$XL7ta{3jmWTS%Q(xS>@=Vy0xrFXs%V==AP? z>W8_lv~M>|h{0(|$T<9p90j5R3!myBHom5?cT^$z#dkqvl!6C8>H zG|3kRigO}AYDeW1j1h!SNC-{ZJi#3>4>l;OY~5|eO|@aXy{~}_BQw*zub@L-W=7jr zD8;8BYrlTi6f%PIWRa%RI}WJSioM**SvC=W?XFtAX2-j%qbCgS*tBxSjZf_6$!%i3 z--7DwTe3)apvx?19LK+wwSRA2*nGMB7SB-HGY4yBBca@6#*jFc4)N9rk5YG*i~tL% zT&KIebN+}Cg*A;12O-9wgHkfQthenT9u}__dmin~#g63pupU6>#w?OCElI_UXxGR6 zRchQbd(xP+$_+4n@dCcqtao$nZdNMFPMz1dC!p!B_3uLC?Rn!?v0v=)D&+oQO+_C6 zwvW}W5~EdK;%JGv{)ggSmmb`E&44s|aBoWAPpU|%y6N8qcs2$59t~FAYCEU#T;u$z zic|+yUyqrx(BFAM3>brupl8wwdgCIt&t7Pg-F)7;hv1^T%R4V*8r(gB(Q?nZWrVx>^ziDCYj>sP3EwlNJoyLr zzay{u4)<>YbGEH$<29}Rz1MD=fgkasei<}bVSl%oTj*uS(XF@R)F0j%+U5MLPc#6* zk?H*wFC%X!bJXqb9j$B<2FRmvpcb>Q4)s}0)8Nz#sVv4r53RMdPi1Ku@O+`{T3(km z&S$nGM`Pm(mfvi%u@gLAF>RWFS%39WtZyEq1D@PoD0G5^pSum_D>hM&>X@>0;`I~6Y+ilRUV6;7_23&^zR zl*9IHd>uH=s5W)W%Scnn@s(MIHmR7*1~DcevxMb0z6>OPCa}Zf<^6V`1A2^t8S}ro z>Jq{TJ8R}XKMcC1?AbK~_d1?}P)U|H&mrMP-t>x$j{GV54eP1#ly&O8OB>akR~2Vg z#uItdN9^DC1106kt`1PTrG2ZE#!mQ`xqIJsT@vKXJzKi_JFAF8wWDRib+(Sp`{YUP zh*LLO?`F`ORWd?K9qdw0Q# zO=wFv+UcN@(6etmbq%9F(Ll5vFS>a!EJjirpok%Js6t^d(~pKk%|A_=xg29!a)uRU z*9mr}v+on%It)E`>{K&ad2NEAJij{EE9v_wXdK4@pnO=Xdb^x!(%VG3xgfs*CEtT> z5&`8MnhYKXUzx{$LUl?zQ`kXK(p>SGcG-lv^+YWh{epwA*F2)|5K6 zn=IslR&iYMi~A~I()#$f)S=zHe;`(k(C5q7w6-=W0{J$!sZwPjEF1!R|f8^hNU6#45>5Q5ui z3UK*%d;9h4oTI4&4zv~B(h zYjrP`IjDy79}vB&{6f3~tNvvu@Yj_Rho{f|>o=KXE4!*A9$ADRT?Bi*zrF>Z>~@{# zaL?oES2N|Bl~ZYxk47G_Ex%{^vHL~Fo52t>Ll--YR2v)L3+F_}L!Ht7l#UtxF1xm4 zOJ=)oiZ2(j=$xS^SXOiXI8Rsfjb{fgMQ#NBwcf+UbjDDj8EWYl7>bViKcDFpgclyW zET51t{{1J%cwRsCJ$C9v`pp(Y%G;w-s=Qdke13MF_DII%D7F^Dc<VErEw*g>Ns>}v#v7X;tYK6nRG}nYh#GpwX#4%9*5g%m?hPqNDO2r?K1<7y z#hAmh2SxY&nokvFDf{?)JRp^TsUmJXg0RkGV>Ud=er@7A1l|HYo?pF=BOZTD|}6_5cL z0C$Pdno}R<8NV|<*a+PVTrkef2-Ia03)G{2`FLSh=C?-V(IXfBU4l*hE1C5?i;iSh z3xJKf2P4i4(U$Lm$Mi_*#X>c&L&I+bzaS)`Ch-F##2$ByA!!1r-*E2~D@w%n6o^L;L={g_A5a;1KLhS^f7oVdIlJYO7kv(M)7q3jm7 zV=Py^%F4Bn9A$$CZZo|#D)s&-c~HE!U}3BC4hcBn>LAwNwmqyv;HEL@Y+g|?)R7lY zcSMw1-?efN(Ttf0xKa$9knM}yd`BJ5{uL{~yHs}Ku!@6GLodU%9q~z_V30VX>)o^~ zNnnNSp{)JCx^t*Vx1af6Avp$Pp~L4v*XS!h|8>RXf6||(}+I}=LpB8QGTA9Puu-2ls_iXUC)>N%hzp3_2bbv@8We&#h+%yozX8hvzKj)|AF4^$b65-jiz@i)jqMy?i-!Y zyw)cg28y0uTQg8{eez?q`Q}Lrjz8m}k-FD>tC^G-wNcP6sCFn(P6(wV;lkmYh;m^S za^bp*((H(PSSPQ}f0kA9BJ!)yTGbnQw0#Sc?@L^67s9XYBLCFKaN7#rUr%@8_U5Tv z^KZ^=lV#0)v!eZ|YoqY+@yGLNzYpZEAjF|Ro7k}J1J+|(4^BQh&wkk8xfT$2_3!|w zirJM+m+6tQ#7Jm1qg6*kNh9Qu2x|g+Q`=r>eC6L}$MDEjw8$YzJ*xhsn1TYrI%C&M z+@ghlEAU*c6$@shE8iAZsyb_3zMDjA6=8*LIA?y^(?^q-0Bn1prQnETM>l$>96IM$ z)Zt|8tH7lkO-%h6S&Dy5-I>iaf9%rlG@da+n+}vN@a2aWp6p14eC6IL9|T$U^Z4ao zCG$oR8fN#XQu#FkRv9mk9KTRBlJwiP6_>}M05ke_r z0#DtsmcU;z_t%eUo$x^#>?I$!Ta43Fa(*F;>oNCl+hSp{6P;xL5N`V{w9L4eoR4rC zWnX!Tx-~1H#5kPUsn#>nByZJw-j`6feJu0mZu`bJW~BK`GYX9SR(%PAF@?K(eb{OQ zERG&5n;2^f=gOZM)!LX3Scwjel*%>so%RK?bWIgg%)D>UU2I;o=8KZ_p7?mI3U^MP8j;HL5c7Wtxj;X2tgI z``&&N0eD9>WVuNbE0Qsg$OW)2JMq{(^Sb!wpy1J$~vrFnf99<%&(wWCTdYMmMn{2dm_xNt}f2~?Vwi9~H7-C;T((jx5VzGvp?s~(}e$A+*gw)UUaYTk5=++(L;Caz+ zCC2VlmwLWDWmP5DGNGDzN>@&mK+Y;WqrEJluhRSErvusO^3jN=Ra{@&Zw6*@I}k_85>n%QmU;1g zqn=(o<>h7t4-8n60W&%_1o`~<_|K42k(AAW=7PPr)1p_Np+4=tHjAKZ+0ymZp)VJ{ z;j{Xq0C`N8>Ige`l*xzHErDRUT}GjJeDOkym`&0(q9-)JA{hw<8ZTY4JC0Xgbuaa*jv2}k6jP&{J!E;%Gu_u*`Pj6lS%^W1}Ha8GZ zu<>HnYnHS!!YjuY{UqWaO7;iE|Al{BcwgK)mS^iGVmb3#ejxc?y1<4s*3o*akszR< z9XrtKee>+<>UniJW8tkw0fwfwBrzb-ZLBc|X1lITvO<<|qx0T>%lU0K-aJn!Q~|vSFYf=_cNE5?@Z=0|<)2+p!))!nqWyz)(Wp zs{U8^IX@_5h59wYja;-AW|!}Ow`W-8(N_z^|Fn`TFSIIZMwd2refvu1pgy0x7_W61 zjl!DuP1J51Y(}I_9zy7PkXB6ian+rh%BO7uxNxIWL{DFdvZ_QJQxo((%Kz80Weu>S8~ElJdo7#*Rp9}=>@AN z?g(`Fa0u2w_dqNGO8Sr06T~7(R#6cmEWl<&(WUEji9N+k4{aHKJcKk40>lfcu)Zzv z-SgQZ1=i9rj+1^SMCP6ooTq=}038&O-`MG)zsrKSER{E7A6~jQjIv+1mu+CPf0qRZ zt_YC_F+b@TG%fuklyRn1(c=eS6z_|G#9mNnTG4Up$>c&nQ*#a3hbiBtkFzmlEi5@s zYGIeM->(6mVLuCgZi&Wb%VvD|q*yM!G8bB>b6#u<;(;D5(>~YeWDNR`u5E8g#-nxb z^h7a9KwGWfc(K3kz%bmAdlnjK8WUtgz=I)h0qgHnv*-W^-q!DUrsj41u(zT3*_cVa zvX*W&woF3qmas2nWVLs1#(LHDjoxPVQb1Ko_V>y>_31F0*<0+Rd=m0uS)N@?#lVux z{PHCs0TJV{c!e72-%P)P949lba!UBApW*73J~oK6&}_WHO$@j!^S2 z^om2{`;>|At*J~eM558(f4k^B4Ei)ZDcANC1tm;oloyk!xa>5r-u#;M#>qnVEbL7_ zZmf*-JWVQnp1v9kzjwRu3yK+ckJ07R@rQwTN?yCNB036cg)5>68CxM6vc4G^>R$EC zju(ob{9L}^AENPh&DcM3KAgU!*}78^rBBjC6HXt^{i9*WK6FgOHr~rd`h2L2T=*ZE zG2LAF5qWEEw=y*pHL$*Vb zgUQchmKK*#)_p&sZ%ER!^k0D2@VY#&&*IVk`NinpwMRwwGRm*6rCh-$0m!gwl&$gzU$Hm=mh$2HsDN&%)Nd8n&U`qHunEb;t>0+{7cNGXG+CgV?m(VQmaC0x>l|;^OyuOYs(&yWn>th+gWmP7rt6} z)Aox?rh9UkjPNGK7-@NHmAFUM6IhNNbHAa1wNX;ge8w@z>)qC$xiisXl{U_?>a|;d#+ag=jGc-iXO)i(b$QmM5@;@a6Vdh*QJ% zt<(MuSvvBU$9S((j<0I|*96(e#~$QR*jBDQm|eV@wK9Ut&Se*i3d(K5(S?Yd#JD3f zt&GF2Xi`kLY3flqA1UD`{LjePwKb6;Xi}7=@{uXKsQO@emOU3{|Iy?);?e5Rd9TR6 zLn<$2ubg#W!A3IM*Q<#?`xqnbRs*&NWgbRf0m|m7pOaXlXYL5tbTwNCVsV^K0A`U^Ms(gQb(r*KR6;b z8f#e2o2YE(VO=#za1TBoo_~iEy22wLbe{My`}gl66}>>%e5@Uz3Ra2UoF-Gg;SNP$ zT^f6ur-IJ7zdZnQdT(~Ra0Gu2Z8=@CNd%4Uh-T`(!lnM&=Fync?E@ux2P`_)Uu70? z&Biu8xIgFx&bqOG)%T$S-Ym5*4oXkdOO0n2AmwYgz49S4QN(TTK3j&bzSVJk5u@BM z=S7trFEuSXab?U{SafdaZg~DG{TVv{fVZsVRj?SnAS_kxxdoZcy6;D4>#yKtk!#3)u6J4Vo1 zGaq#sRUf7#Ob_yxmp|>9;Gz35vc>d>6VG7{@cUTS#K5qdEO%t0mHwqsk^^_g=115b zRJVC0DT%NRwpeoZ`|tFA-c9_y8~p3q(Nm#;W~AAZ^hU=@&_yb1T=`|E?V2<;egoX` zjVr~G{&PlG1#~rf!sJ7T42;++DdBM@%Q)b%e$<>GISA0tAdoxh$Fa?;PKoBgW9pVd z4Dy0^9I2$GvYZ;;%lgeY%%-=&3DB|dj!U3Z4lA&ELm`p8-0a`H@4wsw4$YcxgjiHq zu^yl(>QQXja1}f}+(JUVSzc;4yI%llxi2`IUhxhTUpYQ~$}Z65NN&|W@?V)^DKse3 zURvB{x9)3=p56;L<;Sv?R{G@5z6~mYe7-)W;m9oRx(0rFnc|Vk0}a~74=y42Oq5hp zp;@j-?;Y7=g<);0saeiy#blHARdxB@g%6;l!vsN>pB^1ny&8~_^>O-z;WE@!z?U4Q<9AL+7%qT1*CrY?IxZan<)x=?!V~sFf*%9ar!GdYe~9vNBX+qH09|D z_pi{7VIb@4IvUNdKe;=vTxID12Gp>3bfcF%)xuTKh5HtrSm~u6ssT{;QN+F{A2!^Q z>2(%!anGSjro8r4ggNy8rLK&<)Tg(0$OF$ul{bm*r?`vy{VRW}27tgfOVa-~6=_$R zu7bWbgpF%DK+k|4OMF1=C?#goWfGyGK^W5bZ{9kSri`rN;V?($|Ae^Vef4n0IPJfy zF@CTX_pMm+5PiZ{Rw+@*?jkJRcI+lreO&?2)S$vRd)kftm6MORv!4N1f8vRT<;<76 zeYDH26~`??Fgujr(;0Z&Hq`9k$dSX}mVUzSiZe}^-EJqbkQx`mL4QkQdv&0`WP1f( za3K)e+X(t_E=-HlUz6i}=J8SFxr5Y80z7Yp_%oEsSZrfhnzW_GA3X3ww=Hh=EN!P> z%=N6u5W03gnSYo3V4KzT(5FEM^h3~ZPlqfmo@S@^H)Xl`8tzw^ADwkZRyE{yV0eue zImXT(_~Z2F?Z;>QI+~}>QOblV8%K?(N&c)gDeG^8WTdQvEkl*&1X0;p_l}RGWc)1+ zSFsdyzp1zQJtsE)TY<-o4ui_+H|Jk$Fw=i61Hw?30nLkGLa@NE7e~A){A6JYf9V!O zAojpJo({6EH*Fnl{~P6xc{(~wnAV2~De{)^uW4}H_c)zvzmZ+Zx>cvprg!GA_NdVv znuzTw35nVQ`9p?{S7igw+;||p@b|3rt}nlS>2Akpp3&HPq<)Jka_gvKoXyon${pUCT^sn^&%y2QZ{=g z)6#3Zh<5WLRjEv*Ce@nx>n8Tcs{VHjRW7vhkV3WehJx*BEgq58^wWL+&CuFH))ObDG}7$x$uvTA(U)ZAVjl8L6>Jk0;rB_v&3@ek>{`7t@=Ph-lf z6V7V41TNr{HMD7j^TTa&Qh*mwVAhpElC`rj=jlu(>lH_-Z2cybm|q-N)=VWYzp#TeJ7V)@Cg>TpGfNt<(ljM@}UMJ3^kt|)K>c*KOil5 z_QEpvq2PE=q7C2K{(7SyXb$FPoWP&nlC_6xabZ&JWi=7T?tX`?376Ui9?KuT-?o$) zVBAAKO!MPvSYQudDL^0A@g$zbD=u zmcRlX_TxkTY7(Ou5laHapxdE(AY%)7bBU-6*TuFA?{Q!m7x3ERzGdq{%omb4X1)<8 z7I2?2+GX77fE#Kq!^5LRI&)9X7bflwLdUMTdnmO6JXp+uUWrR(FUFRcw<$^`n&A{n zPyNP@Fm|l&BSI{{?vpWRf=#9O5%Ic+I9)O#hgKpLLc1h^xGY@;LZ~g@?BDacjJj6c z$Hz7jwgsXN<42fUZoGyT5+yE`Ba~3n;d@BNE+Y??d~D(dLPDfxm@;oL!9uuXZgE(z z5%e3tFz&GL(`8~Kxg$2y@xSH zCuoAPclQH_1zk3`d|nuRJqA|U#jeD1_-r$tGuAR5ce> zjsL%|YPRj=i(2NQmbs{9TyI}*rIsnm7pQECE|2c{BwJfPQ$p#1TvEIzbk|3~8_i@0 zgSs81dy?xG;5>$}d4tKq%{bCB9%u4HBIAP?&7n0^(GAR*$W_mTH)jGmMtWGcRq-j^ zy6?0|_Sq!sbXF>=tC_h_AX$tD(Rv)ZR5C-?8%6hStzP#-XS&O}GC;|`iV?+-8qXMto9=xcx{V2>j$k2>~@_eG=lr@uA z(N)1Y*GO-3CN(nvO6*kz6p?NzTemJxtu9NoGep`l!bmsU2qO(+coELC(`>n)XRKRA zNGZKZTAld`;$%J3_s6Av%LA zn6c#9hJPJqnR6lgNW_`LHR)t*kPwNvM@iE5)$-TbKvT^j+O|p+2;rSIt_8I6t{DeG zw}XtQQ#_Vt*o_h8>Fg00KI01KuUh=|!Ag=XAlwJ>*6-A!;JVg|>mX5`r^HK6-7_2r zhH1rQmTAi3O&V65osZh4ont{@c%r_=Z|gI{>511p`9bb<)~W+yRaff7*VpIftu{Nn z59HB@`@;p5LWdH1yBtB$m)$^Sv;kY>fy`?}_~tD*Y5N}sQ77zGqh2X!MuSpV3xiTK z>eNb|D5_NkQ6=bis}JE|k#SOqva9Rc}W9a$fzXu3%FX_c;#;Z009600{~D<0|XQR z1^@^E001EXpeH!*9s>XXJqQ2*4FCWDcx*3oba`xLb1ryoY^+tuZrd;ry%*>|5WFi| zV!5^- z2nSDy{VXXIR~qTvHpO`!^grvCL{PWvl5n=w%sML~7(Y07WF5YZ>!N4L799T{yARPo z4958+L+8j6C?dqK-hcJpK8~i>XWTkWoU2;0x(Um2f zRt9p`Y`3>xqiXoyDr2CMvZQ2WrzGL_m>kHs6OB_FkjE+WXHd|P(0eI1dsf_oo=!W` z8f8&zpb>cza|jMcSWwPyEyAbjn2#-su0paExiHsdis2rxqK-p2ykY2aaE1*vMRV8_ z*x8Fx-v|`_M3xBE_fi zJKIR%+{U1tdDpmf~+$>o?2IRUUbZV7>5W=QQ?~@p|LMv&;2nlP`lPxcol3 z>;K`V@1G`Ui*UATIJ^gXNOmB9d-A1tVmSJ}DnNV6eHqPzi#QCt{5%M~*_ z@;Hn(^V#|;&f^L9_=PV*;D@1mTP*o_WtgCx$x*9Op{W-{0`nJ1_@fg3>D|wM00030 z{{sL}O9KQH00saE0000X0GpScxMP?A070?>02BZK0C;RKb7*05Wn@!ya%pa7b1ryo zY>a(Pk0eKu-CcnG1HprC3eL2!gT;#EA~GW~Gb+Ba<0HGWE`u7fhAb>uO?5Yg9o-t} zAwYlxMj${C1MNX4-4@Uf&?(9PrDyIDnOXjxXZ4{9Rc7uU{$XxzW^Qiw@jw6T*I#|` z-|t?%zJK}f$%nsBe)r)AcMqSv{QUmmi%&irht+@n#}7Yv{pR-J^V_dpKHPos;a~1v zfB4Uz{>T6D@$1)bK0w`v*Pnd&<(oI({L@Dtz5eXWyRUCwCojLbdjRIoFJFCq`v(8M z`r@P4-@LlJ{rvTpcXw~T{_3M=zx&-EKKlCh{^7$9K70A?!<$b&{Kr51;U7Ny;M<4$ z|MupTQ6(;;8C-Tt2l zF)s3QThw{I$h#0}(9Y0Hlu|V>Rl8hg+qK)g8}?zV2Jeb|$o;otjdW>K=9&IHaO=yp z`+Nw$dzFu|Z=v+E9UH3BEG|^qrPWAN24FsR>N^?MC+w2JWHS?j%hPOLwIeNRmhO8R zJ<1W#3jUWr{7#k?@LY|dZi_z2@+AgN9m(6eZH7(Q{;Fsas}{mHVk{d>C(T>Yw=eR% zY{oilqG^*puhXK*6BHt9FPf28LH1GgugQlOZC9%4Z5D~GM4zPiDct+be|5SWfORoP}N zLX0h{sNiNcr?q~P1gLqxcswg$M{fE2>P4!_Md|a$b!DM_p3>Gxne3M(%1zRxR~XAi#8rArN_Wn2`^GwgdRT{If{Y?EJ$!W#Ol zT@p9O2PJe!nbH@E@Xld!tOgoB_-Nm+PGoqR6NaOkSvKsqh z6@Fyd*4v6_aRT%C-8PUFXtWsWGt6b!IKUdJ77m5Q!dc5=i2#<+YDmLQ3$U17D=c;kz#6J%7lp;_ z7InHH{yh^4PTy>-_*kveb^aU|Diw;t=9kD_DcSb?I(S#>q8g@AOd z>r1e_Wp(4j6gXFbB$Bi5^n|o3w297Y5Dpc3qP|lF#<>a?I@K>lr?O%f;SYHta5%cJ ziHeV%61bwSh({dDGYZAq_3ufRJk?NGSLs=F7-*4@MZJaaM@;$no$XlkecCJqbwl&W zMm|?gRSjQ=>$(AltRq!@*x2_QiG&p(+s96Nu5%QY29{NzV5g^ zAgZDf)H`k=oAaS^)Lo!LZy}@FSqglf5%D#>1MFMmf)%2*fMnopLo9xKx$1d|(Um`=pnxGD|>lM4xUv>CIEK?NU6 zLpok3d2d@#*e=?tN3<4|(f~hcP_XBV! z7N%EpeC}BB+TrNUm8`UbxR@5OVKoEM1J({*XO-HfbJ~`)!380ps94RSqbVFZ((sC2 zG+1ApQWFY`Rl##x>rV>XOAXnOZ2&gxrj@ux46xa9%UfP6=KF1ytw2qD@;egSPCFih z$w|+%s{10Zh*#jC%95w5R`IW9PkgnCfAwP3ZEPtG!AmHJVaL#3(M{NVY^zeae3=)!oJeFE!9?M- z>pR|@jxAw#C;4-6YV|Bz+hL6}IqXrtr_Ml(sjB>WSn?4kPndyNt@w&AaH?ZXd$v}% z&qYbtt!5Ihz2jr(TJxGXuf1+0Z-`fTqPLQIT^&We#vc4Ad*~&sQM-zQbWimz({J(M zZ)I|E_FF!Z084;|k)1`(71_^=X4Q5W4o+o0^!i1GE1R$x z7*BQuO9?RM@Hu9N7KXZ_OnSbs>Uf{;a@s2>ZZyHe>}^P6#g?dk@ECFX0KK%pg@+ic zVV(;!Mc=3oFtF&G7LwmJF+22s!ifIhB1GKnSrryzaV?S&+pyUI0n7R6ayH5JUJ(BPIG06D5t^%S9?KNk;T^`%+OAundn&_gU*XhNF2O~ zY`&G4jRI0s1Jh1VW}IS0BkT)kD@|6&z1pxn3r&Ze66>lvI9U-igo}^<`XmG-WDcDSLXDHhF_1N%j4!UYK zqQr3OB2!4mS~A{}93wBLqW4je+o)+q!-;-AwgV1KT7WLi3LFSwaU2pn(N#{&!^$n- z)|9ix1c$B^b{RXOa*_w?dks<-PXcmTltN66l5w$&&>K%7kKU{ZH*@CXuDZ&s4&(w9vb(YIrtNGH?S zh)IFL9R{mEer{-gpi!l%Or=3o9BFaQnpSN2$hMAyT}|V(|qjMD{+wtput2R6tsHz#rz_w^UT$D1w#&lsqo-;Kd!yKg=b9O87i?NbjQFtV5l%_IT4?jts#ux` zik?^rc{v|>M!}=TVL*FTGI-abyjp+@h`L&E6?ng+G;j5(ENygGSncs;3%2K1K{(T$ zaB$@A*`j(@TiE-KwPA&8GWb>y$If?`MH}3M;viu~``Y|+Vw?%;H4L5&vL z#Z|0uWUHVhxjk2k1J3)&cDMYH-ja{2#lELD;;O(E0vYH%RIm<@u%0U|8kSfr1uj)` zUJbYc?O-&DgFb2?R_IYfJ*?apfjb7G#YFS@4sHh*XI~!#W~Vhc#^0 z!dPfm1=87elXjQBWaze$_Y3>2btAee?!w@#qs)WcS+~r$M0Rj^be9}S+ZFI)CmJb7 z7A_3d_WV>0u5ToMfYAKK?z25*|sg@rmMq>ar#~MCS~(3@=5(38z@F zvEH+)Gizn?VZ91k740^_g!T`pIG5|(tXf2;4$5XeR@cngjA|E+`aa$wvRnXHxZGJI zd8_tKjp>X}_;?9O*QvRxuGO%t^~TR^?)Sg{S@DFt#9S{tQkX zVu#xtUT?8;a%LOF4SPzs+lV}Yw*sE&@VCsk#+{vwYe|G%IGbF;O4DSMOJdrXt4I69 zLAL^&@5%b;eSe%|UUE&c>=5O!lj6=B%U4OBNek3m3!4o0?H#0na0RF8+Qc$f*E$v4 z$JmNx0>oS`%EVpGC%QaWzSMQ4)8X!lufgzOK2yHJ*d@5qiA%>3s)N?fH0OZ4J_aU2 z1CYqYY^wx(kr#`xs-k2u+|1qfG$^!0p;ha4ha z2}CS53&9PXh&OXDM}#p%y$HTmo&oSBB3j3lP9Dz4eVdpWRIW0mtOa;RMJB_O$< zP1wx>yoJiCSB;Qk$9^eXdh3EOzC<^@W9WrQB`_5u{tK~k7rG^cqO6PELdClr&Mf@w zqC*G?gVRt^o)#G@*@jnM+KXV#fTtPJj`u$c!FY<;?yP^!v3UJnyw_NEQK5Ki@QIC(*%PI zzCU(!-UH-tf(xvQ3TM%EO?3=`855Wn`2sf+JMqqE0;2#C$IGw3!4=e>?!UT!^A}=z zxNNy}ZKKMI)6U)SMC^f1#fyGpzHy;md-pNgo!)bdH$V~l!gR+WYKU<>eIX)G6LTSt zeWx|HjyN^qYGy*I2i@~^M%Zn2nL$G{JG}%t<2_-z!X5y( z=uyr(LNZMP6Oi>R}p<#updV>ZpeF3YEvvq{1 z*~B2eB)=Y(ll~$xZxOG0hE2?+N!K4@%NDs89^OyG3%?J2g3aCrn^nWGf?(Xr)nw9k z)M7+XH_@Klgst2?gpfbKBFKFRDaR2u8U$0kI=t&y?Dy#AJ&?J7Oj`8gNb2I$N;S46 zj;3lDFSUorX=O>z_VlK75qj7_-{T!db6irwy%|%&VRzPqsJLeMMHapCV%u3cFT|JPDphop0IfD3OAtm8eN<$ATLanc+7D6 zu6ovR!ZwIedieRm`sZCc2vO*`%EIgUY(_+=m{P|3Dsh8Ed=uM2vW?P$TQ_@HJrGuw zWvoY7J7(dJLyXE*oln680i~ht@ZzBP*h%@g0R*J%(sXyR35#0!bfGJVV2a94T}j}# zU71sh@CPaJW_B3$#F%jBu+@HphXO1iK`j9@7=~8Ps~ITA!5po5rH;7Tymn|Fp>r_M z)-K6cIiG;oNCQPLE`c|cu|$$<|3TzexSp`EtT00 zc=p-_o`ffUHgN62Y{}u}_s^?RCmxfXpHi&1po}O^FOi4J>77tHluQn!i|_|hjKa>S z(kr34?4ZzuHNY_T8)(7|H+f4#NlS%X52Dsgt!+xhS@B(*OsYRyV^bpy4X)P39|s5y zEbz5WHI|~nw2#yvAa0A9kgZ4hgUC?$DxY5-&PTwo-*5RLAxoY)PAv1B47+#5lnjs) zibc^#r%{9bHRw}9j{rYqwxGIsK^JLz^~S`KLGVj7oJhrhCp@N*3GD@pHDzOogO;K- zM}fFRRK(*fDR|ofj(7SN>U#$U4wC^S!Y8-cmk6gSg;ntKOu+z(c&EvJ1ES7Lv#ZkQASNOpd9PUK7O$12+E*!LP9Yh? zjM_9J#6n$~Q4|hsQoyx^CQ%Z?NlPkBFyyfJ$LIwiVh?UnmWQ7?XoMHL!t&cfxM$+Z zRaq^C(bUx3h!xlc7)XUVu!sBu`B6{(BV=OcJKX2<>Yb*C26Wn1AZDmW9H;4QZmo#h zcwsYQ$Ef`RmV1>Z()^nbh-wc*4$beRv>JtmUp+&CC9`G6rbB)#4n%$u^*SClDj+O* zi8{lv(y;IAarr*V51vriKu~a$?)|uoo;g<$M2K5T)9+=WG960L5XoZhf&r3oS>`ar zF_Vd0KOFBR;#Jg49;;oL%>@`IW;=yFojc@_6VnKL>z~v{t2+0O8EZxWnhf8-^ywt> z6})ow^BnsS6-Zj)MKw3_Spgm|oaLdD)Tp4G;U32wJ`X|cgwDCth;1=T~u8Sma1 z;$RiB+K|Fkj-@{T&@aLa9wB3MI=uyq81qjI$sAgU{iXem279Vvuj|4pN8gt}Mmk;P z;I){{t%dQVV^p1Tm^C~`6OOF*Moocjr$;~U->AiAOI-;YjJC7^6^C~(${@y&1O@?7 z?D4uqF!W@Et0`4*7?aN~v-y~7&ZS-J+;h%$b!;}yC(`jxR*)VQiR?l%E4iyDx8})M zkzFh(As#{=OF%a7uE6g1{l$jPimBH83nazYr^iW#qBU75>DNC#ictFiudAjxyn~2c z^m{f!<0M5-5ywW3<_s$aVb)S4IK;(!3gd3fQQ}dlA;l|N5`q2mw&^ekvD3gB#6W=V zPe)0BhJzB{RE-sOi>Wf~vFubQ5^J%45j0H-W0OyXIPh!&W-NQNoMA6E%+Dd*%u2)2 z5Ig*ih~ybNm>eZAmWpu4XEo&pRR%?H_qC zQ*tjll*LGEHMpYR@dksHB%`MH8}lGzrEb242`9^P(WSJ=5CPc6TbPfpYFW5dZQUtS1zqN7@oGRb{RV3qezgeE)>GpoYnPU*^M=Fg`!lbcnT{R0%C@9H#=8c43Lf znnGkit0&;GVJcf5b-;`AA$TR{$H~K&1nMVN{V3qZnExE<;E8sEVp9COycQ9k>{!3t zbNVMb^{9;fqXoX>5$20T*Y1(rM>}N3oTY?CH6dz`pT<_zA5>oMmx zJ0eJnMU6$^TvjYCIXTlAw{D%p)i?*voZvDCb*lQ*=WE2;rEfO9Dr_)4xtn&cCYLjI zqk4S&7*=Fd2G)Z*{7>q+T&gq1P3@27|O4?S%&2yIC z46FQJeU(g5v(>n!Dn1|Pm4Lfk&zy?_1I8evJFFpe7H2(dIK5i3AbwVML?EIG-e2>Y z%oSnL!<%3d2p*#&jnwHLQQYBwz8*RFA%>wx=@B}cvK}Rzm4uV)l=--0zG>x~wYXFR zVoGexO`gYwOCM`-xs|AoSanw7GA0b z)#03hQAu_ImdSDrs>TTQkciK<(Xdi)pG=Dz)%706X*vh39g$9{hQ3aw8}JNGjvk@Y z$Lif^dE(Ng`0xa0Cw$8mHxxHE)v&~pgf?MCKDnjK-|CN---q>1$T%jcXf}N{e}KlY z;(7U_YSfo)x&|Mx1bP;pKJ1Ufy1a!NZ}t^qyL1pBBpt*ybwo^YjL{zP(l z?pUO;thYsrz&l;y(rzNnT9?pjT96hvcRo={wngM7+oDq2#L{P~RkNv5^kn2ja5p*) zb@b(=^avg)>X1Rj!s*iYY?5h@r3h#Sl>%IHSuF5ZSfE0bCnH@K>II1q)!fIA*TbmXsx8Ru& zHrb7C+Ut=*OlN zPm2H(M6jj4lAydwtDZ{Xt~`<1V) zQ|u$J@^vg79PeqD_>CuVN=PXe9;=JP9kHQPc&%n|BCJ76vT_S|Coi8sF}N}dK&W+_@(#5gLlktoTJ zLnzM^)Pm|>{E3x^-@*)igAxsS&5nAk=2+d_p zip+uthCPoJDat^0wA0Vf%t*{|b|FO(^I$>klGsgeqD3)XN~9PX7cRjN&!aNy84Mxv z$T~a2y+SQyCMytIU^flR5h*~`PBJW#{Mh7*ggzdLP?#h`x_XgnE-4S6FlPHS>nn?d z+9Eq!Y85Fv6Rw!4xeid#$+D}$E|QT1K|Ugwsg}$CL9*~UQsxbD?Wzb%z9#O~Y=ub9 zbUmGr3FqUpRwGr|t8}mgPuM2KxCiFS!#*Im^@$4Mu*#cj&wt%H|%i zxO;G-h1LnFl3ds)E)m3>mjc5S2?X&vCnfUt%LeNaN4fMUk+fk(=yl|L(pcP8eC@;s zrd2)VW)M3K`+R`Rq%$)>VLD>t=@%ZAvYfdl3c_tm997tk=S?an#IqvK50TBI{=!Eb zX?{W@i3)53Tu6JsCO+Q|5)No1uJfRCIRZ$mF{M=Nvc8f<``26BiKH&zKd}@S<_&f} zXQ9Zi=zLyALh~c^HXgs=?X200Nshtt*52r$C)*sB=K}589$P@d7I#O+L8q(Y@EbPq z5tNU1A7#uw?7g}fy0m){K@1)q=qif`4mGk)!DvNpFNoIFe$7mj@Yok@M0#<-_ z-yE1-1xG&y@`7QHEOYLj)NBhrs8=kwx-&maMMNu;3!xDdizAPa>?*z@nFRaRM=TfH z9%q?_ESK@;CzR;l6{0eC&qv&YIYJ1h?}UbVhgFMwO3#G&*ldMGJaM1A2}zQ%J1iu) z&__ifcIZQ#@67C0{A6R^c#x%7&2kASiW_Nln=XaT42ycu4_reB(W0IW~HVBZ`y#gyLlGhWS`hvyeP-x`cH;zY8Jj zyH{V|QjAUbtL1RFgAv^#qVW{LG5ASTUaaxz8GM%G^IP*S?Pll|<x62K=$~P4c#G_#4X;ONCRO6}(LGPL2Im*BP7E z0TWVJx@H{|-XS3Au~~~HJK`1{?PMf*L%_p=j7T>m1KhkINyJ8o2Z}UEIr5^Uk31K! zn;SM5u|P7~dvo}bAt@`-+l*5gzZ?$PAfxqwQ}w!>l0YKOO<>GoILry-CXvM)WHAqc{;ab^d&K1Xq00Ffgt}yPL}`6&qC5x_78q3n zmQTT$@!^&h5bcOGFa1!eq--dx*pmeifff_#%_0{{1gfYHa942i$Pot@AR^SK9W16> zh+F0j#=hJy@H#)7OM~g@Hw<^yphP>CQHx|_C7nS6#F!Mlz7W!?6!4&K=J*^D1sWjI zLjErZ0^yK7lxD&_#E|+yq$P6!0HNvR%?)DoWg?)-3&^R^-}&u2b26LU`!;TO09b`UO`@^E!w%VI>K2KPUIEEWx6o?%ZT91inF$<)sZ|amo zvfhkf?v)c(=$Q7XiCNH?5%{nWJ4z2bg@mi711g9{Xw}M(862A#_O69)Wt^O*Tx(-m z7xPedku&`d{9k)P2N2FVWQmr||8W_erdh1zrf7gJon@fSKS!XVYfwh*8^v zO#shfyMS4h*u>!-tW=F!v6~Z$sC^Eyhc|FM+{sAIiI_p@wi5)w;Z6>(q|{2SEE z#9siaS(;AUP%1*- zb!(!bt|ZVLHAIab7c`Qo;I!9p*a~}u^~x(Uo#Db0C>-X5nNvo}u{%dE)isB}w*(Ku zR=|@pb73k;{+e+`6UFr|nc{`YzJO}sxbDmk&QK(TPAM4WU`%Dh!bceKoTV`~R&(XD z816h+H^}MiARz{?!jqVeWL}0fByrpbDapxQKCD=`~*Um#_fHk9A!A;$Twy3bi z@i*cY?E<^O1+Qd7P@Q-rb3``D^4vpAI?p0fB$z5_lb`=W!$;gGUcQVR;_L_?l+29d zLZ}KWX(DVm?;>nF;!50+RX8|xk6{KXJHJ`RStHC)Pn#H47u<95#(#l;I(fHgg^XZc zRUAgPAC9lq;0CMRN!A;cC5Y}s)5 zP6Nr_l)=iuu1qTavPBY8x7wMx6Ro&m4`$!ADZeg}!UX3Ecz#KR0gKECJF#TKYsg>` zuOaqttwsdZIQ%uK4}V3(kgzY%zQ%QqEU+QMe3IGbC);q#jz`Ih{3`2Jf2n|UtZC@# z*Lw`n(Ou9e5N-ki`W21gofBPLVZa(Hrr|WIup1|g9L?dcG^GF~i|+6XS+gzLm#Ht2 zcwdH!MEo+8b{69MI`b7mcw_c81i}m&(orZ@;ab+1^H}o1>H>c8CoJHXjUf{9D>CxA zmKgaezp5oyK^{{T#odzCG4~-|oHn~Fu7Y-<6&bZFUCXdtU{)gathKHpcLAzHcR2*f;MR;Y zhVvbvVPdXK7GD9El4>TT8&$g2LA(HG!m&Q79I|fBcIcA)?6}Zi)}GM33gcB;GBvrC zuuo$AWkw19fXE*Uc`$I=Mwkaij*B9*KK?{YZOV9OJVckzr&n!|U( zP~gy}Sil?zr-E3I5?o1laJ?puUcx8JoNGjmHHX@?yBq>T%hCxC5w0MX8J5oDvWo@J z07jk;CR(gFe00x_QM$*g9qb$|T)EM$m;{jv4x$*RHeyelI@TZD<%KiqqHl30AX!8Y z>=CX;-uuahO#CJtXO>M5?&(+rA9KcvunXP368c70_*LV`J5mwTVok zeck06l>}i(y@ezx!mMh3*=A|VBK0^@4MdMVUG*jM6T-ciBqDq2-MdVz?n*tJ$^b2A zGc2PgbjKUTJws|V5j$sdJa&tdi~fvnyD#LRr&#bEoenP}I;N^$pg5jhA zy1F7xVNi%DqOeh;YD0Qtp&1L!#mRv!dL7b`8M|K}z5^E&o_ohK7NlQ%QYdk#;`Wm$ zjaW%$65@DFHZAH4qWLGgoC*iru1r>tT4~a}BA^zEWny#a;snb(zT4gaCRff7{D8rr z!cz|`Lo=u(Zz**n>k3H@S`2UN?(kRP`Hz{Y_?1LkA+03!tMp(xL}zy{K|07D-dC({ z`P{4|+)OT9Z0)b0ta1VvBz=S&3~E*mW_`KO6kqEO--#*rP#PK1-mC6U9$6$TnETd1 zvNXDB!UvZ|j#j3$$-hdpZO#WFxN280&G0R#&%+v)p0cxsU<=X3lqs2yklMW$5<3GS zf)|91A&KEr=iQPl;QOLRfXpoZC|`j>D+C6FA_2<;Ezt(w5Dr72FgxVu%F}~Veyio9 zeX&&|q9)Y7*WnC;423i%*`wA>O5{DZpafyVQxGp2HS)#QoQm9lmhqnLfEF_!arfwu z(ZeyfBcyF2*5cq|A0if;Ip2hx2VWR+-khbMU1oMF@Wnu9ah0uxy~VAz`< z=RSH#>&gCk+A!&%>>TH;n6l@-Mx^u3PY2Tmxm$1ypBN^sOj&uNguMnf8zwzyqe1CLRpXv#&H(SV(ns7 z@yTozhcodZ)rn&=bI>DTZkpMr?g2I^$x#4uw`Y{`P$f(g0ARq&Eiwnml%T><^aK9 z5JLNWYVkP@|ER8xf5*$0NT?ZS)B?dJ6 zWw?ENVOlM`5~9HRVuB4JQaK)YRZwR0ih1#j#hsUU;|mhB7)l6kl9?8*8hBJB(h4%6 zfy^(A0Ym8bCVq;ezl4<_-v1dJ`LgTyy6M?L+F_Jnh6O7Th6R+`n>0tqCCGRux@qKX zaOIH$v04y52+ss7`oOfHO2zT_X6h4m&8EC@3{IP2x_%Szm2$<2k}$T$e0(O)=tw%p z@)_qt8WHotRY84;Jxw=(JOwj?TAUCx*!fP85VJ2&Tr`*yDTYg@L^LSx!YP%Xhm|Cl z;*i45B}q^i3X>*9#NbFh)k{S}}#`?qVM! zJ-<1;g>20-wb5{-JvUf5biMVR*K;f~Sos-7dUoC$)8waS)uE2I)#1lqe<(ROz-EuB z7Z2GHX%_y=!*9Rj8PC`iJoLN6+ncO8d>?)aIK*{X&?--GMUg<^K#E{sWz6X@2x{P( zPdpC?!j_ZMe*n>DWlFIv0GE*g)K~ecI^TBbj<;i(?SK1!w4Z5nH94m*)eXw7PgK3A z+VZ!555IP^Yb(BD!o(nZ2eBFl=F7N=Ih#v@<+%<&vD@w8`wmQqfbd~)h!AfT8&^+z zlW%|fnN8V;R@h}H2DRIw~a^*%Ois@sYwTJ5B>IOh!fvzas z&`ftOV*1mz?AmdIbxe?9svk4nZMZwJR2_%69JDX5z+Ha*;qX?(N@>>|!w&liug(wx zkk-=zuHgMDokjkPw!4{vNb2Ts=PNue=H5q6`R4F;gO2|ww((iB%ronD35x_j)0z4S zK?76LsZ-VRi2VA4y^vV)R^sIy*0hQv{7##eykDrph_w7xua!7?@TMZ^QoR zJ-*AU^6-{;P=kkpDS{i(?%AfM)N=prKqA7`d>?6?RMV3PWIRkpbm^cnKmy}F`61Bh zqNPYT(f9_kIFnDf$7OBBn|mV92pvZB46SBdP$<6qmPYaI!xy)&?m0$mefz~F)X;HElCcw~`{d#KZa93; zA@CN-;q4$iK9B($27i8IGQ;^aUEs;d&2N7T`-Tc4Vyr<~W(KssLF@|=^TsfTpCILu zjlfm%DAg$hv8DM!6;OxhDWI$rV}eA?SO<;FYLgPj1c-Gv0EM!ObFoY^`PI$gC)7!h zx4r5QKVf^(cQ6se9xN3+j{Cu;@GYy}F3kmdP`OWqb7XJUZ;(8#DUng0KmG_S-&EAl zV!}pi>p=)CGNvViMW&pGIh5kR6>3xl7kFpZpbkfa0~TRkbOqEVCr{Tm+J^*s}KcBl28C zhLv5a2Qr&tk&-6rfJg-nuWgzYkMsSt_ZV!ccO}jXL|Q@s8EmDW7Z5utY{K;((?%x> zo}P#?+Ow0$lM}JN<>CA7jira@AdrTEj=Fk|R`huB@Z^YKg~@$T zB#fV`@bi-+BGT68@V42r0moMp`3gLOU!qKK^%mubB6jaFn{=QZ>oJi7$|I54HkC{eo_0#^w~)b zCrxy*%scdz8O)NSC6r6{{fDckNfluyaTjA5=><7Vn8D6PIyH3>E zVL+b0YQct#@=Cz0X~>UkIP2W_k%c_$&7vk5v6{>2Dn4Pj{nW3gCDlxRvKDN&NGnYBFP+pfp^ zR^9KVf3)Y^$gHZ)b0V7ylFdfkh{%W=5%()@+;RG6X4K{LR=f)KE7Q54lP7ur< zU%=+7aBkXDIRY3a}v9!vWqXP^9^36CDM>B6;`w{&FD+g&vq4`e14{ zzwI*$bC_*_9=ClsEvpp*0vRar|Cq0Z10sOb?uiiQ=dgH**u+l6pasFqh>>&P5JAiY z{a{;WXYsMks1zK+MA?^7OsfeG-qR~(iuOEmBqh{ny|KF(!iqDRfkf)%cKU$M)*yuv z(N<%qj#URkkt&*gRSYS*K!SJuye@S~QCZdwrI~?Vl)?Ch=X&J>T(>)NdIl&r?&r*V zU(=;kRQkBzXnzPg07$Y@etxN3YW|ZMXqNiZr&7f_Wi@o3*+`=y3`gL0k6@+4^EEyO zR4#l4?(7Z;I77$I@{ML==zVe}6FQ>Xo%(bGuQ_y*NY*P@MG%tZ7D|}c{?6?1YH`Kb zY^@o}?0h(op1}b$`?_ZLs6X62$;2V9fyHpCn*`z<5?!f;BV8qeB=iWe8Lrfr!Mh^q z6J>)nCejAXOFYni;^sEiTja6H{1JCXxZQpP-$x0AHpwV8NRD2@@-6d(?AC)$3V9jc z;bz6yT_|yGj+4wF4m4EB3Qzu23>G;}>}vO<&v;eserRY%KSFGv#JjQzk@}~_X!jIk zTIGYq?#V=%NrMnWSpx)aE4za@#Irsj$nNL72I^R%9@)Kmg58moQeKN6r0|t3I1M;oJ=FC+UMGxzzU>cSEVK@_>M;x!)9gb z?hRwGl?qx8Wow^wcmD@dje9$S=I}&C59|lH5^Z6&f&FFoD3iXiT}d6?9IuIc5WN;) zRRoe&#t`*!F(jg9060WYPr+6F>Ihla^*XijZzWLJ(^772VIs@yjH!=sPYw8o)93bw+gHZIA1a^&W1eqahSmF(#eXAdA6`w5oN~leaL3|9%ufi+xY6O4-inshttuPe4w1 zZ=y73agj{+@U5HPzFtBs5yG5ahGmgayR!qH9ep5xl0duf6th~l2Arajd?fT1`N$Ec zQAdgR9SNXsmhg}q>dVN^ZHSC9vM=MwAQ#?U-Nh}uc>~7FdBdCPJ9~8ks{wO`tY}(r zFT^~-TpbA%LNKf0_yp`^l!)DIXl+yO`?QQL$<)@Hg1#uixSJn+y<*-UX=+prX>b8%p|=mi zB_1N(zLwMId0QCeGM)h|n%CR?^by*{cuWzKL(6Ru5_*(bax&XMq3FRGJmCQuM1d!8%W5M456G6=+t*G>!b38k3~`EJnG0K% zY}OMU$MEY~7z}Mknx3dK&iWhX9@!ul!@R^&e+?RpYvwhJ^`t&u3C*=%8N7lP;d3xx296H)(! zCuT4qoB#oatzupCAlX5`_>J`Zo1gvCI8Y8#@vrstOnQr1gu?Ex_2I>&3a|s=CCnTF zY*xE}hR0#H)(NOUl#GJ1mPS@a3_kY>G=OCWsR1k$A|oH_x`qRKK@QX;j`Gy!6ro`C zBJTi>uGMfre?@%L0gQ7c2VoxT_4tNpfV)nOW({Z03NQQB9?}5d2&Hkc;gdO=n5TT! zyAp$<6y*9bk6HM@CCyp5H1ZNJrZR;n75t>S#>X=HpBRUGL{mZ1LIaqoUnSO(djzRf z;0y`nLk8vgNdqm$c_ZGPKn$mGOTP{=DH-@J9h?X);HE#ci?zcX*S>APDSmg8PjTilrS^X_&!r+>3hcjA?OGabjLPm_ZV;l3P;#C6L2`7ShwLC z>0tHID5q^)HtSXzEW8HDW=rMjui2D~%V7Xa!0;$Hj+#-fewwM+xoI{KvBagNo-yyR zENZT3dv0P?Yu<>+_s}&(;lrIQH-X?i^K)w!Gwhc*D8%Srz{u*H`Wkcb;1^}6P z;}%4@cfv`##R?g3XT_3Yq>unehwvqF-j+3ltWoB}1r^LNBsoNyEBB4!OrJX811v83 z<$MS;kQ}HqZ1V^{g1LY}2_sq!7bIswo|*t zw4p3XPdYXI;QW)t@UxBiUTDBM3H zT)~e@3thjlE%25Sum9806|8aJIHVW;GVc;`rucpZNIkR1^lCk`G=gGSdjLGY zUOFRW4$;W9fD<=poe&Q;p`T^n4r>ZE9Dz<{7qb;%j4bLKQfamjc9$9AVknH1AF?08 zsc+9RH`p@@k}{SkZFivtW>bZ^y7jtGMn2A+?;qriU%CXVo}V&QB>f>yT7u4U1KZE315Uud_iL) zS8Q!ab<7RCN(Ewe+nxp^r7mVL>*%Z@4N4!_Jq2=hNbShu(e8q?Tvd}fR2(x-zB?!o z%Oa_2GOoaZOL=E9+?VHw5}<>kD~efRTylt5%5dcfey!V~4>7V3IlF}3wql-_qU!gl zO*$7uNTC!qL-$s+A7MEoR;^gJn=&w_>_&Im&q{29#n7OhkYX@Hh*g80W@dvSB_2Do zNnfPENnc1sEHEApAz(*)4gm@?vZ}TL%bDchSEQTrxEghQbeUE3iCZDHqJ{bQy{{Z0@8v&5vcEkbWta>mDqGW^*r{Mi_;LIi&Kr=Nz$X>KYjn zae|5lmWqMivmyiQZ1_{Ij4aIxbXo~`*wtyLmNVuI?y0u7nkSet&{Uqm5hJuOhP@&} zzH2191{(KM0&W6Vj=>S{))eO6z!5I9Kr{^1LhJSoywIqLpf?Em<(r?vOjGjOY}*Jn zj@WeD(uR3I0UOg&1bWY~2wt}Mi;;9`;+L_8LDn@^oOFMq+=%6w74FwAn3$f3s3$NX z6udf-&EG5|FM)HrMK*rZ=*%yw)aPQlx?DwH**aZ9RbmDlTF&9p*T&ghD2?%4=zD6x zU=8EU6j|JGO?Q7?*)Jy_rH5r{Y{v_ze$M>YATix>o1=JDWI*F-g0#oml;>>AiEi*t zoJlB+=m}=thjGLbE+k!@z_P2Ynt2@V+dWn3Lv~SNcMXwNEh~_yOeQu)mx6Uox=-br zaH$zs<7;VAoCV^(G9w2Pl4DTt63x(ZKyrfcC5Q>9~&952S1Q!tx++?u?f}-PvrOsQ~*AF8>FGdR;c#)@;}# zNTc!}VOBaZrroPz<)Zt8k*TXn2FZyGC*jeA$At5Npe@(W25hAc#$nCN_#5C+-5TefH$Lr&!1O*JmgaK{ut;~G?Z!+iwXqK2 z#R5z?>mm5GTTD^*!ircx(KZcetH$$qT+PEIA}bfG%$6bz#LUf_nu~L8nKQ@?MK0?9 ztT0jkq03$kLC6buZMMI2s@^!(M71Z|hDuKxD3+sKYiKI&gQmDqPV_WDUBp$ewfPM# zK|ad!(iv|n!p$%@U~{p35Zv=vsI5%x3P(XV_c`l@*%l6SjcYNibL9}bSs3D}xRPsJ zi!yHUGN4+D@q&hR(95``9T-|}Y=&uoJxXcDBkflb&VTUR-)PwV>Dxbl``usu>{ma3 zuRV;>y0>1A=8$Vde-BVcd4>ESQ{-mX$PUpGIkDl$2>5Wc%Jl-DG&>-amoUbu3x4!` zT_VAg@RULCyh^EZ9L#q&=%sR%QhMWRt2XIDFERkC_tT&UsYDGvb+K_sgt19>M9*C6 z4Opn{nP(t_2Bd3NU4Vtgxe&3U&IT3kKIB-9OC)K=#hn4I*K#$-xQ*8p0vWF%iVBq3 z3-mgE0@TZ%OF5XYtHp>3+hraAA7IwmZZ*7=K3V2*h0D=FsdMK{%)IjxtjE+|(7MXda&8p@A5VgEiK@NY^^M z7ZbpRO^g=C9S_N}s_8l1SWI5O6Ih|I-jHsBya#bjGy*~Y02FK;n`xTAx*0fUF%_*M zfL=&AX?LaV=!NCx2T!` z8@88_0S(GCPz%92h5FS9X^%A2Pl8xH)vX+!;F$(!r7v)>f^O#xET;_@wH#gon9w3r zK?x-i7a@haNxOj*K$R+)!zJs=NK>&K4TJ+h3Q8?b?nio*kgnt$t1VQ1xKuhdT%K1X zH%Q~CA2^5FBuj?({d-%t(MHDq%`e8mt#GqL`WED0VZIbw1zE6%m!?4yIpExC_7P}Y zd*moG$?#0B9LDBD02!wq&< z$9-mH+`HC2Y)lZ}Tm>X%t>=Waubi8k)wwDq^jv|>slDBjw_BvAGVvB`iQYgC!@zwm zg!`-6TGXI$ZKH%zDpo+GFRJ0dbnJ<-)i~%@=#FoR_kj@egqEh_)f(>y^^P2O{zDBl z*i2LUYF1%NqeL$)#XXg{2isL3{U&V+Dv}x%@Ltf_biHzuN^yrS-$;IEx(G(}=6DRx zq3;*Anulc6zR@*^KCStg&pdbwhA$KQ44XJ zuE?81j}STgN039t5o&Q29%rDlQX--sWh`n z5eF~_3*R!g_7o(S^FWkyFl&JsC;_kVbsyaA1wO9jGI^NETkO*wllz!R^_fiK$urF3 z>kyhxQJFyS6U*B>CshUI4@bQ{6xc5-A-)&z3UifHBJqh`x#e_tIbSQ{@PQd`9oip; z+{ue3DTc^pOXE_{Q3X0~-J>vsZ-!dMK$P!oiTBh?c=JFtZ;zBut)Uf8p<@W_5M9p= z_%IV)$tJ2^1_G_fkj2J33#vi&KyJNGpeE~Lohm(rf-{X5Gdg|+*tN!+14a=ya%kUE z!G@;Dk3~TiY6ekT7+$BAr{q13IoF0)k#{NtUdZ$Z#Nx`*+>0VUE%bb|X`qwi(WcAX zyvB_+{D|DHN$5?Aquo{WIL1`WjH1CeJ#I8@LzagQBW7)O z%azvXK^^{*Y`H{O4Ra$fTZ6f38#v@>ZTjB89iRt!Wyz~oy^L#Du%0c`-3PKE z(W6BHM&}%i+?bC8YxjPH*zxQ>fU}rpEr8xO9Ghe*ur~m3VT@G?;&Yo`!5+UHZDgy! z_vIoF!be810}YW2aO^CX;Lu~6eUtzeGsLrMI2Qk#DCAS2r3Qr$m>6SHX{HID z=T4XYOmeJ#E;Ym~L=7>M8BS7KP*9ZiUI3LEY1jsYGMUdWcF#op9!_AERC0h~by^ut z{Q1TJ{nQe`%q#)i`ptJAjCarGxQNDMUU*I~NB-jbP!D_S4D@ViFZ^+TJH$;pk8+`J zS5KgAES@&h2Xv>S~q zl}hG7?TnTc*+GaZJO)LPju^ciZwOS$BjL5gZFz=Q8e7Y&tKsO3aqE;^G_dk&1l?(Bgv+S0>}U;GzV$peS&y5N zQM^RJGUMi4=xUMK3<0jsm`IPi5A3o}5Vw@U9MWWDl@WJi2wlB_J5)h7H(M5bY-x&D zc#}4+M#6cb+$~iA@|cb}neRN%J@sJfHVHR?u;|zp9f(fjAz2_fYd0s`Dd@7%1^|zG zWnL{XMyn!K_86g19z&Lur>@NhBVP%}R zhax#dTS#y+=Wjl9@ae1pG9>xqi zT2q;>le$ID7wf52Vah8&V3tEhB3zooslN`q0yjiViNSE-ezh_V0>~oIm^jPJne2oU zka_oscc*&gS`Pyf5hHS0T?_fQ@a(|k6zrG+RGf|}ELFO>K(;hrQR4tq$dSv^1KqK<)4b&(70GMDPN&*+S?6=5Iqfzo4 zdk09)K-{4SWf!6v2%qe8m7XC`>%~3vGRV~J)?Um`r6e}Kevzd^sbyT^Yr4q#s+s6+S%G&^FvlV<6BMS zQIgT6i+e{)y+)L=JHfSo48Ka;7s`Q5Ox`VK>MOsDv9*f8u+e*{Hy_dN?%J>X0()U zv5}Xf%iX{5#rP7}*Gf#Lh)uA2wov(Z0m~WA&ZR8pXuY^nE!cN5DM!ov>hgj*O9IpV zb0)$FZl+y`k;A=o2R_0Go;|DSZEgN6L-)k}5k&>(8g>BUHCp==U}Lc*NQ7;Agu1qI z5v_7qeGARAD|D1X>vBU8P)>YGk>Z9?=UanOQpTUIlP{NQ9pE*z(dHI84mG;0XnXX; z65I6zs#ndh$(>Vl#f^q4UC2+(`{wq9kJrt@Eom!vA7Qa_ZPo^_Q(V^omvD{mH5UV? zoUBf0m(U^JSR+IVh2!ka01XWACy?RPf zZ7+KgTOyKh^{nz{oHFXLSbQ^xHeB7)P-8yRP-iGM*{)csFMQ)8v;KCY?jM6ix6WtyyozuPQNff6tBuHT>}7cKzPLkjbc%A&t5^&Jk%xEN_48)V*o?lJ5plbK^$l6pFV)w|H_jQZP~XF&-T zvymLc!S(LR)n&3n1mQYV!l8-F!`HjeK?BZxo&kWj()D* zd{Jsel9ygdnxg(Hc&G^dcr6dtVp=i3n2K8xykR$r0?f!~yDXlBhXi zkzk=VcA@%P1QnZIc2&WXh%PYZ#vT2)&adE!Qr935^P(HR>I5T#TvzXKA?||1*8?;r z)v!2XP~VRJ@eZI1%8(iNRCuckj@axs&k$X&;~SU(SzpH#u*^95_}S45-Yeb%?nEOV zZgw_D1dh&W4*8B6N*SUlbPN(F{Pu5MGbNkuKm^ksbac-{ls}0Fs5^)a&{8ghMuwQ7 z&XcLQxZaG`LPO*JoovJe>rF5NxZccfAAze6@i|BxeF>|0sL&>b$CkLq5-qfbGLKds zoESo;V>X3swU7}lG2*iC&{IR;52-h%@LrjNOK!|#U4#bo>=REb5JM4P6W)kt7fgM- z-RM)8>Z}^AHrFZ)QP_w!$d>)6Fix9khb_z^d$=*v%PhP>7bHO)czh9GkDY#_oPC7AgdVz8hT03*Dx zKD&Bfq~_?U#CA5O7Q*PP!E@j~6bYTSW?lKUMX7J&Q4S*I-=*+lo+=Q6X1L$ZjuO|2 zZh$_hh8Y>0IG|0pyGW&?j8Fy;>9D+gq#ayVw|@goZdb00Gb_?SRl@-pfq1E5bM-Cq zc-?D%l=3`xfYSMpIAwunz2&xCi0X2^L5eY@%-E{%09j9_(x4Xo6I9Sv?=HV3uB+R> z!h;-_`0!wmXsB`(2f%Jn? zw1=98G(eI@kdQWTIUE~yl}d}j^;+3yGeelLz|Cti78kk0u=*e# zLM+jCf?ynIjBO%quQ#B(waKo8f)3Kyo)|)0upH8oSo<1Ktqo-H#V;EvAc_%&Lo8@b zzn3uoK}s0GG@0#t>K3EcVA_o`!EdTNsq$POH6=Cm^0=VOTXBgvw|t(JMi*wbe*?(zvU06LGz6$+wtub*)d+ zA+Te}4cK%*kaGoEpCy^w2KqPvRU-uv#g#)fF8h}vMvxK5W>KPpOq&opA!Zw0k4-`Z zmkkt<%vK0kMvNo1tS<=sCBU5u&~|z%3dYW^kC{2%^{Sa5t~Erwp_}sjDxY<&aRO!= zmv}616Hlej9>9)=L0ExN=UNwxa)Y+C3=dyVgG$OsUX64@bjv;}p^9)WCm zLl+)#H6ZmugK~1QfrFGPV>}G1?7$9yhgw@zKaV%=o+-9&|kB6)<#^L ztAR(=L*%)u78@87ZXbz2d4^}I;aL{Dj;t;eeAAf%+nAw&1zc-=01yCB^q|q4pdy$T2uRrC;Eh} z4vqj433Sco0L*F=6yLM649&nFrRyRrYtl4{;@KX%1>l1eoo$~LP7gD!MZ#FRUECmtlDC{#H=-+xOl>te|%BkN^DR?|yvrn5 z?ElaHuRrJvakjMWO{4ZMX#&fer}$xZq!+&=HaZ{nr@9*M_7t z?Mc30?Z3B2{n`HO!fZLxHvnF74EXhlAvATBXFL#whP2u{Qj)%nkARNhBd#AF+2^2( zq_d!=W|E|tpQ6@B`>)Ib193G9Kkk5bp|I&8kHkb2zPFU`Ta4^q<;zi8putNp4`l_P z?0vJkJl`;J-lzG91z#yQ((~(+QXXMC!-uHevF>}EjvMzzwa;2)VDsTunvrq zmXKCBK5`f}844I(*H-gVQ`3XVKNIqE5a8_TJDjBsf`7M8o@P-R=3KuFDe;fw%!cnu z9n=TqKwG6(kAY$04l|dYjxa}ILsAQ=gT05`YUpsQ*8MeNtke&jw4X#kN;>JA{ORj9 zTE?NS`Et8ANZwM5+~?ndOQCiKN{QXC#kUx#_DMM#{Yr1-md^nD+=#DSp^rjX$;b?M zDp1$>$m%ZS#yXF`fGvF+eAwzrGSZK5ka(P40W;9%1Ws|8y9%0;z({4t_4P|1Qz)_Z zUqYm38@C3}fe=5d=Qy3N$0V6@jE{}*^LY?Tl{mgbxz6-YL3JLdHuxU*J1LR!>BBC0 zgSy9F)Bb+5Uhk_*s^Kx^*Mt3eNIgDTt7hC?JNa~Oo+ddwlZ8D7e%;w175D<@BiRY` zy!{2OAW(O8!39)>n;ZN)nItzp7y2c-AvP;n5y`NJAGboT#+R0GG}P&tq?3AmLz-hoLhIEN^sJkM3$HYcrJsfnMomX4AIDN# z#mv{(*4wd?lKN+i(w&R0FY3tMxJfPCc++M`2VA+o{w3yIK3dRhWu&Y=q@?;m0`6>{`8fF=iI<+2 zkN1BD9OuQz5>!FI2t!29H1$O~O{^vDzojag6m#9KM}iAs0``-zOT7T@|GfW{be7^Q zO-3*>vm2VfxMTig_RABTY8^e#R>C%oG`amv4o+Rm+hWQ4qisf_comW_%yR}#OD)%n z6IDXWmQDfU8d36cXYsqhbwCYa{%Wrhb}}5kKsz%cyw#E;MS<@jshpggTmviM$Q~D|2Fur zTV~q7^3Z0oM8zL+QzgEX+LQg0-Y5n`Xpr@%29|7H+81HY{zcxDyF1}Z^3*wfg)8_C zjeZ82_#LfXNq$!z%ze*00#%~(JPI0urA~Q+#C>*%#=JyqDvr}8c^11^w+T5xY(O)n zBtvqJu)goVks4oN=FdCM&jGj6GHs@WcM5OVQuoxU&zKp*5BpC@W4);&8S+&k?J|&@ zz^Kk;$Ynwc??A$`nMgg!e@H?Gn|^@xz{%?@pKrmr$8&whnW^v8vUMQ&XJNHLh}uE6 zPBWgaI)DXfa>gaz<+=0Ad&}HCU)P=H3Cs}AQ)nK;ha1yo6)cu{1p+EkG>X2U=up8^ zC|)9jrrp&;y9z}K9$kN@PXc4& zT#VGl5hDoPsTL-5NSf)MO{sZ0xa9b%y5?@a>vAjAkS+$_z=hI5*FuS-qKVz+_t+h{ z-CrC%Werl*$B+#nn-|7xVyo$SZ*0oz63vrE=Zna92#fVH!SMWJ7yjh0XfizE}9zo z3qDq*WUDG5iTV>D8Pc>72Lk1WeX$J>m$o@7S9Py>6cDSVFI-<0D$mHi^LnQvpmvLD@!$qG{w)BGe_)pGK*_vxv9easGOAUPV?$ja2F zbZ3T3iQpqSnq+EcWhUwfaE~eQAt-mI(;6H~xq#CezX7+t7tx0b9>oVRXheRs?$+`> zB?Tc_S*fhhhXenGg(V}twkp$*?CBe?8j_nG#lsi-CvV#Gp?a?GWIS@>L1S17wM`}f zs&6Ls4Ms-;2MBK-nMvk&2}c;%IkVI`@y@S8@9RXRRP<9lU;>oOP^=oO&gQ`n@J8vY zG%-WgC=NIh8RbrXNOpMA0H?H)L?su59Eqy;Bl#iKwnkJjLnAjkaoXlXDeIzTWb5m@2kDp_j4i_%abkf zMUq4XLEqGTkj$7i5OOVdLrp3K8Pi?=)gX759w2K7OAwqW84=I+XW)yxqxd4bVB2KJ zbyVjgnutQ)^Oq8@;%v~C>nJ6!BBI9?Zl8z(SL5dyfh8du01vE+j)B75|hG{wD~UUDx0|4mlPMwB zqEumXHynX;q&dbJw9}nsZiX~-2m7?WawSP&s2@0=E>%qPHwQe@|0@m<1wY^4E12IB zE@)x+!l8fzU2M z+gZ*QUF-9?`JCMg$e{U-;olsHME@ykLd7;jC&y!sx?t@&BW;g0{4$8*hU1}!?~b~Y znd99$x$)F`g^aVMF=TgCBRb{!Z{VHkLL#Ff>?+G>dLV(ysR|UkX``TNBMz`#QxW@3 ztrL;k?AO@G*>BV`v1K75HojRc!$Oa;Q4Z%)BZIdj7`XGEdq2{=9Z~jkM^b6eZJQ%3 ztKYu+jtSuZn|IqitaktR-FJ51z1=P!?Vit(hJE*ZQ!Q^^JLo;2-P0}7O5MD^d8y5j zm$2$r0P-sx{Qx}4eLlg;&2T9(ZP@Fex_MbG^wU?%YWJZ@ix%a}QXU&LkPmixO zAz@TU>ZCTpQu)nmd3+v#-G}93Zkyn>{j!{EJpDAR8YoZb1Bzg9V|jxZ5G0wgA49ox zwJL!`aUM>kK$pS(`R=*Ql4SJ)wRhsV($`SUS zAV>RMm6My-w=X%)o!A2J0E;zJtBY83i_Je7=q^Ntr<>OsdFqak78+G93O%ZOYlrNZ zH4`n_QRi@ypXU+cC%Mt|wxZ04RAvVGlTaNb1N5jVjGvgz%f3>9lfPn#UsO(>)W3VhyN*%K9{f zbrE~sOrf`XDu)WSch7}Tp;RA$u+TmINab&t502JXS0?Q`KIv#Q`a!yT>>7tF^OOVi z-6IYb>+_9N>ac?STa9*4-A66eC&oTR@-x}k0GM|f^_0%A=Otd~DNGInVzVRZBFIip z!}~NK5hWVHz+@_GU`x$@7~djU%ytL_$^HX+c2AK_vzpr3F?RN}L}*Us4cihKGJ9kk zO1F0(-n<5<>8b+O4?Y0CCc(cT1}#d>h8)Es4O+O|J=b(*)e7TMt}Cl>K46l}g@$zx zY>;FNIs>v=gB=U#=JoEwhe$_;NjDLNLDZvjOdUj~G9b&R< zk|-#GFCg3s6XC*)w42TPhT%Kh%SNezc2?#P%rS|V+nerSX2j9QA!mqbzs7_LViT{Acw zIybUKb*2gHaAx@p1;mInJX5(jlW}4gXQ1f95h0DKawMl|1G!?Tn>KV#!7de1yHW&L zBtkb-|0g-WDHYgTRF znCgUe6goY4E27i@tOGjg!@=$;q}|(>&>>_ISq{xeQQ$R#5DnFofc9d0C_&9-38T=M z0(!a!khB%dZL@|&VO%yb;xQeO)ez{OjDERM0SF1h4lr5d8B+6(Rewu{CD}zGo=K_d z5Fz73%Mw&4(s@>nR=cN4GD=|J4JJG5Rl=WOLvS)Ij=$iF$z~TJ7EOhTLbTjgn}?{N z8{8@`qk3%=RkIO#fkX%Svkk;@5Q32Z_)jfGI%LD)yndAsuZj4e{h5uAu=XIp?a?hm zDtt)sSmRkCQPVld752JP%Eb)f1%;%hy({v>?Mvv-!wmK|NXW6s0h3kB8Re*nL!(n= z*&IS1OgV!&eXuDv>rjqYi5|3PXcfrHmIj-$kSHuNs|p|oW0D<6hPINmjgD=y;PFbPO!e>I?cV&RTIn! zvFdX*lI#nWMi|I&WX=&HX9^);nZ;@V92y8v^JFT`S$q*G-dt4ma9_r2vG(W@e%{o+ zfSOq?7<1mA_G$^<2bN#zEm{v{4k2j(DxPTama!{3XZ%e&m^!7p-F)Us4Mnmrhx2CY zlP*vD(uBLSjmwa-oM;s1+_k7+V4aN~LYbI??ac%`r*Npc?Y^f7t(_J3^FCgpoz0W?X;o zKHNRm+kuqW9hXJ7FQJDjS!*tv^+N}U@}v3=S`94C<+OoZs1q%M#2e8ll@@O)xsU3e z88K?QxyX)E(^axm8N>W&xR~dJIbM~^#OkoNUSKOPR>^GD-VcLvaxPu1Do`1dk&%*K z98l~n+GyLI#jSx*uBSNGYOU(6&DGjwH~rek3+xS~Zi!gk;5?RpBCs9oIbL2xAr zf(?^5HnqE)#K$vk8=G=F1lNe6Ie(ILo#`;L0bPaWC(ItZ%}i%vkg}mZjO);uh<$SO zXvKnmiM>QltKYSF+;DwGgpBj&3V4Px1EDY(*R6(^ssYU&1~+2osP*1I z$^ic9?VlSTz~w)_{j2xh`I+q6br1f2lWG8?zJc8XSR&2TbZJt1kJdG4{K41ls_KtsQxOP!CrB#1nw1OCY#bFc ztTwzDs?B*cp7lq|aryr^ySC;wjw}6Fu%)Vf*ovW)UsJVP17JW7aTyqcfkeKfAaYE? zpa3lZ$}2uuw(MOcQLSy+Ygw{n(^l*#PAYO7Cyrw|RUYzH1^tEnCHtM8!G&|a9x7Wh zCAv>{&*{_Mr_ZfVs|o9P6;1(zBEFX$k_Gh~{8m5=wGtj#>Uee7L-b=`3)s4=ZgMSP z(1^20mb{LyLzGb3#rvpLR(+iUFiCJ|VOJ}e56C810}aJ%<}iZwq8-7y>eiL~C1pkT zBIJqeR|KL&IaA2%2X!!1HJw1wI*tX5%*+Bt#^E)bHLr;+o<-SffS?=U8j1gGxZoQI z71&VY^cr?ZptW-kMyHxGx_ga^S5?FX?$*ICU}W$M7@1%oTW`lVz_YTW7l>c701+XD z%~n!Ar?g!2S*hOCqI}qD))X5;Ct#wak1DI%;ON)FMyu^e1lD}lncGpPP2`alct2H zyj$AO-L19aH$8AHh?KIP4I+wx8S%1I*TP$}Hwkr#Xo2t~681_Sk8KcY z`v@xli4(a+*nwgbAmEvZ za)K8^4HQEMK5MEJHA`6LYH)DCWIJewhvFoXw(~P+63lw4VFq&AzN)Og&B!S$r70JY#EN^2F4B-pteLx7zZXxtyzvvie<6s zR^m7HLQfhAr3n;~9G*+nzYdfz5^d+!X>C(j)H`}!t}Ul+zY^9uw6hulP}>fNnFbeM zZ0I106w0~9bdXslM8>lyev^-)np;dp zCN~6{e!x8TE1)9Ot}3w#tX6?ZC%}D>poFLy7a_rl-cVY*qyFooH53;PEiPIKv^W}pcOXhMUTcC)W}T6H)ei;-u30IQP|3Bpg)hpUau4|ZT1wk7sOg9%oOnmVEs z;M`%8pA>5cEL2gZo#8ccvQ*|zeS0{lm|qt1B6Gm<<&qGn_@Y`2ofM*ZV+Yql=!Tg) zNbJ31kZnuTF50%;tF_v;ZQHAD+qP{Rt8Lr1ZCk6&+k3z7x!-xux8vS8zt5R7M$A#8 zMpVpr#*gNRAH>o8~m>g>0%6GU@nFUkI#43*Yo1R`$l8wo0Bm-t1%3L=@2r7zYdv`~n& z;+te8q1!72kn~vy(~fEW<)>q}!%1cLk#`o>TzeVYKsQ$cycI(}nos&&3Sgl)Ht0U{ zQc}i!O#q+qK72EMCZStU>&tx}c+N-6{73FpCfHhIwtbHUK&>&F?zY(`9T-Dhth^!)r zAcaf!QXrrcs;VqnyV#Taf*l;MM zryv-t@(oMkCz$h~A)EQ|pv$};1qTiN6yU8L6KdZg%bxHatEXtd&lkPH@>o9dqbjCh zT8pYnr|?(nvgO`EsF;57s);ZXYe^2)(N<&S<3P%b-jp|XL)YyUb!IL6iN44bNQZm=^FSUND)X}A;uGq4doDxbdR`I4GA06 zIh=IpVRPfstsgs9tnb)J8sAVjBmXsJ)@Q+PM!!-mnj&0u;VS?^u%r>7_2Q+gmU6d- zFbPqoKgX2t@IIzyqQo@f0?I2I}v1FStD${Wt?ZY*wYOX==Xe_b3E29+7c_F$T& znWu&rO7GV z@>E#DN)vpoc`Y3F6y_${M2c>45ZVpAiX$h60FhFM&qZC3`;G4eoRS?yEz{55J?TkZ zKaH)-Ad&b^Wy6%rTD8AI*NHO(x^T^(5Zz0Mo0kzfB-Xw?D=53k3o=6)!obMf@vI9{ zlwFwH=C&KDc-JHY^mDDKzsq!VR3ny9&VIS3Od-h7Hp&1uml&zZ312b{NOVWLYETR6 z+Ju7bnL!}>47^IX*m-tdnzJHb{o<#&wIi0yLg-@2eREBjjCVrJc3ddz8;_6dM$CwSs;jgRZ+Qk zzd5WF>M#i*PYg;jKo0bnA^#XdAS#^a0jmu{BBMFTl*hDjVAs?aX@LLAaKy-o3Dt@v z0y|JSR`QZ<#cNldAOI#qTR6l)aSM2ryd8Hu3Dt}}ThHNlCnG&G!ecwT)Dssjd-SCRoSKj4|fk^lvo3aVE)^ zGKe}!BOvTp0gO5g-%o#1<+t~`fD5n(`N9$mpr&W7z(zh8!Y_aUEsCS~{MubpWaP#R zqDS0I9pN=5??4ry@ zM8z@-vqf?Nj|zSGoiT(?c(`}fa)e}35*A|;4UCzldm0Hn_cUq%_q?P-fl%~rnm26^ zTN(aE?_}jZNGqyg$InEE6)Bi1k3VX8zJd2dGd-8?eqv^!2&&k1oj_8i)q+8}AXFYb z+?~{7$WF3E$?sP+C_O9Ipg1d1m(0ESapQ#0)lHiz0#h!qW-H3pYAF>RTMa2|ku;|= zC^@#C$ivaNfOUSLJGoMsVptLN>|IfkI-b)42=Q0-gsd4YRQ#f61MrPLRlBZhXHamh z@DZDw^~H*XJcQb?z3gw&B=YC;UgYrvBt_4Qxnm2l;$&lM%-d+ zycQL~-3g9!$Xysbi%M&x+Z0t)3rq#`HJ3ve7S_(o8lrK6GPuKo2Ae6j(v30nnJ1Ql zh%-#h<#x|33CgpZM0+6-!_BiU)h>PyG6)$?!rIawTFV6^2L1wBYJtjR3oeu$5HWi^ z3OC^c9nk5O)c4lP*iWeNN9qz z9CLP&$Sdmj&Jo(=i9dDbriH4k=Axn}`(0htxYW4P^^fLreu;eRECE)ne!ohjaBu{H zmi-BBAt(jX#I%&q{+g_mgty+@m^mY_xcE@@ya@q!z=BIlH>c6*nqlCe5C$a^wsu*| zQPSx&ZB)Hj;!tP*r*}6iRYN=l@L#NhKd?njsrcX}U%=X|-^CYj2KAl9rW;9gf2YR3yW6TEH_ao#H&XyV8#3 zbd{oQ)H`4tu1Zg$R!sUK!s_6^Z4pII@L8K&59+IG0;BJT?p=By|pRHSH-Ysa?us z*B7UjSG3kQlfI}?S0@DfHJ@W>Z-qWx!(F9R_v7bgO6*hU53MM#_#ZH%AmTfIT#~Ic+r!5$pVdj6S4He7$QR%wfu3JzWVfamFda>6CQ40X-wVO z`uL^tMMlbs5oR~ULSAz%?I0$qqUHklL^6|jF!~jk6R-rn&s7JQ`Q>uhBC+F4;m~O` z;0t?%jQSa1zaJsGsqH>qSR#yY+9&-j#Y?`VuY>~oHXk$G&Um&9>1x(dY`W~!fG;BQ z&$JVzari^zK#b!`(kI7}w5d>3H_-{Csh~(edawskqW}(62sM*ym2tmr1L*zIE z14Kqkjc@W*3Eq;_zf=ZS$dXu$=1AKLGo4tDGN@(KESg+;U=f9Rn^ zz4dFoJ(Ydv*|cn4U#^$C_NR#Mk8|@cYnSq@UD_N!DBwkbfvYMEtj~_mvi-0=*4;jl zemd&MesT~&kn&qIO#gb2xyMuFyhG2Ibk=oqe1Yn+g;d|UPw!NRoqxUSwWV`2Vv0=s zp(b9;+&pv=)MwgEcX=&!vB)v2yOINw@82omX+rlSnkGk{GE14C_1J--@=C(|T7t%EzUEP;lnOAZ zbsC=TPLmTEN-cmaxM0qY%@PDyXpAX+&4dnPjRJ^7zKHVK=fzLTkd=xh`qa^pbIV#q zBOtQmQ|FlOjpmp_WUFhvs6#J^@t2)CevdUDqv6NeB;n~eb_`tq(tGH2@RY<(5Nrm( zt9xk9)2mU2aU&chGNGW^7L!V#K9Tm7v|#0nQcCiODs8l2*Olw2w?~eBQ#{FMbET8Z zfas4v-XE5gIos%;sejZW?Kifght?V3{_w6y=wA1iW-+XY8q^R%-zp*0J7+|r2CdLH>p0GzmIIKVsHEK&a}6+ogg%o zXR?mr%x_x1o30F>jTFI4bu#YhJ1`d z2b|EY@wye?*uQ^VT`B9jQ~w~tC*=Y98Y!OzxD2_?0sq+WRzLYf*ltbMq1KDIMI3p1 zAZixetqIWfwqX{dNqC+u)alU)>ik^Xa-`#kjFF4w%zx<(x%$0(21(C0B1*~b3ukAZ z?WbXj@%3Wf;PG-$UOwy16b0rntEX4Z%8orG`sUGT_U=Rz9UEZT;i(K2{;GJ6**MlD zdwC;UQGuv4*=Jtfu}d|U&+Y7qd&&A#glKOQU3FiLi-D-;JIOZb-GCJZ9uPMI0b>pV zL0BMX>eCJG-P*mKpw>-Z{rfO>)4bc?}Dv9Sf%V_E8#Fg@J@ni(h%@ zIDaatcn+}M*(vX1PAW^_c67fLj`vtS1W!8*zaO&yoY&hNLTphWa=8OgP&21ti*p~W zXyHNCXmz7B!Tr26roMi98QYkwVsyM@&TDU7i_B^jEm+%({IykrB{8H)U?^iu$4*9^ z(APLGiWq)zqDPdncTK{k^yL1HQCd`YKJfILa7WdPzHKFh^q zIXO2?P7J_kRZ3!BjyM+i4im&z4)i!Qx%G&<#-A0gO(v#MQ>a?dH9P%$f=1!jIgk3l zT})VjfgO(0IIy*K93HM)ZI~D+&Jb&B;~8qu&x2Tk4NLnF4D5l5$aOKZ+HYbrkv-An z@wjxczS<1>em8Vcz6xK@Ys{+Aw3lP-J(11zc396keRWlPDwzI(*y$aB+r__cqukF)_EGQW72tm2`6#`+8nq~YVxwm% z3zA~8Y-Bn;52EAd47~3S>&nG4@PdyywyM(}V&lpE_PFXZ)z$md)!#fVayp#vjR0G{_-uctyKj%C|(#B;{|$^P1(y1w}K zE4iz-3i5CkC2{C(?WKrZDfp*cLdMS=6-hYcK!KTl!&Oyxro0_S=bIu_2boqQRaJt_ z%#Sdb-ptHf8){LLwSxuv&V}5lp%K}IhP+U~d2D*!uz2b0)a4V@&}R>K_mnz1A}r&? zUsmiI+TERCkyJaqSw~GO{{MJ6D zK?-Np=4R|x(RkC9C~gK;Li3+=#VI#yy@wwG9|0Xax(Aw>ek_#rodU((!1d>k*}oQY zeW!n8EXtc3T}?7$>d(rMRN-A-A1~(jg}iF&8%>>)`cnVw25lsaM1M115je`~5}X zwQka7Pk9H2=9sg3sjS=gX&=i4a6q7i9Ge-d)7J*X0hCd&MPT*kmkWr|DUcT{qF&1*KGhSQ`*&g!6%j~FkQ;Q$Vj{i0n(ldX%fv$#_?2e~>Tc(3=jby@JPUpa z3R&49#|VQ6BXk$l=Ni1IAbzIhCw}MyQ4_q`g=of$K0AvFh(=hmi-*NZ`G+^ymqC08 zfW7HMPLtuUO+wNxY}8zqjzfqq-R!LeYluFCTiikYl*GMC%01`fHQGzIQ`_uwfr%W% z%hG03pysrkUr3)~IQ^vpXO*Hw(=d1XTcCVIBG31GpMNx{K9*KQCE{zVW6(hl8H5r~ zCx>gLqGW(7{pj9m{)h@ZFc-D9Nwu#yj9La4i~$?gbfqqz7}e<+6*44N`A(_Ghsx0f z#KtaiJ=hkD)EiIgQ%b##iJf%YQ?yM5YIAzD_Z4O)Dp~t$Ckub+DT>tv)_c3k9~RH7 zm~#I&1F_>4)Gyf-P2hOPILIhR>>{qVB_WRC=qbKhr26dfqGNUm`_%@nSY5q&N_d9s z(W2|=s|g;q1aVbcOYLK>AMa4`EGWx;?#n}EakBU9Zp<|3?}Qy|z09JV<<`k|&AmhA zSy0x_nWN=)G~?%vW~A&LKhjcyOuo8XnA6qSnjpeW7>(i^olBndw`eqvWF zj96!QxV})J%H2#$=!UtK5&J6zV762yf;5QMi^fG*3vbr0G-BupI{uQ068X)fS&NQyHD#fbBg2-x5|j`2T4?1C<7(CIlh6a1Gmmyl;z)ks zKKyzu0OnYI@6KuBd_8~E#jB@Afw@4htb$tt^5!v$ra{DoKNaN0+IcfJvo|w32|4H* z=p1@X$bXs$uEhYgqR#T#LJCprfCcHZQ(~_u#VN9_M*!r?v^M5@x31s(Dy=ypucSV$ zSsN0kz#tl?=-)amdKQ#4H=)cVVQv52EFk$=6@TA_xmW72ZYif`@z3_TL4wJ-^O|AV3?zU4?YUC2W{b9VQKLgdL)6kxY6 zQBq4mkzai8>Y&3c2W4R9rq*gks+I8<<&iVKU2|3UFEjWeevzm9=kuUD_TnhX>LPSSd+C8X)w8O@Px}CxAcW z01{@B7Z@Qj0p(+k;I{RN!`=|9G7VZ90;jN*V_{u~Bk{Fh=zGr)8J)Q!{al?NACcO5 z2~+uW9{$L?U_QyTP+?nuQ8rIV&6}L%nPmz_XCIG-X2A%0RmGY}beY}=p*@{okoixy zvk*V|Ltxh11j$PFbc_?K{68WkCM$lJVM5g{AyKz9*cjJSJQ&wEl-gK_63yXuXJWdXS&-klW> z^`ZHG88IUGcHPw(U>(=6;1zm?X~@RuQ!b`VvxO=W{0SNTNf~B-8LWX{w}Hni7jh<` z(gq&wfM-7+zvlAH(t=y{Lh2H;ErD{CsmOuv#_bIaUsUSGaxwBC>^MZK2}V}d+v>JvfxOtALN=jTjLgiVq^?IhB5`f5$%}kdv4dZ ze`~5q7UO)6Q{*sNqy1G(6(`9J**u+`j0oAftvGvNu`i}$Vje^D6fB0AbV;~^r}aJ8 z$SQ=NxXroPbjOJKEJo$xa6s9RF9kU*Yt8@8nsnkiDjLt(@_PN8f0N}c%32;}ZPvBS zPZB@9FsN}>+k_W`zKwQCzx4vTi}v{~>BH4$J1UA+?R7>%{1xcw20a!2hj+1&atn-_ zrz^q~yboL5vBY)imB>IdmASxu zv*3AO*pGxEbG@c`^TSo?wk=a^ggWvG5<^M`LnWHEn=6NkIAKjBs+J3P7mjolWV5Mx zS0(qsg-L5EPex#L#@G5?rG4-84q6VHqKd|4O{;FmmppT>5Ir?%G}}FN_2c9e{D*(* z z!10~t-5JRRmE|_}w{DY0OxjU#a_UxaXmX@K>$?gT!keet?s7F&e{*RW8nt<&jz%wcZdkbB6keS-d+u7P6{O^rUA zi{miDEKmA}ANYJvAbsPe!8{!N8*!fnX7tX(!q9@PpITW(;o3v;|8p(@-Ry^+4jB==yR&EuLd|R zT~1rga*EsQ`dtx~$KHA~qMDVpnafBUX#&#jI%gS-0tO-m#HkOBh-s2|%0daKc*zms zYjr_|Ml92(Kc}0^>+ZfWHrZX{t{Q zS=PDBiiRkf`?A*<&kYVr0Xm_og+jc3CtOhwExW(XR<*^Tm%4j7Eq5&l_JwKd;V&4= zFO|mS@eb#}#cV%|=h3fiBVMwdNjJr8k2Gceay;GFXgSZNpj1L0d^-qLL7(Sqf{q_i zX%TArkvc6~ZzMN@%h%Knj(N?xRsV<3}{A(gUd$$Qmnng+<%oaOt45AWx6WfCh;? zNRGbqFO9G^lWh&8_y?iS zCmhGdXjY#w37L~Qg%0NOoSy?4KR<)0g3;Gxpq-Lll*i<=gq#-dv-<)n!j~)G;+He- zHCYgrSnrnlP*X2Yb=J<>M6`5vaxhXG5T$BO4>-SaZ}SP4%v8OKEBC>CVk0N~#APlg{mHhx?TL z%OapnI*Z<%7@^jRGHDjBK*^PgEf`qRRwf1GmC99e8_r!Dg00N0bYQx^p4g=Hrk~QS zG)x|ND;JRTU@c%XuL~;QeAPNC<5wqtZ3ov|i|LJdC_WQGyE_9EWv!+{$DFZ79$R;4 z$#IyS#FIXn8^`|S+OGf)dCcDpShZ2LO(4)#y;18uq-s3Y8Y}FB-a5uEnZ!9@fxyM& zJna!G^MP5*zXAxb4&Zi8t$+UZLp*=CT4Vd#VZbckA=iWU-ZQCrJgi!501ap#PJ2J} zDPfm}HG^HAQC!z6Dp>0mN~0sVb0nY+pE)M8#4~7R*aXBlBeQ9e0Z9=p_mo%-9WTzp zFr9axwYK~fKHV^JvHt3};xnZD48=x;G462-%JDI->r_zrAZ`osm5b_4=y^E?wYGqf zK|OTg$z!b6VT^9j#D>M#R_D1Eks?RX@tywB@cB$dy_#?bL7c(k7AIS!mdTSH#KyGS zyg8j(VDtvT{`QuP?n5qQkVN$)$MgaGpU5f6P{eEcP0p16PjWK+i=0~Dk!@G)>9?L5_o{ zGNV5f{fRNC0Q+25RC!BaPFSP{OcDV487dS7IB-h4wL2Ja zRK4t4`l@BHJJKTFdKzDl}TM4oy~Q z7qD@Uqmilws2U*TA&xF4$o~o(AIDcTt1p$yq?Ms5xpFhkoa+<=FPUiQ4%4) zB(b!rpzamWu-#v}5l@cEKx>kz-A)OYfU#8Z4Bmom*s-#2uuy|JzRDVn+p`Kha*yS}2x1Jo@&vPpx zr7_S|I?-DE_OP-+8y@sc>|izG`lbO}y?>I&RS>s)lR+ym`4;~mco?|<*#Ku!x^AX> z_746ZwI_yP?$6ur9$P>F0Nj6RPZuM7BUeWwYX>tMYX@3KJqHV0J!>Ne8dobz!4x zH9BQjsDxsg!rs2&xAS>uGPD#9R(SzvRl}9$82ENjBkO@%GiOM(q||}QS5v*jy1Dd^ z&{;dwfa!q+$wPAUv$q|2TGxRHU3Cw&uY(h5D|0KDhHhvj(N8681qgPS0$&brxM4cb zR&}y7Vn+t`m2?xiG&Jap>&UmV$d9P>FLtvbrKuBgQBqBLTTiiA_$RRbXXxnv0xUC*U$UPM zA?Vuw3;%+9RZDtLJw0*dOd4c8f7yz`3@v`MrhX0Ww!6I)QGFm{7}NF7R)-D8x{K8f z99q&W<}M;3;K`hlgbibzHg}*u$Vp+9p#_uS0f3nC%i*9Scmj^4?lm9$TF>_Ei7KX9 z<~eDi{wg0S{h5*(Z2Fq;@6(b}3uPegq`g(4Y7CP^2c!}TLOi>=&OZU22K<`tH^6oo~G9^YaQFerY zZ_vKz7P7@GO$l&>2F-*)LFC&UGgGQl3g&r9Qwaki4{S_yLL7&QKHvEM_emasV}b$b zyGQ98dBT4qZ)0!aU}|LK=s>GuZ)E8}`*-7S)M@@|SHw?B0Mnz0Jo#TETy1TLgQA)h zK}XC~E`URfX5n#&2XFkkT+hf0os!dP&De;^WY>v*IO>Pr6AOF8U?NlV%hfG77cAd( z`ttk(;9n0p1w$Vjj7)ENc3%Df4NH%DL0*}f6P>v)ec(A8+AAh-E?ggj-5eeb&ruz)QqgWSx|PQJE29e zKf*11O@08xRQkJuf6QoT+%Nt&z>ZP#BCR#Ldk$#xY?m|?`k6r~y)f7O+mFxEyidjb zlMpM0>%&hIf-5eA_o*bR?fKcA3+jcOe zo2qa`!OAqRtFn9{y-qxm0V-BtWIO8=b||MCP9v(a&99Y&2Rw%$N2U~`=sq}Qte&aE z6AU#NV%7bR?DPSQ%S4<-69g8qjp{TpD->0ax;zw{|5(lx?MSKF$d zK#7@+m?r5X0KU_DC`t65X+X11i+ps`;q+(wF0RUhq#BnfaGM#wyd&2u8`g7cCJ$V0 z;R@SY5+SB*Y|qTK%O-1O(*m)YCz5g!8CbHy9HRV&K(9o9n0}U2W&m~L1S840wKK`P zu_`bqjIi7 z$(H+PECsfO}X^5DdvYeJi?K;qB7i1IbqR!if|-m8POCP)Nd z;Y9dNB@B1g%3PozT5W>1II;GqA5FPy1S104X(TfYV|gdAyST=7GEixRkti*_NX7SK#|BJz69@$F|Y z+9rjO`Qk+*FR*%+Sa6NjNMVW}!^4s)1a4ybY1pk@rtLws!}aoHONag55Y>KxYYCab zA_^)%?r}e}3g%C&knbxeE<4OTfL?NeILeX3mkL)CYY2803mC0HzG9GTZ3WMyoV*Y% zClSOa*UA1`1_%fi<2tSnB1@JDXoW*E<1wDl`snP~*?Lz}=yrJiNu;+oy?^Mq#e4aA zFlERF@S07zUgGb0VTh{)&IwZgW^fUn!0}Fm#7ij{RDP++kn{2VnEyKo3b`Na*uF{7 z`F)W6iv*6Q_C|V!Muvj_CIjvNa^Y`s7{-oU2hbr1KPA1wCqE6r<1#~pe&o7BI`Ok;mJDlxvnSLMXi;`@OBQFtZAdnyR1gXPBtveejS zV^UxQC_C3D?dFrhx=*pL{g@;#VQ%6FZl=*Q+e+NO;#{Cq3A?aRIh5WDnkByi)4w2Y znKE91^V`vUC{iwZ6Mm}ryENqFcr>d}z1XD-tg&)sy`LI9D_6YkqRfxv7uv^TiJGxI z-)R*8F7ls*kpxJD=}eJEL{nstNMF}`P8c5x=r;+X8Y&LBd8uI_!ic1yr0}R~!41)K ziJ{>GCJ;rEDnvpzB}ZgZAb(b432}=wl;*XtuZ5C=V|HKc6S&&$jF_$+t{1BmkB^X7 zBy*o^)()Q69uKRx8HT^2QvUf3MRohV5f%~vpn??ufb?JJ+uEC1I~v(58aX=tZ4>`5 zZF(AgGwTcW%@{0IRNjohnbV*Oxp9iJT9eB@IZtzIVJqK5Q2w1_>#2{E|6znb!FufV0fuiTI=wUjo;(Mqg|$`q5J}=O&6Vr{5Ovu{x>D&Kidu(ze+zo-PAx z)9`9i+ic-oUN#lzhg$1Ifz}rz_dgS*!`oh;vUuhW$_VvcW6p7HL37gWn>8tNIsOj$`YQw zd1`?ZP_{~tlr<*OKM%%(7S&j`QL^Qt^w5_^d4;y41l- z+=15eiORXW*a@LPUk$+~mD|A1uj~Yh8V7zz{sPn4SKI253y_$$HpH!eCWPLkb)vStrH(+rl^k`>ABYHZc(ox?w-L|RR=uO4CUPtt zRAb>Dw%YhPdhvK`H+FfRD&9O9338J;PiL?M6qR!yWV5Xl%X=h_8DSvMkG+jQU+ENl z1TOI6hxVrDV6J00Lv%4#w$XD}jnE;KG8ZycXiKSjDYN;hK!8NBwK+kc0!0Q@=oE|*oxIFn3s5#|G zg2f3LRz6+8x&&}lXqd9ZRSu1+n(bT=HIg(Faac^l=fox|5uQpG=>7vV^7V@2g9;T@ z*!3GGN^n77EiAFPvc)Q7Hh=WUkI+&tisklH33)!kq6M;UmsZ6;>3H9p`z`US8-a(h z$cBQKnt^nXgfsPc&NDszHM7Cip@w+!&WfrxYhsOdd%8j~jlh6oTTzW7x!N=}R<(Wx zF5Q(d`jNt0^`ADkrvV-pVpO(Ow$e<$M4ahc$WtdS=Brc4m&&W6$6-mPJRStAkCcp? zKL|5R888==5OyzUs^!h>wH=v1rZ3I1OqT6~TTx;3od8AB(1wU4$oEOpjm@htfP*Vm z56dw%Lo9;H3bdX*n!ET^BTyEA)L=F$++ zu_{o`zH`?t%3ehsv`-^nFaJK-y0Yl>*!Do%?Ro^6UAFtU&P4pA7~@T<@xPlUswjAe&OGt==1S}&8Fi8S zra`#I+Wc4$KUy=G_`>8cJJUReGur(Uzq2cQcUd-aX;1`Emi$6+-e#waat zHz305yc!$!%>VR7#q`gpp%YX<#FiDwd9 zU^eq^;EnhLAEWn!l8^b&2aTSJFBBQjXHLP1EY}{tLZ6IV6Vc0SKpV+t!5ut_XP}lg z$cabfSSLT7q=RN(m#&|yz+J!-udH@FfY#R%&yqc=*^q}Va{k|c>az;q%Pq88plj+m zBJY!{2F?=>x(Ala&h6QhiSKgUwR~3&{x?Mx&z2KLEMiirI8F9A_D`(^tzj%goVzO_ zGeiy+C%8n)lWEa)yUq^Ra{U5jG;XUfZ;9un*%T2c&u$iQhdBV9E59w2oh(VOmC>GjSPM*{nW9NSf(&8FXbZHD+mfP2jKKyS3m+j(2^e&Gcsfp|gD^jA9aA1;oubrA=^E6!;3tleEVB>P*^ zlxyQfJP@yeaTY8_0(d<8%NsW>cf2SkYR?6x+ErzRYQngs1Tk&;2CkfARg$P`6|e+S z9H>3NrdNN&Vo&#SOE=Twp7yHz&{sWj+Y5(q6}5`ca#zO!{zH=s)^T`#|BJBl-If*S zvL@sy7LBl<@{4Jz0N0hbd0?}jYAuep-c1K_RWD34Z4#?1rZ!fq{#gMa;zz^FQ;yob zMY{_E?0w%8E0#VEXX>o~h}@R+o#}X_OpHgNPKfM92EBo{j5&LD)cMWJ(3KSa#zRoK zBa=(2AJ73lGEd<)-WKwnD!>No?S~#f_4Aa;f(W2

w4*98On&CkosYfVX!ne6~&4 z?(rSnr|AXB`XK8B8F(F}w`_sf!~1ko zf?Rk=zfk*}Ui7LY;VraW8;%6EI)F_LypFyQc3DyHI!_2|N4@$oT^AmMTybyQQKkc2 ztA=3)V7NTNp1bK^UaTO$l9BPga$s)Wka_OgV7Ms2#P54X`!1xPRt|Hw|NKVP9a0~+@Xtog--hzvMhzd#Uq;P9W=3Sa z;r>7r&PaUEca-TeFHwP{2^*f1k%4jFQ|@(}l2si4!pI9D-jbV}xAXAK5Egp|T$%P1Dn3Gx`WfK&eV082e~1lD{H+S63~*@kAd0%oZPAI?{^7hzzTNBC=F? z*v9-$MVRJ8J9w#}OdtTGG7esxAwk8Alm;S=niohv$>)3x(Z)u3fz!&7>#HA0cl^Tb zdZau{FQ3#0nCHc%M*HQQUq+y$D zCP~5RKFi@vV)RkH8iNGO`UA%%QG{dDXMJMI>|Qe6ka7n)&fXUg*HY^<1ETHAXX_ZK z4k1(7poRCHT5;<~?VuFLUKsko-mt~IMGUGY>lZd$aKQD#%lf6C`AVh)Q*LCQfiI&I zLYR0dlzstlh)3x3%nah{w?)&+sQY^rhAWQ{yOF+Tcw{~qu0wg5D_YJXpyw}(rV1Do zF6KFV5JOwe4Lq64c*cXWruC7jt6S5HX{~cq8BZ#%rK0d{L7*V zyiR(BOTHdrVeSSZ@Z&pc{CynWgA`j6Rii;%c6a48cRzDf=drSCV?w%)bLh{b$5^kz zeuUc(p;R5BhGYM}#6mPQvLXY!nU8w#3<7o?J{(e!#c~3MK|xJSW)TZap0Gc<#{7HX z9(iQS)&7dr>fW?dTe57}?#eIyq0WK?fD-+T5m(WppM4QE#K-mYHo8Pr?rN!obyaQu z^wM_N06%H7!Zz+h--c!u%ph8aO++xmAm=5s#`mV?;Uf{0+(i52Q%eh5og1G0GhFy| z*^WwVhZP@@z?cZGAUsjF15LayJsi{|@+g9(tW^xCHxwd$B#FX)Ef!IQ)d+KuDy;9$ z^AeSQ|mNRd${UjvU%j>YaqhrAF)>-)GLPj#@_V%K>fEp zV`T5}mt)%Y@87?@(y@Per4fX#gFXXhHj^{QqLK8$k@U$5xf|~*B+h>g#7C+~BTIaB z(vaWBo?lF?>#3aAV~s!(tnFB+A7mokaA2InGkbZNF^WTy97g@{4@uVsIBjd~)`(Ih zdF~t(A_*K$e5j}3cX0_xW}$N7lYjJ&0jN`3zRi_y@Y27HbwmINKtme?IeQyh2U-Igd!xU=`4aT(o& z_Op-n_;Y`Fut(z=-As@OF7Z|WbLlybV(3U}n>-ViKTwAOA-(q#F9l{b#_uCPdf4&t z?BJszk1*hRl%@v`{82nb&So7Ef3gL`xyw!Ir^~;qp7-28P6lGAk$IfTtSvj%wi`lw z!%dx`Q=05DNv}49vvl}``&@N#4=E^?3RJTZDU-YI7M|y>HNVPZ>Gm}Nz%AXzXc4EYIYEIGwTDH`eo=xK_wNe3V{{eS}>1>D4# zucWzEK-&SLVpZVm#!+^pp_z)yIfC5nl+& z6e{#^9=l2HtR`rhV$zT9c`rQWVEkf5Dl4`56frfPJ`EC-6hV&Y2l|?b*UcCggvEcHL z&hv5mVfJZarO1_Mt8o7cCl`DQ+1CueXy(VDgPzSekoq}TkxD>~PuP%<9e#)xw4j2ZjtmJB27IihEKiYrmaZ6LhBa z$Bq6SvwFUMMzuNOo#R+CXEKqC8{h-|pXiL#SI9H@P3KEc0089w(C$tSjy6{Rm(l;l z=!Qhe@8H8<`u!8UpmQjmU@%^sK?)ZbIEx{+A;Q|9N=q81*3gpJitOWB`4x)XaX&t4 zCx3j_g+|rH{_FRuiRj5I$1{1p3y9HBso94bW0dW4Yr zjx29Wa8F|Ylov2MR> zTMLlB{$4y|!~0a5F?a=#@U9?xw{7_)&i+H9HMnH~v5E{+4+i-=G8>zZQdl@xoFVro zZ($n2W!Jso98Y6yHN!VNFZX)>OmlL{e?Aip1X&W5gos*?gRLqu5%iFdhN?X*=ynIT zl#okndX1b1tth`GRzc4?X=w373qo=O97E0jiBX+e0VD| zm}EJov4M7Jtr!V;YgiOY*>{8l(Nk)cy?UR0nTGvNcH%vLzW)0ERqB91)Byjr2JG#v z@#6kl4$8h=-^l;<%>R9+A!zgM0k?M4QF602a?tuqD&!>pa{$!$n^>yv0UW>Q1?c}6 zfckso&cDKUe@E>8pP_3klgR7L0BR}$E=B{but0z=V0#R>7OfmOO`VjVpN-8{A46zv52&|5%*1fSu9q9ER`V0&bDCX4h!W{vfD@V+dqkCn7 zytsG;+!n)X3b>bpZpy+vt70R8UcaY+9u=`Sz^0UBq~@jqr+|PYc;W}$ zfCcIn@8y62VhapWpzD#q^ZQ@}ic2bUAj?tEwa%VCcSrIJ1_sAXOu)o}3=AGav}Pn0 zrKSXz0H;LK!3$c@O+a5vh%n=5H6y;Ih3E#MFZ@CnlvIbyAn-CUbc4_r=pYPYZUh^I zyygULxemHn=<8PyW*IbLF$>vbtWkx&Dg$BOs%D6J1lDMvI}m-10>Z$zEqDw>tXM!d z5PfDIVW3AF79Zo9r$;vyeWn{>tbaS$Sd@8hblvDvqzK(1onYN)^P|96MV&##8X4$Q ze+W}Ab%9MqoA}d5|jE}9PU zD8>i@y0=h!#Rvl!7#`1HWWeYnqiaX)MIdYcG#g1fv`>Mq8@17ftoz_RB;CpICR~6w TD;r3eBoMv>vW_nV))@={L!ErA literal 0 HcmV?d00001 diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/bg/config b/PhpOffice/PhpSpreadsheet/Calculation/locale/bg/config old mode 100755 new mode 100644 diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/bg/functions b/PhpOffice/PhpSpreadsheet/Calculation/locale/bg/functions old mode 100755 new mode 100644 diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/cs/config b/PhpOffice/PhpSpreadsheet/Calculation/locale/cs/config old mode 100755 new mode 100644 index df51373..49f40fc --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/cs/config +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/cs/config @@ -1,23 +1,20 @@ +############################################################ ## -## PhpSpreadsheet +## PhpSpreadsheet - locale settings ## +## Ceština (Czech) +## +############################################################ -ArgumentSeparator = ; - +ArgumentSeparator = ; ## -## (For future use) +## Error Codes ## -currencySymbol = Kč - - -## -## Excel Error Codes (For future use) -## -NULL = #NULL! -DIV0 = #DIV/0! -VALUE = #HODNOTA! -REF = #REF! -NAME = #NÁZEV? -NUM = #NUM! -NA = #N/A +NULL +DIV0 = #DĚLENÍ_NULOU! +VALUE = #HODNOTA! +REF = #ODKAZ! +NAME = #NÁZEV? +NUM = #ČÍSLO! +NA = #NENÍ_K_DISPOZICI diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/cs/functions b/PhpOffice/PhpSpreadsheet/Calculation/locale/cs/functions old mode 100755 new mode 100644 index 733d406..49c4945 --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/cs/functions +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/cs/functions @@ -1,416 +1,520 @@ +############################################################ ## -## PhpSpreadsheet -## -## Data in this file derived from https://www.excel-function-translation.com/ +## PhpSpreadsheet - function name translations ## +## Ceština (Czech) ## +############################################################ ## -## Add-in and Automation functions Funkce doplňků a automatizace +## Funkce pro práci s datovými krychlemi (Cube Functions) ## -GETPIVOTDATA = ZÍSKATKONTDATA ## Vrátí data uložená v kontingenční tabulce. Pomocí funkce ZÍSKATKONTDATA můžete načíst souhrnná data z kontingenční tabulky, pokud jsou tato data v kontingenční sestavě zobrazena. - +CUBEKPIMEMBER = CUBEKPIMEMBER +CUBEMEMBER = CUBEMEMBER +CUBEMEMBERPROPERTY = CUBEMEMBERPROPERTY +CUBERANKEDMEMBER = CUBERANKEDMEMBER +CUBESET = CUBESET +CUBESETCOUNT = CUBESETCOUNT +CUBEVALUE = CUBEVALUE ## -## Cube functions Funkce pro práci s krychlemi +## Funkce databáze (Database Functions) ## -CUBEKPIMEMBER = CUBEKPIMEMBER ## Vrátí název, vlastnost a velikost klíčového ukazatele výkonu (KUV) a zobrazí v buňce název a vlastnost. Klíčový ukazatel výkonu je kvantifikovatelná veličina, například hrubý měsíční zisk nebo čtvrtletní obrat na zaměstnance, která se používá pro sledování výkonnosti organizace. -CUBEMEMBER = CUBEMEMBER ## Vrátí člen nebo n-tici v hierarchii krychle. Slouží k ověření, zda v krychli existuje člen nebo n-tice. -CUBEMEMBERPROPERTY = CUBEMEMBERPROPERTY ## Vrátí hodnotu vlastnosti člena v krychli. Slouží k ověření, zda v krychli existuje člen s daným názvem, a k vrácení konkrétní vlastnosti tohoto člena. -CUBERANKEDMEMBER = CUBERANKEDMEMBER ## Vrátí n-tý nebo pořadový člen sady. Použijte ji pro vrácení jednoho nebo více prvků sady, například obchodníka s nejvyšším obratem nebo deseti nejlepších studentů. -CUBESET = CUBESET ## Definuje vypočtenou sadu členů nebo n-tic odesláním výrazu sady do krychle na serveru, který vytvoří sadu a potom ji vrátí do aplikace Microsoft Office Excel. -CUBESETCOUNT = CUBESETCOUNT ## Vrátí počet položek v množině -CUBEVALUE = CUBEVALUE ## Vrátí úhrnnou hodnotu z krychle. - +DAVERAGE = DPRŮMĚR +DCOUNT = DPOČET +DCOUNTA = DPOČET2 +DGET = DZÍSKAT +DMAX = DMAX +DMIN = DMIN +DPRODUCT = DSOUČIN +DSTDEV = DSMODCH.VÝBĚR +DSTDEVP = DSMODCH +DSUM = DSUMA +DVAR = DVAR.VÝBĚR +DVARP = DVAR ## -## Database functions Funkce databáze +## Funkce data a času (Date & Time Functions) ## -DAVERAGE = DPRŮMĚR ## Vrátí průměr vybraných položek databáze. -DCOUNT = DPOČET ## Spočítá buňky databáze obsahující čísla. -DCOUNTA = DPOČET2 ## Spočítá buňky databáze, které nejsou prázdné. -DGET = DZÍSKAT ## Extrahuje z databáze jeden záznam splňující zadaná kritéria. -DMAX = DMAX ## Vrátí maximální hodnotu z vybraných položek databáze. -DMIN = DMIN ## Vrátí minimální hodnotu z vybraných položek databáze. -DPRODUCT = DSOUČIN ## Vynásobí hodnoty určitého pole záznamů v databázi, které splňují daná kritéria. -DSTDEV = DSMODCH.VÝBĚR ## Odhadne směrodatnou odchylku výběru vybraných položek databáze. -DSTDEVP = DSMODCH ## Vypočte směrodatnou odchylku základního souboru vybraných položek databáze. -DSUM = DSUMA ## Sečte čísla ve sloupcovém poli záznamů databáze, která splňují daná kritéria. -DVAR = DVAR.VÝBĚR ## Odhadne rozptyl výběru vybraných položek databáze. -DVARP = DVAR ## Vypočte rozptyl základního souboru vybraných položek databáze. - +DATE = DATUM +DATEVALUE = DATUMHODN +DAY = DEN +DAYS = DAYS +DAYS360 = ROK360 +EDATE = EDATE +EOMONTH = EOMONTH +HOUR = HODINA +ISOWEEKNUM = ISOWEEKNUM +MINUTE = MINUTA +MONTH = MĚSÍC +NETWORKDAYS = NETWORKDAYS +NETWORKDAYS.INTL = NETWORKDAYS.INTL +NOW = NYNÍ +SECOND = SEKUNDA +TIME = ČAS +TIMEVALUE = ČASHODN +TODAY = DNES +WEEKDAY = DENTÝDNE +WEEKNUM = WEEKNUM +WORKDAY = WORKDAY +WORKDAY.INTL = WORKDAY.INTL +YEAR = ROK +YEARFRAC = YEARFRAC ## -## Date and time functions Funkce data a času +## Inženýrské funkce (Engineering Functions) ## -DATE = DATUM ## Vrátí pořadové číslo určitého data. -DATEVALUE = DATUMHODN ## Převede datum ve formě textu na pořadové číslo. -DAY = DEN ## Převede pořadové číslo na den v měsíci. -DAYS360 = ROK360 ## Vrátí počet dní mezi dvěma daty na základě roku s 360 dny. -EDATE = EDATE ## Vrátí pořadové číslo data, které označuje určený počet měsíců před nebo po počátečním datu. -EOMONTH = EOMONTH ## Vrátí pořadové číslo posledního dne měsíce před nebo po zadaném počtu měsíců. -HOUR = HODINA ## Převede pořadové číslo na hodinu. -MINUTE = MINUTA ## Převede pořadové číslo na minutu. -MONTH = MĚSÍC ## Převede pořadové číslo na měsíc. -NETWORKDAYS = NETWORKDAYS ## Vrátí počet celých pracovních dní mezi dvěma daty. -NOW = NYNÍ ## Vrátí pořadové číslo aktuálního data a času. -SECOND = SEKUNDA ## Převede pořadové číslo na sekundu. -TIME = ČAS ## Vrátí pořadové číslo určitého času. -TIMEVALUE = ČASHODN ## Převede čas ve formě textu na pořadové číslo. -TODAY = DNES ## Vrátí pořadové číslo dnešního data. -WEEKDAY = DENTÝDNE ## Převede pořadové číslo na den v týdnu. -WEEKNUM = WEEKNUM ## Převede pořadové číslo na číslo představující číselnou pozici týdne v roce. -WORKDAY = WORKDAY ## Vrátí pořadové číslo data před nebo po zadaném počtu pracovních dní. -YEAR = ROK ## Převede pořadové číslo na rok. -YEARFRAC = YEARFRAC ## Vrátí část roku vyjádřenou zlomkem a představující počet celých dní mezi počátečním a koncovým datem. - +BESSELI = BESSELI +BESSELJ = BESSELJ +BESSELK = BESSELK +BESSELY = BESSELY +BIN2DEC = BIN2DEC +BIN2HEX = BIN2HEX +BIN2OCT = BIN2OCT +BITAND = BITAND +BITLSHIFT = BITLSHIFT +BITOR = BITOR +BITRSHIFT = BITRSHIFT +BITXOR = BITXOR +COMPLEX = COMPLEX +CONVERT = CONVERT +DEC2BIN = DEC2BIN +DEC2HEX = DEC2HEX +DEC2OCT = DEC2OCT +DELTA = DELTA +ERF = ERF +ERF.PRECISE = ERF.PRECISE +ERFC = ERFC +ERFC.PRECISE = ERFC.PRECISE +GESTEP = GESTEP +HEX2BIN = HEX2BIN +HEX2DEC = HEX2DEC +HEX2OCT = HEX2OCT +IMABS = IMABS +IMAGINARY = IMAGINARY +IMARGUMENT = IMARGUMENT +IMCONJUGATE = IMCONJUGATE +IMCOS = IMCOS +IMCOSH = IMCOSH +IMCOT = IMCOT +IMCSC = IMCSC +IMCSCH = IMCSCH +IMDIV = IMDIV +IMEXP = IMEXP +IMLN = IMLN +IMLOG10 = IMLOG10 +IMLOG2 = IMLOG2 +IMPOWER = IMPOWER +IMPRODUCT = IMPRODUCT +IMREAL = IMREAL +IMSEC = IMSEC +IMSECH = IMSECH +IMSIN = IMSIN +IMSINH = IMSINH +IMSQRT = IMSQRT +IMSUB = IMSUB +IMSUM = IMSUM +IMTAN = IMTAN +OCT2BIN = OCT2BIN +OCT2DEC = OCT2DEC +OCT2HEX = OCT2HEX ## -## Engineering functions Inženýrské funkce (Technické funkce) +## Finanční funkce (Financial Functions) ## -BESSELI = BESSELI ## Vrátí modifikovanou Besselovu funkci In(x). -BESSELJ = BESSELJ ## Vrátí modifikovanou Besselovu funkci Jn(x). -BESSELK = BESSELK ## Vrátí modifikovanou Besselovu funkci Kn(x). -BESSELY = BESSELY ## Vrátí Besselovu funkci Yn(x). -BIN2DEC = BIN2DEC ## Převede binární číslo na desítkové. -BIN2HEX = BIN2HEX ## Převede binární číslo na šestnáctkové. -BIN2OCT = BIN2OCT ## Převede binární číslo na osmičkové. -COMPLEX = COMPLEX ## Převede reálnou a imaginární část na komplexní číslo. -CONVERT = CONVERT ## Převede číslo do jiného jednotkového měrného systému. -DEC2BIN = DEC2BIN ## Převede desítkového čísla na dvojkové -DEC2HEX = DEC2HEX ## Převede desítkové číslo na šestnáctkové. -DEC2OCT = DEC2OCT ## Převede desítkové číslo na osmičkové. -DELTA = DELTA ## Testuje rovnost dvou hodnot. -ERF = ERF ## Vrátí chybovou funkci. -ERFC = ERFC ## Vrátí doplňkovou chybovou funkci. -GESTEP = GESTEP ## Testuje, zda je číslo větší než mezní hodnota. -HEX2BIN = HEX2BIN ## Převede šestnáctkové číslo na binární. -HEX2DEC = HEX2DEC ## Převede šestnáctkové číslo na desítkové. -HEX2OCT = HEX2OCT ## Převede šestnáctkové číslo na osmičkové. -IMABS = IMABS ## Vrátí absolutní hodnotu (modul) komplexního čísla. -IMAGINARY = IMAGINARY ## Vrátí imaginární část komplexního čísla. -IMARGUMENT = IMARGUMENT ## Vrátí argument théta, úhel vyjádřený v radiánech. -IMCONJUGATE = IMCONJUGATE ## Vrátí komplexně sdružené číslo ke komplexnímu číslu. -IMCOS = IMCOS ## Vrátí kosinus komplexního čísla. -IMDIV = IMDIV ## Vrátí podíl dvou komplexních čísel. -IMEXP = IMEXP ## Vrátí exponenciální tvar komplexního čísla. -IMLN = IMLN ## Vrátí přirozený logaritmus komplexního čísla. -IMLOG10 = IMLOG10 ## Vrátí dekadický logaritmus komplexního čísla. -IMLOG2 = IMLOG2 ## Vrátí logaritmus komplexního čísla při základu 2. -IMPOWER = IMPOWER ## Vrátí komplexní číslo umocněné na celé číslo. -IMPRODUCT = IMPRODUCT ## Vrátí součin komplexních čísel. -IMREAL = IMREAL ## Vrátí reálnou část komplexního čísla. -IMSIN = IMSIN ## Vrátí sinus komplexního čísla. -IMSQRT = IMSQRT ## Vrátí druhou odmocninu komplexního čísla. -IMSUB = IMSUB ## Vrátí rozdíl mezi dvěma komplexními čísly. -IMSUM = IMSUM ## Vrátí součet dvou komplexních čísel. -OCT2BIN = OCT2BIN ## Převede osmičkové číslo na binární. -OCT2DEC = OCT2DEC ## Převede osmičkové číslo na desítkové. -OCT2HEX = OCT2HEX ## Převede osmičkové číslo na šestnáctkové. - +ACCRINT = ACCRINT +ACCRINTM = ACCRINTM +AMORDEGRC = AMORDEGRC +AMORLINC = AMORLINC +COUPDAYBS = COUPDAYBS +COUPDAYS = COUPDAYS +COUPDAYSNC = COUPDAYSNC +COUPNCD = COUPNCD +COUPNUM = COUPNUM +COUPPCD = COUPPCD +CUMIPMT = CUMIPMT +CUMPRINC = CUMPRINC +DB = ODPIS.ZRYCH +DDB = ODPIS.ZRYCH2 +DISC = DISC +DOLLARDE = DOLLARDE +DOLLARFR = DOLLARFR +DURATION = DURATION +EFFECT = EFFECT +FV = BUDHODNOTA +FVSCHEDULE = FVSCHEDULE +INTRATE = INTRATE +IPMT = PLATBA.ÚROK +IRR = MÍRA.VÝNOSNOSTI +ISPMT = ISPMT +MDURATION = MDURATION +MIRR = MOD.MÍRA.VÝNOSNOSTI +NOMINAL = NOMINAL +NPER = POČET.OBDOBÍ +NPV = ČISTÁ.SOUČHODNOTA +ODDFPRICE = ODDFPRICE +ODDFYIELD = ODDFYIELD +ODDLPRICE = ODDLPRICE +ODDLYIELD = ODDLYIELD +PDURATION = PDURATION +PMT = PLATBA +PPMT = PLATBA.ZÁKLAD +PRICE = PRICE +PRICEDISC = PRICEDISC +PRICEMAT = PRICEMAT +PV = SOUČHODNOTA +RATE = ÚROKOVÁ.MÍRA +RECEIVED = RECEIVED +RRI = RRI +SLN = ODPIS.LIN +SYD = ODPIS.NELIN +TBILLEQ = TBILLEQ +TBILLPRICE = TBILLPRICE +TBILLYIELD = TBILLYIELD +VDB = ODPIS.ZA.INT +XIRR = XIRR +XNPV = XNPV +YIELD = YIELD +YIELDDISC = YIELDDISC +YIELDMAT = YIELDMAT ## -## Financial functions Finanční funkce +## Informační funkce (Information Functions) ## -ACCRINT = ACCRINT ## Vrátí nahromaděný úrok z cenného papíru, ze kterého je úrok placen v pravidelných termínech. -ACCRINTM = ACCRINTM ## Vrátí nahromaděný úrok z cenného papíru, ze kterého je úrok placen k datu splatnosti. -AMORDEGRC = AMORDEGRC ## Vrátí lineární amortizaci v každém účetním období pomocí koeficientu amortizace. -AMORLINC = AMORLINC ## Vrátí lineární amortizaci v každém účetním období. -COUPDAYBS = COUPDAYBS ## Vrátí počet dnů od začátku období placení kupónů do data splatnosti. -COUPDAYS = COUPDAYS ## Vrátí počet dnů v období placení kupónů, které obsahuje den zúčtování. -COUPDAYSNC = COUPDAYSNC ## Vrátí počet dnů od data zúčtování do následujícího data placení kupónu. -COUPNCD = COUPNCD ## Vrátí následující datum placení kupónu po datu zúčtování. -COUPNUM = COUPNUM ## Vrátí počet kupónů splatných mezi datem zúčtování a datem splatnosti. -COUPPCD = COUPPCD ## Vrátí předchozí datum placení kupónu před datem zúčtování. -CUMIPMT = CUMIPMT ## Vrátí kumulativní úrok splacený mezi dvěma obdobími. -CUMPRINC = CUMPRINC ## Vrátí kumulativní jistinu splacenou mezi dvěma obdobími půjčky. -DB = ODPIS.ZRYCH ## Vrátí odpis aktiva za určité období pomocí degresivní metody odpisu s pevným zůstatkem. -DDB = ODPIS.ZRYCH2 ## Vrátí odpis aktiva za určité období pomocí dvojité degresivní metody odpisu nebo jiné metody, kterou zadáte. -DISC = DISC ## Vrátí diskontní sazbu cenného papíru. -DOLLARDE = DOLLARDE ## Převede částku v korunách vyjádřenou zlomkem na částku v korunách vyjádřenou desetinným číslem. -DOLLARFR = DOLLARFR ## Převede částku v korunách vyjádřenou desetinným číslem na částku v korunách vyjádřenou zlomkem. -DURATION = DURATION ## Vrátí roční dobu cenného papíru s pravidelnými úrokovými sazbami. -EFFECT = EFFECT ## Vrátí efektivní roční úrokovou sazbu. -FV = BUDHODNOTA ## Vrátí budoucí hodnotu investice. -FVSCHEDULE = FVSCHEDULE ## Vrátí budoucí hodnotu počáteční jistiny po použití série sazeb složitého úroku. -INTRATE = INTRATE ## Vrátí úrokovou sazbu plně investovaného cenného papíru. -IPMT = PLATBA.ÚROK ## Vrátí výšku úroku investice za dané období. -IRR = MÍRA.VÝNOSNOSTI ## Vrátí vnitřní výnosové procento série peněžních toků. -ISPMT = ISPMT ## Vypočte výši úroku z investice zaplaceného během určitého období. -MDURATION = MDURATION ## Vrátí Macauleyho modifikovanou dobu cenného papíru o nominální hodnotě 100 Kč. -MIRR = MOD.MÍRA.VÝNOSNOSTI ## Vrátí vnitřní sazbu výnosu, přičemž kladné a záporné hodnoty peněžních prostředků jsou financovány podle různých sazeb. -NOMINAL = NOMINAL ## Vrátí nominální roční úrokovou sazbu. -NPER = POČET.OBDOBÍ ## Vrátí počet období pro investici. -NPV = ČISTÁ.SOUČHODNOTA ## Vrátí čistou současnou hodnotu investice vypočítanou na základě série pravidelných peněžních toků a diskontní sazby. -ODDFPRICE = ODDFPRICE ## Vrátí cenu cenného papíru o nominální hodnotě 100 Kč s odlišným prvním obdobím. -ODDFYIELD = ODDFYIELD ## Vrátí výnos cenného papíru s odlišným prvním obdobím. -ODDLPRICE = ODDLPRICE ## Vrátí cenu cenného papíru o nominální hodnotě 100 Kč s odlišným posledním obdobím. -ODDLYIELD = ODDLYIELD ## Vrátí výnos cenného papíru s odlišným posledním obdobím. -PMT = PLATBA ## Vrátí hodnotu pravidelné splátky anuity. -PPMT = PLATBA.ZÁKLAD ## Vrátí hodnotu splátky jistiny pro zadanou investici za dané období. -PRICE = PRICE ## Vrátí cenu cenného papíru o nominální hodnotě 100 Kč, ze kterého je úrok placen v pravidelných termínech. -PRICEDISC = PRICEDISC ## Vrátí cenu diskontního cenného papíru o nominální hodnotě 100 Kč. -PRICEMAT = PRICEMAT ## Vrátí cenu cenného papíru o nominální hodnotě 100 Kč, ze kterého je úrok placen k datu splatnosti. -PV = SOUČHODNOTA ## Vrátí současnou hodnotu investice. -RATE = ÚROKOVÁ.MÍRA ## Vrátí úrokovou sazbu vztaženou na období anuity. -RECEIVED = RECEIVED ## Vrátí částku obdrženou k datu splatnosti plně investovaného cenného papíru. -SLN = ODPIS.LIN ## Vrátí přímé odpisy aktiva pro jedno období. -SYD = ODPIS.NELIN ## Vrátí směrné číslo ročních odpisů aktiva pro zadané období. -TBILLEQ = TBILLEQ ## Vrátí výnos směnky státní pokladny ekvivalentní výnosu obligace. -TBILLPRICE = TBILLPRICE ## Vrátí cenu směnky státní pokladny o nominální hodnotě 100 Kč. -TBILLYIELD = TBILLYIELD ## Vrátí výnos směnky státní pokladny. -VDB = ODPIS.ZA.INT ## Vrátí odpis aktiva pro určité období nebo část období pomocí degresivní metody odpisu. -XIRR = XIRR ## Vrátí vnitřní výnosnost pro harmonogram peněžních toků, který nemusí být nutně periodický. -XNPV = XNPV ## Vrátí čistou současnou hodnotu pro harmonogram peněžních toků, který nemusí být nutně periodický. -YIELD = YIELD ## Vrátí výnos cenného papíru, ze kterého je úrok placen v pravidelných termínech. -YIELDDISC = YIELDDISC ## Vrátí roční výnos diskontního cenného papíru, například směnky státní pokladny. -YIELDMAT = YIELDMAT ## Vrátí roční výnos cenného papíru, ze kterého je úrok placen k datu splatnosti. - +CELL = POLÍČKO +ERROR.TYPE = CHYBA.TYP +INFO = O.PROSTŘEDÍ +ISBLANK = JE.PRÁZDNÉ +ISERR = JE.CHYBA +ISERROR = JE.CHYBHODN +ISEVEN = ISEVEN +ISFORMULA = ISFORMULA +ISLOGICAL = JE.LOGHODN +ISNA = JE.NEDEF +ISNONTEXT = JE.NETEXT +ISNUMBER = JE.ČISLO +ISODD = ISODD +ISREF = JE.ODKAZ +ISTEXT = JE.TEXT +N = N +NA = NEDEF +SHEET = SHEET +SHEETS = SHEETS +TYPE = TYP ## -## Information functions Informační funkce +## Logické funkce (Logical Functions) ## -CELL = POLÍČKO ## Vrátí informace o formátování, umístění nebo obsahu buňky. -ERROR.TYPE = CHYBA.TYP ## Vrátí číslo odpovídající typu chyby. -INFO = O.PROSTŘEDÍ ## Vrátí informace o aktuálním pracovním prostředí. -ISBLANK = JE.PRÁZDNÉ ## Vrátí hodnotu PRAVDA, pokud se argument hodnota odkazuje na prázdnou buňku. -ISERR = JE.CHYBA ## Vrátí hodnotu PRAVDA, pokud je argument hodnota libovolná chybová hodnota (kromě #N/A). -ISERROR = JE.CHYBHODN ## Vrátí hodnotu PRAVDA, pokud je argument hodnota libovolná chybová hodnota. -ISEVEN = ISEVEN ## Vrátí hodnotu PRAVDA, pokud je číslo sudé. -ISLOGICAL = JE.LOGHODN ## Vrátí hodnotu PRAVDA, pokud je argument hodnota logická hodnota. -ISNA = JE.NEDEF ## Vrátí hodnotu PRAVDA, pokud je argument hodnota chybová hodnota #N/A. -ISNONTEXT = JE.NETEXT ## Vrátí hodnotu PRAVDA, pokud argument hodnota není text. -ISNUMBER = JE.ČÍSLO ## Vrátí hodnotu PRAVDA, pokud je argument hodnota číslo. -ISODD = ISODD ## Vrátí hodnotu PRAVDA, pokud je číslo liché. -ISREF = JE.ODKAZ ## Vrátí hodnotu PRAVDA, pokud je argument hodnota odkaz. -ISTEXT = JE.TEXT ## Vrátí hodnotu PRAVDA, pokud je argument hodnota text. -N = N ## Vrátí hodnotu převedenou na číslo. -NA = NEDEF ## Vrátí chybovou hodnotu #N/A. -TYPE = TYP ## Vrátí číslo označující datový typ hodnoty. - +AND = A +FALSE = NEPRAVDA +IF = KDYŽ +IFERROR = IFERROR +IFNA = IFNA +IFS = IFS +NOT = NE +OR = NEBO +SWITCH = SWITCH +TRUE = PRAVDA +XOR = XOR ## -## Logical functions Logické funkce +## Vyhledávací funkce a funkce pro odkazy (Lookup & Reference Functions) ## -AND = A ## Vrátí hodnotu PRAVDA, mají-li všechny argumenty hodnotu PRAVDA. -FALSE = NEPRAVDA ## Vrátí logickou hodnotu NEPRAVDA. -IF = KDYŽ ## Určí, který logický test má proběhnout. -IFERROR = IFERROR ## Pokud je vzorec vyhodnocen jako chyba, vrátí zadanou hodnotu. V opačném případě vrátí výsledek vzorce. -NOT = NE ## Provede logickou negaci argumentu funkce. -OR = NEBO ## Vrátí hodnotu PRAVDA, je-li alespoň jeden argument roven hodnotě PRAVDA. -TRUE = PRAVDA ## Vrátí logickou hodnotu PRAVDA. - +ADDRESS = ODKAZ +AREAS = POČET.BLOKŮ +CHOOSE = ZVOLIT +COLUMN = SLOUPEC +COLUMNS = SLOUPCE +FORMULATEXT = FORMULATEXT +GETPIVOTDATA = ZÍSKATKONTDATA +HLOOKUP = VVYHLEDAT +HYPERLINK = HYPERTEXTOVÝ.ODKAZ +INDEX = INDEX +INDIRECT = NEPŘÍMÝ.ODKAZ +LOOKUP = VYHLEDAT +MATCH = POZVYHLEDAT +OFFSET = POSUN +ROW = ŘÁDEK +ROWS = ŘÁDKY +RTD = RTD +TRANSPOSE = TRANSPOZICE +VLOOKUP = SVYHLEDAT ## -## Lookup and reference functions Vyhledávací funkce +## Matematické a trigonometrické funkce (Math & Trig Functions) ## -ADDRESS = ODKAZ ## Vrátí textový odkaz na jednu buňku listu. -AREAS = POČET.BLOKŮ ## Vrátí počet oblastí v odkazu. -CHOOSE = ZVOLIT ## Zvolí hodnotu ze seznamu hodnot. -COLUMN = SLOUPEC ## Vrátí číslo sloupce odkazu. -COLUMNS = SLOUPCE ## Vrátí počet sloupců v odkazu. -HLOOKUP = VVYHLEDAT ## Prohledá horní řádek matice a vrátí hodnotu určené buňky. -HYPERLINK = HYPERTEXTOVÝ.ODKAZ ## Vytvoří zástupce nebo odkaz, který otevře dokument uložený na síťovém serveru, v síti intranet nebo Internet. -INDEX = INDEX ## Pomocí rejstříku zvolí hodnotu z odkazu nebo matice. -INDIRECT = NEPŘÍMÝ.ODKAZ ## Vrátí odkaz určený textovou hodnotou. -LOOKUP = VYHLEDAT ## Vyhledá hodnoty ve vektoru nebo matici. -MATCH = POZVYHLEDAT ## Vyhledá hodnoty v odkazu nebo matici. -OFFSET = POSUN ## Vrátí posun odkazu od zadaného odkazu. -ROW = ŘÁDEK ## Vrátí číslo řádku odkazu. -ROWS = ŘÁDKY ## Vrátí počet řádků v odkazu. -RTD = RTD ## Načte data reálného času z programu, který podporuje automatizaci modelu COM (Automatizace: Způsob práce s objekty určité aplikace z jiné aplikace nebo nástroje pro vývoj. Automatizace (dříve nazývaná automatizace OLE) je počítačovým standardem a je funkcí modelu COM (Component Object Model).). -TRANSPOSE = TRANSPOZICE ## Vrátí transponovanou matici. -VLOOKUP = SVYHLEDAT ## Prohledá první sloupec matice, přesune kurzor v řádku a vrátí hodnotu buňky. - +ABS = ABS +ACOS = ARCCOS +ACOSH = ARCCOSH +ACOT = ACOT +ACOTH = ACOTH +AGGREGATE = AGGREGATE +ARABIC = ARABIC +ASIN = ARCSIN +ASINH = ARCSINH +ATAN = ARCTG +ATAN2 = ARCTG2 +ATANH = ARCTGH +BASE = BASE +CEILING.MATH = CEILING.MATH +COMBIN = KOMBINACE +COMBINA = COMBINA +COS = COS +COSH = COSH +COT = COT +COTH = COTH +CSC = CSC +CSCH = CSCH +DECIMAL = DECIMAL +DEGREES = DEGREES +EVEN = ZAOKROUHLIT.NA.SUDÉ +EXP = EXP +FACT = FAKTORIÁL +FACTDOUBLE = FACTDOUBLE +FLOOR.MATH = FLOOR.MATH +GCD = GCD +INT = CELÁ.ČÁST +LCM = LCM +LN = LN +LOG = LOGZ +LOG10 = LOG +MDETERM = DETERMINANT +MINVERSE = INVERZE +MMULT = SOUČIN.MATIC +MOD = MOD +MROUND = MROUND +MULTINOMIAL = MULTINOMIAL +MUNIT = MUNIT +ODD = ZAOKROUHLIT.NA.LICHÉ +PI = PI +POWER = POWER +PRODUCT = SOUČIN +QUOTIENT = QUOTIENT +RADIANS = RADIANS +RAND = NÁHČÍSLO +RANDBETWEEN = RANDBETWEEN +ROMAN = ROMAN +ROUND = ZAOKROUHLIT +ROUNDDOWN = ROUNDDOWN +ROUNDUP = ROUNDUP +SEC = SEC +SECH = SECH +SERIESSUM = SERIESSUM +SIGN = SIGN +SIN = SIN +SINH = SINH +SQRT = ODMOCNINA +SQRTPI = SQRTPI +SUBTOTAL = SUBTOTAL +SUM = SUMA +SUMIF = SUMIF +SUMIFS = SUMIFS +SUMPRODUCT = SOUČIN.SKALÁRNÍ +SUMSQ = SUMA.ČTVERCŮ +SUMX2MY2 = SUMX2MY2 +SUMX2PY2 = SUMX2PY2 +SUMXMY2 = SUMXMY2 +TAN = TG +TANH = TGH +TRUNC = USEKNOUT ## -## Math and trigonometry functions Matematické a trigonometrické funkce +## Statistické funkce (Statistical Functions) ## -ABS = ABS ## Vrátí absolutní hodnotu čísla. -ACOS = ARCCOS ## Vrátí arkuskosinus čísla. -ACOSH = ARCCOSH ## Vrátí hyperbolický arkuskosinus čísla. -ASIN = ARCSIN ## Vrátí arkussinus čísla. -ASINH = ARCSINH ## Vrátí hyperbolický arkussinus čísla. -ATAN = ARCTG ## Vrátí arkustangens čísla. -ATAN2 = ARCTG2 ## Vrátí arkustangens x-ové a y-ové souřadnice. -ATANH = ARCTGH ## Vrátí hyperbolický arkustangens čísla. -CEILING = ZAOKR.NAHORU ## Zaokrouhlí číslo na nejbližší celé číslo nebo na nejbližší násobek zadané hodnoty. -COMBIN = KOMBINACE ## Vrátí počet kombinací pro daný počet položek. -COS = COS ## Vrátí kosinus čísla. -COSH = COSH ## Vrátí hyperbolický kosinus čísla. -DEGREES = DEGREES ## Převede radiány na stupně. -EVEN = ZAOKROUHLIT.NA.SUDÉ ## Zaokrouhlí číslo nahoru na nejbližší celé sudé číslo. -EXP = EXP ## Vrátí základ přirozeného logaritmu e umocněný na zadané číslo. -FACT = FAKTORIÁL ## Vrátí faktoriál čísla. -FACTDOUBLE = FACTDOUBLE ## Vrátí dvojitý faktoriál čísla. -FLOOR = ZAOKR.DOLŮ ## Zaokrouhlí číslo dolů, směrem k nule. -GCD = GCD ## Vrátí největší společný dělitel. -INT = CELÁ.ČÁST ## Zaokrouhlí číslo dolů na nejbližší celé číslo. -LCM = LCM ## Vrátí nejmenší společný násobek. -LN = LN ## Vrátí přirozený logaritmus čísla. -LOG = LOGZ ## Vrátí logaritmus čísla při zadaném základu. -LOG10 = LOG ## Vrátí dekadický logaritmus čísla. -MDETERM = DETERMINANT ## Vrátí determinant matice. -MINVERSE = INVERZE ## Vrátí inverzní matici. -MMULT = SOUČIN.MATIC ## Vrátí součin dvou matic. -MOD = MOD ## Vrátí zbytek po dělení. -MROUND = MROUND ## Vrátí číslo zaokrouhlené na požadovaný násobek. -MULTINOMIAL = MULTINOMIAL ## Vrátí mnohočlen z množiny čísel. -ODD = ZAOKROUHLIT.NA.LICHÉ ## Zaokrouhlí číslo nahoru na nejbližší celé liché číslo. -PI = PI ## Vrátí hodnotu čísla pí. -POWER = POWER ## Umocní číslo na zadanou mocninu. -PRODUCT = SOUČIN ## Vynásobí argumenty funkce. -QUOTIENT = QUOTIENT ## Vrátí celou část dělení. -RADIANS = RADIANS ## Převede stupně na radiány. -RAND = NÁHČÍSLO ## Vrátí náhodné číslo mezi 0 a 1. -RANDBETWEEN = RANDBETWEEN ## Vrátí náhodné číslo mezi zadanými čísly. -ROMAN = ROMAN ## Převede arabskou číslici na římskou ve formátu textu. -ROUND = ZAOKROUHLIT ## Zaokrouhlí číslo na zadaný počet číslic. -ROUNDDOWN = ROUNDDOWN ## Zaokrouhlí číslo dolů, směrem k nule. -ROUNDUP = ROUNDUP ## Zaokrouhlí číslo nahoru, směrem od nuly. -SERIESSUM = SERIESSUM ## Vrátí součet mocninné řady určené podle vzorce. -SIGN = SIGN ## Vrátí znaménko čísla. -SIN = SIN ## Vrátí sinus daného úhlu. -SINH = SINH ## Vrátí hyperbolický sinus čísla. -SQRT = ODMOCNINA ## Vrátí kladnou druhou odmocninu. -SQRTPI = SQRTPI ## Vrátí druhou odmocninu výrazu (číslo * pí). -SUBTOTAL = SUBTOTAL ## Vrátí souhrn v seznamu nebo databázi. -SUM = SUMA ## Sečte argumenty funkce. -SUMIF = SUMIF ## Sečte buňky vybrané podle zadaných kritérií. -SUMIFS = SUMIFS ## Sečte buňky určené více zadanými podmínkami. -SUMPRODUCT = SOUČIN.SKALÁRNÍ ## Vrátí součet součinů odpovídajících prvků matic. -SUMSQ = SUMA.ČTVERCŮ ## Vrátí součet čtverců argumentů. -SUMX2MY2 = SUMX2MY2 ## Vrátí součet rozdílu čtverců odpovídajících hodnot ve dvou maticích. -SUMX2PY2 = SUMX2PY2 ## Vrátí součet součtu čtverců odpovídajících hodnot ve dvou maticích. -SUMXMY2 = SUMXMY2 ## Vrátí součet čtverců rozdílů odpovídajících hodnot ve dvou maticích. -TAN = TGTG ## Vrátí tangens čísla. -TANH = TGH ## Vrátí hyperbolický tangens čísla. -TRUNC = USEKNOUT ## Zkrátí číslo na celé číslo. - +AVEDEV = PRŮMODCHYLKA +AVERAGE = PRŮMĚR +AVERAGEA = AVERAGEA +AVERAGEIF = AVERAGEIF +AVERAGEIFS = AVERAGEIFS +BETA.DIST = BETA.DIST +BETA.INV = BETA.INV +BINOM.DIST = BINOM.DIST +BINOM.DIST.RANGE = BINOM.DIST.RANGE +BINOM.INV = BINOM.INV +CHISQ.DIST = CHISQ.DIST +CHISQ.DIST.RT = CHISQ.DIST.RT +CHISQ.INV = CHISQ.INV +CHISQ.INV.RT = CHISQ.INV.RT +CHISQ.TEST = CHISQ.TEST +CONFIDENCE.NORM = CONFIDENCE.NORM +CONFIDENCE.T = CONFIDENCE.T +CORREL = CORREL +COUNT = POČET +COUNTA = POČET2 +COUNTBLANK = COUNTBLANK +COUNTIF = COUNTIF +COUNTIFS = COUNTIFS +COVARIANCE.P = COVARIANCE.P +COVARIANCE.S = COVARIANCE.S +DEVSQ = DEVSQ +EXPON.DIST = EXPON.DIST +F.DIST = F.DIST +F.DIST.RT = F.DIST.RT +F.INV = F.INV +F.INV.RT = F.INV.RT +F.TEST = F.TEST +FISHER = FISHER +FISHERINV = FISHERINV +FORECAST.ETS = FORECAST.ETS +FORECAST.ETS.CONFINT = FORECAST.ETS.CONFINT +FORECAST.ETS.SEASONALITY = FORECAST.ETS.SEASONALITY +FORECAST.ETS.STAT = FORECAST.ETS.STAT +FORECAST.LINEAR = FORECAST.LINEAR +FREQUENCY = ČETNOSTI +GAMMA = GAMMA +GAMMA.DIST = GAMMA.DIST +GAMMA.INV = GAMMA.INV +GAMMALN = GAMMALN +GAMMALN.PRECISE = GAMMALN.PRECISE +GAUSS = GAUSS +GEOMEAN = GEOMEAN +GROWTH = LOGLINTREND +HARMEAN = HARMEAN +HYPGEOM.DIST = HYPGEOM.DIST +INTERCEPT = INTERCEPT +KURT = KURT +LARGE = LARGE +LINEST = LINREGRESE +LOGEST = LOGLINREGRESE +LOGNORM.DIST = LOGNORM.DIST +LOGNORM.INV = LOGNORM.INV +MAX = MAX +MAXA = MAXA +MAXIFS = MAXIFS +MEDIAN = MEDIAN +MIN = MIN +MINA = MINA +MINIFS = MINIFS +MODE.MULT = MODE.MULT +MODE.SNGL = MODE.SNGL +NEGBINOM.DIST = NEGBINOM.DIST +NORM.DIST = NORM.DIST +NORM.INV = NORM.INV +NORM.S.DIST = NORM.S.DIST +NORM.S.INV = NORM.S.INV +PEARSON = PEARSON +PERCENTILE.EXC = PERCENTIL.EXC +PERCENTILE.INC = PERCENTIL.INC +PERCENTRANK.EXC = PERCENTRANK.EXC +PERCENTRANK.INC = PERCENTRANK.INC +PERMUT = PERMUTACE +PERMUTATIONA = PERMUTATIONA +PHI = PHI +POISSON.DIST = POISSON.DIST +PROB = PROB +QUARTILE.EXC = QUARTIL.EXC +QUARTILE.INC = QUARTIL.INC +RANK.AVG = RANK.AVG +RANK.EQ = RANK.EQ +RSQ = RKQ +SKEW = SKEW +SKEW.P = SKEW.P +SLOPE = SLOPE +SMALL = SMALL +STANDARDIZE = STANDARDIZE +STDEV.P = SMODCH.P +STDEV.S = SMODCH.VÝBĚR.S +STDEVA = STDEVA +STDEVPA = STDEVPA +STEYX = STEYX +T.DIST = T.DIST +T.DIST.2T = T.DIST.2T +T.DIST.RT = T.DIST.RT +T.INV = T.INV +T.INV.2T = T.INV.2T +T.TEST = T.TEST +TREND = LINTREND +TRIMMEAN = TRIMMEAN +VAR.P = VAR.P +VAR.S = VAR.S +VARA = VARA +VARPA = VARPA +WEIBULL.DIST = WEIBULL.DIST +Z.TEST = Z.TEST ## -## Statistical functions Statistické funkce +## Textové funkce (Text Functions) ## -AVEDEV = PRŮMODCHYLKA ## Vrátí průměrnou hodnotu absolutních odchylek datových bodů od jejich střední hodnoty. -AVERAGE = PRŮMĚR ## Vrátí průměrnou hodnotu argumentů. -AVERAGEA = AVERAGEA ## Vrátí průměrnou hodnotu argumentů včetně čísel, textu a logických hodnot. -AVERAGEIF = AVERAGEIF ## Vrátí průměrnou hodnotu (aritmetický průměr) všech buněk v oblasti, které vyhovují příslušné podmínce. -AVERAGEIFS = AVERAGEIFS ## Vrátí průměrnou hodnotu (aritmetický průměr) všech buněk vyhovujících několika podmínkám. -BETADIST = BETADIST ## Vrátí hodnotu součtového rozdělení beta. -BETAINV = BETAINV ## Vrátí inverzní hodnotu součtového rozdělení pro zadané rozdělení beta. -BINOMDIST = BINOMDIST ## Vrátí hodnotu binomického rozdělení pravděpodobnosti jednotlivých veličin. -CHIDIST = CHIDIST ## Vrátí jednostrannou pravděpodobnost rozdělení chí-kvadrát. -CHIINV = CHIINV ## Vrátí hodnotu funkce inverzní k distribuční funkci jednostranné pravděpodobnosti rozdělení chí-kvadrát. -CHITEST = CHITEST ## Vrátí test nezávislosti. -CONFIDENCE = CONFIDENCE ## Vrátí interval spolehlivosti pro střední hodnotu základního souboru. -CORREL = CORREL ## Vrátí korelační koeficient mezi dvěma množinami dat. -COUNT = POČET ## Vrátí počet čísel v seznamu argumentů. -COUNTA = POČET2 ## Vrátí počet hodnot v seznamu argumentů. -COUNTBLANK = COUNTBLANK ## Spočítá počet prázdných buněk v oblasti. -COUNTIF = COUNTIF ## Spočítá buňky v oblasti, které odpovídají zadaným kritériím. -COUNTIFS = COUNTIFS ## Spočítá buňky v oblasti, které odpovídají více kritériím. -COVAR = COVAR ## Vrátí hodnotu kovariance, průměrnou hodnotu součinů párových odchylek -CRITBINOM = CRITBINOM ## Vrátí nejmenší hodnotu, pro kterou má součtové binomické rozdělení hodnotu větší nebo rovnu hodnotě kritéria. -DEVSQ = DEVSQ ## Vrátí součet čtverců odchylek. -EXPONDIST = EXPONDIST ## Vrátí hodnotu exponenciálního rozdělení. -FDIST = FDIST ## Vrátí hodnotu rozdělení pravděpodobnosti F. -FINV = FINV ## Vrátí hodnotu inverzní funkce k distribuční funkci rozdělení F. -FISHER = FISHER ## Vrátí hodnotu Fisherovy transformace. -FISHERINV = FISHERINV ## Vrátí hodnotu inverzní funkce k Fisherově transformaci. -FORECAST = FORECAST ## Vrátí hodnotu lineárního trendu. -FREQUENCY = ČETNOSTI ## Vrátí četnost rozdělení jako svislou matici. -FTEST = FTEST ## Vrátí výsledek F-testu. -GAMMADIST = GAMMADIST ## Vrátí hodnotu rozdělení gama. -GAMMAINV = GAMMAINV ## Vrátí hodnotu inverzní funkce k distribuční funkci součtového rozdělení gama. -GAMMALN = GAMMALN ## Vrátí přirozený logaritmus funkce gama, Γ(x). -GEOMEAN = GEOMEAN ## Vrátí geometrický průměr. -GROWTH = LOGLINTREND ## Vrátí hodnoty exponenciálního trendu. -HARMEAN = HARMEAN ## Vrátí harmonický průměr. -HYPGEOMDIST = HYPGEOMDIST ## Vrátí hodnotu hypergeometrického rozdělení. -INTERCEPT = INTERCEPT ## Vrátí úsek lineární regresní čáry. -KURT = KURT ## Vrátí hodnotu excesu množiny dat. -LARGE = LARGE ## Vrátí k-tou největší hodnotu množiny dat. -LINEST = LINREGRESE ## Vrátí parametry lineárního trendu. -LOGEST = LOGLINREGRESE ## Vrátí parametry exponenciálního trendu. -LOGINV = LOGINV ## Vrátí inverzní funkci k distribuční funkci logaritmicko-normálního rozdělení. -LOGNORMDIST = LOGNORMDIST ## Vrátí hodnotu součtového logaritmicko-normálního rozdělení. -MAX = MAX ## Vrátí maximální hodnotu seznamu argumentů. -MAXA = MAXA ## Vrátí maximální hodnotu seznamu argumentů včetně čísel, textu a logických hodnot. -MEDIAN = MEDIAN ## Vrátí střední hodnotu zadaných čísel. -MIN = MIN ## Vrátí minimální hodnotu seznamu argumentů. -MINA = MINA ## Vrátí nejmenší hodnotu v seznamu argumentů včetně čísel, textu a logických hodnot. -MODE = MODE ## Vrátí hodnotu, která se v množině dat vyskytuje nejčastěji. -NEGBINOMDIST = NEGBINOMDIST ## Vrátí hodnotu negativního binomického rozdělení. -NORMDIST = NORMDIST ## Vrátí hodnotu normálního součtového rozdělení. -NORMINV = NORMINV ## Vrátí inverzní funkci k funkci normálního součtového rozdělení. -NORMSDIST = NORMSDIST ## Vrátí hodnotu standardního normálního součtového rozdělení. -NORMSINV = NORMSINV ## Vrátí inverzní funkci k funkci standardního normálního součtového rozdělení. -PEARSON = PEARSON ## Vrátí Pearsonův výsledný momentový korelační koeficient. -PERCENTILE = PERCENTIL ## Vrátí hodnotu k-tého percentilu hodnot v oblasti. -PERCENTRANK = PERCENTRANK ## Vrátí pořadí hodnoty v množině dat vyjádřené procentuální částí množiny dat. -PERMUT = PERMUTACE ## Vrátí počet permutací pro zadaný počet objektů. -POISSON = POISSON ## Vrátí hodnotu distribuční funkce Poissonova rozdělení. -PROB = PROB ## Vrátí pravděpodobnost výskytu hodnot v oblasti mezi dvěma mezními hodnotami. -QUARTILE = QUARTIL ## Vrátí hodnotu kvartilu množiny dat. -RANK = RANK ## Vrátí pořadí čísla v seznamu čísel. -RSQ = RKQ ## Vrátí druhou mocninu Pearsonova výsledného momentového korelačního koeficientu. -SKEW = SKEW ## Vrátí zešikmení rozdělení. -SLOPE = SLOPE ## Vrátí směrnici lineární regresní čáry. -SMALL = SMALL ## Vrátí k-tou nejmenší hodnotu množiny dat. -STANDARDIZE = STANDARDIZE ## Vrátí normalizovanou hodnotu. -STDEV = SMODCH.VÝBĚR ## Vypočte směrodatnou odchylku výběru. -STDEVA = STDEVA ## Vypočte směrodatnou odchylku výběru včetně čísel, textu a logických hodnot. -STDEVP = SMODCH ## Vypočte směrodatnou odchylku základního souboru. -STDEVPA = STDEVPA ## Vypočte směrodatnou odchylku základního souboru včetně čísel, textu a logických hodnot. -STEYX = STEYX ## Vrátí standardní chybu předpovězené hodnoty y pro každou hodnotu x v regresi. -TDIST = TDIST ## Vrátí hodnotu Studentova t-rozdělení. -TINV = TINV ## Vrátí inverzní funkci k distribuční funkci Studentova t-rozdělení. -TREND = LINTREND ## Vrátí hodnoty lineárního trendu. -TRIMMEAN = TRIMMEAN ## Vrátí střední hodnotu vnitřní části množiny dat. -TTEST = TTEST ## Vrátí pravděpodobnost spojenou se Studentovým t-testem. -VAR = VAR.VÝBĚR ## Vypočte rozptyl výběru. -VARA = VARA ## Vypočte rozptyl výběru včetně čísel, textu a logických hodnot. -VARP = VAR ## Vypočte rozptyl základního souboru. -VARPA = VARPA ## Vypočte rozptyl základního souboru včetně čísel, textu a logických hodnot. -WEIBULL = WEIBULL ## Vrátí hodnotu Weibullova rozdělení. -ZTEST = ZTEST ## Vrátí jednostrannou P-hodnotu z-testu. - +BAHTTEXT = BAHTTEXT +CHAR = ZNAK +CLEAN = VYČISTIT +CODE = KÓD +CONCAT = CONCAT +DOLLAR = KČ +EXACT = STEJNÉ +FIND = NAJÍT +FIXED = ZAOKROUHLIT.NA.TEXT +LEFT = ZLEVA +LEN = DÉLKA +LOWER = MALÁ +MID = ČÁST +NUMBERVALUE = NUMBERVALUE +PHONETIC = ZVUKOVÉ +PROPER = VELKÁ2 +REPLACE = NAHRADIT +REPT = OPAKOVAT +RIGHT = ZPRAVA +SEARCH = HLEDAT +SUBSTITUTE = DOSADIT +T = T +TEXT = HODNOTA.NA.TEXT +TEXTJOIN = TEXTJOIN +TRIM = PROČISTIT +UNICHAR = UNICHAR +UNICODE = UNICODE +UPPER = VELKÁ +VALUE = HODNOTA ## -## Text functions Textové funkce +## Webové funkce (Web Functions) ## -ASC = ASC ## Změní znaky s plnou šířkou (dvoubajtové)v řetězci znaků na znaky s poloviční šířkou (jednobajtové). -BAHTTEXT = BAHTTEXT ## Převede číslo na text ve formátu, měny ß (baht). -CHAR = ZNAK ## Vrátí znak určený číslem kódu. -CLEAN = VYČISTIT ## Odebere z textu všechny netisknutelné znaky. -CODE = KÓD ## Vrátí číselný kód prvního znaku zadaného textového řetězce. -CONCATENATE = CONCATENATE ## Spojí několik textových položek do jedné. -DOLLAR = KČ ## Převede číslo na text ve formátu měny Kč (česká koruna). -EXACT = STEJNÉ ## Zkontroluje, zda jsou dvě textové hodnoty shodné. -FIND = NAJÍT ## Nalezne textovou hodnotu uvnitř jiné (rozlišuje malá a velká písmena). -FINDB = FINDB ## Nalezne textovou hodnotu uvnitř jiné (rozlišuje malá a velká písmena). -FIXED = ZAOKROUHLIT.NA.TEXT ## Zformátuje číslo jako text s pevným počtem desetinných míst. -JIS = JIS ## Změní znaky s poloviční šířkou (jednobajtové) v řetězci znaků na znaky s plnou šířkou (dvoubajtové). -LEFT = ZLEVA ## Vrátí první znaky textové hodnoty umístěné nejvíce vlevo. -LEFTB = LEFTB ## Vrátí první znaky textové hodnoty umístěné nejvíce vlevo. -LEN = DÉLKA ## Vrátí počet znaků textového řetězce. -LENB = LENB ## Vrátí počet znaků textového řetězce. -LOWER = MALÁ ## Převede text na malá písmena. -MID = ČÁST ## Vrátí určitý počet znaků textového řetězce počínaje zadaným místem. -MIDB = MIDB ## Vrátí určitý počet znaků textového řetězce počínaje zadaným místem. -PHONETIC = ZVUKOVÉ ## Extrahuje fonetické znaky (furigana) z textového řetězce. -PROPER = VELKÁ2 ## Převede první písmeno každého slova textové hodnoty na velké. -REPLACE = NAHRADIT ## Nahradí znaky uvnitř textu. -REPLACEB = NAHRADITB ## Nahradí znaky uvnitř textu. -REPT = OPAKOVAT ## Zopakuje text podle zadaného počtu opakování. -RIGHT = ZPRAVA ## Vrátí první znaky textové hodnoty umístěné nejvíce vpravo. -RIGHTB = RIGHTB ## Vrátí první znaky textové hodnoty umístěné nejvíce vpravo. -SEARCH = HLEDAT ## Nalezne textovou hodnotu uvnitř jiné (malá a velká písmena nejsou rozlišována). -SEARCHB = SEARCHB ## Nalezne textovou hodnotu uvnitř jiné (malá a velká písmena nejsou rozlišována). -SUBSTITUTE = DOSADIT ## V textovém řetězci nahradí starý text novým. -T = T ## Převede argumenty na text. -TEXT = HODNOTA.NA.TEXT ## Zformátuje číslo a převede ho na text. -TRIM = PROČISTIT ## Odstraní z textu mezery. -UPPER = VELKÁ ## Převede text na velká písmena. -VALUE = HODNOTA ## Převede textový argument na číslo. +ENCODEURL = ENCODEURL +FILTERXML = FILTERXML +WEBSERVICE = WEBSERVICE + +## +## Funkce pro kompatibilitu (Compatibility Functions) +## +BETADIST = BETADIST +BETAINV = BETAINV +BINOMDIST = BINOMDIST +CEILING = ZAOKR.NAHORU +CHIDIST = CHIDIST +CHIINV = CHIINV +CHITEST = CHITEST +CONCATENATE = CONCATENATE +CONFIDENCE = CONFIDENCE +COVAR = COVAR +CRITBINOM = CRITBINOM +EXPONDIST = EXPONDIST +FDIST = FDIST +FINV = FINV +FLOOR = ZAOKR.DOLŮ +FORECAST = FORECAST +FTEST = FTEST +GAMMADIST = GAMMADIST +GAMMAINV = GAMMAINV +HYPGEOMDIST = HYPGEOMDIST +LOGINV = LOGINV +LOGNORMDIST = LOGNORMDIST +MODE = MODE +NEGBINOMDIST = NEGBINOMDIST +NORMDIST = NORMDIST +NORMINV = NORMINV +NORMSDIST = NORMSDIST +NORMSINV = NORMSINV +PERCENTILE = PERCENTIL +PERCENTRANK = PERCENTRANK +POISSON = POISSON +QUARTILE = QUARTIL +RANK = RANK +STDEV = SMODCH.VÝBĚR +STDEVP = SMODCH +TDIST = TDIST +TINV = TINV +TTEST = TTEST +VAR = VAR.VÝBĚR +VARP = VAR +WEIBULL = WEIBULL +ZTEST = ZTEST diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/da/config b/PhpOffice/PhpSpreadsheet/Calculation/locale/da/config old mode 100755 new mode 100644 index a7aa8fe..284b249 --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/da/config +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/da/config @@ -1,25 +1,20 @@ +############################################################ ## -## PhpSpreadsheet +## PhpSpreadsheet - locale settings ## +## Dansk (Danish) +## +############################################################ -ArgumentSeparator = ; - +ArgumentSeparator = ; ## -## (For future use) +## Error Codes ## -currencySymbol = kr - - - -## -## Excel Error Codes (For future use) - -## -NULL = #NUL! -DIV0 = #DIVISION/0! -VALUE = #VÆRDI! -REF = #REFERENCE! -NAME = #NAVN? -NUM = #NUM! -NA = #I/T +NULL = #NUL! +DIV0 +VALUE = #VÆRDI! +REF = #REFERENCE! +NAME = #NAVN? +NUM +NA = #I/T diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/da/functions b/PhpOffice/PhpSpreadsheet/Calculation/locale/da/functions old mode 100755 new mode 100644 index d02aa2e..03b68c9 --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/da/functions +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/da/functions @@ -1,416 +1,538 @@ +############################################################ ## -## PhpSpreadsheet -## -## Data in this file derived from https://www.excel-function-translation.com/ +## PhpSpreadsheet - function name translations ## +## Dansk (Danish) ## +############################################################ ## -## Add-in and Automation functions Tilføjelsesprogram- og automatiseringsfunktioner +## Kubefunktioner (Cube Functions) ## -GETPIVOTDATA = HENTPIVOTDATA ## Returnerer data, der er lagret i en pivottabelrapport - +CUBEKPIMEMBER = KUBE.KPI.MEDLEM +CUBEMEMBER = KUBEMEDLEM +CUBEMEMBERPROPERTY = KUBEMEDLEM.EGENSKAB +CUBERANKEDMEMBER = KUBERANGERET.MEDLEM +CUBESET = KUBESÆT +CUBESETCOUNT = KUBESÆT.ANTAL +CUBEVALUE = KUBEVÆRDI ## -## Cube functions Kubefunktioner +## Databasefunktioner (Database Functions) ## -CUBEKPIMEMBER = KUBE.KPI.MEDLEM ## Returnerer navn, egenskab og mål for en KPI-indikator og viser navnet og egenskaben i cellen. En KPI-indikator er en målbar størrelse, f.eks. bruttooverskud pr. måned eller personaleudskiftning pr. kvartal, der bruges til at overvåge en organisations præstationer. -CUBEMEMBER = KUBE.MEDLEM ## Returnerer et medlem eller en tupel fra kubehierarkiet. Bruges til at validere, om et medlem eller en tupel findes i kuben. -CUBEMEMBERPROPERTY = KUBEMEDLEM.EGENSKAB ## Returnerer værdien af en egenskab for et medlem i kuben. Bruges til at validere, om et medlemsnavn findes i kuben, og returnere den angivne egenskab for medlemmet. -CUBERANKEDMEMBER = KUBEMEDLEM.RANG ## Returnerer det n'te eller rangordnede medlem i et sæt. Bruges til at returnere et eller flere elementer i et sæt, f.eks. topsælgere eller de 10 bedste elever. -CUBESET = KUBESÆT ## Definerer et beregnet sæt medlemmer eller tupler ved at sende et sætudtryk til kuben på serveren, som opretter sættet og returnerer det til Microsoft Office Excel. -CUBESETCOUNT = KUBESÆT.TÆL ## Returnerer antallet af elementer i et sæt. -CUBEVALUE = KUBEVÆRDI ## Returnerer en sammenlagt (aggregeret) værdi fra en kube. - +DAVERAGE = DMIDDEL +DCOUNT = DTÆL +DCOUNTA = DTÆLV +DGET = DHENT +DMAX = DMAKS +DMIN = DMIN +DPRODUCT = DPRODUKT +DSTDEV = DSTDAFV +DSTDEVP = DSTDAFVP +DSUM = DSUM +DVAR = DVARIANS +DVARP = DVARIANSP ## -## Database functions Databasefunktioner +## Dato- og klokkeslætfunktioner (Date & Time Functions) ## -DAVERAGE = DMIDDEL ## Returnerer gennemsnittet af markerede databaseposter -DCOUNT = DTÆL ## Tæller de celler, der indeholder tal, i en database -DCOUNTA = DTÆLV ## Tæller udfyldte celler i en database -DGET = DHENT ## Uddrager en enkelt post, der opfylder de angivne kriterier, fra en database -DMAX = DMAKS ## Returnerer den største værdi blandt markerede databaseposter -DMIN = DMIN ## Returnerer den mindste værdi blandt markerede databaseposter -DPRODUCT = DPRODUKT ## Ganger værdierne i et bestemt felt med poster, der opfylder kriterierne i en database -DSTDEV = DSTDAFV ## Beregner et skøn over standardafvigelsen baseret på en stikprøve af markerede databaseposter -DSTDEVP = DSTDAFVP ## Beregner standardafvigelsen baseret på hele populationen af markerede databaseposter -DSUM = DSUM ## Sammenlægger de tal i feltkolonnen i databasen, der opfylder kriterierne -DVAR = DVARIANS ## Beregner varians baseret på en stikprøve af markerede databaseposter -DVARP = DVARIANSP ## Beregner varians baseret på hele populationen af markerede databaseposter - +DATE = DATO +DATEDIF = DATO.FORSKEL +DATESTRING = DATOSTRENG +DATEVALUE = DATOVÆRDI +DAY = DAG +DAYS = DAGE +DAYS360 = DAGE360 +EDATE = EDATO +EOMONTH = SLUT.PÅ.MÅNED +HOUR = TIME +ISOWEEKNUM = ISOUGE.NR +MINUTE = MINUT +MONTH = MÅNED +NETWORKDAYS = ANTAL.ARBEJDSDAGE +NETWORKDAYS.INTL = ANTAL.ARBEJDSDAGE.INTL +NOW = NU +SECOND = SEKUND +THAIDAYOFWEEK = THAILANDSKUGEDAG +THAIMONTHOFYEAR = THAILANDSKMÅNED +THAIYEAR = THAILANDSKÅR +TIME = TID +TIMEVALUE = TIDSVÆRDI +TODAY = IDAG +WEEKDAY = UGEDAG +WEEKNUM = UGE.NR +WORKDAY = ARBEJDSDAG +WORKDAY.INTL = ARBEJDSDAG.INTL +YEAR = ÅR +YEARFRAC = ÅR.BRØK ## -## Date and time functions Dato- og klokkeslætsfunktioner +## Tekniske funktioner (Engineering Functions) ## -DATE = DATO ## Returnerer serienummeret for en bestemt dato -DATEVALUE = DATOVÆRDI ## Konverterer en dato i form af tekst til et serienummer -DAY = DAG ## Konverterer et serienummer til en dag i måneden -DAYS360 = DAGE360 ## Beregner antallet af dage mellem to datoer på grundlag af et år med 360 dage -EDATE = EDATO ## Returnerer serienummeret for den dato, der ligger det angivne antal måneder før eller efter startdatoen -EOMONTH = SLUT.PÅ.MÅNED ## Returnerer serienummeret på den sidste dag i måneden før eller efter et angivet antal måneder -HOUR = TIME ## Konverterer et serienummer til en time -MINUTE = MINUT ## Konverterer et serienummer til et minut -MONTH = MÅNED ## Konverterer et serienummer til en måned -NETWORKDAYS = ANTAL.ARBEJDSDAGE ## Returnerer antallet af hele arbejdsdage mellem to datoer -NOW = NU ## Returnerer serienummeret for den aktuelle dato eller det aktuelle klokkeslæt -SECOND = SEKUND ## Konverterer et serienummer til et sekund -TIME = KLOKKESLÆT ## Returnerer serienummeret for et bestemt klokkeslæt -TIMEVALUE = TIDSVÆRDI ## Konverterer et klokkeslæt i form af tekst til et serienummer -TODAY = IDAG ## Returnerer serienummeret for dags dato -WEEKDAY = UGEDAG ## Konverterer et serienummer til en ugedag -WEEKNUM = UGE.NR ## Konverterer et serienummer til et tal, der angiver ugenummeret i året -WORKDAY = ARBEJDSDAG ## Returnerer serienummeret for dagen før eller efter det angivne antal arbejdsdage -YEAR = ÅR ## Konverterer et serienummer til et år -YEARFRAC = ÅR.BRØK ## Returnerer årsbrøken, der repræsenterer antallet af hele dage mellem startdato og slutdato - +BESSELI = BESSELI +BESSELJ = BESSELJ +BESSELK = BESSELK +BESSELY = BESSELY +BIN2DEC = BIN.TIL.DEC +BIN2HEX = BIN.TIL.HEX +BIN2OCT = BIN.TIL.OKT +BITAND = BITOG +BITLSHIFT = BITLSKIFT +BITOR = BITELLER +BITRSHIFT = BITRSKIFT +BITXOR = BITXELLER +COMPLEX = KOMPLEKS +CONVERT = KONVERTER +DEC2BIN = DEC.TIL.BIN +DEC2HEX = DEC.TIL.HEX +DEC2OCT = DEC.TIL.OKT +DELTA = DELTA +ERF = FEJLFUNK +ERF.PRECISE = ERF.PRECISE +ERFC = FEJLFUNK.KOMP +ERFC.PRECISE = ERFC.PRECISE +GESTEP = GETRIN +HEX2BIN = HEX.TIL.BIN +HEX2DEC = HEX.TIL.DEC +HEX2OCT = HEX.TIL.OKT +IMABS = IMAGABS +IMAGINARY = IMAGINÆR +IMARGUMENT = IMAGARGUMENT +IMCONJUGATE = IMAGKONJUGERE +IMCOS = IMAGCOS +IMCOSH = IMAGCOSH +IMCOT = IMAGCOT +IMCSC = IMAGCSC +IMCSCH = IMAGCSCH +IMDIV = IMAGDIV +IMEXP = IMAGEKSP +IMLN = IMAGLN +IMLOG10 = IMAGLOG10 +IMLOG2 = IMAGLOG2 +IMPOWER = IMAGPOTENS +IMPRODUCT = IMAGPRODUKT +IMREAL = IMAGREELT +IMSEC = IMAGSEC +IMSECH = IMAGSECH +IMSIN = IMAGSIN +IMSINH = IMAGSINH +IMSQRT = IMAGKVROD +IMSUB = IMAGSUB +IMSUM = IMAGSUM +IMTAN = IMAGTAN +OCT2BIN = OKT.TIL.BIN +OCT2DEC = OKT.TIL.DEC +OCT2HEX = OKT.TIL.HEX ## -## Engineering functions Tekniske funktioner +## Finansielle funktioner (Financial Functions) ## -BESSELI = BESSELI ## Returnerer den modificerede Bessel-funktion In(x) -BESSELJ = BESSELJ ## Returnerer Bessel-funktionen Jn(x) -BESSELK = BESSELK ## Returnerer den modificerede Bessel-funktion Kn(x) -BESSELY = BESSELY ## Returnerer Bessel-funktionen Yn(x) -BIN2DEC = BIN.TIL.DEC ## Konverterer et binært tal til et decimaltal -BIN2HEX = BIN.TIL.HEX ## Konverterer et binært tal til et heksadecimalt tal -BIN2OCT = BIN.TIL.OKT ## Konverterer et binært tal til et oktaltal. -COMPLEX = KOMPLEKS ## Konverterer reelle og imaginære koefficienter til et komplekst tal -CONVERT = KONVERTER ## Konverterer et tal fra én måleenhed til en anden -DEC2BIN = DEC.TIL.BIN ## Konverterer et decimaltal til et binært tal -DEC2HEX = DEC.TIL.HEX ## Konverterer et decimaltal til et heksadecimalt tal -DEC2OCT = DEC.TIL.OKT ## Konverterer et decimaltal til et oktaltal -DELTA = DELTA ## Tester, om to værdier er ens -ERF = FEJLFUNK ## Returner fejlfunktionen -ERFC = FEJLFUNK.KOMP ## Returnerer den komplementære fejlfunktion -GESTEP = GETRIN ## Tester, om et tal er større end en grænseværdi -HEX2BIN = HEX.TIL.BIN ## Konverterer et heksadecimalt tal til et binært tal -HEX2DEC = HEX.TIL.DEC ## Konverterer et decimaltal til et heksadecimalt tal -HEX2OCT = HEX.TIL.OKT ## Konverterer et heksadecimalt tal til et oktaltal -IMABS = IMAGABS ## Returnerer den absolutte værdi (modulus) for et komplekst tal -IMAGINARY = IMAGINÆR ## Returnerer den imaginære koefficient for et komplekst tal -IMARGUMENT = IMAGARGUMENT ## Returnerer argumentet theta, en vinkel udtrykt i radianer -IMCONJUGATE = IMAGKONJUGERE ## Returnerer den komplekse konjugation af et komplekst tal -IMCOS = IMAGCOS ## Returnerer et komplekst tals cosinus -IMDIV = IMAGDIV ## Returnerer kvotienten for to komplekse tal -IMEXP = IMAGEKSP ## Returnerer et komplekst tals eksponentialfunktion -IMLN = IMAGLN ## Returnerer et komplekst tals naturlige logaritme -IMLOG10 = IMAGLOG10 ## Returnerer et komplekst tals sædvanlige logaritme (titalslogaritme) -IMLOG2 = IMAGLOG2 ## Returnerer et komplekst tals sædvanlige logaritme (totalslogaritme) -IMPOWER = IMAGPOTENS ## Returnerer et komplekst tal opløftet i en heltalspotens -IMPRODUCT = IMAGPRODUKT ## Returnerer produktet af komplekse tal -IMREAL = IMAGREELT ## Returnerer den reelle koefficient for et komplekst tal -IMSIN = IMAGSIN ## Returnerer et komplekst tals sinus -IMSQRT = IMAGKVROD ## Returnerer et komplekst tals kvadratrod -IMSUB = IMAGSUB ## Returnerer forskellen mellem to komplekse tal -IMSUM = IMAGSUM ## Returnerer summen af komplekse tal -OCT2BIN = OKT.TIL.BIN ## Konverterer et oktaltal til et binært tal -OCT2DEC = OKT.TIL.DEC ## Konverterer et oktaltal til et decimaltal -OCT2HEX = OKT.TIL.HEX ## Konverterer et oktaltal til et heksadecimalt tal - +ACCRINT = PÅLØBRENTE +ACCRINTM = PÅLØBRENTE.UDLØB +AMORDEGRC = AMORDEGRC +AMORLINC = AMORLINC +COUPDAYBS = KUPONDAGE.SA +COUPDAYS = KUPONDAGE.A +COUPDAYSNC = KUPONDAGE.ANK +COUPNCD = KUPONDAG.NÆSTE +COUPNUM = KUPONBETALINGER +COUPPCD = KUPONDAG.FORRIGE +CUMIPMT = AKKUM.RENTE +CUMPRINC = AKKUM.HOVEDSTOL +DB = DB +DDB = DSA +DISC = DISKONTO +DOLLARDE = KR.DECIMAL +DOLLARFR = KR.BRØK +DURATION = VARIGHED +EFFECT = EFFEKTIV.RENTE +FV = FV +FVSCHEDULE = FVTABEL +INTRATE = RENTEFOD +IPMT = R.YDELSE +IRR = IA +ISPMT = ISPMT +MDURATION = MVARIGHED +MIRR = MIA +NOMINAL = NOMINEL +NPER = NPER +NPV = NUTIDSVÆRDI +ODDFPRICE = ULIGE.KURS.PÅLYDENDE +ODDFYIELD = ULIGE.FØRSTE.AFKAST +ODDLPRICE = ULIGE.SIDSTE.KURS +ODDLYIELD = ULIGE.SIDSTE.AFKAST +PDURATION = PVARIGHED +PMT = YDELSE +PPMT = H.YDELSE +PRICE = KURS +PRICEDISC = KURS.DISKONTO +PRICEMAT = KURS.UDLØB +PV = NV +RATE = RENTE +RECEIVED = MODTAGET.VED.UDLØB +RRI = RRI +SLN = LA +SYD = ÅRSAFSKRIVNING +TBILLEQ = STATSOBLIGATION +TBILLPRICE = STATSOBLIGATION.KURS +TBILLYIELD = STATSOBLIGATION.AFKAST +VDB = VSA +XIRR = INTERN.RENTE +XNPV = NETTO.NUTIDSVÆRDI +YIELD = AFKAST +YIELDDISC = AFKAST.DISKONTO +YIELDMAT = AFKAST.UDLØBSDATO ## -## Financial functions Finansielle funktioner +## Informationsfunktioner (Information Functions) ## -ACCRINT = PÅLØBRENTE ## Returnerer den påløbne rente for et værdipapir med periodiske renteudbetalinger -ACCRINTM = PÅLØBRENTE.UDLØB ## Returnerer den påløbne rente for et værdipapir, hvor renteudbetalingen finder sted ved papirets udløb -AMORDEGRC = AMORDEGRC ## Returnerer afskrivningsbeløbet for hver regnskabsperiode ved hjælp af en afskrivningskoefficient -AMORLINC = AMORLINC ## Returnerer afskrivningsbeløbet for hver regnskabsperiode -COUPDAYBS = KUPONDAGE.SA ## Returnerer antallet af dage fra starten af kuponperioden til afregningsdatoen -COUPDAYS = KUPONDAGE.A ## Returnerer antallet af dage fra begyndelsen af kuponperioden til afregningsdatoen -COUPDAYSNC = KUPONDAGE.ANK ## Returnerer antallet af dage i den kuponperiode, der indeholder afregningsdatoen -COUPNCD = KUPONDAG.NÆSTE ## Returnerer den næste kupondato efter afregningsdatoen -COUPNUM = KUPONBETALINGER ## Returnerer antallet af kuponudbetalinger mellem afregnings- og udløbsdatoen -COUPPCD = KUPONDAG.FORRIGE ## Returnerer den forrige kupondato før afregningsdatoen -CUMIPMT = AKKUM.RENTE ## Returnerer den akkumulerede rente, der betales på et lån mellem to perioder -CUMPRINC = AKKUM.HOVEDSTOL ## Returnerer den akkumulerede nedbringelse af hovedstol mellem to perioder -DB = DB ## Returnerer afskrivningen på et aktiv i en angivet periode ved anvendelse af saldometoden -DDB = DSA ## Returnerer afskrivningsbeløbet for et aktiv over en bestemt periode ved anvendelse af dobbeltsaldometoden eller en anden afskrivningsmetode, som du angiver -DISC = DISKONTO ## Returnerer et værdipapirs diskonto -DOLLARDE = KR.DECIMAL ## Konverterer en kronepris udtrykt som brøk til en kronepris udtrykt som decimaltal -DOLLARFR = KR.BRØK ## Konverterer en kronepris udtrykt som decimaltal til en kronepris udtrykt som brøk -DURATION = VARIGHED ## Returnerer den årlige løbetid for et værdipapir med periodiske renteudbetalinger -EFFECT = EFFEKTIV.RENTE ## Returnerer den årlige effektive rente -FV = FV ## Returnerer fremtidsværdien af en investering -FVSCHEDULE = FVTABEL ## Returnerer den fremtidige værdi af en hovedstol, når der er tilskrevet rente og rentes rente efter forskellige rentesatser -INTRATE = RENTEFOD ## Returnerer renten på et fuldt ud investeret værdipapir -IPMT = R.YDELSE ## Returnerer renten fra en investering for en given periode -IRR = IA ## Returnerer den interne rente for en række pengestrømme -ISPMT = ISPMT ## Beregner den betalte rente i løbet af en bestemt investeringsperiode -MDURATION = MVARIGHED ## Returnerer Macauleys modificerede løbetid for et værdipapir med en formodet pari på kr. 100 -MIRR = MIA ## Returnerer den interne forrentning, hvor positive og negative pengestrømme finansieres til forskellig rente -NOMINAL = NOMINEL ## Returnerer den årlige nominelle rente -NPER = NPER ## Returnerer antallet af perioder for en investering -NPV = NUTIDSVÆRDI ## Returnerer nettonutidsværdien for en investering baseret på en række periodiske pengestrømme og en diskonteringssats -ODDFPRICE = ULIGE.KURS.PÅLYDENDE ## Returnerer kursen pr. kr. 100 nominel værdi for et værdipapir med en ulige (kort eller lang) første periode -ODDFYIELD = ULIGE.FØRSTE.AFKAST ## Returnerer afkastet for et værdipapir med ulige første periode -ODDLPRICE = ULIGE.SIDSTE.KURS ## Returnerer kursen pr. kr. 100 nominel værdi for et værdipapir med ulige sidste periode -ODDLYIELD = ULIGE.SIDSTE.AFKAST ## Returnerer afkastet for et værdipapir med ulige sidste periode -PMT = YDELSE ## Returnerer renten fra en investering for en given periode -PPMT = H.YDELSE ## Returnerer ydelsen på hovedstolen for en investering i en given periode -PRICE = KURS ## Returnerer kursen pr. kr 100 nominel værdi for et værdipapir med periodiske renteudbetalinger -PRICEDISC = KURS.DISKONTO ## Returnerer kursen pr. kr 100 nominel værdi for et diskonteret værdipapir -PRICEMAT = KURS.UDLØB ## Returnerer kursen pr. kr 100 nominel værdi for et værdipapir, hvor renten udbetales ved papirets udløb -PV = NV ## Returnerer den nuværende værdi af en investering -RATE = RENTE ## Returnerer renten i hver periode for en annuitet -RECEIVED = MODTAGET.VED.UDLØB ## Returnerer det beløb, der modtages ved udløbet af et fuldt ud investeret værdipapir -SLN = LA ## Returnerer den lineære afskrivning for et aktiv i en enkelt periode -SYD = ÅRSAFSKRIVNING ## Returnerer den årlige afskrivning på et aktiv i en bestemt periode -TBILLEQ = STATSOBLIGATION ## Returnerer det obligationsækvivalente afkast for en statsobligation -TBILLPRICE = STATSOBLIGATION.KURS ## Returnerer kursen pr. kr 100 nominel værdi for en statsobligation -TBILLYIELD = STATSOBLIGATION.AFKAST ## Returnerer en afkastet på en statsobligation -VDB = VSA ## Returnerer afskrivningen på et aktiv i en angivet periode, herunder delperioder, ved brug af dobbeltsaldometoden -XIRR = INTERN.RENTE ## Returnerer den interne rente for en plan over pengestrømme, der ikke behøver at være periodiske -XNPV = NETTO.NUTIDSVÆRDI ## Returnerer nutidsværdien for en plan over pengestrømme, der ikke behøver at være periodiske -YIELD = AFKAST ## Returnerer afkastet for et værdipapir med periodiske renteudbetalinger -YIELDDISC = AFKAST.DISKONTO ## Returnerer det årlige afkast for et diskonteret værdipapir, f.eks. en statsobligation -YIELDMAT = AFKAST.UDLØBSDATO ## Returnerer det årlige afkast for et værdipapir, hvor renten udbetales ved papirets udløb - +CELL = CELLE +ERROR.TYPE = FEJLTYPE +INFO = INFO +ISBLANK = ER.TOM +ISERR = ER.FJL +ISERROR = ER.FEJL +ISEVEN = ER.LIGE +ISFORMULA = ER.FORMEL +ISLOGICAL = ER.LOGISK +ISNA = ER.IKKE.TILGÆNGELIG +ISNONTEXT = ER.IKKE.TEKST +ISNUMBER = ER.TAL +ISODD = ER.ULIGE +ISREF = ER.REFERENCE +ISTEXT = ER.TEKST +N = TAL +NA = IKKE.TILGÆNGELIG +SHEET = ARK +SHEETS = ARK.FLERE +TYPE = VÆRDITYPE ## -## Information functions Informationsfunktioner +## Logiske funktioner (Logical Functions) ## -CELL = CELLE ## Returnerer oplysninger om formatering, placering eller indhold af en celle -ERROR.TYPE = FEJLTYPE ## Returnerer et tal, der svarer til en fejltype -INFO = INFO ## Returnerer oplysninger om det aktuelle operativmiljø -ISBLANK = ER.TOM ## Returnerer SAND, hvis værdien er tom -ISERR = ER.FJL ## Returnerer SAND, hvis værdien er en fejlværdi undtagen #I/T -ISERROR = ER.FEJL ## Returnerer SAND, hvis værdien er en fejlværdi -ISEVEN = ER.LIGE ## Returnerer SAND, hvis tallet er lige -ISLOGICAL = ER.LOGISK ## Returnerer SAND, hvis værdien er en logisk værdi -ISNA = ER.IKKE.TILGÆNGELIG ## Returnerer SAND, hvis værdien er fejlværdien #I/T -ISNONTEXT = ER.IKKE.TEKST ## Returnerer SAND, hvis værdien ikke er tekst -ISNUMBER = ER.TAL ## Returnerer SAND, hvis værdien er et tal -ISODD = ER.ULIGE ## Returnerer SAND, hvis tallet er ulige -ISREF = ER.REFERENCE ## Returnerer SAND, hvis værdien er en reference -ISTEXT = ER.TEKST ## Returnerer SAND, hvis værdien er tekst -N = TAL ## Returnerer en værdi konverteret til et tal -NA = IKKE.TILGÆNGELIG ## Returnerer fejlværdien #I/T -TYPE = VÆRDITYPE ## Returnerer et tal, der angiver datatypen for en værdi - +AND = OG +FALSE = FALSK +IF = HVIS +IFERROR = HVIS.FEJL +IFNA = HVISIT +IFS = HVISER +NOT = IKKE +OR = ELLER +SWITCH = SKIFT +TRUE = SAND +XOR = XELLER ## -## Logical functions Logiske funktioner +## Opslags- og referencefunktioner (Lookup & Reference Functions) ## -AND = OG ## Returnerer SAND, hvis alle argumenterne er sande -FALSE = FALSK ## Returnerer den logiske værdi FALSK -IF = HVIS ## Angiver en logisk test, der skal udføres -IFERROR = HVIS.FEJL ## Returnerer en værdi, du angiver, hvis en formel evauleres som en fejl. Returnerer i modsat fald resultatet af formlen -NOT = IKKE ## Vender argumentets logik om -OR = ELLER ## Returneret værdien SAND, hvis mindst ét argument er sandt -TRUE = SAND ## Returnerer den logiske værdi SAND - +ADDRESS = ADRESSE +AREAS = OMRÅDER +CHOOSE = VÆLG +COLUMN = KOLONNE +COLUMNS = KOLONNER +FORMULATEXT = FORMELTEKST +GETPIVOTDATA = GETPIVOTDATA +HLOOKUP = VOPSLAG +HYPERLINK = HYPERLINK +INDEX = INDEKS +INDIRECT = INDIREKTE +LOOKUP = SLÅ.OP +MATCH = SAMMENLIGN +OFFSET = FORSKYDNING +ROW = RÆKKE +ROWS = RÆKKER +RTD = RTD +TRANSPOSE = TRANSPONER +VLOOKUP = LOPSLAG +*RC = RK ## -## Lookup and reference functions Opslags- og referencefunktioner +## Matematiske og trigonometriske funktioner (Math & Trig Functions) ## -ADDRESS = ADRESSE ## Returnerer en reference som tekst til en enkelt celle i et regneark -AREAS = OMRÅDER ## Returnerer antallet af områder i en reference -CHOOSE = VÆLG ## Vælger en værdi på en liste med værdier -COLUMN = KOLONNE ## Returnerer kolonnenummeret i en reference -COLUMNS = KOLONNER ## Returnerer antallet af kolonner i en reference -HLOOKUP = VOPSLAG ## Søger i den øverste række af en matrix og returnerer værdien af den angivne celle -HYPERLINK = HYPERLINK ## Opretter en genvej kaldet et hyperlink, der åbner et dokument, som er lagret på en netværksserver, på et intranet eller på internettet -INDEX = INDEKS ## Anvender et indeks til at vælge en værdi fra en reference eller en matrix -INDIRECT = INDIREKTE ## Returnerer en reference, der er angivet af en tekstværdi -LOOKUP = SLÅ.OP ## Søger værdier i en vektor eller en matrix -MATCH = SAMMENLIGN ## Søger værdier i en reference eller en matrix -OFFSET = FORSKYDNING ## Returnerer en reference forskudt i forhold til en given reference -ROW = RÆKKE ## Returnerer rækkenummeret for en reference -ROWS = RÆKKER ## Returnerer antallet af rækker i en reference -RTD = RTD ## Henter realtidsdata fra et program, der understøtter COM-automatisering (Automation: En metode til at arbejde med objekter fra et andet program eller udviklingsværktøj. Automation, som tidligere blev kaldt OLE Automation, er en industristandard og en funktion i COM (Component Object Model).) -TRANSPOSE = TRANSPONER ## Returnerer en transponeret matrix -VLOOKUP = LOPSLAG ## Søger i øverste række af en matrix og flytter på tværs af rækken for at returnere en celleværdi - +ABS = ABS +ACOS = ARCCOS +ACOSH = ARCCOSH +ACOT = ARCCOT +ACOTH = ARCCOTH +AGGREGATE = SAMLING +ARABIC = ARABISK +ASIN = ARCSIN +ASINH = ARCSINH +ATAN = ARCTAN +ATAN2 = ARCTAN2 +ATANH = ARCTANH +BASE = BASIS +CEILING.MATH = LOFT.MAT +CEILING.PRECISE = LOFT.PRECISE +COMBIN = KOMBIN +COMBINA = KOMBINA +COS = COS +COSH = COSH +COT = COT +COTH = COTH +CSC = CSC +CSCH = CSCH +DECIMAL = DECIMAL +DEGREES = GRADER +ECMA.CEILING = ECMA.LOFT +EVEN = LIGE +EXP = EKSP +FACT = FAKULTET +FACTDOUBLE = DOBBELT.FAKULTET +FLOOR.MATH = AFRUND.BUND.MAT +FLOOR.PRECISE = AFRUND.GULV.PRECISE +GCD = STØRSTE.FÆLLES.DIVISOR +INT = HELTAL +ISO.CEILING = ISO.LOFT +LCM = MINDSTE.FÆLLES.MULTIPLUM +LN = LN +LOG = LOG +LOG10 = LOG10 +MDETERM = MDETERM +MINVERSE = MINVERT +MMULT = MPRODUKT +MOD = REST +MROUND = MAFRUND +MULTINOMIAL = MULTINOMIAL +MUNIT = MENHED +ODD = ULIGE +PI = PI +POWER = POTENS +PRODUCT = PRODUKT +QUOTIENT = KVOTIENT +RADIANS = RADIANER +RAND = SLUMP +RANDBETWEEN = SLUMPMELLEM +ROMAN = ROMERTAL +ROUND = AFRUND +ROUNDBAHTDOWN = RUNDBAHTNED +ROUNDBAHTUP = RUNDBAHTOP +ROUNDDOWN = RUND.NED +ROUNDUP = RUND.OP +SEC = SEC +SECH = SECH +SERIESSUM = SERIESUM +SIGN = FORTEGN +SIN = SIN +SINH = SINH +SQRT = KVROD +SQRTPI = KVRODPI +SUBTOTAL = SUBTOTAL +SUM = SUM +SUMIF = SUM.HVIS +SUMIFS = SUM.HVISER +SUMPRODUCT = SUMPRODUKT +SUMSQ = SUMKV +SUMX2MY2 = SUMX2MY2 +SUMX2PY2 = SUMX2PY2 +SUMXMY2 = SUMXMY2 +TAN = TAN +TANH = TANH +TRUNC = AFKORT ## -## Math and trigonometry functions Matematiske og trigonometriske funktioner +## Statistiske funktioner (Statistical Functions) ## -ABS = ABS ## Returnerer den absolutte værdi af et tal -ACOS = ARCCOS ## Returnerer et tals arcus cosinus -ACOSH = ARCCOSH ## Returnerer den inverse hyperbolske cosinus af tal -ASIN = ARCSIN ## Returnerer et tals arcus sinus -ASINH = ARCSINH ## Returnerer den inverse hyperbolske sinus for tal -ATAN = ARCTAN ## Returnerer et tals arcus tangens -ATAN2 = ARCTAN2 ## Returnerer de angivne x- og y-koordinaters arcus tangens -ATANH = ARCTANH ## Returnerer et tals inverse hyperbolske tangens -CEILING = AFRUND.LOFT ## Afrunder et tal til nærmeste heltal eller til nærmeste multiplum af betydning -COMBIN = KOMBIN ## Returnerer antallet af kombinationer for et givet antal objekter -COS = COS ## Returnerer et tals cosinus -COSH = COSH ## Returnerer den inverse hyperbolske cosinus af et tal -DEGREES = GRADER ## Konverterer radianer til grader -EVEN = LIGE ## Runder et tal op til nærmeste lige heltal -EXP = EKSP ## Returnerer e opløftet til en potens af et angivet tal -FACT = FAKULTET ## Returnerer et tals fakultet -FACTDOUBLE = DOBBELT.FAKULTET ## Returnerer et tals dobbelte fakultet -FLOOR = AFRUND.GULV ## Runder et tal ned mod nul -GCD = STØRSTE.FÆLLES.DIVISOR ## Returnerer den største fælles divisor -INT = HELTAL ## Nedrunder et tal til det nærmeste heltal -LCM = MINDSTE.FÆLLES.MULTIPLUM ## Returnerer det mindste fælles multiplum -LN = LN ## Returnerer et tals naturlige logaritme -LOG = LOG ## Returnerer logaritmen for et tal på grundlag af et angivet grundtal -LOG10 = LOG10 ## Returnerer titalslogaritmen af et tal -MDETERM = MDETERM ## Returnerer determinanten for en matrix -MINVERSE = MINVERT ## Returnerer den inverse matrix for en matrix -MMULT = MPRODUKT ## Returnerer matrixproduktet af to matrixer -MOD = REST ## Returnerer restværdien fra division -MROUND = MAFRUND ## Returnerer et tal afrundet til det ønskede multiplum -MULTINOMIAL = MULTINOMIAL ## Returnerer et multinomialt talsæt -ODD = ULIGE ## Runder et tal op til nærmeste ulige heltal -PI = PI ## Returnerer værdien af pi -POWER = POTENS ## Returnerer resultatet af et tal opløftet til en potens -PRODUCT = PRODUKT ## Multiplicerer argumenterne -QUOTIENT = KVOTIENT ## Returnerer heltalsdelen ved division -RADIANS = RADIANER ## Konverterer grader til radianer -RAND = SLUMP ## Returnerer et tilfældigt tal mellem 0 og 1 -RANDBETWEEN = SLUMP.MELLEM ## Returnerer et tilfældigt tal mellem de tal, der angives -ROMAN = ROMERTAL ## Konverterer et arabertal til romertal som tekst -ROUND = AFRUND ## Afrunder et tal til et angivet antal decimaler -ROUNDDOWN = RUND.NED ## Runder et tal ned mod nul -ROUNDUP = RUND.OP ## Runder et tal op, væk fra 0 (nul) -SERIESSUM = SERIESUM ## Returnerer summen af en potensserie baseret på en formel -SIGN = FORTEGN ## Returnerer et tals fortegn -SIN = SIN ## Returnerer en given vinkels sinusværdi -SINH = SINH ## Returnerer den hyperbolske sinus af et tal -SQRT = KVROD ## Returnerer en positiv kvadratrod -SQRTPI = KVRODPI ## Returnerer kvadratroden af (tal * pi;) -SUBTOTAL = SUBTOTAL ## Returnerer en subtotal på en liste eller i en database -SUM = SUM ## Lægger argumenterne sammen -SUMIF = SUM.HVIS ## Lægger de celler sammen, der er specificeret af et givet kriterium. -SUMIFS = SUM.HVISER ## Lægger de celler i et område sammen, der opfylder flere kriterier. -SUMPRODUCT = SUMPRODUKT ## Returnerer summen af produkter af ens matrixkomponenter -SUMSQ = SUMKV ## Returnerer summen af argumenternes kvadrater -SUMX2MY2 = SUMX2MY2 ## Returnerer summen af differensen mellem kvadrater af ens værdier i to matrixer -SUMX2PY2 = SUMX2PY2 ## Returnerer summen af summen af kvadrater af tilsvarende værdier i to matrixer -SUMXMY2 = SUMXMY2 ## Returnerer summen af kvadrater af differenser mellem ens værdier i to matrixer -TAN = TAN ## Returnerer et tals tangens -TANH = TANH ## Returnerer et tals hyperbolske tangens -TRUNC = AFKORT ## Afkorter et tal til et heltal - +AVEDEV = MAD +AVERAGE = MIDDEL +AVERAGEA = MIDDELV +AVERAGEIF = MIDDEL.HVIS +AVERAGEIFS = MIDDEL.HVISER +BETA.DIST = BETA.FORDELING +BETA.INV = BETA.INV +BINOM.DIST = BINOMIAL.FORDELING +BINOM.DIST.RANGE = BINOMIAL.DIST.INTERVAL +BINOM.INV = BINOMIAL.INV +CHISQ.DIST = CHI2.FORDELING +CHISQ.DIST.RT = CHI2.FORD.RT +CHISQ.INV = CHI2.INV +CHISQ.INV.RT = CHI2.INV.RT +CHISQ.TEST = CHI2.TEST +CONFIDENCE.NORM = KONFIDENS.NORM +CONFIDENCE.T = KONFIDENST +CORREL = KORRELATION +COUNT = TÆL +COUNTA = TÆLV +COUNTBLANK = ANTAL.BLANKE +COUNTIF = TÆL.HVIS +COUNTIFS = TÆL.HVISER +COVARIANCE.P = KOVARIANS.P +COVARIANCE.S = KOVARIANS.S +DEVSQ = SAK +EXPON.DIST = EKSP.FORDELING +F.DIST = F.FORDELING +F.DIST.RT = F.FORDELING.RT +F.INV = F.INV +F.INV.RT = F.INV.RT +F.TEST = F.TEST +FISHER = FISHER +FISHERINV = FISHERINV +FORECAST.ETS = PROGNOSE.ETS +FORECAST.ETS.CONFINT = PROGNOSE.ETS.CONFINT +FORECAST.ETS.SEASONALITY = PROGNOSE.ETS.SÆSONUDSVING +FORECAST.ETS.STAT = PROGNOSE.ETS.STAT +FORECAST.LINEAR = PROGNOSE.LINEÆR +FREQUENCY = FREKVENS +GAMMA = GAMMA +GAMMA.DIST = GAMMA.FORDELING +GAMMA.INV = GAMMA.INV +GAMMALN = GAMMALN +GAMMALN.PRECISE = GAMMALN.PRECISE +GAUSS = GAUSS +GEOMEAN = GEOMIDDELVÆRDI +GROWTH = FORØGELSE +HARMEAN = HARMIDDELVÆRDI +HYPGEOM.DIST = HYPGEO.FORDELING +INTERCEPT = SKÆRING +KURT = TOPSTEJL +LARGE = STØRSTE +LINEST = LINREGR +LOGEST = LOGREGR +LOGNORM.DIST = LOGNORM.FORDELING +LOGNORM.INV = LOGNORM.INV +MAX = MAKS +MAXA = MAKSV +MAXIFS = MAKSHVISER +MEDIAN = MEDIAN +MIN = MIN +MINA = MINV +MINIFS = MINHVISER +MODE.MULT = HYPPIGST.FLERE +MODE.SNGL = HYPPIGST.ENKELT +NEGBINOM.DIST = NEGBINOM.FORDELING +NORM.DIST = NORMAL.FORDELING +NORM.INV = NORM.INV +NORM.S.DIST = STANDARD.NORM.FORDELING +NORM.S.INV = STANDARD.NORM.INV +PEARSON = PEARSON +PERCENTILE.EXC = FRAKTIL.UDELAD +PERCENTILE.INC = FRAKTIL.MEDTAG +PERCENTRANK.EXC = PROCENTPLADS.UDELAD +PERCENTRANK.INC = PROCENTPLADS.MEDTAG +PERMUT = PERMUT +PERMUTATIONA = PERMUTATIONA +PHI = PHI +POISSON.DIST = POISSON.FORDELING +PROB = SANDSYNLIGHED +QUARTILE.EXC = KVARTIL.UDELAD +QUARTILE.INC = KVARTIL.MEDTAG +RANK.AVG = PLADS.GNSN +RANK.EQ = PLADS.LIGE +RSQ = FORKLARINGSGRAD +SKEW = SKÆVHED +SKEW.P = SKÆVHED.P +SLOPE = STIGNING +SMALL = MINDSTE +STANDARDIZE = STANDARDISER +STDEV.P = STDAFV.P +STDEV.S = STDAFV.S +STDEVA = STDAFVV +STDEVPA = STDAFVPV +STEYX = STFYX +T.DIST = T.FORDELING +T.DIST.2T = T.FORDELING.2T +T.DIST.RT = T.FORDELING.RT +T.INV = T.INV +T.INV.2T = T.INV.2T +T.TEST = T.TEST +TREND = TENDENS +TRIMMEAN = TRIMMIDDELVÆRDI +VAR.P = VARIANS.P +VAR.S = VARIANS.S +VARA = VARIANSV +VARPA = VARIANSPV +WEIBULL.DIST = WEIBULL.FORDELING +Z.TEST = Z.TEST ## -## Statistical functions Statistiske funktioner +## Tekstfunktioner (Text Functions) ## -AVEDEV = MAD ## Returnerer den gennemsnitlige numeriske afvigelse fra stikprøvens middelværdi -AVERAGE = MIDDEL ## Returnerer middelværdien af argumenterne -AVERAGEA = MIDDELV ## Returnerer middelværdien af argumenterne og medtager tal, tekst og logiske værdier -AVERAGEIF = MIDDEL.HVIS ## Returnerer gennemsnittet (den aritmetiske middelværdi) af alle de celler, der opfylder et givet kriterium, i et område -AVERAGEIFS = MIDDEL.HVISER ## Returnerer gennemsnittet (den aritmetiske middelværdi) af alle de celler, der opfylder flere kriterier. -BETADIST = BETAFORDELING ## Returnerer den kumulative betafordelingsfunktion -BETAINV = BETAINV ## Returnerer den inverse kumulative fordelingsfunktion for en angivet betafordeling -BINOMDIST = BINOMIALFORDELING ## Returnerer punktsandsynligheden for binomialfordelingen -CHIDIST = CHIFORDELING ## Returnerer fraktilsandsynligheden for en chi2-fordeling -CHIINV = CHIINV ## Returnerer den inverse fraktilsandsynlighed for en chi2-fordeling -CHITEST = CHITEST ## Foretager en test for uafhængighed -CONFIDENCE = KONFIDENSINTERVAL ## Returnerer et konfidensinterval for en population -CORREL = KORRELATION ## Returnerer korrelationskoefficienten mellem to datasæt -COUNT = TÆL ## Tæller antallet af tal på en liste med argumenter -COUNTA = TÆLV ## Tæller antallet af værdier på en liste med argumenter -COUNTBLANK = ANTAL.BLANKE ## Tæller antallet af tomme celler i et område -COUNTIF = TÆLHVIS ## Tæller antallet af celler, som opfylder de givne kriterier, i et område -COUNTIFS = TÆL.HVISER ## Tæller antallet af de celler, som opfylder flere kriterier, i et område -COVAR = KOVARIANS ## Beregner kovariansen mellem to stokastiske variabler -CRITBINOM = KRITBINOM ## Returnerer den mindste værdi for x, for hvilken det gælder, at fordelingsfunktionen er mindre end eller lig med kriterieværdien. -DEVSQ = SAK ## Returnerer summen af de kvadrerede afvigelser fra middelværdien -EXPONDIST = EKSPFORDELING ## Returnerer eksponentialfordelingen -FDIST = FFORDELING ## Returnerer fraktilsandsynligheden for F-fordelingen -FINV = FINV ## Returnerer den inverse fraktilsandsynlighed for F-fordelingen -FISHER = FISHER ## Returnerer Fisher-transformationen -FISHERINV = FISHERINV ## Returnerer den inverse Fisher-transformation -FORECAST = PROGNOSE ## Returnerer en prognoseværdi baseret på lineær tendens -FREQUENCY = FREKVENS ## Returnerer en frekvensfordeling i en søjlevektor -FTEST = FTEST ## Returnerer resultatet af en F-test til sammenligning af varians -GAMMADIST = GAMMAFORDELING ## Returnerer fordelingsfunktionen for gammafordelingen -GAMMAINV = GAMMAINV ## Returnerer den inverse fordelingsfunktion for gammafordelingen -GAMMALN = GAMMALN ## Returnerer den naturlige logaritme til gammafordelingen, G(x) -GEOMEAN = GEOMIDDELVÆRDI ## Returnerer det geometriske gennemsnit -GROWTH = FORØGELSE ## Returnerer værdier langs en eksponentiel tendens -HARMEAN = HARMIDDELVÆRDI ## Returnerer det harmoniske gennemsnit -HYPGEOMDIST = HYPGEOFORDELING ## Returnerer punktsandsynligheden i en hypergeometrisk fordeling -INTERCEPT = SKÆRING ## Returnerer afskæringsværdien på y-aksen i en lineær regression -KURT = TOPSTEJL ## Returnerer kurtosisværdien for en stokastisk variabel -LARGE = STOR ## Returnerer den k'te største værdi i et datasæt -LINEST = LINREGR ## Returnerer parameterestimaterne for en lineær tendens -LOGEST = LOGREGR ## Returnerer parameterestimaterne for en eksponentiel tendens -LOGINV = LOGINV ## Returnerer den inverse fordelingsfunktion for lognormalfordelingen -LOGNORMDIST = LOGNORMFORDELING ## Returnerer fordelingsfunktionen for lognormalfordelingen -MAX = MAKS ## Returnerer den maksimale værdi på en liste med argumenter. -MAXA = MAKSV ## Returnerer den maksimale værdi på en liste med argumenter og medtager tal, tekst og logiske værdier -MEDIAN = MEDIAN ## Returnerer medianen for de angivne tal -MIN = MIN ## Returnerer den mindste værdi på en liste med argumenter. -MINA = MINV ## Returnerer den mindste værdi på en liste med argumenter og medtager tal, tekst og logiske værdier -MODE = HYPPIGST ## Returnerer den hyppigste værdi i et datasæt -NEGBINOMDIST = NEGBINOMFORDELING ## Returnerer den negative binomialfordeling -NORMDIST = NORMFORDELING ## Returnerer fordelingsfunktionen for normalfordelingen -NORMINV = NORMINV ## Returnerer den inverse fordelingsfunktion for normalfordelingen -NORMSDIST = STANDARDNORMFORDELING ## Returnerer fordelingsfunktionen for standardnormalfordelingen -NORMSINV = STANDARDNORMINV ## Returnerer den inverse fordelingsfunktion for standardnormalfordelingen -PEARSON = PEARSON ## Returnerer Pearsons korrelationskoefficient -PERCENTILE = FRAKTIL ## Returnerer den k'te fraktil for datasættet -PERCENTRANK = PROCENTPLADS ## Returnerer den procentuelle rang for en given værdi i et datasæt -PERMUT = PERMUT ## Returnerer antallet af permutationer for et givet sæt objekter -POISSON = POISSON ## Returnerer fordelingsfunktionen for en Poisson-fordeling -PROB = SANDSYNLIGHED ## Returnerer intervalsandsynligheden -QUARTILE = KVARTIL ## Returnerer kvartilen i et givet datasæt -RANK = PLADS ## Returnerer rangen for et tal på en liste med tal -RSQ = FORKLARINGSGRAD ## Returnerer R2-værdien fra en simpel lineær regression -SKEW = SKÆVHED ## Returnerer skævheden for en stokastisk variabel -SLOPE = HÆLDNING ## Returnerer estimatet på hældningen fra en simpel lineær regression -SMALL = MINDSTE ## Returnerer den k'te mindste værdi i datasættet -STANDARDIZE = STANDARDISER ## Returnerer en standardiseret værdi -STDEV = STDAFV ## Estimerer standardafvigelsen på basis af en stikprøve -STDEVA = STDAFVV ## Beregner standardafvigelsen på basis af en prøve og medtager tal, tekst og logiske værdier -STDEVP = STDAFVP ## Beregner standardafvigelsen på basis af en hel population -STDEVPA = STDAFVPV ## Beregner standardafvigelsen på basis af en hel population og medtager tal, tekst og logiske værdier -STEYX = STFYX ## Returnerer standardafvigelsen for de estimerede y-værdier i den simple lineære regression -TDIST = TFORDELING ## Returnerer fordelingsfunktionen for Student's t-fordeling -TINV = TINV ## Returnerer den inverse fordelingsfunktion for Student's t-fordeling -TREND = TENDENS ## Returnerer værdi under antagelse af en lineær tendens -TRIMMEAN = TRIMMIDDELVÆRDI ## Returnerer den trimmede middelværdi for datasættet -TTEST = TTEST ## Returnerer den sandsynlighed, der er forbundet med Student's t-test -VAR = VARIANS ## Beregner variansen på basis af en prøve -VARA = VARIANSV ## Beregner variansen på basis af en prøve og medtager tal, tekst og logiske værdier -VARP = VARIANSP ## Beregner variansen på basis af hele populationen -VARPA = VARIANSPV ## Beregner variansen på basis af hele populationen og medtager tal, tekst og logiske værdier -WEIBULL = WEIBULL ## Returnerer fordelingsfunktionen for Weibull-fordelingen -ZTEST = ZTEST ## Returnerer sandsynlighedsværdien ved en en-sidet z-test - +BAHTTEXT = BAHTTEKST +CHAR = TEGN +CLEAN = RENS +CODE = KODE +CONCAT = CONCAT +DOLLAR = KR +EXACT = EKSAKT +FIND = FIND +FIXED = FAST +ISTHAIDIGIT = ERTHAILANDSKCIFFER +LEFT = VENSTRE +LEN = LÆNGDE +LOWER = SMÅ.BOGSTAVER +MID = MIDT +NUMBERSTRING = TALSTRENG +NUMBERVALUE = TALVÆRDI +PHONETIC = FONETISK +PROPER = STORT.FORBOGSTAV +REPLACE = ERSTAT +REPT = GENTAG +RIGHT = HØJRE +SEARCH = SØG +SUBSTITUTE = UDSKIFT +T = T +TEXT = TEKST +TEXTJOIN = TEKST.KOMBINER +THAIDIGIT = THAILANDSKCIFFER +THAINUMSOUND = THAILANDSKNUMLYD +THAINUMSTRING = THAILANDSKNUMSTRENG +THAISTRINGLENGTH = THAILANDSKSTRENGLÆNGDE +TRIM = FJERN.OVERFLØDIGE.BLANKE +UNICHAR = UNICHAR +UNICODE = UNICODE +UPPER = STORE.BOGSTAVER +VALUE = VÆRDI ## -## Text functions Tekstfunktioner +## Webfunktioner (Web Functions) ## -ASC = ASC ## Ændrer engelske tegn i fuld bredde (dobbelt-byte) eller katakana i en tegnstreng til tegn i halv bredde (enkelt-byte) -BAHTTEXT = BAHTTEKST ## Konverterer et tal til tekst ved hjælp af valutaformatet ß (baht) -CHAR = TEGN ## Returnerer det tegn, der svarer til kodenummeret -CLEAN = RENS ## Fjerner alle tegn, der ikke kan udskrives, fra tekst -CODE = KODE ## Returnerer en numerisk kode for det første tegn i en tekststreng -CONCATENATE = SAMMENKÆDNING ## Sammenkæder adskillige tekstelementer til ét tekstelement -DOLLAR = KR ## Konverterer et tal til tekst ved hjælp af valutaformatet kr. (kroner) -EXACT = EKSAKT ## Kontrollerer, om to tekstværdier er identiske -FIND = FIND ## Søger efter en tekstværdi i en anden tekstværdi (der skelnes mellem store og små bogstaver) -FINDB = FINDB ## Søger efter en tekstværdi i en anden tekstværdi (der skelnes mellem store og små bogstaver) -FIXED = FAST ## Formaterer et tal som tekst med et fast antal decimaler -JIS = JIS ## Ændrer engelske tegn i halv bredde (enkelt-byte) eller katakana i en tegnstreng til tegn i fuld bredde (dobbelt-byte) -LEFT = VENSTRE ## Returnerer tegnet længst til venstre i en tekstværdi -LEFTB = VENSTREB ## Returnerer tegnet længst til venstre i en tekstværdi -LEN = LÆNGDE ## Returnerer antallet af tegn i en tekststreng -LENB = LÆNGDEB ## Returnerer antallet af tegn i en tekststreng -LOWER = SMÅ.BOGSTAVER ## Konverterer tekst til små bogstaver -MID = MIDT ## Returnerer et bestemt antal tegn fra en tekststreng fra og med den angivne startposition -MIDB = MIDTB ## Returnerer et bestemt antal tegn fra en tekststreng fra og med den angivne startposition -PHONETIC = FONETISK ## Uddrager de fonetiske (furigana) tegn fra en tekststreng -PROPER = STORT.FORBOGSTAV ## Konverterer første bogstav i hvert ord i teksten til stort bogstav -REPLACE = ERSTAT ## Erstatter tegn i tekst -REPLACEB = ERSTATB ## Erstatter tegn i tekst -REPT = GENTAG ## Gentager tekst et givet antal gange -RIGHT = HØJRE ## Returnerer tegnet længste til højre i en tekstværdi -RIGHTB = HØJREB ## Returnerer tegnet længste til højre i en tekstværdi -SEARCH = SØG ## Søger efter en tekstværdi i en anden tekstværdi (der skelnes ikke mellem store og små bogstaver) -SEARCHB = SØGB ## Søger efter en tekstværdi i en anden tekstværdi (der skelnes ikke mellem store og små bogstaver) -SUBSTITUTE = UDSKIFT ## Udskifter gammel tekst med ny tekst i en tekststreng -T = T ## Konverterer argumenterne til tekst -TEXT = TEKST ## Formaterer et tal og konverterer det til tekst -TRIM = FJERN.OVERFLØDIGE.BLANKE ## Fjerner mellemrum fra tekst -UPPER = STORE.BOGSTAVER ## Konverterer tekst til store bogstaver -VALUE = VÆRDI ## Konverterer et tekstargument til et tal +ENCODEURL = KODNINGSURL +FILTERXML = FILTRERXML +WEBSERVICE = WEBTJENESTE + +## +## Kompatibilitetsfunktioner (Compatibility Functions) +## +BETADIST = BETAFORDELING +BETAINV = BETAINV +BINOMDIST = BINOMIALFORDELING +CEILING = AFRUND.LOFT +CHIDIST = CHIFORDELING +CHIINV = CHIINV +CHITEST = CHITEST +CONCATENATE = SAMMENKÆDNING +CONFIDENCE = KONFIDENSINTERVAL +COVAR = KOVARIANS +CRITBINOM = KRITBINOM +EXPONDIST = EKSPFORDELING +FDIST = FFORDELING +FINV = FINV +FLOOR = AFRUND.GULV +FORECAST = PROGNOSE +FTEST = FTEST +GAMMADIST = GAMMAFORDELING +GAMMAINV = GAMMAINV +HYPGEOMDIST = HYPGEOFORDELING +LOGINV = LOGINV +LOGNORMDIST = LOGNORMFORDELING +MODE = HYPPIGST +NEGBINOMDIST = NEGBINOMFORDELING +NORMDIST = NORMFORDELING +NORMINV = NORMINV +NORMSDIST = STANDARDNORMFORDELING +NORMSINV = STANDARDNORMINV +PERCENTILE = FRAKTIL +PERCENTRANK = PROCENTPLADS +POISSON = POISSON +QUARTILE = KVARTIL +RANK = PLADS +STDEV = STDAFV +STDEVP = STDAFVP +TDIST = TFORDELING +TINV = TINV +TTEST = TTEST +VAR = VARIANS +VARP = VARIANSP +WEIBULL = WEIBULL +ZTEST = ZTEST diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/de/config b/PhpOffice/PhpSpreadsheet/Calculation/locale/de/config old mode 100755 new mode 100644 index 9751c4b..4ca2b82 --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/de/config +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/de/config @@ -1,24 +1,20 @@ +############################################################ ## -## PhpSpreadsheet +## PhpSpreadsheet - locale settings ## +## Deutsch (German) +## +############################################################ -ArgumentSeparator = ; - +ArgumentSeparator = ; ## -## (For future use) +## Error Codes ## -currencySymbol = € - - -## -## Excel Error Codes (For future use) - -## -NULL = #NULL! -DIV0 = #DIV/0! -VALUE = #WERT! -REF = #BEZUG! -NAME = #NAME? -NUM = #ZAHL! -NA = #NV +NULL +DIV0 +VALUE = #WERT! +REF = #BEZUG! +NAME +NUM = #ZAHL! +NA = #NV diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/de/functions b/PhpOffice/PhpSpreadsheet/Calculation/locale/de/functions old mode 100755 new mode 100644 index 01df42f..d49fc5f --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/de/functions +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/de/functions @@ -1,416 +1,534 @@ +############################################################ ## -## PhpSpreadsheet -## -## Data in this file derived from https://www.excel-function-translation.com/ +## PhpSpreadsheet - function name translations ## +## Deutsch (German) ## +############################################################ ## -## Add-in and Automation functions Add-In- und Automatisierungsfunktionen +## Cubefunktionen (Cube Functions) ## -GETPIVOTDATA = PIVOTDATENZUORDNEN ## In einem PivotTable-Bericht gespeicherte Daten werden zurückgegeben. - +CUBEKPIMEMBER = CUBEKPIELEMENT +CUBEMEMBER = CUBEELEMENT +CUBEMEMBERPROPERTY = CUBEELEMENTEIGENSCHAFT +CUBERANKEDMEMBER = CUBERANGELEMENT +CUBESET = CUBEMENGE +CUBESETCOUNT = CUBEMENGENANZAHL +CUBEVALUE = CUBEWERT ## -## Cube functions Cubefunktionen +## Datenbankfunktionen (Database Functions) ## -CUBEKPIMEMBER = CUBEKPIELEMENT ## Gibt Name, Eigenschaft und Measure eines Key Performance Indicators (KPI) zurück und zeigt den Namen und die Eigenschaft in der Zelle an. Ein KPI ist ein quantifizierbares Maß, wie z. B. der monatliche Bruttogewinn oder die vierteljährliche Mitarbeiterfluktuation, mit dessen Hilfe das Leistungsverhalten eines Unternehmens überwacht werden kann. -CUBEMEMBER = CUBEELEMENT ## Gibt ein Element oder ein Tuple in einer Cubehierarchie zurück. Wird verwendet, um zu überprüfen, ob das Element oder Tuple im Cube vorhanden ist. -CUBEMEMBERPROPERTY = CUBEELEMENTEIGENSCHAFT ## Gibt den Wert einer Elementeigenschaft im Cube zurück. Wird verwendet, um zu überprüfen, ob ein Elementname im Cube vorhanden ist, und um die für dieses Element angegebene Eigenschaft zurückzugeben. -CUBERANKEDMEMBER = CUBERANGELEMENT ## Gibt das n-te oder n-rangige Element in einer Menge zurück. Wird verwendet, um mindestens ein Element in einer Menge zurückzugeben, wie z. B. bester Vertriebsmitarbeiter oder 10 beste Kursteilnehmer. -CUBESET = CUBEMENGE ## Definiert eine berechnete Menge Elemente oder Tuples durch Senden eines Mengenausdrucks an den Cube auf dem Server, der die Menge erstellt und an Microsoft Office Excel zurückgibt. -CUBESETCOUNT = CUBEMENGENANZAHL ## Gibt die Anzahl der Elemente in einer Menge zurück. -CUBEVALUE = CUBEWERT ## Gibt einen Aggregatwert aus einem Cube zurück. - +DAVERAGE = DBMITTELWERT +DCOUNT = DBANZAHL +DCOUNTA = DBANZAHL2 +DGET = DBAUSZUG +DMAX = DBMAX +DMIN = DBMIN +DPRODUCT = DBPRODUKT +DSTDEV = DBSTDABW +DSTDEVP = DBSTDABWN +DSUM = DBSUMME +DVAR = DBVARIANZ +DVARP = DBVARIANZEN ## -## Database functions Datenbankfunktionen +## Datums- und Uhrzeitfunktionen (Date & Time Functions) ## -DAVERAGE = DBMITTELWERT ## Gibt den Mittelwert der ausgewählten Datenbankeinträge zurück -DCOUNT = DBANZAHL ## Zählt die Zellen mit Zahlen in einer Datenbank -DCOUNTA = DBANZAHL2 ## Zählt nicht leere Zellen in einer Datenbank -DGET = DBAUSZUG ## Extrahiert aus einer Datenbank einen einzelnen Datensatz, der den angegebenen Kriterien entspricht -DMAX = DBMAX ## Gibt den größten Wert aus ausgewählten Datenbankeinträgen zurück -DMIN = DBMIN ## Gibt den kleinsten Wert aus ausgewählten Datenbankeinträgen zurück -DPRODUCT = DBPRODUKT ## Multipliziert die Werte in einem bestimmten Feld mit Datensätzen, die den Kriterien in einer Datenbank entsprechen -DSTDEV = DBSTDABW ## Schätzt die Standardabweichung auf der Grundlage einer Stichprobe aus ausgewählten Datenbankeinträgen -DSTDEVP = DBSTDABWN ## Berechnet die Standardabweichung auf der Grundlage der Grundgesamtheit ausgewählter Datenbankeinträge -DSUM = DBSUMME ## Addiert die Zahlen in der Feldspalte mit Datensätzen in der Datenbank, die den Kriterien entsprechen -DVAR = DBVARIANZ ## Schätzt die Varianz auf der Grundlage ausgewählter Datenbankeinträge -DVARP = DBVARIANZEN ## Berechnet die Varianz auf der Grundlage der Grundgesamtheit ausgewählter Datenbankeinträge - +DATE = DATUM +DATEVALUE = DATWERT +DAY = TAG +DAYS = TAGE +DAYS360 = TAGE360 +EDATE = EDATUM +EOMONTH = MONATSENDE +HOUR = STUNDE +ISOWEEKNUM = ISOKALENDERWOCHE +MINUTE = MINUTE +MONTH = MONAT +NETWORKDAYS = NETTOARBEITSTAGE +NETWORKDAYS.INTL = NETTOARBEITSTAGE.INTL +NOW = JETZT +SECOND = SEKUNDE +THAIDAYOFWEEK = THAIWOCHENTAG +THAIMONTHOFYEAR = THAIMONATDESJAHRES +THAIYEAR = THAIJAHR +TIME = ZEIT +TIMEVALUE = ZEITWERT +TODAY = HEUTE +WEEKDAY = WOCHENTAG +WEEKNUM = KALENDERWOCHE +WORKDAY = ARBEITSTAG +WORKDAY.INTL = ARBEITSTAG.INTL +YEAR = JAHR +YEARFRAC = BRTEILJAHRE ## -## Date and time functions Datums- und Zeitfunktionen +## Technische Funktionen (Engineering Functions) ## -DATE = DATUM ## Gibt die fortlaufende Zahl eines bestimmten Datums zurück -DATEVALUE = DATWERT ## Wandelt ein Datum in Form von Text in eine fortlaufende Zahl um -DAY = TAG ## Wandelt eine fortlaufende Zahl in den Tag des Monats um -DAYS360 = TAGE360 ## Berechnet die Anzahl der Tage zwischen zwei Datumsangaben ausgehend von einem Jahr, das 360 Tage hat -EDATE = EDATUM ## Gibt die fortlaufende Zahl des Datums zurück, bei dem es sich um die angegebene Anzahl von Monaten vor oder nach dem Anfangstermin handelt -EOMONTH = MONATSENDE ## Gibt die fortlaufende Zahl des letzten Tags des Monats vor oder nach einer festgelegten Anzahl von Monaten zurück -HOUR = STUNDE ## Wandelt eine fortlaufende Zahl in eine Stunde um -MINUTE = MINUTE ## Wandelt eine fortlaufende Zahl in eine Minute um -MONTH = MONAT ## Wandelt eine fortlaufende Zahl in einen Monat um -NETWORKDAYS = NETTOARBEITSTAGE ## Gibt die Anzahl von ganzen Arbeitstagen zwischen zwei Datumswerten zurück -NOW = JETZT ## Gibt die fortlaufende Zahl des aktuellen Datums und der aktuellen Uhrzeit zurück -SECOND = SEKUNDE ## Wandelt eine fortlaufende Zahl in eine Sekunde um -TIME = ZEIT ## Gibt die fortlaufende Zahl einer bestimmten Uhrzeit zurück -TIMEVALUE = ZEITWERT ## Wandelt eine Uhrzeit in Form von Text in eine fortlaufende Zahl um -TODAY = HEUTE ## Gibt die fortlaufende Zahl des heutigen Datums zurück -WEEKDAY = WOCHENTAG ## Wandelt eine fortlaufende Zahl in den Wochentag um -WEEKNUM = KALENDERWOCHE ## Wandelt eine fortlaufende Zahl in eine Zahl um, die angibt, in welche Woche eines Jahres das angegebene Datum fällt -WORKDAY = ARBEITSTAG ## Gibt die fortlaufende Zahl des Datums vor oder nach einer bestimmten Anzahl von Arbeitstagen zurück -YEAR = JAHR ## Wandelt eine fortlaufende Zahl in ein Jahr um -YEARFRAC = BRTEILJAHRE ## Gibt die Anzahl der ganzen Tage zwischen Ausgangsdatum und Enddatum in Bruchteilen von Jahren zurück - +BESSELI = BESSELI +BESSELJ = BESSELJ +BESSELK = BESSELK +BESSELY = BESSELY +BIN2DEC = BININDEZ +BIN2HEX = BININHEX +BIN2OCT = BININOKT +BITAND = BITUND +BITLSHIFT = BITLVERSCHIEB +BITOR = BITODER +BITRSHIFT = BITRVERSCHIEB +BITXOR = BITXODER +COMPLEX = KOMPLEXE +CONVERT = UMWANDELN +DEC2BIN = DEZINBIN +DEC2HEX = DEZINHEX +DEC2OCT = DEZINOKT +DELTA = DELTA +ERF = GAUSSFEHLER +ERF.PRECISE = GAUSSF.GENAU +ERFC = GAUSSFKOMPL +ERFC.PRECISE = GAUSSFKOMPL.GENAU +GESTEP = GGANZZAHL +HEX2BIN = HEXINBIN +HEX2DEC = HEXINDEZ +HEX2OCT = HEXINOKT +IMABS = IMABS +IMAGINARY = IMAGINÄRTEIL +IMARGUMENT = IMARGUMENT +IMCONJUGATE = IMKONJUGIERTE +IMCOS = IMCOS +IMCOSH = IMCOSHYP +IMCOT = IMCOT +IMCSC = IMCOSEC +IMCSCH = IMCOSECHYP +IMDIV = IMDIV +IMEXP = IMEXP +IMLN = IMLN +IMLOG10 = IMLOG10 +IMLOG2 = IMLOG2 +IMPOWER = IMAPOTENZ +IMPRODUCT = IMPRODUKT +IMREAL = IMREALTEIL +IMSEC = IMSEC +IMSECH = IMSECHYP +IMSIN = IMSIN +IMSINH = IMSINHYP +IMSQRT = IMWURZEL +IMSUB = IMSUB +IMSUM = IMSUMME +IMTAN = IMTAN +OCT2BIN = OKTINBIN +OCT2DEC = OKTINDEZ +OCT2HEX = OKTINHEX ## -## Engineering functions Konstruktionsfunktionen +## Finanzmathematische Funktionen (Financial Functions) ## -BESSELI = BESSELI ## Gibt die geänderte Besselfunktion In(x) zurück -BESSELJ = BESSELJ ## Gibt die Besselfunktion Jn(x) zurück -BESSELK = BESSELK ## Gibt die geänderte Besselfunktion Kn(x) zurück -BESSELY = BESSELY ## Gibt die Besselfunktion Yn(x) zurück -BIN2DEC = BININDEZ ## Wandelt eine binäre Zahl (Dualzahl) in eine dezimale Zahl um -BIN2HEX = BININHEX ## Wandelt eine binäre Zahl (Dualzahl) in eine hexadezimale Zahl um -BIN2OCT = BININOKT ## Wandelt eine binäre Zahl (Dualzahl) in eine oktale Zahl um -COMPLEX = KOMPLEXE ## Wandelt den Real- und Imaginärteil in eine komplexe Zahl um -CONVERT = UMWANDELN ## Wandelt eine Zahl von einem Maßsystem in ein anderes um -DEC2BIN = DEZINBIN ## Wandelt eine dezimale Zahl in eine binäre Zahl (Dualzahl) um -DEC2HEX = DEZINHEX ## Wandelt eine dezimale Zahl in eine hexadezimale Zahl um -DEC2OCT = DEZINOKT ## Wandelt eine dezimale Zahl in eine oktale Zahl um -DELTA = DELTA ## Überprüft, ob zwei Werte gleich sind -ERF = GAUSSFEHLER ## Gibt die Gauss'sche Fehlerfunktion zurück -ERFC = GAUSSFKOMPL ## Gibt das Komplement zur Gauss'schen Fehlerfunktion zurück -GESTEP = GGANZZAHL ## Überprüft, ob eine Zahl größer als ein gegebener Schwellenwert ist -HEX2BIN = HEXINBIN ## Wandelt eine hexadezimale Zahl in eine Binärzahl um -HEX2DEC = HEXINDEZ ## Wandelt eine hexadezimale Zahl in eine dezimale Zahl um -HEX2OCT = HEXINOKT ## Wandelt eine hexadezimale Zahl in eine Oktalzahl um -IMABS = IMABS ## Gibt den Absolutbetrag (Modulo) einer komplexen Zahl zurück -IMAGINARY = IMAGINÄRTEIL ## Gibt den Imaginärteil einer komplexen Zahl zurück -IMARGUMENT = IMARGUMENT ## Gibt das Argument Theta zurück, einen Winkel, der als Bogenmaß ausgedrückt wird -IMCONJUGATE = IMKONJUGIERTE ## Gibt die konjugierte komplexe Zahl zu einer komplexen Zahl zurück -IMCOS = IMCOS ## Gibt den Kosinus einer komplexen Zahl zurück -IMDIV = IMDIV ## Gibt den Quotienten zweier komplexer Zahlen zurück -IMEXP = IMEXP ## Gibt die algebraische Form einer in exponentieller Schreibweise vorliegenden komplexen Zahl zurück -IMLN = IMLN ## Gibt den natürlichen Logarithmus einer komplexen Zahl zurück -IMLOG10 = IMLOG10 ## Gibt den Logarithmus einer komplexen Zahl zur Basis 10 zurück -IMLOG2 = IMLOG2 ## Gibt den Logarithmus einer komplexen Zahl zur Basis 2 zurück -IMPOWER = IMAPOTENZ ## Potenziert eine komplexe Zahl mit einer ganzen Zahl -IMPRODUCT = IMPRODUKT ## Gibt das Produkt von komplexen Zahlen zurück -IMREAL = IMREALTEIL ## Gibt den Realteil einer komplexen Zahl zurück -IMSIN = IMSIN ## Gibt den Sinus einer komplexen Zahl zurück -IMSQRT = IMWURZEL ## Gibt die Quadratwurzel einer komplexen Zahl zurück -IMSUB = IMSUB ## Gibt die Differenz zwischen zwei komplexen Zahlen zurück -IMSUM = IMSUMME ## Gibt die Summe von komplexen Zahlen zurück -OCT2BIN = OKTINBIN ## Wandelt eine oktale Zahl in eine binäre Zahl (Dualzahl) um -OCT2DEC = OKTINDEZ ## Wandelt eine oktale Zahl in eine dezimale Zahl um -OCT2HEX = OKTINHEX ## Wandelt eine oktale Zahl in eine hexadezimale Zahl um - +ACCRINT = AUFGELZINS +ACCRINTM = AUFGELZINSF +AMORDEGRC = AMORDEGRK +AMORLINC = AMORLINEARK +COUPDAYBS = ZINSTERMTAGVA +COUPDAYS = ZINSTERMTAGE +COUPDAYSNC = ZINSTERMTAGNZ +COUPNCD = ZINSTERMNZ +COUPNUM = ZINSTERMZAHL +COUPPCD = ZINSTERMVZ +CUMIPMT = KUMZINSZ +CUMPRINC = KUMKAPITAL +DB = GDA2 +DDB = GDA +DISC = DISAGIO +DOLLARDE = NOTIERUNGDEZ +DOLLARFR = NOTIERUNGBRU +DURATION = DURATION +EFFECT = EFFEKTIV +FV = ZW +FVSCHEDULE = ZW2 +INTRATE = ZINSSATZ +IPMT = ZINSZ +IRR = IKV +ISPMT = ISPMT +MDURATION = MDURATION +MIRR = QIKV +NOMINAL = NOMINAL +NPER = ZZR +NPV = NBW +ODDFPRICE = UNREGER.KURS +ODDFYIELD = UNREGER.REND +ODDLPRICE = UNREGLE.KURS +ODDLYIELD = UNREGLE.REND +PDURATION = PDURATION +PMT = RMZ +PPMT = KAPZ +PRICE = KURS +PRICEDISC = KURSDISAGIO +PRICEMAT = KURSFÄLLIG +PV = BW +RATE = ZINS +RECEIVED = AUSZAHLUNG +RRI = ZSATZINVEST +SLN = LIA +SYD = DIA +TBILLEQ = TBILLÄQUIV +TBILLPRICE = TBILLKURS +TBILLYIELD = TBILLRENDITE +VDB = VDB +XIRR = XINTZINSFUSS +XNPV = XKAPITALWERT +YIELD = RENDITE +YIELDDISC = RENDITEDIS +YIELDMAT = RENDITEFÄLL ## -## Financial functions Finanzmathematische Funktionen +## Informationsfunktionen (Information Functions) ## -ACCRINT = AUFGELZINS ## Gibt die aufgelaufenen Zinsen (Stückzinsen) eines Wertpapiers mit periodischen Zinszahlungen zurück -ACCRINTM = AUFGELZINSF ## Gibt die aufgelaufenen Zinsen (Stückzinsen) eines Wertpapiers zurück, die bei Fälligkeit ausgezahlt werden -AMORDEGRC = AMORDEGRK ## Gibt die Abschreibung für die einzelnen Abschreibungszeiträume mithilfe eines Abschreibungskoeffizienten zurück -AMORLINC = AMORLINEARK ## Gibt die Abschreibung für die einzelnen Abschreibungszeiträume zurück -COUPDAYBS = ZINSTERMTAGVA ## Gibt die Anzahl der Tage vom Anfang des Zinstermins bis zum Abrechnungstermin zurück -COUPDAYS = ZINSTERMTAGE ## Gibt die Anzahl der Tage der Zinsperiode zurück, die den Abrechnungstermin einschließt -COUPDAYSNC = ZINSTERMTAGNZ ## Gibt die Anzahl der Tage vom Abrechnungstermin bis zum nächsten Zinstermin zurück -COUPNCD = ZINSTERMNZ ## Gibt das Datum des ersten Zinstermins nach dem Abrechnungstermin zurück -COUPNUM = ZINSTERMZAHL ## Gibt die Anzahl der Zinstermine zwischen Abrechnungs- und Fälligkeitsdatum zurück -COUPPCD = ZINSTERMVZ ## Gibt das Datum des letzten Zinstermins vor dem Abrechnungstermin zurück -CUMIPMT = KUMZINSZ ## Berechnet die kumulierten Zinsen, die zwischen zwei Perioden zu zahlen sind -CUMPRINC = KUMKAPITAL ## Berechnet die aufgelaufene Tilgung eines Darlehens, die zwischen zwei Perioden zu zahlen ist -DB = GDA2 ## Gibt die geometrisch-degressive Abschreibung eines Wirtschaftsguts für eine bestimmte Periode zurück -DDB = GDA ## Gibt die Abschreibung eines Anlageguts für einen angegebenen Zeitraum unter Verwendung der degressiven Doppelraten-Abschreibung oder eines anderen von Ihnen angegebenen Abschreibungsverfahrens zurück -DISC = DISAGIO ## Gibt den in Prozent ausgedrückten Abzinsungssatz eines Wertpapiers zurück -DOLLARDE = NOTIERUNGDEZ ## Wandelt eine Notierung, die als Dezimalbruch ausgedrückt wurde, in eine Dezimalzahl um -DOLLARFR = NOTIERUNGBRU ## Wandelt eine Notierung, die als Dezimalzahl ausgedrückt wurde, in einen Dezimalbruch um -DURATION = DURATION ## Gibt die jährliche Duration eines Wertpapiers mit periodischen Zinszahlungen zurück -EFFECT = EFFEKTIV ## Gibt die jährliche Effektivverzinsung zurück -FV = ZW ## Gibt den zukünftigen Wert (Endwert) einer Investition zurück -FVSCHEDULE = ZW2 ## Gibt den aufgezinsten Wert des Anfangskapitals für eine Reihe periodisch unterschiedlicher Zinssätze zurück -INTRATE = ZINSSATZ ## Gibt den Zinssatz eines voll investierten Wertpapiers zurück -IPMT = ZINSZ ## Gibt die Zinszahlung einer Investition für die angegebene Periode zurück -IRR = IKV ## Gibt den internen Zinsfuß einer Investition ohne Finanzierungskosten oder Reinvestitionsgewinne zurück -ISPMT = ISPMT ## Berechnet die während eines bestimmten Zeitraums für eine Investition gezahlten Zinsen -MDURATION = MDURATION ## Gibt die geänderte Dauer für ein Wertpapier mit einem angenommenen Nennwert von 100 € zurück -MIRR = QIKV ## Gibt den internen Zinsfuß zurück, wobei positive und negative Zahlungen zu unterschiedlichen Sätzen finanziert werden -NOMINAL = NOMINAL ## Gibt die jährliche Nominalverzinsung zurück -NPER = ZZR ## Gibt die Anzahl der Zahlungsperioden einer Investition zurück -NPV = NBW ## Gibt den Nettobarwert einer Investition auf Basis periodisch anfallender Zahlungen und eines Abzinsungsfaktors zurück -ODDFPRICE = UNREGER.KURS ## Gibt den Kurs pro 100 € Nennwert eines Wertpapiers mit einem unregelmäßigen ersten Zinstermin zurück -ODDFYIELD = UNREGER.REND ## Gibt die Rendite eines Wertpapiers mit einem unregelmäßigen ersten Zinstermin zurück -ODDLPRICE = UNREGLE.KURS ## Gibt den Kurs pro 100 € Nennwert eines Wertpapiers mit einem unregelmäßigen letzten Zinstermin zurück -ODDLYIELD = UNREGLE.REND ## Gibt die Rendite eines Wertpapiers mit einem unregelmäßigen letzten Zinstermin zurück -PMT = RMZ ## Gibt die periodische Zahlung für eine Annuität zurück -PPMT = KAPZ ## Gibt die Kapitalrückzahlung einer Investition für eine angegebene Periode zurück -PRICE = KURS ## Gibt den Kurs pro 100 € Nennwert eines Wertpapiers zurück, das periodisch Zinsen auszahlt -PRICEDISC = KURSDISAGIO ## Gibt den Kurs pro 100 € Nennwert eines unverzinslichen Wertpapiers zurück -PRICEMAT = KURSFÄLLIG ## Gibt den Kurs pro 100 € Nennwert eines Wertpapiers zurück, das Zinsen am Fälligkeitsdatum auszahlt -PV = BW ## Gibt den Barwert einer Investition zurück -RATE = ZINS ## Gibt den Zinssatz pro Zeitraum einer Annuität zurück -RECEIVED = AUSZAHLUNG ## Gibt den Auszahlungsbetrag eines voll investierten Wertpapiers am Fälligkeitstermin zurück -SLN = LIA ## Gibt die lineare Abschreibung eines Wirtschaftsguts pro Periode zurück -SYD = DIA ## Gibt die arithmetisch-degressive Abschreibung eines Wirtschaftsguts für eine bestimmte Periode zurück -TBILLEQ = TBILLÄQUIV ## Gibt die Rendite für ein Wertpapier zurück -TBILLPRICE = TBILLKURS ## Gibt den Kurs pro 100 € Nennwert eines Wertpapiers zurück -TBILLYIELD = TBILLRENDITE ## Gibt die Rendite für ein Wertpapier zurück -VDB = VDB ## Gibt die degressive Abschreibung eines Wirtschaftsguts für eine bestimmte Periode oder Teilperiode zurück -XIRR = XINTZINSFUSS ## Gibt den internen Zinsfuß einer Reihe nicht periodisch anfallender Zahlungen zurück -XNPV = XKAPITALWERT ## Gibt den Nettobarwert (Kapitalwert) einer Reihe nicht periodisch anfallender Zahlungen zurück -YIELD = RENDITE ## Gibt die Rendite eines Wertpapiers zurück, das periodisch Zinsen auszahlt -YIELDDISC = RENDITEDIS ## Gibt die jährliche Rendite eines unverzinslichen Wertpapiers zurück -YIELDMAT = RENDITEFÄLL ## Gibt die jährliche Rendite eines Wertpapiers zurück, das Zinsen am Fälligkeitsdatum auszahlt - +CELL = ZELLE +ERROR.TYPE = FEHLER.TYP +INFO = INFO +ISBLANK = ISTLEER +ISERR = ISTFEHL +ISERROR = ISTFEHLER +ISEVEN = ISTGERADE +ISFORMULA = ISTFORMEL +ISLOGICAL = ISTLOG +ISNA = ISTNV +ISNONTEXT = ISTKTEXT +ISNUMBER = ISTZAHL +ISODD = ISTUNGERADE +ISREF = ISTBEZUG +ISTEXT = ISTTEXT +N = N +NA = NV +SHEET = BLATT +SHEETS = BLÄTTER +TYPE = TYP ## -## Information functions Informationsfunktionen +## Logische Funktionen (Logical Functions) ## -CELL = ZELLE ## Gibt Informationen zu Formatierung, Position oder Inhalt einer Zelle zurück -ERROR.TYPE = FEHLER.TYP ## Gibt eine Zahl zurück, die einem Fehlertyp entspricht -INFO = INFO ## Gibt Informationen zur aktuellen Betriebssystemumgebung zurück -ISBLANK = ISTLEER ## Gibt WAHR zurück, wenn der Wert leer ist -ISERR = ISTFEHL ## Gibt WAHR zurück, wenn der Wert ein beliebiger Fehlerwert außer #N/V ist -ISERROR = ISTFEHLER ## Gibt WAHR zurück, wenn der Wert ein beliebiger Fehlerwert ist -ISEVEN = ISTGERADE ## Gibt WAHR zurück, wenn es sich um eine gerade Zahl handelt -ISLOGICAL = ISTLOG ## Gibt WAHR zurück, wenn der Wert ein Wahrheitswert ist -ISNA = ISTNV ## Gibt WAHR zurück, wenn der Wert der Fehlerwert #N/V ist -ISNONTEXT = ISTKTEXT ## Gibt WAHR zurück, wenn der Wert ein Element ist, das keinen Text enthält -ISNUMBER = ISTZAHL ## Gibt WAHR zurück, wenn der Wert eine Zahl ist -ISODD = ISTUNGERADE ## Gibt WAHR zurück, wenn es sich um eine ungerade Zahl handelt -ISREF = ISTBEZUG ## Gibt WAHR zurück, wenn der Wert ein Bezug ist -ISTEXT = ISTTEXT ## Gibt WAHR zurück, wenn der Wert ein Element ist, das Text enthält -N = N ## Gibt den in eine Zahl umgewandelten Wert zurück -NA = NV ## Gibt den Fehlerwert #NV zurück -TYPE = TYP ## Gibt eine Zahl zurück, die den Datentyp des angegebenen Werts anzeigt - +AND = UND +FALSE = FALSCH +IF = WENN +IFERROR = WENNFEHLER +IFNA = WENNNV +IFS = WENNS +NOT = NICHT +OR = ODER +SWITCH = ERSTERWERT +TRUE = WAHR +XOR = XODER ## -## Logical functions Logische Funktionen +## Nachschlage- und Verweisfunktionen (Lookup & Reference Functions) ## -AND = UND ## Gibt WAHR zurück, wenn alle zugehörigen Argumente WAHR sind -FALSE = FALSCH ## Gibt den Wahrheitswert FALSCH zurück -IF = WENN ## Gibt einen logischen Test zum Ausführen an -IFERROR = WENNFEHLER ## Gibt einen von Ihnen festgelegten Wert zurück, wenn die Auswertung der Formel zu einem Fehler führt; andernfalls wird das Ergebnis der Formel zurückgegeben -NOT = NICHT ## Kehrt den Wahrheitswert der zugehörigen Argumente um -OR = ODER ## Gibt WAHR zurück, wenn ein Argument WAHR ist -TRUE = WAHR ## Gibt den Wahrheitswert WAHR zurück - +ADDRESS = ADRESSE +AREAS = BEREICHE +CHOOSE = WAHL +COLUMN = SPALTE +COLUMNS = SPALTEN +FORMULATEXT = FORMELTEXT +GETPIVOTDATA = PIVOTDATENZUORDNEN +HLOOKUP = WVERWEIS +HYPERLINK = HYPERLINK +INDEX = INDEX +INDIRECT = INDIREKT +LOOKUP = VERWEIS +MATCH = VERGLEICH +OFFSET = BEREICH.VERSCHIEBEN +ROW = ZEILE +ROWS = ZEILEN +RTD = RTD +TRANSPOSE = MTRANS +VLOOKUP = SVERWEIS +*RC = ZS ## -## Lookup and reference functions Nachschlage- und Verweisfunktionen +## Mathematische und trigonometrische Funktionen (Math & Trig Functions) ## -ADDRESS = ADRESSE ## Gibt einen Bezug auf eine einzelne Zelle in einem Tabellenblatt als Text zurück -AREAS = BEREICHE ## Gibt die Anzahl der innerhalb eines Bezugs aufgeführten Bereiche zurück -CHOOSE = WAHL ## Wählt einen Wert aus eine Liste mit Werten aus -COLUMN = SPALTE ## Gibt die Spaltennummer eines Bezugs zurück -COLUMNS = SPALTEN ## Gibt die Anzahl der Spalten in einem Bezug zurück -HLOOKUP = HVERWEIS ## Sucht in der obersten Zeile einer Matrix und gibt den Wert der angegebenen Zelle zurück -HYPERLINK = HYPERLINK ## Erstellt eine Verknüpfung, über die ein auf einem Netzwerkserver, in einem Intranet oder im Internet gespeichertes Dokument geöffnet wird -INDEX = INDEX ## Verwendet einen Index, um einen Wert aus einem Bezug oder einer Matrix auszuwählen -INDIRECT = INDIREKT ## Gibt einen Bezug zurück, der von einem Textwert angegeben wird -LOOKUP = LOOKUP ## Sucht Werte in einem Vektor oder einer Matrix -MATCH = VERGLEICH ## Sucht Werte in einem Bezug oder einer Matrix -OFFSET = BEREICH.VERSCHIEBEN ## Gibt einen Bezugoffset aus einem gegebenen Bezug zurück -ROW = ZEILE ## Gibt die Zeilennummer eines Bezugs zurück -ROWS = ZEILEN ## Gibt die Anzahl der Zeilen in einem Bezug zurück -RTD = RTD ## Ruft Echtzeitdaten von einem Programm ab, das die COM-Automatisierung (Automatisierung: Ein Verfahren, bei dem aus einer Anwendung oder einem Entwicklungstool heraus mit den Objekten einer anderen Anwendung gearbeitet wird. Die früher als OLE-Automatisierung bezeichnete Automatisierung ist ein Industriestandard und eine Funktion von COM (Component Object Model).) unterstützt -TRANSPOSE = MTRANS ## Gibt die transponierte Matrix einer Matrix zurück -VLOOKUP = SVERWEIS ## Sucht in der ersten Spalte einer Matrix und arbeitet sich durch die Zeile, um den Wert einer Zelle zurückzugeben - +ABS = ABS +ACOS = ARCCOS +ACOSH = ARCCOSHYP +ACOT = ARCCOT +ACOTH = ARCCOTHYP +AGGREGATE = AGGREGAT +ARABIC = ARABISCH +ASIN = ARCSIN +ASINH = ARCSINHYP +ATAN = ARCTAN +ATAN2 = ARCTAN2 +ATANH = ARCTANHYP +BASE = BASIS +CEILING.MATH = OBERGRENZE.MATHEMATIK +CEILING.PRECISE = OBERGRENZE.GENAU +COMBIN = KOMBINATIONEN +COMBINA = KOMBINATIONEN2 +COS = COS +COSH = COSHYP +COT = COT +COTH = COTHYP +CSC = COSEC +CSCH = COSECHYP +DECIMAL = DEZIMAL +DEGREES = GRAD +ECMA.CEILING = ECMA.OBERGRENZE +EVEN = GERADE +EXP = EXP +FACT = FAKULTÄT +FACTDOUBLE = ZWEIFAKULTÄT +FLOOR.MATH = UNTERGRENZE.MATHEMATIK +FLOOR.PRECISE = UNTERGRENZE.GENAU +GCD = GGT +INT = GANZZAHL +ISO.CEILING = ISO.OBERGRENZE +LCM = KGV +LN = LN +LOG = LOG +LOG10 = LOG10 +MDETERM = MDET +MINVERSE = MINV +MMULT = MMULT +MOD = REST +MROUND = VRUNDEN +MULTINOMIAL = POLYNOMIAL +MUNIT = MEINHEIT +ODD = UNGERADE +PI = PI +POWER = POTENZ +PRODUCT = PRODUKT +QUOTIENT = QUOTIENT +RADIANS = BOGENMASS +RAND = ZUFALLSZAHL +RANDBETWEEN = ZUFALLSBEREICH +ROMAN = RÖMISCH +ROUND = RUNDEN +ROUNDBAHTDOWN = RUNDBAHTNED +ROUNDBAHTUP = BAHTAUFRUNDEN +ROUNDDOWN = ABRUNDEN +ROUNDUP = AUFRUNDEN +SEC = SEC +SECH = SECHYP +SERIESSUM = POTENZREIHE +SIGN = VORZEICHEN +SIN = SIN +SINH = SINHYP +SQRT = WURZEL +SQRTPI = WURZELPI +SUBTOTAL = TEILERGEBNIS +SUM = SUMME +SUMIF = SUMMEWENN +SUMIFS = SUMMEWENNS +SUMPRODUCT = SUMMENPRODUKT +SUMSQ = QUADRATESUMME +SUMX2MY2 = SUMMEX2MY2 +SUMX2PY2 = SUMMEX2PY2 +SUMXMY2 = SUMMEXMY2 +TAN = TAN +TANH = TANHYP +TRUNC = KÜRZEN ## -## Math and trigonometry functions Mathematische und trigonometrische Funktionen +## Statistische Funktionen (Statistical Functions) ## -ABS = ABS ## Gibt den Absolutwert einer Zahl zurück -ACOS = ARCCOS ## Gibt den Arkuskosinus einer Zahl zurück -ACOSH = ARCCOSHYP ## Gibt den umgekehrten hyperbolischen Kosinus einer Zahl zurück -ASIN = ARCSIN ## Gibt den Arkussinus einer Zahl zurück -ASINH = ARCSINHYP ## Gibt den umgekehrten hyperbolischen Sinus einer Zahl zurück -ATAN = ARCTAN ## Gibt den Arkustangens einer Zahl zurück -ATAN2 = ARCTAN2 ## Gibt den Arkustangens einer x- und einer y-Koordinate zurück -ATANH = ARCTANHYP ## Gibt den umgekehrten hyperbolischen Tangens einer Zahl zurück -CEILING = OBERGRENZE ## Rundet eine Zahl auf die nächste ganze Zahl oder das nächste Vielfache von Schritt -COMBIN = KOMBINATIONEN ## Gibt die Anzahl der Kombinationen für eine bestimmte Anzahl von Objekten zurück -COS = COS ## Gibt den Kosinus einer Zahl zurück -COSH = COSHYP ## Gibt den hyperbolischen Kosinus einer Zahl zurück -DEGREES = GRAD ## Wandelt Bogenmaß (Radiant) in Grad um -EVEN = GERADE ## Rundet eine Zahl auf die nächste gerade ganze Zahl auf -EXP = EXP ## Potenziert die Basis e mit der als Argument angegebenen Zahl -FACT = FAKULTÄT ## Gibt die Fakultät einer Zahl zurück -FACTDOUBLE = ZWEIFAKULTÄT ## Gibt die Fakultät zu Zahl mit Schrittlänge 2 zurück -FLOOR = UNTERGRENZE ## Rundet die Zahl auf Anzahl_Stellen ab -GCD = GGT ## Gibt den größten gemeinsamen Teiler zurück -INT = GANZZAHL ## Rundet eine Zahl auf die nächstkleinere ganze Zahl ab -LCM = KGV ## Gibt das kleinste gemeinsame Vielfache zurück -LN = LN ## Gibt den natürlichen Logarithmus einer Zahl zurück -LOG = LOG ## Gibt den Logarithmus einer Zahl zu der angegebenen Basis zurück -LOG10 = LOG10 ## Gibt den Logarithmus einer Zahl zur Basis 10 zurück -MDETERM = MDET ## Gibt die Determinante einer Matrix zurück -MINVERSE = MINV ## Gibt die inverse Matrix einer Matrix zurück -MMULT = MMULT ## Gibt das Produkt zweier Matrizen zurück -MOD = REST ## Gibt den Rest einer Division zurück -MROUND = VRUNDEN ## Gibt eine auf das gewünschte Vielfache gerundete Zahl zurück -MULTINOMIAL = POLYNOMIAL ## Gibt den Polynomialkoeffizienten einer Gruppe von Zahlen zurück -ODD = UNGERADE ## Rundet eine Zahl auf die nächste ungerade ganze Zahl auf -PI = PI ## Gibt den Wert Pi zurück -POWER = POTENZ ## Gibt als Ergebnis eine potenzierte Zahl zurück -PRODUCT = PRODUKT ## Multipliziert die zugehörigen Argumente -QUOTIENT = QUOTIENT ## Gibt den ganzzahligen Anteil einer Division zurück -RADIANS = BOGENMASS ## Wandelt Grad in Bogenmaß (Radiant) um -RAND = ZUFALLSZAHL ## Gibt eine Zufallszahl zwischen 0 und 1 zurück -RANDBETWEEN = ZUFALLSBEREICH ## Gibt eine Zufallszahl aus dem festgelegten Bereich zurück -ROMAN = RÖMISCH ## Wandelt eine arabische Zahl in eine römische Zahl als Text um -ROUND = RUNDEN ## Rundet eine Zahl auf eine bestimmte Anzahl von Dezimalstellen -ROUNDDOWN = ABRUNDEN ## Rundet die Zahl auf Anzahl_Stellen ab -ROUNDUP = AUFRUNDEN ## Rundet die Zahl auf Anzahl_Stellen auf -SERIESSUM = POTENZREIHE ## Gibt die Summe von Potenzen (zur Berechnung von Potenzreihen und dichotomen Wahrscheinlichkeiten) zurück -SIGN = VORZEICHEN ## Gibt das Vorzeichen einer Zahl zurück -SIN = SIN ## Gibt den Sinus einer Zahl zurück -SINH = SINHYP ## Gibt den hyperbolischen Sinus einer Zahl zurück -SQRT = WURZEL ## Gibt die Quadratwurzel einer Zahl zurück -SQRTPI = WURZELPI ## Gibt die Wurzel aus der mit Pi (pi) multiplizierten Zahl zurück -SUBTOTAL = TEILERGEBNIS ## Gibt ein Teilergebnis in einer Liste oder Datenbank zurück -SUM = SUMME ## Addiert die zugehörigen Argumente -SUMIF = SUMMEWENN ## Addiert Zahlen, die mit den Suchkriterien übereinstimmen -SUMIFS = SUMMEWENNS ## Die Zellen, die mehrere Kriterien erfüllen, werden in einem Bereich hinzugefügt -SUMPRODUCT = SUMMENPRODUKT ## Gibt die Summe der Produkte zusammengehöriger Matrixkomponenten zurück -SUMSQ = QUADRATESUMME ## Gibt die Summe der quadrierten Argumente zurück -SUMX2MY2 = SUMMEX2MY2 ## Gibt die Summe der Differenzen der Quadrate für zusammengehörige Komponenten zweier Matrizen zurück -SUMX2PY2 = SUMMEX2PY2 ## Gibt die Summe der Quadrate für zusammengehörige Komponenten zweier Matrizen zurück -SUMXMY2 = SUMMEXMY2 ## Gibt die Summe der quadrierten Differenzen für zusammengehörige Komponenten zweier Matrizen zurück -TAN = TAN ## Gibt den Tangens einer Zahl zurück -TANH = TANHYP ## Gibt den hyperbolischen Tangens einer Zahl zurück -TRUNC = KÜRZEN ## Schneidet die Kommastellen einer Zahl ab und gibt als Ergebnis eine ganze Zahl zurück - +AVEDEV = MITTELABW +AVERAGE = MITTELWERT +AVERAGEA = MITTELWERTA +AVERAGEIF = MITTELWERTWENN +AVERAGEIFS = MITTELWERTWENNS +BETA.DIST = BETA.VERT +BETA.INV = BETA.INV +BINOM.DIST = BINOM.VERT +BINOM.DIST.RANGE = BINOM.VERT.BEREICH +BINOM.INV = BINOM.INV +CHISQ.DIST = CHIQU.VERT +CHISQ.DIST.RT = CHIQU.VERT.RE +CHISQ.INV = CHIQU.INV +CHISQ.INV.RT = CHIQU.INV.RE +CHISQ.TEST = CHIQU.TEST +CONFIDENCE.NORM = KONFIDENZ.NORM +CONFIDENCE.T = KONFIDENZ.T +CORREL = KORREL +COUNT = ANZAHL +COUNTA = ANZAHL2 +COUNTBLANK = ANZAHLLEEREZELLEN +COUNTIF = ZÄHLENWENN +COUNTIFS = ZÄHLENWENNS +COVARIANCE.P = KOVARIANZ.P +COVARIANCE.S = KOVARIANZ.S +DEVSQ = SUMQUADABW +EXPON.DIST = EXPON.VERT +F.DIST = F.VERT +F.DIST.RT = F.VERT.RE +F.INV = F.INV +F.INV.RT = F.INV.RE +F.TEST = F.TEST +FISHER = FISHER +FISHERINV = FISHERINV +FORECAST.ETS = PROGNOSE.ETS +FORECAST.ETS.CONFINT = PROGNOSE.ETS.KONFINT +FORECAST.ETS.SEASONALITY = PROGNOSE.ETS.SAISONALITÄT +FORECAST.ETS.STAT = PROGNOSE.ETS.STAT +FORECAST.LINEAR = PROGNOSE.LINEAR +FREQUENCY = HÄUFIGKEIT +GAMMA = GAMMA +GAMMA.DIST = GAMMA.VERT +GAMMA.INV = GAMMA.INV +GAMMALN = GAMMALN +GAMMALN.PRECISE = GAMMALN.GENAU +GAUSS = GAUSS +GEOMEAN = GEOMITTEL +GROWTH = VARIATION +HARMEAN = HARMITTEL +HYPGEOM.DIST = HYPGEOM.VERT +INTERCEPT = ACHSENABSCHNITT +KURT = KURT +LARGE = KGRÖSSTE +LINEST = RGP +LOGEST = RKP +LOGNORM.DIST = LOGNORM.VERT +LOGNORM.INV = LOGNORM.INV +MAX = MAX +MAXA = MAXA +MAXIFS = MAXWENNS +MEDIAN = MEDIAN +MIN = MIN +MINA = MINA +MINIFS = MINWENNS +MODE.MULT = MODUS.VIELF +MODE.SNGL = MODUS.EINF +NEGBINOM.DIST = NEGBINOM.VERT +NORM.DIST = NORM.VERT +NORM.INV = NORM.INV +NORM.S.DIST = NORM.S.VERT +NORM.S.INV = NORM.S.INV +PEARSON = PEARSON +PERCENTILE.EXC = QUANTIL.EXKL +PERCENTILE.INC = QUANTIL.INKL +PERCENTRANK.EXC = QUANTILSRANG.EXKL +PERCENTRANK.INC = QUANTILSRANG.INKL +PERMUT = VARIATIONEN +PERMUTATIONA = VARIATIONEN2 +PHI = PHI +POISSON.DIST = POISSON.VERT +PROB = WAHRSCHBEREICH +QUARTILE.EXC = QUARTILE.EXKL +QUARTILE.INC = QUARTILE.INKL +RANK.AVG = RANG.MITTELW +RANK.EQ = RANG.GLEICH +RSQ = BESTIMMTHEITSMASS +SKEW = SCHIEFE +SKEW.P = SCHIEFE.P +SLOPE = STEIGUNG +SMALL = KKLEINSTE +STANDARDIZE = STANDARDISIERUNG +STDEV.P = STABW.N +STDEV.S = STABW.S +STDEVA = STABWA +STDEVPA = STABWNA +STEYX = STFEHLERYX +T.DIST = T.VERT +T.DIST.2T = T.VERT.2S +T.DIST.RT = T.VERT.RE +T.INV = T.INV +T.INV.2T = T.INV.2S +T.TEST = T.TEST +TREND = TREND +TRIMMEAN = GESTUTZTMITTEL +VAR.P = VAR.P +VAR.S = VAR.S +VARA = VARIANZA +VARPA = VARIANZENA +WEIBULL.DIST = WEIBULL.VERT +Z.TEST = G.TEST ## -## Statistical functions Statistische Funktionen +## Textfunktionen (Text Functions) ## -AVEDEV = MITTELABW ## Gibt die durchschnittliche absolute Abweichung einer Reihe von Merkmalsausprägungen und ihrem Mittelwert zurück -AVERAGE = MITTELWERT ## Gibt den Mittelwert der zugehörigen Argumente zurück -AVERAGEA = MITTELWERTA ## Gibt den Mittelwert der zugehörigen Argumente, die Zahlen, Text und Wahrheitswerte enthalten, zurück -AVERAGEIF = MITTELWERTWENN ## Der Durchschnittswert (arithmetisches Mittel) für alle Zellen in einem Bereich, die einem angegebenen Kriterium entsprechen, wird zurückgegeben -AVERAGEIFS = MITTELWERTWENNS ## Gibt den Durchschnittswert (arithmetisches Mittel) aller Zellen zurück, die mehreren Kriterien entsprechen -BETADIST = BETAVERT ## Gibt die Werte der kumulierten Betaverteilungsfunktion zurück -BETAINV = BETAINV ## Gibt das Quantil der angegebenen Betaverteilung zurück -BINOMDIST = BINOMVERT ## Gibt Wahrscheinlichkeiten einer binomialverteilten Zufallsvariablen zurück -CHIDIST = CHIVERT ## Gibt Werte der Verteilungsfunktion (1-Alpha) einer Chi-Quadrat-verteilten Zufallsgröße zurück -CHIINV = CHIINV ## Gibt Quantile der Verteilungsfunktion (1-Alpha) der Chi-Quadrat-Verteilung zurück -CHITEST = CHITEST ## Gibt die Teststatistik eines Unabhängigkeitstests zurück -CONFIDENCE = KONFIDENZ ## Ermöglicht die Berechnung des 1-Alpha Konfidenzintervalls für den Erwartungswert einer Zufallsvariablen -CORREL = KORREL ## Gibt den Korrelationskoeffizienten zweier Reihen von Merkmalsausprägungen zurück -COUNT = ANZAHL ## Gibt die Anzahl der Zahlen in der Liste mit Argumenten an -COUNTA = ANZAHL2 ## Gibt die Anzahl der Werte in der Liste mit Argumenten an -COUNTBLANK = ANZAHLLEEREZELLEN ## Gibt die Anzahl der leeren Zellen in einem Bereich an -COUNTIF = ZÄHLENWENN ## Gibt die Anzahl der Zellen in einem Bereich an, deren Inhalte mit den Suchkriterien übereinstimmen -COUNTIFS = ZÄHLENWENNS ## Gibt die Anzahl der Zellen in einem Bereich an, deren Inhalte mit mehreren Suchkriterien übereinstimmen -COVAR = KOVAR ## Gibt die Kovarianz zurück, den Mittelwert der für alle Datenpunktpaare gebildeten Produkte der Abweichungen -CRITBINOM = KRITBINOM ## Gibt den kleinsten Wert zurück, für den die kumulierten Wahrscheinlichkeiten der Binomialverteilung kleiner oder gleich einer Grenzwahrscheinlichkeit sind -DEVSQ = SUMQUADABW ## Gibt die Summe der quadrierten Abweichungen der Datenpunkte von ihrem Stichprobenmittelwert zurück -EXPONDIST = EXPONVERT ## Gibt Wahrscheinlichkeiten einer exponential verteilten Zufallsvariablen zurück -FDIST = FVERT ## Gibt Werte der Verteilungsfunktion (1-Alpha) einer F-verteilten Zufallsvariablen zurück -FINV = FINV ## Gibt Quantile der F-Verteilung zurück -FISHER = FISHER ## Gibt die Fisher-Transformation zurück -FISHERINV = FISHERINV ## Gibt die Umkehrung der Fisher-Transformation zurück -FORECAST = PROGNOSE ## Gibt einen Wert zurück, der sich aus einem linearen Trend ergibt -FREQUENCY = HÄUFIGKEIT ## Gibt eine Häufigkeitsverteilung als vertikale Matrix zurück -FTEST = FTEST ## Gibt die Teststatistik eines F-Tests zurück -GAMMADIST = GAMMAVERT ## Gibt Wahrscheinlichkeiten einer gammaverteilten Zufallsvariablen zurück -GAMMAINV = GAMMAINV ## Gibt Quantile der Gammaverteilung zurück -GAMMALN = GAMMALN ## Gibt den natürlichen Logarithmus der Gammafunktion zurück, Γ(x) -GEOMEAN = GEOMITTEL ## Gibt das geometrische Mittel zurück -GROWTH = VARIATION ## Gibt Werte zurück, die sich aus einem exponentiellen Trend ergeben -HARMEAN = HARMITTEL ## Gibt das harmonische Mittel zurück -HYPGEOMDIST = HYPGEOMVERT ## Gibt Wahrscheinlichkeiten einer hypergeometrisch-verteilten Zufallsvariablen zurück -INTERCEPT = ACHSENABSCHNITT ## Gibt den Schnittpunkt der Regressionsgeraden zurück -KURT = KURT ## Gibt die Kurtosis (Exzess) einer Datengruppe zurück -LARGE = KGRÖSSTE ## Gibt den k-größten Wert einer Datengruppe zurück -LINEST = RGP ## Gibt die Parameter eines linearen Trends zurück -LOGEST = RKP ## Gibt die Parameter eines exponentiellen Trends zurück -LOGINV = LOGINV ## Gibt Quantile der Lognormalverteilung zurück -LOGNORMDIST = LOGNORMVERT ## Gibt Werte der Verteilungsfunktion einer lognormalverteilten Zufallsvariablen zurück -MAX = MAX ## Gibt den Maximalwert einer Liste mit Argumenten zurück -MAXA = MAXA ## Gibt den Maximalwert einer Liste mit Argumenten zurück, die Zahlen, Text und Wahrheitswerte enthalten -MEDIAN = MEDIAN ## Gibt den Median der angegebenen Zahlen zurück -MIN = MIN ## Gibt den Minimalwert einer Liste mit Argumenten zurück -MINA = MINA ## Gibt den kleinsten Wert einer Liste mit Argumenten zurück, die Zahlen, Text und Wahrheitswerte enthalten -MODE = MODALWERT ## Gibt den am häufigsten vorkommenden Wert in einer Datengruppe zurück -NEGBINOMDIST = NEGBINOMVERT ## Gibt Wahrscheinlichkeiten einer negativen, binominal verteilten Zufallsvariablen zurück -NORMDIST = NORMVERT ## Gibt Wahrscheinlichkeiten einer normal verteilten Zufallsvariablen zurück -NORMINV = NORMINV ## Gibt Quantile der Normalverteilung zurück -NORMSDIST = STANDNORMVERT ## Gibt Werte der Verteilungsfunktion einer standardnormalverteilten Zufallsvariablen zurück -NORMSINV = STANDNORMINV ## Gibt Quantile der Standardnormalverteilung zurück -PEARSON = PEARSON ## Gibt den Pearsonschen Korrelationskoeffizienten zurück -PERCENTILE = QUANTIL ## Gibt das Alpha-Quantil einer Gruppe von Daten zurück -PERCENTRANK = QUANTILSRANG ## Gibt den prozentualen Rang (Alpha) eines Werts in einer Datengruppe zurück -PERMUT = VARIATIONEN ## Gibt die Anzahl der Möglichkeiten zurück, um k Elemente aus einer Menge von n Elementen ohne Zurücklegen zu ziehen -POISSON = POISSON ## Gibt Wahrscheinlichkeiten einer poissonverteilten Zufallsvariablen zurück -PROB = WAHRSCHBEREICH ## Gibt die Wahrscheinlichkeit für ein von zwei Werten eingeschlossenes Intervall zurück -QUARTILE = QUARTILE ## Gibt die Quartile der Datengruppe zurück -RANK = RANG ## Gibt den Rang zurück, den eine Zahl innerhalb einer Liste von Zahlen einnimmt -RSQ = BESTIMMTHEITSMASS ## Gibt das Quadrat des Pearsonschen Korrelationskoeffizienten zurück -SKEW = SCHIEFE ## Gibt die Schiefe einer Verteilung zurück -SLOPE = STEIGUNG ## Gibt die Steigung der Regressionsgeraden zurück -SMALL = KKLEINSTE ## Gibt den k-kleinsten Wert einer Datengruppe zurück -STANDARDIZE = STANDARDISIERUNG ## Gibt den standardisierten Wert zurück -STDEV = STABW ## Schätzt die Standardabweichung ausgehend von einer Stichprobe -STDEVA = STABWA ## Schätzt die Standardabweichung ausgehend von einer Stichprobe, die Zahlen, Text und Wahrheitswerte enthält -STDEVP = STABWN ## Berechnet die Standardabweichung ausgehend von der Grundgesamtheit -STDEVPA = STABWNA ## Berechnet die Standardabweichung ausgehend von der Grundgesamtheit, die Zahlen, Text und Wahrheitswerte enthält -STEYX = STFEHLERYX ## Gibt den Standardfehler der geschätzten y-Werte für alle x-Werte der Regression zurück -TDIST = TVERT ## Gibt Werte der Verteilungsfunktion (1-Alpha) einer (Student) t-verteilten Zufallsvariablen zurück -TINV = TINV ## Gibt Quantile der t-Verteilung zurück -TREND = TREND ## Gibt Werte zurück, die sich aus einem linearen Trend ergeben -TRIMMEAN = GESTUTZTMITTEL ## Gibt den Mittelwert einer Datengruppe zurück, ohne die Randwerte zu berücksichtigen -TTEST = TTEST ## Gibt die Teststatistik eines Student'schen t-Tests zurück -VAR = VARIANZ ## Schätzt die Varianz ausgehend von einer Stichprobe -VARA = VARIANZA ## Schätzt die Varianz ausgehend von einer Stichprobe, die Zahlen, Text und Wahrheitswerte enthält -VARP = VARIANZEN ## Berechnet die Varianz ausgehend von der Grundgesamtheit -VARPA = VARIANZENA ## Berechnet die Varianz ausgehend von der Grundgesamtheit, die Zahlen, Text und Wahrheitswerte enthält -WEIBULL = WEIBULL ## Gibt Wahrscheinlichkeiten einer weibullverteilten Zufallsvariablen zurück -ZTEST = GTEST ## Gibt den einseitigen Wahrscheinlichkeitswert für einen Gausstest (Normalverteilung) zurück - +BAHTTEXT = BAHTTEXT +CHAR = ZEICHEN +CLEAN = SÄUBERN +CODE = CODE +CONCAT = TEXTKETTE +DOLLAR = DM +EXACT = IDENTISCH +FIND = FINDEN +FIXED = FEST +ISTHAIDIGIT = ISTTHAIZAHLENWORT +LEFT = LINKS +LEN = LÄNGE +LOWER = KLEIN +MID = TEIL +NUMBERVALUE = ZAHLENWERT +PROPER = GROSS2 +REPLACE = ERSETZEN +REPT = WIEDERHOLEN +RIGHT = RECHTS +SEARCH = SUCHEN +SUBSTITUTE = WECHSELN +T = T +TEXT = TEXT +TEXTJOIN = TEXTVERKETTEN +THAIDIGIT = THAIZAHLENWORT +THAINUMSOUND = THAIZAHLSOUND +THAINUMSTRING = THAILANDSKNUMSTRENG +THAISTRINGLENGTH = THAIZEICHENFOLGENLÄNGE +TRIM = GLÄTTEN +UNICHAR = UNIZEICHEN +UNICODE = UNICODE +UPPER = GROSS +VALUE = WERT ## -## Text functions Textfunktionen +## Webfunktionen (Web Functions) ## -ASC = ASC ## Konvertiert DB-Text in einer Zeichenfolge (lateinische Buchstaben oder Katakana) in SB-Text -BAHTTEXT = BAHTTEXT ## Wandelt eine Zahl in Text im Währungsformat ß (Baht) um -CHAR = ZEICHEN ## Gibt das der Codezahl entsprechende Zeichen zurück -CLEAN = SÄUBERN ## Löscht alle nicht druckbaren Zeichen aus einem Text -CODE = CODE ## Gibt die Codezahl des ersten Zeichens in einem Text zurück -CONCATENATE = VERKETTEN ## Verknüpft mehrere Textelemente zu einem Textelement -DOLLAR = DM ## Wandelt eine Zahl in Text im Währungsformat € (Euro) um -EXACT = IDENTISCH ## Prüft, ob zwei Textwerte identisch sind -FIND = FINDEN ## Sucht nach einem Textwert, der in einem anderen Textwert enthalten ist (Groß-/Kleinschreibung wird unterschieden) -FINDB = FINDENB ## Sucht nach einem Textwert, der in einem anderen Textwert enthalten ist (Groß-/Kleinschreibung wird unterschieden) -FIXED = FEST ## Formatiert eine Zahl als Text mit einer festen Anzahl von Dezimalstellen -JIS = JIS ## Konvertiert SB-Text in einer Zeichenfolge (lateinische Buchstaben oder Katakana) in DB-Text -LEFT = LINKS ## Gibt die Zeichen ganz links in einem Textwert zurück -LEFTB = LINKSB ## Gibt die Zeichen ganz links in einem Textwert zurück -LEN = LÄNGE ## Gibt die Anzahl der Zeichen in einer Zeichenfolge zurück -LENB = LÄNGEB ## Gibt die Anzahl der Zeichen in einer Zeichenfolge zurück -LOWER = KLEIN ## Wandelt Text in Kleinbuchstaben um -MID = TEIL ## Gibt eine bestimmte Anzahl Zeichen aus einer Zeichenfolge ab der von Ihnen angegebenen Stelle zurück -MIDB = TEILB ## Gibt eine bestimmte Anzahl Zeichen aus einer Zeichenfolge ab der von Ihnen angegebenen Stelle zurück -PHONETIC = PHONETIC ## Extrahiert die phonetischen (Furigana-)Zeichen aus einer Textzeichenfolge -PROPER = GROSS2 ## Wandelt den ersten Buchstaben aller Wörter eines Textwerts in Großbuchstaben um -REPLACE = ERSETZEN ## Ersetzt Zeichen in Text -REPLACEB = ERSETZENB ## Ersetzt Zeichen in Text -REPT = WIEDERHOLEN ## Wiederholt einen Text so oft wie angegeben -RIGHT = RECHTS ## Gibt die Zeichen ganz rechts in einem Textwert zurück -RIGHTB = RECHTSB ## Gibt die Zeichen ganz rechts in einem Textwert zurück -SEARCH = SUCHEN ## Sucht nach einem Textwert, der in einem anderen Textwert enthalten ist (Groß-/Kleinschreibung wird nicht unterschieden) -SEARCHB = SUCHENB ## Sucht nach einem Textwert, der in einem anderen Textwert enthalten ist (Groß-/Kleinschreibung wird nicht unterschieden) -SUBSTITUTE = WECHSELN ## Ersetzt in einer Zeichenfolge neuen Text gegen alten -T = T ## Wandelt die zugehörigen Argumente in Text um -TEXT = TEXT ## Formatiert eine Zahl und wandelt sie in Text um -TRIM = GLÄTTEN ## Entfernt Leerzeichen aus Text -UPPER = GROSS ## Wandelt Text in Großbuchstaben um -VALUE = WERT ## Wandelt ein Textargument in eine Zahl um +ENCODEURL = URLCODIEREN +FILTERXML = XMLFILTERN +WEBSERVICE = WEBDIENST + +## +## Kompatibilitätsfunktionen (Compatibility Functions) +## +BETADIST = BETAVERT +BETAINV = BETAINV +BINOMDIST = BINOMVERT +CEILING = OBERGRENZE +CHIDIST = CHIVERT +CHIINV = CHIINV +CHITEST = CHITEST +CONCATENATE = VERKETTEN +CONFIDENCE = KONFIDENZ +COVAR = KOVAR +CRITBINOM = KRITBINOM +EXPONDIST = EXPONVERT +FDIST = FVERT +FINV = FINV +FLOOR = UNTERGRENZE +FORECAST = SCHÄTZER +FTEST = FTEST +GAMMADIST = GAMMAVERT +GAMMAINV = GAMMAINV +HYPGEOMDIST = HYPGEOMVERT +LOGINV = LOGINV +LOGNORMDIST = LOGNORMVERT +MODE = MODALWERT +NEGBINOMDIST = NEGBINOMVERT +NORMDIST = NORMVERT +NORMINV = NORMINV +NORMSDIST = STANDNORMVERT +NORMSINV = STANDNORMINV +PERCENTILE = QUANTIL +PERCENTRANK = QUANTILSRANG +POISSON = POISSON +QUARTILE = QUARTILE +RANK = RANG +STDEV = STABW +STDEVP = STABWN +TDIST = TVERT +TINV = TINV +TTEST = TTEST +VAR = VARIANZ +VARP = VARIANZEN +WEIBULL = WEIBULL +ZTEST = GTEST diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/en/uk/config b/PhpOffice/PhpSpreadsheet/Calculation/locale/en/uk/config old mode 100755 new mode 100644 diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/es/config b/PhpOffice/PhpSpreadsheet/Calculation/locale/es/config old mode 100755 new mode 100644 index 5b9b948..fe044ef --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/es/config +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/es/config @@ -1,24 +1,20 @@ +############################################################ ## -## PhpSpreadsheet +## PhpSpreadsheet - locale settings ## +## Español (Spanish) +## +############################################################ -ArgumentSeparator = ; - +ArgumentSeparator = ; ## -## (For future use) +## Error Codes ## -currencySymbol = $ ## I'm surprised that the Excel Documentation suggests $ rather than € - - -## -## Excel Error Codes (For future use) - -## -NULL = #¡NULO! -DIV0 = #¡DIV/0! -VALUE = #¡VALOR! -REF = #¡REF! -NAME = #¿NOMBRE? -NUM = #¡NÚM! -NA = #N/A +NULL = #¡NULO! +DIV0 = #¡DIV/0! +VALUE = #¡VALOR! +REF = #¡REF! +NAME = #¿NOMBRE? +NUM = #¡NUM! +NA diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/es/functions b/PhpOffice/PhpSpreadsheet/Calculation/locale/es/functions old mode 100755 new mode 100644 index ac1ac86..88012aa --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/es/functions +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/es/functions @@ -1,416 +1,538 @@ +############################################################ ## -## PhpSpreadsheet -## -## Data in this file derived from https://www.excel-function-translation.com/ +## PhpSpreadsheet - function name translations ## +## Español (Spanish) ## +############################################################ ## -## Add-in and Automation functions Funciones de complementos y automatización +## Funciones de cubo (Cube Functions) ## -GETPIVOTDATA = IMPORTARDATOSDINAMICOS ## Devuelve los datos almacenados en un informe de tabla dinámica. - +CUBEKPIMEMBER = MIEMBROKPICUBO +CUBEMEMBER = MIEMBROCUBO +CUBEMEMBERPROPERTY = PROPIEDADMIEMBROCUBO +CUBERANKEDMEMBER = MIEMBRORANGOCUBO +CUBESET = CONJUNTOCUBO +CUBESETCOUNT = RECUENTOCONJUNTOCUBO +CUBEVALUE = VALORCUBO ## -## Cube functions Funciones de cubo +## Funciones de base de datos (Database Functions) ## -CUBEKPIMEMBER = MIEMBROKPICUBO ## Devuelve un nombre, propiedad y medida de indicador de rendimiento clave (KPI) y muestra el nombre y la propiedad en la celda. Un KPI es una medida cuantificable, como los beneficios brutos mensuales o la facturación trimestral por empleado, que se usa para supervisar el rendimiento de una organización. -CUBEMEMBER = MIEMBROCUBO ## Devuelve un miembro o tupla en una jerarquía de cubo. Se usa para validar la existencia del miembro o la tupla en el cubo. -CUBEMEMBERPROPERTY = PROPIEDADMIEMBROCUBO ## Devuelve el valor de una propiedad de miembro del cubo Se usa para validar la existencia de un nombre de miembro en el cubo y para devolver la propiedad especificada para este miembro. -CUBERANKEDMEMBER = MIEMBRORANGOCUBO ## Devuelve el miembro n, o clasificado, de un conjunto. Se usa para devolver uno o más elementos de un conjunto, por ejemplo, el representante con mejores ventas o los diez mejores alumnos. -CUBESET = CONJUNTOCUBO ## Define un conjunto calculado de miembros o tuplas mediante el envío de una expresión de conjunto al cubo en el servidor, lo que crea el conjunto y, después, devuelve dicho conjunto a Microsoft Office Excel. -CUBESETCOUNT = RECUENTOCONJUNTOCUBO ## Devuelve el número de elementos de un conjunto. -CUBEVALUE = VALORCUBO ## Devuelve un valor agregado de un cubo. - +DAVERAGE = BDPROMEDIO +DCOUNT = BDCONTAR +DCOUNTA = BDCONTARA +DGET = BDEXTRAER +DMAX = BDMAX +DMIN = BDMIN +DPRODUCT = BDPRODUCTO +DSTDEV = BDDESVEST +DSTDEVP = BDDESVESTP +DSUM = BDSUMA +DVAR = BDVAR +DVARP = BDVARP ## -## Database functions Funciones de base de datos +## Funciones de fecha y hora (Date & Time Functions) ## -DAVERAGE = BDPROMEDIO ## Devuelve el promedio de las entradas seleccionadas en la base de datos. -DCOUNT = BDCONTAR ## Cuenta el número de celdas que contienen números en una base de datos. -DCOUNTA = BDCONTARA ## Cuenta el número de celdas no vacías en una base de datos. -DGET = BDEXTRAER ## Extrae de una base de datos un único registro que cumple los criterios especificados. -DMAX = BDMAX ## Devuelve el valor máximo de las entradas seleccionadas de la base de datos. -DMIN = BDMIN ## Devuelve el valor mínimo de las entradas seleccionadas de la base de datos. -DPRODUCT = BDPRODUCTO ## Multiplica los valores de un campo concreto de registros de una base de datos que cumplen los criterios especificados. -DSTDEV = BDDESVEST ## Calcula la desviación estándar a partir de una muestra de entradas seleccionadas en la base de datos. -DSTDEVP = BDDESVESTP ## Calcula la desviación estándar en función de la población total de las entradas seleccionadas de la base de datos. -DSUM = BDSUMA ## Suma los números de la columna de campo de los registros de la base de datos que cumplen los criterios. -DVAR = BDVAR ## Calcula la varianza a partir de una muestra de entradas seleccionadas de la base de datos. -DVARP = BDVARP ## Calcula la varianza a partir de la población total de entradas seleccionadas de la base de datos. - +DATE = FECHA +DATEDIF = SIFECHA +DATESTRING = CADENA.FECHA +DATEVALUE = FECHANUMERO +DAY = DIA +DAYS = DIAS +DAYS360 = DIAS360 +EDATE = FECHA.MES +EOMONTH = FIN.MES +HOUR = HORA +ISOWEEKNUM = ISO.NUM.DE.SEMANA +MINUTE = MINUTO +MONTH = MES +NETWORKDAYS = DIAS.LAB +NETWORKDAYS.INTL = DIAS.LAB.INTL +NOW = AHORA +SECOND = SEGUNDO +THAIDAYOFWEEK = DIASEMTAI +THAIMONTHOFYEAR = MESAÑOTAI +THAIYEAR = AÑOTAI +TIME = NSHORA +TIMEVALUE = HORANUMERO +TODAY = HOY +WEEKDAY = DIASEM +WEEKNUM = NUM.DE.SEMANA +WORKDAY = DIA.LAB +WORKDAY.INTL = DIA.LAB.INTL +YEAR = AÑO +YEARFRAC = FRAC.AÑO ## -## Date and time functions Funciones de fecha y hora +## Funciones de ingeniería (Engineering Functions) ## -DATE = FECHA ## Devuelve el número de serie correspondiente a una fecha determinada. -DATEVALUE = FECHANUMERO ## Convierte una fecha con formato de texto en un valor de número de serie. -DAY = DIA ## Convierte un número de serie en un valor de día del mes. -DAYS360 = DIAS360 ## Calcula el número de días entre dos fechas a partir de un año de 360 días. -EDATE = FECHA.MES ## Devuelve el número de serie de la fecha equivalente al número indicado de meses anteriores o posteriores a la fecha inicial. -EOMONTH = FIN.MES ## Devuelve el número de serie correspondiente al último día del mes anterior o posterior a un número de meses especificado. -HOUR = HORA ## Convierte un número de serie en un valor de hora. -MINUTE = MINUTO ## Convierte un número de serie en un valor de minuto. -MONTH = MES ## Convierte un número de serie en un valor de mes. -NETWORKDAYS = DIAS.LAB ## Devuelve el número de todos los días laborables existentes entre dos fechas. -NOW = AHORA ## Devuelve el número de serie correspondiente a la fecha y hora actuales. -SECOND = SEGUNDO ## Convierte un número de serie en un valor de segundo. -TIME = HORA ## Devuelve el número de serie correspondiente a una hora determinada. -TIMEVALUE = HORANUMERO ## Convierte una hora con formato de texto en un valor de número de serie. -TODAY = HOY ## Devuelve el número de serie correspondiente al día actual. -WEEKDAY = DIASEM ## Convierte un número de serie en un valor de día de la semana. -WEEKNUM = NUM.DE.SEMANA ## Convierte un número de serie en un número que representa el lugar numérico correspondiente a una semana de un año. -WORKDAY = DIA.LAB ## Devuelve el número de serie de la fecha que tiene lugar antes o después de un número determinado de días laborables. -YEAR = AÑO ## Convierte un número de serie en un valor de año. -YEARFRAC = FRAC.AÑO ## Devuelve la fracción de año que representa el número total de días existentes entre el valor de fecha_inicial y el de fecha_final. - +BESSELI = BESSELI +BESSELJ = BESSELJ +BESSELK = BESSELK +BESSELY = BESSELY +BIN2DEC = BIN.A.DEC +BIN2HEX = BIN.A.HEX +BIN2OCT = BIN.A.OCT +BITAND = BIT.Y +BITLSHIFT = BIT.DESPLIZQDA +BITOR = BIT.O +BITRSHIFT = BIT.DESPLDCHA +BITXOR = BIT.XO +COMPLEX = COMPLEJO +CONVERT = CONVERTIR +DEC2BIN = DEC.A.BIN +DEC2HEX = DEC.A.HEX +DEC2OCT = DEC.A.OCT +DELTA = DELTA +ERF = FUN.ERROR +ERF.PRECISE = FUN.ERROR.EXACTO +ERFC = FUN.ERROR.COMPL +ERFC.PRECISE = FUN.ERROR.COMPL.EXACTO +GESTEP = MAYOR.O.IGUAL +HEX2BIN = HEX.A.BIN +HEX2DEC = HEX.A.DEC +HEX2OCT = HEX.A.OCT +IMABS = IM.ABS +IMAGINARY = IMAGINARIO +IMARGUMENT = IM.ANGULO +IMCONJUGATE = IM.CONJUGADA +IMCOS = IM.COS +IMCOSH = IM.COSH +IMCOT = IM.COT +IMCSC = IM.CSC +IMCSCH = IM.CSCH +IMDIV = IM.DIV +IMEXP = IM.EXP +IMLN = IM.LN +IMLOG10 = IM.LOG10 +IMLOG2 = IM.LOG2 +IMPOWER = IM.POT +IMPRODUCT = IM.PRODUCT +IMREAL = IM.REAL +IMSEC = IM.SEC +IMSECH = IM.SECH +IMSIN = IM.SENO +IMSINH = IM.SENOH +IMSQRT = IM.RAIZ2 +IMSUB = IM.SUSTR +IMSUM = IM.SUM +IMTAN = IM.TAN +OCT2BIN = OCT.A.BIN +OCT2DEC = OCT.A.DEC +OCT2HEX = OCT.A.HEX ## -## Engineering functions Funciones de ingeniería +## Funciones financieras (Financial Functions) ## -BESSELI = BESSELI ## Devuelve la función Bessel In(x) modificada. -BESSELJ = BESSELJ ## Devuelve la función Bessel Jn(x). -BESSELK = BESSELK ## Devuelve la función Bessel Kn(x) modificada. -BESSELY = BESSELY ## Devuelve la función Bessel Yn(x). -BIN2DEC = BIN.A.DEC ## Convierte un número binario en decimal. -BIN2HEX = BIN.A.HEX ## Convierte un número binario en hexadecimal. -BIN2OCT = BIN.A.OCT ## Convierte un número binario en octal. -COMPLEX = COMPLEJO ## Convierte coeficientes reales e imaginarios en un número complejo. -CONVERT = CONVERTIR ## Convierte un número de un sistema de medida a otro. -DEC2BIN = DEC.A.BIN ## Convierte un número decimal en binario. -DEC2HEX = DEC.A.HEX ## Convierte un número decimal en hexadecimal. -DEC2OCT = DEC.A.OCT ## Convierte un número decimal en octal. -DELTA = DELTA ## Comprueba si dos valores son iguales. -ERF = FUN.ERROR ## Devuelve la función de error. -ERFC = FUN.ERROR.COMPL ## Devuelve la función de error complementario. -GESTEP = MAYOR.O.IGUAL ## Comprueba si un número es mayor que un valor de umbral. -HEX2BIN = HEX.A.BIN ## Convierte un número hexadecimal en binario. -HEX2DEC = HEX.A.DEC ## Convierte un número hexadecimal en decimal. -HEX2OCT = HEX.A.OCT ## Convierte un número hexadecimal en octal. -IMABS = IM.ABS ## Devuelve el valor absoluto (módulo) de un número complejo. -IMAGINARY = IMAGINARIO ## Devuelve el coeficiente imaginario de un número complejo. -IMARGUMENT = IM.ANGULO ## Devuelve el argumento theta, un ángulo expresado en radianes. -IMCONJUGATE = IM.CONJUGADA ## Devuelve la conjugada compleja de un número complejo. -IMCOS = IM.COS ## Devuelve el coseno de un número complejo. -IMDIV = IM.DIV ## Devuelve el cociente de dos números complejos. -IMEXP = IM.EXP ## Devuelve el valor exponencial de un número complejo. -IMLN = IM.LN ## Devuelve el logaritmo natural (neperiano) de un número complejo. -IMLOG10 = IM.LOG10 ## Devuelve el logaritmo en base 10 de un número complejo. -IMLOG2 = IM.LOG2 ## Devuelve el logaritmo en base 2 de un número complejo. -IMPOWER = IM.POT ## Devuelve un número complejo elevado a una potencia entera. -IMPRODUCT = IM.PRODUCT ## Devuelve el producto de números complejos. -IMREAL = IM.REAL ## Devuelve el coeficiente real de un número complejo. -IMSIN = IM.SENO ## Devuelve el seno de un número complejo. -IMSQRT = IM.RAIZ2 ## Devuelve la raíz cuadrada de un número complejo. -IMSUB = IM.SUSTR ## Devuelve la diferencia entre dos números complejos. -IMSUM = IM.SUM ## Devuelve la suma de números complejos. -OCT2BIN = OCT.A.BIN ## Convierte un número octal en binario. -OCT2DEC = OCT.A.DEC ## Convierte un número octal en decimal. -OCT2HEX = OCT.A.HEX ## Convierte un número octal en hexadecimal. - +ACCRINT = INT.ACUM +ACCRINTM = INT.ACUM.V +AMORDEGRC = AMORTIZ.PROGRE +AMORLINC = AMORTIZ.LIN +COUPDAYBS = CUPON.DIAS.L1 +COUPDAYS = CUPON.DIAS +COUPDAYSNC = CUPON.DIAS.L2 +COUPNCD = CUPON.FECHA.L2 +COUPNUM = CUPON.NUM +COUPPCD = CUPON.FECHA.L1 +CUMIPMT = PAGO.INT.ENTRE +CUMPRINC = PAGO.PRINC.ENTRE +DB = DB +DDB = DDB +DISC = TASA.DESC +DOLLARDE = MONEDA.DEC +DOLLARFR = MONEDA.FRAC +DURATION = DURACION +EFFECT = INT.EFECTIVO +FV = VF +FVSCHEDULE = VF.PLAN +INTRATE = TASA.INT +IPMT = PAGOINT +IRR = TIR +ISPMT = INT.PAGO.DIR +MDURATION = DURACION.MODIF +MIRR = TIRM +NOMINAL = TASA.NOMINAL +NPER = NPER +NPV = VNA +ODDFPRICE = PRECIO.PER.IRREGULAR.1 +ODDFYIELD = RENDTO.PER.IRREGULAR.1 +ODDLPRICE = PRECIO.PER.IRREGULAR.2 +ODDLYIELD = RENDTO.PER.IRREGULAR.2 +PDURATION = P.DURACION +PMT = PAGO +PPMT = PAGOPRIN +PRICE = PRECIO +PRICEDISC = PRECIO.DESCUENTO +PRICEMAT = PRECIO.VENCIMIENTO +PV = VA +RATE = TASA +RECEIVED = CANTIDAD.RECIBIDA +RRI = RRI +SLN = SLN +SYD = SYD +TBILLEQ = LETRA.DE.TEST.EQV.A.BONO +TBILLPRICE = LETRA.DE.TES.PRECIO +TBILLYIELD = LETRA.DE.TES.RENDTO +VDB = DVS +XIRR = TIR.NO.PER +XNPV = VNA.NO.PER +YIELD = RENDTO +YIELDDISC = RENDTO.DESC +YIELDMAT = RENDTO.VENCTO ## -## Financial functions Funciones financieras +## Funciones de información (Information Functions) ## -ACCRINT = INT.ACUM ## Devuelve el interés acumulado de un valor bursátil con pagos de interés periódicos. -ACCRINTM = INT.ACUM.V ## Devuelve el interés acumulado de un valor bursátil con pagos de interés al vencimiento. -AMORDEGRC = AMORTIZ.PROGRE ## Devuelve la amortización de cada período contable mediante el uso de un coeficiente de amortización. -AMORLINC = AMORTIZ.LIN ## Devuelve la amortización de cada uno de los períodos contables. -COUPDAYBS = CUPON.DIAS.L1 ## Devuelve el número de días desde el principio del período de un cupón hasta la fecha de liquidación. -COUPDAYS = CUPON.DIAS ## Devuelve el número de días del período (entre dos cupones) donde se encuentra la fecha de liquidación. -COUPDAYSNC = CUPON.DIAS.L2 ## Devuelve el número de días desde la fecha de liquidación hasta la fecha del próximo cupón. -COUPNCD = CUPON.FECHA.L2 ## Devuelve la fecha del próximo cupón después de la fecha de liquidación. -COUPNUM = CUPON.NUM ## Devuelve el número de pagos de cupón entre la fecha de liquidación y la fecha de vencimiento. -COUPPCD = CUPON.FECHA.L1 ## Devuelve la fecha de cupón anterior a la fecha de liquidación. -CUMIPMT = PAGO.INT.ENTRE ## Devuelve el interés acumulado pagado entre dos períodos. -CUMPRINC = PAGO.PRINC.ENTRE ## Devuelve el capital acumulado pagado de un préstamo entre dos períodos. -DB = DB ## Devuelve la amortización de un bien durante un período específico a través del método de amortización de saldo fijo. -DDB = DDB ## Devuelve la amortización de un bien durante un período específico a través del método de amortización por doble disminución de saldo u otro método que se especifique. -DISC = TASA.DESC ## Devuelve la tasa de descuento de un valor bursátil. -DOLLARDE = MONEDA.DEC ## Convierte una cotización de un valor bursátil expresada en forma fraccionaria en una cotización de un valor bursátil expresada en forma decimal. -DOLLARFR = MONEDA.FRAC ## Convierte una cotización de un valor bursátil expresada en forma decimal en una cotización de un valor bursátil expresada en forma fraccionaria. -DURATION = DURACION ## Devuelve la duración anual de un valor bursátil con pagos de interés periódico. -EFFECT = INT.EFECTIVO ## Devuelve la tasa de interés anual efectiva. -FV = VF ## Devuelve el valor futuro de una inversión. -FVSCHEDULE = VF.PLAN ## Devuelve el valor futuro de un capital inicial después de aplicar una serie de tasas de interés compuesto. -INTRATE = TASA.INT ## Devuelve la tasa de interés para la inversión total de un valor bursátil. -IPMT = PAGOINT ## Devuelve el pago de intereses de una inversión durante un período determinado. -IRR = TIR ## Devuelve la tasa interna de retorno para una serie de flujos de efectivo periódicos. -ISPMT = INT.PAGO.DIR ## Calcula el interés pagado durante un período específico de una inversión. -MDURATION = DURACION.MODIF ## Devuelve la duración de Macauley modificada de un valor bursátil con un valor nominal supuesto de 100 $. -MIRR = TIRM ## Devuelve la tasa interna de retorno donde se financian flujos de efectivo positivos y negativos a tasas diferentes. -NOMINAL = TASA.NOMINAL ## Devuelve la tasa nominal de interés anual. -NPER = NPER ## Devuelve el número de períodos de una inversión. -NPV = VNA ## Devuelve el valor neto actual de una inversión en función de una serie de flujos periódicos de efectivo y una tasa de descuento. -ODDFPRICE = PRECIO.PER.IRREGULAR.1 ## Devuelve el precio por un valor nominal de 100 $ de un valor bursátil con un primer período impar. -ODDFYIELD = RENDTO.PER.IRREGULAR.1 ## Devuelve el rendimiento de un valor bursátil con un primer período impar. -ODDLPRICE = PRECIO.PER.IRREGULAR.2 ## Devuelve el precio por un valor nominal de 100 $ de un valor bursátil con un último período impar. -ODDLYIELD = RENDTO.PER.IRREGULAR.2 ## Devuelve el rendimiento de un valor bursátil con un último período impar. -PMT = PAGO ## Devuelve el pago periódico de una anualidad. -PPMT = PAGOPRIN ## Devuelve el pago de capital de una inversión durante un período determinado. -PRICE = PRECIO ## Devuelve el precio por un valor nominal de 100 $ de un valor bursátil que paga una tasa de interés periódico. -PRICEDISC = PRECIO.DESCUENTO ## Devuelve el precio por un valor nominal de 100 $ de un valor bursátil con descuento. -PRICEMAT = PRECIO.VENCIMIENTO ## Devuelve el precio por un valor nominal de 100 $ de un valor bursátil que paga interés a su vencimiento. -PV = VALACT ## Devuelve el valor actual de una inversión. -RATE = TASA ## Devuelve la tasa de interés por período de una anualidad. -RECEIVED = CANTIDAD.RECIBIDA ## Devuelve la cantidad recibida al vencimiento de un valor bursátil completamente invertido. -SLN = SLN ## Devuelve la amortización por método directo de un bien en un período dado. -SYD = SYD ## Devuelve la amortización por suma de dígitos de los años de un bien durante un período especificado. -TBILLEQ = LETRA.DE.TES.EQV.A.BONO ## Devuelve el rendimiento de un bono equivalente a una letra del Tesoro (de EE.UU.) -TBILLPRICE = LETRA.DE.TES.PRECIO ## Devuelve el precio por un valor nominal de 100 $ de una letra del Tesoro (de EE.UU.) -TBILLYIELD = LETRA.DE.TES.RENDTO ## Devuelve el rendimiento de una letra del Tesoro (de EE.UU.) -VDB = DVS ## Devuelve la amortización de un bien durante un período específico o parcial a través del método de cálculo del saldo en disminución. -XIRR = TIR.NO.PER ## Devuelve la tasa interna de retorno para un flujo de efectivo que no es necesariamente periódico. -XNPV = VNA.NO.PER ## Devuelve el valor neto actual para un flujo de efectivo que no es necesariamente periódico. -YIELD = RENDTO ## Devuelve el rendimiento de un valor bursátil que paga intereses periódicos. -YIELDDISC = RENDTO.DESC ## Devuelve el rendimiento anual de un valor bursátil con descuento; por ejemplo, una letra del Tesoro (de EE.UU.) -YIELDMAT = RENDTO.VENCTO ## Devuelve el rendimiento anual de un valor bursátil que paga intereses al vencimiento. - +CELL = CELDA +ERROR.TYPE = TIPO.DE.ERROR +INFO = INFO +ISBLANK = ESBLANCO +ISERR = ESERR +ISERROR = ESERROR +ISEVEN = ES.PAR +ISFORMULA = ESFORMULA +ISLOGICAL = ESLOGICO +ISNA = ESNOD +ISNONTEXT = ESNOTEXTO +ISNUMBER = ESNUMERO +ISODD = ES.IMPAR +ISREF = ESREF +ISTEXT = ESTEXTO +N = N +NA = NOD +SHEET = HOJA +SHEETS = HOJAS +TYPE = TIPO ## -## Information functions Funciones de información +## Funciones lógicas (Logical Functions) ## -CELL = CELDA ## Devuelve información acerca del formato, la ubicación o el contenido de una celda. -ERROR.TYPE = TIPO.DE.ERROR ## Devuelve un número que corresponde a un tipo de error. -INFO = INFO ## Devuelve información acerca del entorno operativo en uso. -ISBLANK = ESBLANCO ## Devuelve VERDADERO si el valor está en blanco. -ISERR = ESERR ## Devuelve VERDADERO si el valor es cualquier valor de error excepto #N/A. -ISERROR = ESERROR ## Devuelve VERDADERO si el valor es cualquier valor de error. -ISEVEN = ES.PAR ## Devuelve VERDADERO si el número es par. -ISLOGICAL = ESLOGICO ## Devuelve VERDADERO si el valor es un valor lógico. -ISNA = ESNOD ## Devuelve VERDADERO si el valor es el valor de error #N/A. -ISNONTEXT = ESNOTEXTO ## Devuelve VERDADERO si el valor no es texto. -ISNUMBER = ESNUMERO ## Devuelve VERDADERO si el valor es un número. -ISODD = ES.IMPAR ## Devuelve VERDADERO si el número es impar. -ISREF = ESREF ## Devuelve VERDADERO si el valor es una referencia. -ISTEXT = ESTEXTO ## Devuelve VERDADERO si el valor es texto. -N = N ## Devuelve un valor convertido en un número. -NA = ND ## Devuelve el valor de error #N/A. -TYPE = TIPO ## Devuelve un número que indica el tipo de datos de un valor. - +AND = Y +FALSE = FALSO +IF = SI +IFERROR = SI.ERROR +IFNA = SI.ND +IFS = SI.CONJUNTO +NOT = NO +OR = O +SWITCH = CAMBIAR +TRUE = VERDADERO +XOR = XO ## -## Logical functions Funciones lógicas +## Funciones de búsqueda y referencia (Lookup & Reference Functions) ## -AND = Y ## Devuelve VERDADERO si todos sus argumentos son VERDADERO. -FALSE = FALSO ## Devuelve el valor lógico FALSO. -IF = SI ## Especifica una prueba lógica que realizar. -IFERROR = SI.ERROR ## Devuelve un valor que se especifica si una fórmula lo evalúa como un error; de lo contrario, devuelve el resultado de la fórmula. -NOT = NO ## Invierte el valor lógico del argumento. -OR = O ## Devuelve VERDADERO si cualquier argumento es VERDADERO. -TRUE = VERDADERO ## Devuelve el valor lógico VERDADERO. - +ADDRESS = DIRECCION +AREAS = AREAS +CHOOSE = ELEGIR +COLUMN = COLUMNA +COLUMNS = COLUMNAS +FORMULATEXT = FORMULATEXTO +GETPIVOTDATA = IMPORTARDATOSDINAMICOS +HLOOKUP = BUSCARH +HYPERLINK = HIPERVINCULO +INDEX = INDICE +INDIRECT = INDIRECTO +LOOKUP = BUSCAR +MATCH = COINCIDIR +OFFSET = DESREF +ROW = FILA +ROWS = FILAS +RTD = RDTR +TRANSPOSE = TRANSPONER +VLOOKUP = BUSCARV +*RC = FC ## -## Lookup and reference functions Funciones de búsqueda y referencia +## Funciones matemáticas y trigonométricas (Math & Trig Functions) ## -ADDRESS = DIRECCION ## Devuelve una referencia como texto a una sola celda de una hoja de cálculo. -AREAS = AREAS ## Devuelve el número de áreas de una referencia. -CHOOSE = ELEGIR ## Elige un valor de una lista de valores. -COLUMN = COLUMNA ## Devuelve el número de columna de una referencia. -COLUMNS = COLUMNAS ## Devuelve el número de columnas de una referencia. -HLOOKUP = BUSCARH ## Busca en la fila superior de una matriz y devuelve el valor de la celda indicada. -HYPERLINK = HIPERVINCULO ## Crea un acceso directo o un salto que abre un documento almacenado en un servidor de red, en una intranet o en Internet. -INDEX = INDICE ## Usa un índice para elegir un valor de una referencia o matriz. -INDIRECT = INDIRECTO ## Devuelve una referencia indicada por un valor de texto. -LOOKUP = BUSCAR ## Busca valores de un vector o una matriz. -MATCH = COINCIDIR ## Busca valores de una referencia o matriz. -OFFSET = DESREF ## Devuelve un desplazamiento de referencia respecto a una referencia dada. -ROW = FILA ## Devuelve el número de fila de una referencia. -ROWS = FILAS ## Devuelve el número de filas de una referencia. -RTD = RDTR ## Recupera datos en tiempo real desde un programa compatible con la automatización COM (automatización: modo de trabajar con los objetos de una aplicación desde otra aplicación o herramienta de entorno. La automatización, antes denominada automatización OLE, es un estándar de la industria y una función del Modelo de objetos componentes (COM).). -TRANSPOSE = TRANSPONER ## Devuelve la transposición de una matriz. -VLOOKUP = BUSCARV ## Busca en la primera columna de una matriz y se mueve en horizontal por la fila para devolver el valor de una celda. - +ABS = ABS +ACOS = ACOS +ACOSH = ACOSH +ACOT = ACOT +ACOTH = ACOTH +AGGREGATE = AGREGAR +ARABIC = NUMERO.ARABE +ASIN = ASENO +ASINH = ASENOH +ATAN = ATAN +ATAN2 = ATAN2 +ATANH = ATANH +BASE = BASE +CEILING.MATH = MULTIPLO.SUPERIOR.MAT +CEILING.PRECISE = MULTIPLO.SUPERIOR.EXACTO +COMBIN = COMBINAT +COMBINA = COMBINA +COS = COS +COSH = COSH +COT = COT +COTH = COTH +CSC = CSC +CSCH = CSCH +DECIMAL = CONV.DECIMAL +DEGREES = GRADOS +ECMA.CEILING = MULTIPLO.SUPERIOR.ECMA +EVEN = REDONDEA.PAR +EXP = EXP +FACT = FACT +FACTDOUBLE = FACT.DOBLE +FLOOR.MATH = MULTIPLO.INFERIOR.MAT +FLOOR.PRECISE = MULTIPLO.INFERIOR.EXACTO +GCD = M.C.D +INT = ENTERO +ISO.CEILING = MULTIPLO.SUPERIOR.ISO +LCM = M.C.M +LN = LN +LOG = LOG +LOG10 = LOG10 +MDETERM = MDETERM +MINVERSE = MINVERSA +MMULT = MMULT +MOD = RESIDUO +MROUND = REDOND.MULT +MULTINOMIAL = MULTINOMIAL +MUNIT = M.UNIDAD +ODD = REDONDEA.IMPAR +PI = PI +POWER = POTENCIA +PRODUCT = PRODUCTO +QUOTIENT = COCIENTE +RADIANS = RADIANES +RAND = ALEATORIO +RANDBETWEEN = ALEATORIO.ENTRE +ROMAN = NUMERO.ROMANO +ROUND = REDONDEAR +ROUNDBAHTDOWN = REDONDEAR.BAHT.MAS +ROUNDBAHTUP = REDONDEAR.BAHT.MENOS +ROUNDDOWN = REDONDEAR.MENOS +ROUNDUP = REDONDEAR.MAS +SEC = SEC +SECH = SECH +SERIESSUM = SUMA.SERIES +SIGN = SIGNO +SIN = SENO +SINH = SENOH +SQRT = RAIZ +SQRTPI = RAIZ2PI +SUBTOTAL = SUBTOTALES +SUM = SUMA +SUMIF = SUMAR.SI +SUMIFS = SUMAR.SI.CONJUNTO +SUMPRODUCT = SUMAPRODUCTO +SUMSQ = SUMA.CUADRADOS +SUMX2MY2 = SUMAX2MENOSY2 +SUMX2PY2 = SUMAX2MASY2 +SUMXMY2 = SUMAXMENOSY2 +TAN = TAN +TANH = TANH +TRUNC = TRUNCAR ## -## Math and trigonometry functions Funciones matemáticas y trigonométricas +## Funciones estadísticas (Statistical Functions) ## -ABS = ABS ## Devuelve el valor absoluto de un número. -ACOS = ACOS ## Devuelve el arcocoseno de un número. -ACOSH = ACOSH ## Devuelve el coseno hiperbólico inverso de un número. -ASIN = ASENO ## Devuelve el arcoseno de un número. -ASINH = ASENOH ## Devuelve el seno hiperbólico inverso de un número. -ATAN = ATAN ## Devuelve la arcotangente de un número. -ATAN2 = ATAN2 ## Devuelve la arcotangente de las coordenadas "x" e "y". -ATANH = ATANH ## Devuelve la tangente hiperbólica inversa de un número. -CEILING = MULTIPLO.SUPERIOR ## Redondea un número al entero más próximo o al múltiplo significativo más cercano. -COMBIN = COMBINAT ## Devuelve el número de combinaciones para un número determinado de objetos. -COS = COS ## Devuelve el coseno de un número. -COSH = COSH ## Devuelve el coseno hiperbólico de un número. -DEGREES = GRADOS ## Convierte radianes en grados. -EVEN = REDONDEA.PAR ## Redondea un número hasta el entero par más próximo. -EXP = EXP ## Devuelve e elevado a la potencia de un número dado. -FACT = FACT ## Devuelve el factorial de un número. -FACTDOUBLE = FACT.DOBLE ## Devuelve el factorial doble de un número. -FLOOR = MULTIPLO.INFERIOR ## Redondea un número hacia abajo, en dirección hacia cero. -GCD = M.C.D ## Devuelve el máximo común divisor. -INT = ENTERO ## Redondea un número hacia abajo hasta el entero más próximo. -LCM = M.C.M ## Devuelve el mínimo común múltiplo. -LN = LN ## Devuelve el logaritmo natural (neperiano) de un número. -LOG = LOG ## Devuelve el logaritmo de un número en una base especificada. -LOG10 = LOG10 ## Devuelve el logaritmo en base 10 de un número. -MDETERM = MDETERM ## Devuelve la determinante matricial de una matriz. -MINVERSE = MINVERSA ## Devuelve la matriz inversa de una matriz. -MMULT = MMULT ## Devuelve el producto de matriz de dos matrices. -MOD = RESIDUO ## Devuelve el resto de la división. -MROUND = REDOND.MULT ## Devuelve un número redondeado al múltiplo deseado. -MULTINOMIAL = MULTINOMIAL ## Devuelve el polinomio de un conjunto de números. -ODD = REDONDEA.IMPAR ## Redondea un número hacia arriba hasta el entero impar más próximo. -PI = PI ## Devuelve el valor de pi. -POWER = POTENCIA ## Devuelve el resultado de elevar un número a una potencia. -PRODUCT = PRODUCTO ## Multiplica sus argumentos. -QUOTIENT = COCIENTE ## Devuelve la parte entera de una división. -RADIANS = RADIANES ## Convierte grados en radianes. -RAND = ALEATORIO ## Devuelve un número aleatorio entre 0 y 1. -RANDBETWEEN = ALEATORIO.ENTRE ## Devuelve un número aleatorio entre los números que especifique. -ROMAN = NUMERO.ROMANO ## Convierte un número arábigo en número romano, con formato de texto. -ROUND = REDONDEAR ## Redondea un número al número de decimales especificado. -ROUNDDOWN = REDONDEAR.MENOS ## Redondea un número hacia abajo, en dirección hacia cero. -ROUNDUP = REDONDEAR.MAS ## Redondea un número hacia arriba, en dirección contraria a cero. -SERIESSUM = SUMA.SERIES ## Devuelve la suma de una serie de potencias en función de la fórmula. -SIGN = SIGNO ## Devuelve el signo de un número. -SIN = SENO ## Devuelve el seno de un ángulo determinado. -SINH = SENOH ## Devuelve el seno hiperbólico de un número. -SQRT = RAIZ ## Devuelve la raíz cuadrada positiva de un número. -SQRTPI = RAIZ2PI ## Devuelve la raíz cuadrada de un número multiplicado por PI (número * pi). -SUBTOTAL = SUBTOTALES ## Devuelve un subtotal en una lista o base de datos. -SUM = SUMA ## Suma sus argumentos. -SUMIF = SUMAR.SI ## Suma las celdas especificadas que cumplen unos criterios determinados. -SUMIFS = SUMAR.SI.CONJUNTO ## Suma las celdas de un rango que cumplen varios criterios. -SUMPRODUCT = SUMAPRODUCTO ## Devuelve la suma de los productos de los correspondientes componentes de matriz. -SUMSQ = SUMA.CUADRADOS ## Devuelve la suma de los cuadrados de los argumentos. -SUMX2MY2 = SUMAX2MENOSY2 ## Devuelve la suma de la diferencia de los cuadrados de los valores correspondientes de dos matrices. -SUMX2PY2 = SUMAX2MASY2 ## Devuelve la suma de la suma de los cuadrados de los valores correspondientes de dos matrices. -SUMXMY2 = SUMAXMENOSY2 ## Devuelve la suma de los cuadrados de las diferencias de los valores correspondientes de dos matrices. -TAN = TAN ## Devuelve la tangente de un número. -TANH = TANH ## Devuelve la tangente hiperbólica de un número. -TRUNC = TRUNCAR ## Trunca un número a un entero. - +AVEDEV = DESVPROM +AVERAGE = PROMEDIO +AVERAGEA = PROMEDIOA +AVERAGEIF = PROMEDIO.SI +AVERAGEIFS = PROMEDIO.SI.CONJUNTO +BETA.DIST = DISTR.BETA.N +BETA.INV = INV.BETA.N +BINOM.DIST = DISTR.BINOM.N +BINOM.DIST.RANGE = DISTR.BINOM.SERIE +BINOM.INV = INV.BINOM +CHISQ.DIST = DISTR.CHICUAD +CHISQ.DIST.RT = DISTR.CHICUAD.CD +CHISQ.INV = INV.CHICUAD +CHISQ.INV.RT = INV.CHICUAD.CD +CHISQ.TEST = PRUEBA.CHICUAD +CONFIDENCE.NORM = INTERVALO.CONFIANZA.NORM +CONFIDENCE.T = INTERVALO.CONFIANZA.T +CORREL = COEF.DE.CORREL +COUNT = CONTAR +COUNTA = CONTARA +COUNTBLANK = CONTAR.BLANCO +COUNTIF = CONTAR.SI +COUNTIFS = CONTAR.SI.CONJUNTO +COVARIANCE.P = COVARIANCE.P +COVARIANCE.S = COVARIANZA.M +DEVSQ = DESVIA2 +EXPON.DIST = DISTR.EXP.N +F.DIST = DISTR.F.N +F.DIST.RT = DISTR.F.CD +F.INV = INV.F +F.INV.RT = INV.F.CD +F.TEST = PRUEBA.F.N +FISHER = FISHER +FISHERINV = PRUEBA.FISHER.INV +FORECAST.ETS = PRONOSTICO.ETS +FORECAST.ETS.CONFINT = PRONOSTICO.ETS.CONFINT +FORECAST.ETS.SEASONALITY = PRONOSTICO.ETS.ESTACIONALIDAD +FORECAST.ETS.STAT = PRONOSTICO.ETS.STAT +FORECAST.LINEAR = PRONOSTICO.LINEAL +FREQUENCY = FRECUENCIA +GAMMA = GAMMA +GAMMA.DIST = DISTR.GAMMA.N +GAMMA.INV = INV.GAMMA +GAMMALN = GAMMA.LN +GAMMALN.PRECISE = GAMMA.LN.EXACTO +GAUSS = GAUSS +GEOMEAN = MEDIA.GEOM +GROWTH = CRECIMIENTO +HARMEAN = MEDIA.ARMO +HYPGEOM.DIST = DISTR.HIPERGEOM.N +INTERCEPT = INTERSECCION.EJE +KURT = CURTOSIS +LARGE = K.ESIMO.MAYOR +LINEST = ESTIMACION.LINEAL +LOGEST = ESTIMACION.LOGARITMICA +LOGNORM.DIST = DISTR.LOGNORM +LOGNORM.INV = INV.LOGNORM +MAX = MAX +MAXA = MAXA +MAXIFS = MAX.SI.CONJUNTO +MEDIAN = MEDIANA +MIN = MIN +MINA = MINA +MINIFS = MIN.SI.CONJUNTO +MODE.MULT = MODA.VARIOS +MODE.SNGL = MODA.UNO +NEGBINOM.DIST = NEGBINOM.DIST +NORM.DIST = DISTR.NORM.N +NORM.INV = INV.NORM +NORM.S.DIST = DISTR.NORM.ESTAND.N +NORM.S.INV = INV.NORM.ESTAND +PEARSON = PEARSON +PERCENTILE.EXC = PERCENTIL.EXC +PERCENTILE.INC = PERCENTIL.INC +PERCENTRANK.EXC = RANGO.PERCENTIL.EXC +PERCENTRANK.INC = RANGO.PERCENTIL.INC +PERMUT = PERMUTACIONES +PERMUTATIONA = PERMUTACIONES.A +PHI = FI +POISSON.DIST = POISSON.DIST +PROB = PROBABILIDAD +QUARTILE.EXC = CUARTIL.EXC +QUARTILE.INC = CUARTIL.INC +RANK.AVG = JERARQUIA.MEDIA +RANK.EQ = JERARQUIA.EQV +RSQ = COEFICIENTE.R2 +SKEW = COEFICIENTE.ASIMETRIA +SKEW.P = COEFICIENTE.ASIMETRIA.P +SLOPE = PENDIENTE +SMALL = K.ESIMO.MENOR +STANDARDIZE = NORMALIZACION +STDEV.P = DESVEST.P +STDEV.S = DESVEST.M +STDEVA = DESVESTA +STDEVPA = DESVESTPA +STEYX = ERROR.TIPICO.XY +T.DIST = DISTR.T.N +T.DIST.2T = DISTR.T.2C +T.DIST.RT = DISTR.T.CD +T.INV = INV.T +T.INV.2T = INV.T.2C +T.TEST = PRUEBA.T.N +TREND = TENDENCIA +TRIMMEAN = MEDIA.ACOTADA +VAR.P = VAR.P +VAR.S = VAR.S +VARA = VARA +VARPA = VARPA +WEIBULL.DIST = DISTR.WEIBULL +Z.TEST = PRUEBA.Z.N ## -## Statistical functions Funciones estadísticas +## Funciones de texto (Text Functions) ## -AVEDEV = DESVPROM ## Devuelve el promedio de las desviaciones absolutas de la media de los puntos de datos. -AVERAGE = PROMEDIO ## Devuelve el promedio de sus argumentos. -AVERAGEA = PROMEDIOA ## Devuelve el promedio de sus argumentos, incluidos números, texto y valores lógicos. -AVERAGEIF = PROMEDIO.SI ## Devuelve el promedio (media aritmética) de todas las celdas de un rango que cumplen unos criterios determinados. -AVERAGEIFS = PROMEDIO.SI.CONJUNTO ## Devuelve el promedio (media aritmética) de todas las celdas que cumplen múltiples criterios. -BETADIST = DISTR.BETA ## Devuelve la función de distribución beta acumulativa. -BETAINV = DISTR.BETA.INV ## Devuelve la función inversa de la función de distribución acumulativa de una distribución beta especificada. -BINOMDIST = DISTR.BINOM ## Devuelve la probabilidad de una variable aleatoria discreta siguiendo una distribución binomial. -CHIDIST = DISTR.CHI ## Devuelve la probabilidad de una variable aleatoria continua siguiendo una distribución chi cuadrado de una sola cola. -CHIINV = PRUEBA.CHI.INV ## Devuelve la función inversa de la probabilidad de una variable aleatoria continua siguiendo una distribución chi cuadrado de una sola cola. -CHITEST = PRUEBA.CHI ## Devuelve la prueba de independencia. -CONFIDENCE = INTERVALO.CONFIANZA ## Devuelve el intervalo de confianza de la media de una población. -CORREL = COEF.DE.CORREL ## Devuelve el coeficiente de correlación entre dos conjuntos de datos. -COUNT = CONTAR ## Cuenta cuántos números hay en la lista de argumentos. -COUNTA = CONTARA ## Cuenta cuántos valores hay en la lista de argumentos. -COUNTBLANK = CONTAR.BLANCO ## Cuenta el número de celdas en blanco de un rango. -COUNTIF = CONTAR.SI ## Cuenta el número de celdas, dentro del rango, que cumplen el criterio especificado. -COUNTIFS = CONTAR.SI.CONJUNTO ## Cuenta el número de celdas, dentro del rango, que cumplen varios criterios. -COVAR = COVAR ## Devuelve la covarianza, que es el promedio de los productos de las desviaciones para cada pareja de puntos de datos. -CRITBINOM = BINOM.CRIT ## Devuelve el menor valor cuya distribución binomial acumulativa es menor o igual a un valor de criterio. -DEVSQ = DESVIA2 ## Devuelve la suma de los cuadrados de las desviaciones. -EXPONDIST = DISTR.EXP ## Devuelve la distribución exponencial. -FDIST = DISTR.F ## Devuelve la distribución de probabilidad F. -FINV = DISTR.F.INV ## Devuelve la función inversa de la distribución de probabilidad F. -FISHER = FISHER ## Devuelve la transformación Fisher. -FISHERINV = PRUEBA.FISHER.INV ## Devuelve la función inversa de la transformación Fisher. -FORECAST = PRONOSTICO ## Devuelve un valor en una tendencia lineal. -FREQUENCY = FRECUENCIA ## Devuelve una distribución de frecuencia como una matriz vertical. -FTEST = PRUEBA.F ## Devuelve el resultado de una prueba F. -GAMMADIST = DISTR.GAMMA ## Devuelve la distribución gamma. -GAMMAINV = DISTR.GAMMA.INV ## Devuelve la función inversa de la distribución gamma acumulativa. -GAMMALN = GAMMA.LN ## Devuelve el logaritmo natural de la función gamma, G(x). -GEOMEAN = MEDIA.GEOM ## Devuelve la media geométrica. -GROWTH = CRECIMIENTO ## Devuelve valores en una tendencia exponencial. -HARMEAN = MEDIA.ARMO ## Devuelve la media armónica. -HYPGEOMDIST = DISTR.HIPERGEOM ## Devuelve la distribución hipergeométrica. -INTERCEPT = INTERSECCION.EJE ## Devuelve la intersección de la línea de regresión lineal. -KURT = CURTOSIS ## Devuelve la curtosis de un conjunto de datos. -LARGE = K.ESIMO.MAYOR ## Devuelve el k-ésimo mayor valor de un conjunto de datos. -LINEST = ESTIMACION.LINEAL ## Devuelve los parámetros de una tendencia lineal. -LOGEST = ESTIMACION.LOGARITMICA ## Devuelve los parámetros de una tendencia exponencial. -LOGINV = DISTR.LOG.INV ## Devuelve la función inversa de la distribución logarítmico-normal. -LOGNORMDIST = DISTR.LOG.NORM ## Devuelve la distribución logarítmico-normal acumulativa. -MAX = MAX ## Devuelve el valor máximo de una lista de argumentos. -MAXA = MAXA ## Devuelve el valor máximo de una lista de argumentos, incluidos números, texto y valores lógicos. -MEDIAN = MEDIANA ## Devuelve la mediana de los números dados. -MIN = MIN ## Devuelve el valor mínimo de una lista de argumentos. -MINA = MINA ## Devuelve el valor mínimo de una lista de argumentos, incluidos números, texto y valores lógicos. -MODE = MODA ## Devuelve el valor más común de un conjunto de datos. -NEGBINOMDIST = NEGBINOMDIST ## Devuelve la distribución binomial negativa. -NORMDIST = DISTR.NORM ## Devuelve la distribución normal acumulativa. -NORMINV = DISTR.NORM.INV ## Devuelve la función inversa de la distribución normal acumulativa. -NORMSDIST = DISTR.NORM.ESTAND ## Devuelve la distribución normal estándar acumulativa. -NORMSINV = DISTR.NORM.ESTAND.INV ## Devuelve la función inversa de la distribución normal estándar acumulativa. -PEARSON = PEARSON ## Devuelve el coeficiente de momento de correlación de producto Pearson. -PERCENTILE = PERCENTIL ## Devuelve el k-ésimo percentil de los valores de un rango. -PERCENTRANK = RANGO.PERCENTIL ## Devuelve el rango porcentual de un valor de un conjunto de datos. -PERMUT = PERMUTACIONES ## Devuelve el número de permutaciones de un número determinado de objetos. -POISSON = POISSON ## Devuelve la distribución de Poisson. -PROB = PROBABILIDAD ## Devuelve la probabilidad de que los valores de un rango se encuentren entre dos límites. -QUARTILE = CUARTIL ## Devuelve el cuartil de un conjunto de datos. -RANK = JERARQUIA ## Devuelve la jerarquía de un número en una lista de números. -RSQ = COEFICIENTE.R2 ## Devuelve el cuadrado del coeficiente de momento de correlación de producto Pearson. -SKEW = COEFICIENTE.ASIMETRIA ## Devuelve la asimetría de una distribución. -SLOPE = PENDIENTE ## Devuelve la pendiente de la línea de regresión lineal. -SMALL = K.ESIMO.MENOR ## Devuelve el k-ésimo menor valor de un conjunto de datos. -STANDARDIZE = NORMALIZACION ## Devuelve un valor normalizado. -STDEV = DESVEST ## Calcula la desviación estándar a partir de una muestra. -STDEVA = DESVESTA ## Calcula la desviación estándar a partir de una muestra, incluidos números, texto y valores lógicos. -STDEVP = DESVESTP ## Calcula la desviación estándar en función de toda la población. -STDEVPA = DESVESTPA ## Calcula la desviación estándar en función de toda la población, incluidos números, texto y valores lógicos. -STEYX = ERROR.TIPICO.XY ## Devuelve el error estándar del valor de "y" previsto para cada "x" de la regresión. -TDIST = DISTR.T ## Devuelve la distribución de t de Student. -TINV = DISTR.T.INV ## Devuelve la función inversa de la distribución de t de Student. -TREND = TENDENCIA ## Devuelve valores en una tendencia lineal. -TRIMMEAN = MEDIA.ACOTADA ## Devuelve la media del interior de un conjunto de datos. -TTEST = PRUEBA.T ## Devuelve la probabilidad asociada a una prueba t de Student. -VAR = VAR ## Calcula la varianza en función de una muestra. -VARA = VARA ## Calcula la varianza en función de una muestra, incluidos números, texto y valores lógicos. -VARP = VARP ## Calcula la varianza en función de toda la población. -VARPA = VARPA ## Calcula la varianza en función de toda la población, incluidos números, texto y valores lógicos. -WEIBULL = DIST.WEIBULL ## Devuelve la distribución de Weibull. -ZTEST = PRUEBA.Z ## Devuelve el valor de una probabilidad de una cola de una prueba z. - +BAHTTEXT = TEXTOBAHT +CHAR = CARACTER +CLEAN = LIMPIAR +CODE = CODIGO +CONCAT = CONCAT +DOLLAR = MONEDA +EXACT = IGUAL +FIND = ENCONTRAR +FIXED = DECIMAL +ISTHAIDIGIT = ESDIGITOTAI +LEFT = IZQUIERDA +LEN = LARGO +LOWER = MINUSC +MID = EXTRAE +NUMBERSTRING = CADENA.NUMERO +NUMBERVALUE = VALOR.NUMERO +PHONETIC = FONETICO +PROPER = NOMPROPIO +REPLACE = REEMPLAZAR +REPT = REPETIR +RIGHT = DERECHA +SEARCH = HALLAR +SUBSTITUTE = SUSTITUIR +T = T +TEXT = TEXTO +TEXTJOIN = UNIRCADENAS +THAIDIGIT = DIGITOTAI +THAINUMSOUND = SONNUMTAI +THAINUMSTRING = CADENANUMTAI +THAISTRINGLENGTH = LONGCADENATAI +TRIM = ESPACIOS +UNICHAR = UNICAR +UNICODE = UNICODE +UPPER = MAYUSC +VALUE = VALOR ## -## Text functions Funciones de texto +## Funciones web (Web Functions) ## -ASC = ASC ## Convierte las letras inglesas o katakana de ancho completo (de dos bytes) dentro de una cadena de caracteres en caracteres de ancho medio (de un byte). -BAHTTEXT = TEXTOBAHT ## Convierte un número en texto, con el formato de moneda ß (Baht). -CHAR = CARACTER ## Devuelve el carácter especificado por el número de código. -CLEAN = LIMPIAR ## Quita del texto todos los caracteres no imprimibles. -CODE = CODIGO ## Devuelve un código numérico del primer carácter de una cadena de texto. -CONCATENATE = CONCATENAR ## Concatena varios elementos de texto en uno solo. -DOLLAR = MONEDA ## Convierte un número en texto, con el formato de moneda $ (dólar). -EXACT = IGUAL ## Comprueba si dos valores de texto son idénticos. -FIND = ENCONTRAR ## Busca un valor de texto dentro de otro (distingue mayúsculas de minúsculas). -FINDB = ENCONTRARB ## Busca un valor de texto dentro de otro (distingue mayúsculas de minúsculas). -FIXED = DECIMAL ## Da formato a un número como texto con un número fijo de decimales. -JIS = JIS ## Convierte las letras inglesas o katakana de ancho medio (de un byte) dentro de una cadena de caracteres en caracteres de ancho completo (de dos bytes). -LEFT = IZQUIERDA ## Devuelve los caracteres del lado izquierdo de un valor de texto. -LEFTB = IZQUIERDAB ## Devuelve los caracteres del lado izquierdo de un valor de texto. -LEN = LARGO ## Devuelve el número de caracteres de una cadena de texto. -LENB = LARGOB ## Devuelve el número de caracteres de una cadena de texto. -LOWER = MINUSC ## Pone el texto en minúsculas. -MID = EXTRAE ## Devuelve un número específico de caracteres de una cadena de texto que comienza en la posición que se especifique. -MIDB = EXTRAEB ## Devuelve un número específico de caracteres de una cadena de texto que comienza en la posición que se especifique. -PHONETIC = FONETICO ## Extrae los caracteres fonéticos (furigana) de una cadena de texto. -PROPER = NOMPROPIO ## Pone en mayúscula la primera letra de cada palabra de un valor de texto. -REPLACE = REEMPLAZAR ## Reemplaza caracteres de texto. -REPLACEB = REEMPLAZARB ## Reemplaza caracteres de texto. -REPT = REPETIR ## Repite el texto un número determinado de veces. -RIGHT = DERECHA ## Devuelve los caracteres del lado derecho de un valor de texto. -RIGHTB = DERECHAB ## Devuelve los caracteres del lado derecho de un valor de texto. -SEARCH = HALLAR ## Busca un valor de texto dentro de otro (no distingue mayúsculas de minúsculas). -SEARCHB = HALLARB ## Busca un valor de texto dentro de otro (no distingue mayúsculas de minúsculas). -SUBSTITUTE = SUSTITUIR ## Sustituye texto nuevo por texto antiguo en una cadena de texto. -T = T ## Convierte sus argumentos a texto. -TEXT = TEXTO ## Da formato a un número y lo convierte en texto. -TRIM = ESPACIOS ## Quita los espacios del texto. -UPPER = MAYUSC ## Pone el texto en mayúsculas. -VALUE = VALOR ## Convierte un argumento de texto en un número. +ENCODEURL = URLCODIF +FILTERXML = XMLFILTRO +WEBSERVICE = SERVICIOWEB + +## +## Funciones de compatibilidad (Compatibility Functions) +## +BETADIST = DISTR.BETA +BETAINV = DISTR.BETA.INV +BINOMDIST = DISTR.BINOM +CEILING = MULTIPLO.SUPERIOR +CHIDIST = DISTR.CHI +CHIINV = PRUEBA.CHI.INV +CHITEST = PRUEBA.CHI +CONCATENATE = CONCATENAR +CONFIDENCE = INTERVALO.CONFIANZA +COVAR = COVAR +CRITBINOM = BINOM.CRIT +EXPONDIST = DISTR.EXP +FDIST = DISTR.F +FINV = DISTR.F.INV +FLOOR = MULTIPLO.INFERIOR +FORECAST = PRONOSTICO +FTEST = PRUEBA.F +GAMMADIST = DISTR.GAMMA +GAMMAINV = DISTR.GAMMA.INV +HYPGEOMDIST = DISTR.HIPERGEOM +LOGINV = DISTR.LOG.INV +LOGNORMDIST = DISTR.LOG.NORM +MODE = MODA +NEGBINOMDIST = NEGBINOMDIST +NORMDIST = DISTR.NORM +NORMINV = DISTR.NORM.INV +NORMSDIST = DISTR.NORM.ESTAND +NORMSINV = DISTR.NORM.ESTAND.INV +PERCENTILE = PERCENTIL +PERCENTRANK = RANGO.PERCENTIL +POISSON = POISSON +QUARTILE = CUARTIL +RANK = JERARQUIA +STDEV = DESVEST +STDEVP = DESVESTP +TDIST = DISTR.T +TINV = DISTR.T.INV +TTEST = PRUEBA.T +VAR = VAR +VARP = VARP +WEIBULL = DIST.WEIBULL +ZTEST = PRUEBA.Z diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/fi/config b/PhpOffice/PhpSpreadsheet/Calculation/locale/fi/config old mode 100755 new mode 100644 index 22aaf58..5388f93 --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/fi/config +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/fi/config @@ -1,24 +1,20 @@ +############################################################ ## -## PhpSpreadsheet +## PhpSpreadsheet - locale settings ## +## Suomi (Finnish) +## +############################################################ -ArgumentSeparator = ; - +ArgumentSeparator = ; ## -## (For future use) +## Error Codes ## -currencySymbol = $ # Symbol not known, should it be a € (Euro)? - - -## -## Excel Error Codes (For future use) - -## -NULL = #TYHJÄ! -DIV0 = #JAKO/0! -VALUE = #ARVO! -REF = #VIITTAUS! -NAME = #NIMI? -NUM = #LUKU! -NA = #PUUTTUU +NULL = #TYHJÄ! +DIV0 = #JAKO/0! +VALUE = #ARVO! +REF = #VIITTAUS! +NAME = #NIMI? +NUM = #LUKU! +NA = #PUUTTUU! diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/fi/functions b/PhpOffice/PhpSpreadsheet/Calculation/locale/fi/functions old mode 100755 new mode 100644 index 289e0ea..18f7c8c --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/fi/functions +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/fi/functions @@ -1,416 +1,538 @@ +############################################################ ## -## PhpSpreadsheet -## -## Data in this file derived from https://www.excel-function-translation.com/ +## PhpSpreadsheet - function name translations ## +## Suomi (Finnish) ## +############################################################ ## -## Add-in and Automation functions Apuohjelma- ja automaatiofunktiot +## Kuutiofunktiot (Cube Functions) ## -GETPIVOTDATA = NOUDA.PIVOT.TIEDOT ## Palauttaa pivot-taulukkoraporttiin tallennettuja tietoja. - +CUBEKPIMEMBER = KUUTIOKPIJÄSEN +CUBEMEMBER = KUUTIONJÄSEN +CUBEMEMBERPROPERTY = KUUTIONJÄSENENOMINAISUUS +CUBERANKEDMEMBER = KUUTIONLUOKITELTUJÄSEN +CUBESET = KUUTIOJOUKKO +CUBESETCOUNT = KUUTIOJOUKKOJENMÄÄRÄ +CUBEVALUE = KUUTIONARVO ## -## Cube functions Kuutiofunktiot +## Tietokantafunktiot (Database Functions) ## -CUBEKPIMEMBER = KUUTIOKPIJÄSEN ## Palauttaa suorituskykyilmaisimen (KPI) nimen, ominaisuuden sekä mitan ja näyttää nimen sekä ominaisuuden solussa. KPI on mitattavissa oleva suure, kuten kuukauden bruttotuotto tai vuosineljänneksen työntekijäkohtainen liikevaihto, joiden avulla tarkkaillaan organisaation suorituskykyä. -CUBEMEMBER = KUUTIONJÄSEN ## Palauttaa kuutiohierarkian jäsenen tai monikon. Tällä funktiolla voit tarkistaa, että jäsen tai monikko on olemassa kuutiossa. -CUBEMEMBERPROPERTY = KUUTIONJÄSENENOMINAISUUS ## Palauttaa kuution jäsenominaisuuden arvon. Tällä funktiolla voit tarkistaa, että nimi on olemassa kuutiossa, ja palauttaa tämän jäsenen määritetyn ominaisuuden. -CUBERANKEDMEMBER = KUUTIONLUOKITELTUJÄSEN ## Palauttaa joukon n:nnen jäsenen. Tällä funktiolla voit palauttaa joukosta elementtejä, kuten parhaan myyjän tai 10 parasta opiskelijaa. -CUBESET = KUUTIOJOUKKO ## Määrittää lasketun jäsen- tai monikkojoukon lähettämällä joukon lausekkeita palvelimessa olevalle kuutiolle. Palvelin luo joukon ja palauttaa sen Microsoft Office Excelille. -CUBESETCOUNT = KUUTIOJOUKKOJENMÄÄRÄ ## Palauttaa joukon kohteiden määrän. -CUBEVALUE = KUUTIONARVO ## Palauttaa koostetun arvon kuutiosta. - +DAVERAGE = TKESKIARVO +DCOUNT = TLASKE +DCOUNTA = TLASKEA +DGET = TNOUDA +DMAX = TMAKS +DMIN = TMIN +DPRODUCT = TTULO +DSTDEV = TKESKIHAJONTA +DSTDEVP = TKESKIHAJONTAP +DSUM = TSUMMA +DVAR = TVARIANSSI +DVARP = TVARIANSSIP ## -## Database functions Tietokantafunktiot +## Päivämäärä- ja aikafunktiot (Date & Time Functions) ## -DAVERAGE = TKESKIARVO ## Palauttaa valittujen tietokantamerkintöjen keskiarvon. -DCOUNT = TLASKE ## Laskee tietokannan lukuja sisältävien solujen määrän. -DCOUNTA = TLASKEA ## Laskee tietokannan tietoja sisältävien solujen määrän. -DGET = TNOUDA ## Hakee määritettyjä ehtoja vastaavan tietueen tietokannasta. -DMAX = TMAKS ## Palauttaa suurimman arvon tietokannasta valittujen arvojen joukosta. -DMIN = TMIN ## Palauttaa pienimmän arvon tietokannasta valittujen arvojen joukosta. -DPRODUCT = TTULO ## Kertoo määritetyn ehdon täyttävien tietokannan tietueiden tietyssä kentässä olevat arvot. -DSTDEV = TKESKIHAJONTA ## Laskee keskihajonnan tietokannasta valituista arvoista muodostuvan otoksen perusteella. -DSTDEVP = TKESKIHAJONTAP ## Laskee keskihajonnan tietokannasta valittujen arvojen koko populaation perusteella. -DSUM = TSUMMA ## Lisää luvut määritetyn ehdon täyttävien tietokannan tietueiden kenttäsarakkeeseen. -DVAR = TVARIANSSI ## Laskee varianssin tietokannasta valittujen arvojen otoksen perusteella. -DVARP = TVARIANSSIP ## Laskee varianssin tietokannasta valittujen arvojen koko populaation perusteella. - +DATE = PÄIVÄYS +DATEDIF = PVMERO +DATESTRING = PVMMERKKIJONO +DATEVALUE = PÄIVÄYSARVO +DAY = PÄIVÄ +DAYS = PÄIVÄT +DAYS360 = PÄIVÄT360 +EDATE = PÄIVÄ.KUUKAUSI +EOMONTH = KUUKAUSI.LOPPU +HOUR = TUNNIT +ISOWEEKNUM = VIIKKO.ISO.NRO +MINUTE = MINUUTIT +MONTH = KUUKAUSI +NETWORKDAYS = TYÖPÄIVÄT +NETWORKDAYS.INTL = TYÖPÄIVÄT.KANSVÄL +NOW = NYT +SECOND = SEKUNNIT +THAIDAYOFWEEK = THAI.VIIKONPÄIVÄ +THAIMONTHOFYEAR = THAI.KUUKAUSI +THAIYEAR = THAI.VUOSI +TIME = AIKA +TIMEVALUE = AIKA_ARVO +TODAY = TÄMÄ.PÄIVÄ +WEEKDAY = VIIKONPÄIVÄ +WEEKNUM = VIIKKO.NRO +WORKDAY = TYÖPÄIVÄ +WORKDAY.INTL = TYÖPÄIVÄ.KANSVÄL +YEAR = VUOSI +YEARFRAC = VUOSI.OSA ## -## Date and time functions Päivämäärä- ja aikafunktiot +## Tekniset funktiot (Engineering Functions) ## -DATE = PÄIVÄYS ## Palauttaa annetun päivämäärän järjestysluvun. -DATEVALUE = PÄIVÄYSARVO ## Muuntaa tekstimuodossa olevan päivämäärän järjestysluvuksi. -DAY = PÄIVÄ ## Muuntaa järjestysluvun kuukauden päiväksi. -DAYS360 = PÄIVÄT360 ## Laskee kahden päivämäärän välisten päivien määrän käyttäen perustana 360-päiväistä vuotta. -EDATE = PÄIVÄ.KUUKAUSI ## Palauttaa järjestyslukuna päivämäärän, joka poikkeaa aloituspäivän päivämäärästä annetun kuukausimäärän verran joko eteen- tai taaksepäin. -EOMONTH = KUUKAUSI.LOPPU ## Palauttaa järjestyslukuna sen kuukauden viimeisen päivämäärän, joka poikkeaa annetun kuukausimäärän verran eteen- tai taaksepäin. -HOUR = TUNNIT ## Muuntaa järjestysluvun tunneiksi. -MINUTE = MINUUTIT ## Muuntaa järjestysluvun minuuteiksi. -MONTH = KUUKAUSI ## Muuntaa järjestysluvun kuukausiksi. -NETWORKDAYS = TYÖPÄIVÄT ## Palauttaa kahden päivämäärän välissä olevien täysien työpäivien määrän. -NOW = NYT ## Palauttaa kuluvan päivämäärän ja ajan järjestysnumeron. -SECOND = SEKUNNIT ## Muuntaa järjestysluvun sekunneiksi. -TIME = AIKA ## Palauttaa annetun kellonajan järjestysluvun. -TIMEVALUE = AIKA_ARVO ## Muuntaa tekstimuodossa olevan kellonajan järjestysluvuksi. -TODAY = TÄMÄ.PÄIVÄ ## Palauttaa kuluvan päivän päivämäärän järjestysluvun. -WEEKDAY = VIIKONPÄIVÄ ## Muuntaa järjestysluvun viikonpäiväksi. -WEEKNUM = VIIKKO.NRO ## Muuntaa järjestysluvun luvuksi, joka ilmaisee viikon järjestysluvun vuoden alusta laskettuna. -WORKDAY = TYÖPÄIVÄ ## Palauttaa järjestysluvun päivämäärälle, joka sijaitsee annettujen työpäivien verran eteen tai taaksepäin. -YEAR = VUOSI ## Muuntaa järjestysluvun vuosiksi. -YEARFRAC = VUOSI.OSA ## Palauttaa määritettyjen päivämäärien (aloituspäivä ja lopetuspäivä) välisen osan vuodesta. - +BESSELI = BESSELI +BESSELJ = BESSELJ +BESSELK = BESSELK +BESSELY = BESSELY +BIN2DEC = BINDES +BIN2HEX = BINHEKSA +BIN2OCT = BINOKT +BITAND = BITTI.JA +BITLSHIFT = BITTI.SIIRTO.V +BITOR = BITTI.TAI +BITRSHIFT = BITTI.SIIRTO.O +BITXOR = BITTI.EHDOTON.TAI +COMPLEX = KOMPLEKSI +CONVERT = MUUNNA +DEC2BIN = DESBIN +DEC2HEX = DESHEKSA +DEC2OCT = DESOKT +DELTA = SAMA.ARVO +ERF = VIRHEFUNKTIO +ERF.PRECISE = VIRHEFUNKTIO.TARKKA +ERFC = VIRHEFUNKTIO.KOMPLEMENTTI +ERFC.PRECISE = VIRHEFUNKTIO.KOMPLEMENTTI.TARKKA +GESTEP = RAJA +HEX2BIN = HEKSABIN +HEX2DEC = HEKSADES +HEX2OCT = HEKSAOKT +IMABS = KOMPLEKSI.ABS +IMAGINARY = KOMPLEKSI.IMAG +IMARGUMENT = KOMPLEKSI.ARG +IMCONJUGATE = KOMPLEKSI.KONJ +IMCOS = KOMPLEKSI.COS +IMCOSH = IMCOSH +IMCOT = KOMPLEKSI.COT +IMCSC = KOMPLEKSI.KOSEK +IMCSCH = KOMPLEKSI.KOSEKH +IMDIV = KOMPLEKSI.OSAM +IMEXP = KOMPLEKSI.EKSP +IMLN = KOMPLEKSI.LN +IMLOG10 = KOMPLEKSI.LOG10 +IMLOG2 = KOMPLEKSI.LOG2 +IMPOWER = KOMPLEKSI.POT +IMPRODUCT = KOMPLEKSI.TULO +IMREAL = KOMPLEKSI.REAALI +IMSEC = KOMPLEKSI.SEK +IMSECH = KOMPLEKSI.SEKH +IMSIN = KOMPLEKSI.SIN +IMSINH = KOMPLEKSI.SINH +IMSQRT = KOMPLEKSI.NELIÖJ +IMSUB = KOMPLEKSI.EROTUS +IMSUM = KOMPLEKSI.SUM +IMTAN = KOMPLEKSI.TAN +OCT2BIN = OKTBIN +OCT2DEC = OKTDES +OCT2HEX = OKTHEKSA ## -## Engineering functions Tekniset funktiot +## Rahoitusfunktiot (Financial Functions) ## -BESSELI = BESSELI ## Palauttaa muunnetun Bessel-funktion In(x). -BESSELJ = BESSELJ ## Palauttaa Bessel-funktion Jn(x). -BESSELK = BESSELK ## Palauttaa muunnetun Bessel-funktion Kn(x). -BESSELY = BESSELY ## Palauttaa Bessel-funktion Yn(x). -BIN2DEC = BINDES ## Muuntaa binaariluvun desimaaliluvuksi. -BIN2HEX = BINHEKSA ## Muuntaa binaariluvun heksadesimaaliluvuksi. -BIN2OCT = BINOKT ## Muuntaa binaariluvun oktaaliluvuksi. -COMPLEX = KOMPLEKSI ## Muuntaa reaali- ja imaginaariosien kertoimet kompleksiluvuksi. -CONVERT = MUUNNA ## Muuntaa luvun toisen mittajärjestelmän mukaiseksi. -DEC2BIN = DESBIN ## Muuntaa desimaaliluvun binaariluvuksi. -DEC2HEX = DESHEKSA ## Muuntaa kymmenjärjestelmän luvun heksadesimaaliluvuksi. -DEC2OCT = DESOKT ## Muuntaa kymmenjärjestelmän luvun oktaaliluvuksi. -DELTA = SAMA.ARVO ## Tarkistaa, ovatko kaksi arvoa yhtä suuria. -ERF = VIRHEFUNKTIO ## Palauttaa virhefunktion. -ERFC = VIRHEFUNKTIO.KOMPLEMENTTI ## Palauttaa komplementtivirhefunktion. -GESTEP = RAJA ## Testaa, onko luku suurempi kuin kynnysarvo. -HEX2BIN = HEKSABIN ## Muuntaa heksadesimaaliluvun binaariluvuksi. -HEX2DEC = HEKSADES ## Muuntaa heksadesimaaliluvun desimaaliluvuksi. -HEX2OCT = HEKSAOKT ## Muuntaa heksadesimaaliluvun oktaaliluvuksi. -IMABS = KOMPLEKSI.ITSEISARVO ## Palauttaa kompleksiluvun itseisarvon (moduluksen). -IMAGINARY = KOMPLEKSI.IMAG ## Palauttaa kompleksiluvun imaginaariosan kertoimen. -IMARGUMENT = KOMPLEKSI.ARG ## Palauttaa theeta-argumentin, joka on radiaaneina annettu kulma. -IMCONJUGATE = KOMPLEKSI.KONJ ## Palauttaa kompleksiluvun konjugaattiluvun. -IMCOS = KOMPLEKSI.COS ## Palauttaa kompleksiluvun kosinin. -IMDIV = KOMPLEKSI.OSAM ## Palauttaa kahden kompleksiluvun osamäärän. -IMEXP = KOMPLEKSI.EKSP ## Palauttaa kompleksiluvun eksponentin. -IMLN = KOMPLEKSI.LN ## Palauttaa kompleksiluvun luonnollisen logaritmin. -IMLOG10 = KOMPLEKSI.LOG10 ## Palauttaa kompleksiluvun kymmenkantaisen logaritmin. -IMLOG2 = KOMPLEKSI.LOG2 ## Palauttaa kompleksiluvun kaksikantaisen logaritmin. -IMPOWER = KOMPLEKSI.POT ## Palauttaa kokonaislukupotenssiin korotetun kompleksiluvun. -IMPRODUCT = KOMPLEKSI.TULO ## Palauttaa kompleksilukujen tulon. -IMREAL = KOMPLEKSI.REAALI ## Palauttaa kompleksiluvun reaaliosan kertoimen. -IMSIN = KOMPLEKSI.SIN ## Palauttaa kompleksiluvun sinin. -IMSQRT = KOMPLEKSI.NELIÖJ ## Palauttaa kompleksiluvun neliöjuuren. -IMSUB = KOMPLEKSI.EROTUS ## Palauttaa kahden kompleksiluvun erotuksen. -IMSUM = KOMPLEKSI.SUM ## Palauttaa kompleksilukujen summan. -OCT2BIN = OKTBIN ## Muuntaa oktaaliluvun binaariluvuksi. -OCT2DEC = OKTDES ## Muuntaa oktaaliluvun desimaaliluvuksi. -OCT2HEX = OKTHEKSA ## Muuntaa oktaaliluvun heksadesimaaliluvuksi. - +ACCRINT = KERTYNYT.KORKO +ACCRINTM = KERTYNYT.KORKO.LOPUSSA +AMORDEGRC = AMORDEGRC +AMORLINC = AMORLINC +COUPDAYBS = KORKOPÄIVÄT.ALUSTA +COUPDAYS = KORKOPÄIVÄT +COUPDAYSNC = KORKOPÄIVÄT.SEURAAVA +COUPNCD = KORKOPÄIVÄ.SEURAAVA +COUPNUM = KORKOPÄIVÄ.JAKSOT +COUPPCD = KORKOPÄIVÄ.EDELLINEN +CUMIPMT = MAKSETTU.KORKO +CUMPRINC = MAKSETTU.LYHENNYS +DB = DB +DDB = DDB +DISC = DISKONTTOKORKO +DOLLARDE = VALUUTTA.DES +DOLLARFR = VALUUTTA.MURTO +DURATION = KESTO +EFFECT = KORKO.EFEKT +FV = TULEVA.ARVO +FVSCHEDULE = TULEVA.ARVO.ERIKORKO +INTRATE = KORKO.ARVOPAPERI +IPMT = IPMT +IRR = SISÄINEN.KORKO +ISPMT = ISPMT +MDURATION = KESTO.MUUNN +MIRR = MSISÄINEN +NOMINAL = KORKO.VUOSI +NPER = NJAKSO +NPV = NNA +ODDFPRICE = PARITON.ENS.NIMELLISARVO +ODDFYIELD = PARITON.ENS.TUOTTO +ODDLPRICE = PARITON.VIIM.NIMELLISARVO +ODDLYIELD = PARITON.VIIM.TUOTTO +PDURATION = KESTO.JAKSO +PMT = MAKSU +PPMT = PPMT +PRICE = HINTA +PRICEDISC = HINTA.DISK +PRICEMAT = HINTA.LUNASTUS +PV = NA +RATE = KORKO +RECEIVED = SAATU.HINTA +RRI = TOT.ROI +SLN = STP +SYD = VUOSIPOISTO +TBILLEQ = OBLIG.TUOTTOPROS +TBILLPRICE = OBLIG.HINTA +TBILLYIELD = OBLIG.TUOTTO +VDB = VDB +XIRR = SISÄINEN.KORKO.JAKSOTON +XNPV = NNA.JAKSOTON +YIELD = TUOTTO +YIELDDISC = TUOTTO.DISK +YIELDMAT = TUOTTO.ERÄP ## -## Financial functions Rahoitusfunktiot +## Tietofunktiot (Information Functions) ## -ACCRINT = KERTYNYT.KORKO ## Laskee arvopaperille kertyneen koron, kun korko kertyy säännöllisin väliajoin. -ACCRINTM = KERTYNYT.KORKO.LOPUSSA ## Laskee arvopaperille kertyneen koron, kun korko maksetaan eräpäivänä. -AMORDEGRC = AMORDEGRC ## Laskee kunkin laskentakauden poiston poistokerrointa käyttämällä. -AMORLINC = AMORLINC ## Palauttaa kunkin laskentakauden poiston. -COUPDAYBS = KORKOPÄIVÄT.ALUSTA ## Palauttaa koronmaksukauden aloituspäivän ja tilityspäivän välisen ajanjakson päivien määrän. -COUPDAYS = KORKOPÄIVÄT ## Palauttaa päivien määrän koronmaksukaudelta, johon tilityspäivä kuuluu. -COUPDAYSNC = KORKOPÄIVÄT.SEURAAVA ## Palauttaa tilityspäivän ja seuraavan koronmaksupäivän välisen ajanjakson päivien määrän. -COUPNCD = KORKOMAKSU.SEURAAVA ## Palauttaa tilityspäivän jälkeisen seuraavan koronmaksupäivän. -COUPNUM = KORKOPÄIVÄJAKSOT ## Palauttaa arvopaperin ostopäivän ja erääntymispäivän välisten koronmaksupäivien määrän. -COUPPCD = KORKOPÄIVÄ.EDELLINEN ## Palauttaa tilityspäivää edeltävän koronmaksupäivän. -CUMIPMT = MAKSETTU.KORKO ## Palauttaa kahden jakson välisenä aikana kertyneen koron. -CUMPRINC = MAKSETTU.LYHENNYS ## Palauttaa lainalle kahden jakson välisenä aikana kertyneen lyhennyksen. -DB = DB ## Palauttaa kauden kirjanpidollisen poiston amerikkalaisen DB-menetelmän (Fixed-declining balance) mukaan. -DDB = DDB ## Palauttaa kauden kirjanpidollisen poiston amerikkalaisen DDB-menetelmän (Double-Declining Balance) tai jonkin muun määrittämäsi menetelmän mukaan. -DISC = DISKONTTOKORKO ## Palauttaa arvopaperin diskonttokoron. -DOLLARDE = VALUUTTA.DES ## Muuntaa murtolukuna ilmoitetun valuuttamäärän desimaaliluvuksi. -DOLLARFR = VALUUTTA.MURTO ## Muuntaa desimaalilukuna ilmaistun valuuttamäärän murtoluvuksi. -DURATION = KESTO ## Palauttaa keston arvopaperille, jonka koronmaksu tapahtuu säännöllisesti. -EFFECT = KORKO.EFEKT ## Palauttaa todellisen vuosikoron. -FV = TULEVA.ARVO ## Palauttaa sijoituksen tulevan arvon. -FVSCHEDULE = TULEVA.ARVO.ERIKORKO ## Palauttaa pääoman tulevan arvon, kun pääomalle on kertynyt korkoa vaihtelevasti. -INTRATE = KORKO.ARVOPAPERI ## Palauttaa arvopaperin korkokannan täysin sijoitetulle arvopaperille. -IPMT = IPMT ## Laskee sijoitukselle tai lainalle tiettynä ajanjaksona kertyvän koron. -IRR = SISÄINEN.KORKO ## Laskee sisäisen korkokannan kassavirrasta muodostuvalle sarjalle. -ISPMT = ONMAKSU ## Laskee sijoituksen maksetun koron tietyllä jaksolla. -MDURATION = KESTO.MUUNN ## Palauttaa muunnetun Macauley-keston arvopaperille, jonka oletettu nimellisarvo on 100 euroa. -MIRR = MSISÄINEN ## Palauttaa sisäisen korkokannan, kun positiivisten ja negatiivisten kassavirtojen rahoituskorko on erilainen. -NOMINAL = KORKO.VUOSI ## Palauttaa vuosittaisen nimelliskoron. -NPER = NJAKSO ## Palauttaa sijoituksen jaksojen määrän. -NPV = NNA ## Palauttaa sijoituksen nykyarvon toistuvista kassavirroista muodostuvan sarjan ja diskonttokoron perusteella. -ODDFPRICE = PARITON.ENS.NIMELLISARVO ## Palauttaa arvopaperin hinnan tilanteessa, jossa ensimmäinen jakso on pariton. -ODDFYIELD = PARITON.ENS.TUOTTO ## Palauttaa arvopaperin tuoton tilanteessa, jossa ensimmäinen jakso on pariton. -ODDLPRICE = PARITON.VIIM.NIMELLISARVO ## Palauttaa arvopaperin hinnan tilanteessa, jossa viimeinen jakso on pariton. -ODDLYIELD = PARITON.VIIM.TUOTTO ## Palauttaa arvopaperin tuoton tilanteessa, jossa viimeinen jakso on pariton. -PMT = MAKSU ## Palauttaa annuiteetin kausittaisen maksuerän. -PPMT = PPMT ## Laskee sijoitukselle tai lainalle tiettynä ajanjaksona maksettavan lyhennyksen. -PRICE = HINTA ## Palauttaa hinnan 100 euron nimellisarvoa kohden arvopaperille, jonka korko maksetaan säännöllisin väliajoin. -PRICEDISC = HINTA.DISK ## Palauttaa diskontatun arvopaperin hinnan 100 euron nimellisarvoa kohden. -PRICEMAT = HINTA.LUNASTUS ## Palauttaa hinnan 100 euron nimellisarvoa kohden arvopaperille, jonka korko maksetaan erääntymispäivänä. -PV = NA ## Palauttaa sijoituksen nykyarvon. -RATE = KORKO ## Palauttaa annuiteetin kausittaisen korkokannan. -RECEIVED = SAATU.HINTA ## Palauttaa arvopaperin tuoton erääntymispäivänä kokonaan maksetulle sijoitukselle. -SLN = STP ## Palauttaa sijoituksen tasapoiston yhdeltä jaksolta. -SYD = VUOSIPOISTO ## Palauttaa sijoituksen vuosipoiston annettuna kautena amerikkalaisen SYD-menetelmän (Sum-of-Year's Digits) avulla. -TBILLEQ = OBLIG.TUOTTOPROS ## Palauttaa valtion obligaation tuoton vastaavana joukkovelkakirjan tuottona. -TBILLPRICE = OBLIG.HINTA ## Palauttaa obligaation hinnan 100 euron nimellisarvoa kohden. -TBILLYIELD = OBLIG.TUOTTO ## Palauttaa obligaation tuoton. -VDB = VDB ## Palauttaa annetun kauden tai kauden osan kirjanpidollisen poiston amerikkalaisen DB-menetelmän (Fixed-declining balance) mukaan. -XIRR = SISÄINEN.KORKO.JAKSOTON ## Palauttaa sisäisen korkokannan kassavirtojen sarjoille, jotka eivät välttämättä ole säännöllisiä. -XNPV = NNA.JAKSOTON ## Palauttaa nettonykyarvon kassavirtasarjalle, joka ei välttämättä ole kausittainen. -YIELD = TUOTTO ## Palauttaa tuoton arvopaperille, jonka korko maksetaan säännöllisin väliajoin. -YIELDDISC = TUOTTO.DISK ## Palauttaa diskontatun arvopaperin, kuten obligaation, vuosittaisen tuoton. -YIELDMAT = TUOTTO.ERÄP ## Palauttaa erääntymispäivänään korkoa tuottavan arvopaperin vuosittaisen tuoton. - +CELL = SOLU +ERROR.TYPE = VIRHEEN.LAJI +INFO = KUVAUS +ISBLANK = ONTYHJÄ +ISERR = ONVIRH +ISERROR = ONVIRHE +ISEVEN = ONPARILLINEN +ISFORMULA = ONKAAVA +ISLOGICAL = ONTOTUUS +ISNA = ONPUUTTUU +ISNONTEXT = ONEI_TEKSTI +ISNUMBER = ONLUKU +ISODD = ONPARITON +ISREF = ONVIITT +ISTEXT = ONTEKSTI +N = N +NA = PUUTTUU +SHEET = TAULUKKO +SHEETS = TAULUKOT +TYPE = TYYPPI ## -## Information functions Erikoisfunktiot +## Loogiset funktiot (Logical Functions) ## -CELL = SOLU ## Palauttaa tietoja solun muotoilusta, sijainnista ja sisällöstä. -ERROR.TYPE = VIRHEEN.LAJI ## Palauttaa virhetyyppiä vastaavan luvun. -INFO = KUVAUS ## Palauttaa tietoja nykyisestä käyttöympäristöstä. -ISBLANK = ONTYHJÄ ## Palauttaa arvon TOSI, jos arvo on tyhjä. -ISERR = ONVIRH ## Palauttaa arvon TOSI, jos arvo on mikä tahansa virhearvo paitsi arvo #PUUTTUU!. -ISERROR = ONVIRHE ## Palauttaa arvon TOSI, jos arvo on mikä tahansa virhearvo. -ISEVEN = ONPARILLINEN ## Palauttaa arvon TOSI, jos arvo on parillinen. -ISLOGICAL = ONTOTUUS ## Palauttaa arvon TOSI, jos arvo on mikä tahansa looginen arvo. -ISNA = ONPUUTTUU ## Palauttaa arvon TOSI, jos virhearvo on #PUUTTUU!. -ISNONTEXT = ONEI_TEKSTI ## Palauttaa arvon TOSI, jos arvo ei ole teksti. -ISNUMBER = ONLUKU ## Palauttaa arvon TOSI, jos arvo on luku. -ISODD = ONPARITON ## Palauttaa arvon TOSI, jos arvo on pariton. -ISREF = ONVIITT ## Palauttaa arvon TOSI, jos arvo on viittaus. -ISTEXT = ONTEKSTI ## Palauttaa arvon TOSI, jos arvo on teksti. -N = N ## Palauttaa arvon luvuksi muunnettuna. -NA = PUUTTUU ## Palauttaa virhearvon #PUUTTUU!. -TYPE = TYYPPI ## Palauttaa luvun, joka ilmaisee arvon tietotyypin. - +AND = JA +FALSE = EPÄTOSI +IF = JOS +IFERROR = JOSVIRHE +IFNA = JOSPUUTTUU +IFS = JOSS +NOT = EI +OR = TAI +SWITCH = MUUTA +TRUE = TOSI +XOR = EHDOTON.TAI ## -## Logical functions Loogiset funktiot +## Haku- ja viitefunktiot (Lookup & Reference Functions) ## -AND = JA ## Palauttaa arvon TOSI, jos kaikkien argumenttien arvo on TOSI. -FALSE = EPÄTOSI ## Palauttaa totuusarvon EPÄTOSI. -IF = JOS ## Määrittää suoritettavan loogisen testin. -IFERROR = JOSVIRHE ## Palauttaa määrittämäsi arvon, jos kaavan tulos on virhe; muussa tapauksessa palauttaa kaavan tuloksen. -NOT = EI ## Kääntää argumentin loogisen arvon. -OR = TAI ## Palauttaa arvon TOSI, jos minkä tahansa argumentin arvo on TOSI. -TRUE = TOSI ## Palauttaa totuusarvon TOSI. - +ADDRESS = OSOITE +AREAS = ALUEET +CHOOSE = VALITSE.INDEKSI +COLUMN = SARAKE +COLUMNS = SARAKKEET +FORMULATEXT = KAAVA.TEKSTI +GETPIVOTDATA = NOUDA.PIVOT.TIEDOT +HLOOKUP = VHAKU +HYPERLINK = HYPERLINKKI +INDEX = INDEKSI +INDIRECT = EPÄSUORA +LOOKUP = HAKU +MATCH = VASTINE +OFFSET = SIIRTYMÄ +ROW = RIVI +ROWS = RIVIT +RTD = RTD +TRANSPOSE = TRANSPONOI +VLOOKUP = PHAKU +*RC = RS ## -## Lookup and reference functions Haku- ja viitefunktiot +## Matemaattiset ja trigonometriset funktiot (Math & Trig Functions) ## -ADDRESS = OSOITE ## Palauttaa laskentataulukon soluun osoittavan viittauksen tekstinä. -AREAS = ALUEET ## Palauttaa viittauksessa olevien alueiden määrän. -CHOOSE = VALITSE.INDEKSI ## Valitsee arvon arvoluettelosta. -COLUMN = SARAKE ## Palauttaa viittauksen sarakenumeron. -COLUMNS = SARAKKEET ## Palauttaa viittauksessa olevien sarakkeiden määrän. -HLOOKUP = VHAKU ## Suorittaa haun matriisin ylimmältä riviltä ja palauttaa määritetyn solun arvon. -HYPERLINK = HYPERLINKKI ## Luo pikakuvakkeen tai tekstin, joka avaa verkkopalvelimeen, intranetiin tai Internetiin tallennetun tiedoston. -INDEX = INDEKSI ## Valitsee arvon viittauksesta tai matriisista indeksin mukaan. -INDIRECT = EPÄSUORA ## Palauttaa tekstiarvona ilmaistun viittauksen. -LOOKUP = HAKU ## Etsii arvoja vektorista tai matriisista. -MATCH = VASTINE ## Etsii arvoja viittauksesta tai matriisista. -OFFSET = SIIRTYMÄ ## Palauttaa annetun viittauksen siirtymän. -ROW = RIVI ## Palauttaa viittauksen rivinumeron. -ROWS = RIVIT ## Palauttaa viittauksessa olevien rivien määrän. -RTD = RTD ## Noutaa COM-automaatiota (automaatio: Tapa käsitellä sovelluksen objekteja toisesta sovelluksesta tai kehitystyökalusta. Automaatio, jota aiemmin kutsuttiin OLE-automaatioksi, on teollisuusstandardi ja COM-mallin (Component Object Model) ominaisuus.) tukevasta ohjelmasta reaaliaikaisia tietoja. -TRANSPOSE = TRANSPONOI ## Palauttaa matriisin käänteismatriisin. -VLOOKUP = PHAKU ## Suorittaa haun matriisin ensimmäisestä sarakkeesta ja palauttaa rivillä olevan solun arvon. - +ABS = ITSEISARVO +ACOS = ACOS +ACOSH = ACOSH +ACOT = ACOT +ACOTH = ACOTH +AGGREGATE = KOOSTE +ARABIC = ARABIA +ASIN = ASIN +ASINH = ASINH +ATAN = ATAN +ATAN2 = ATAN2 +ATANH = ATANH +BASE = PERUS +CEILING.MATH = PYÖRISTÄ.KERR.YLÖS.MATEMAATTINEN +CEILING.PRECISE = PYÖRISTÄ.KERR.YLÖS.TARKKA +COMBIN = KOMBINAATIO +COMBINA = KOMBINAATIOA +COS = COS +COSH = COSH +COT = COT +COTH = COTH +CSC = KOSEK +CSCH = KOSEKH +DECIMAL = DESIMAALI +DEGREES = ASTEET +ECMA.CEILING = ECMA.PYÖRISTÄ.KERR.YLÖS +EVEN = PARILLINEN +EXP = EKSPONENTTI +FACT = KERTOMA +FACTDOUBLE = KERTOMA.OSA +FLOOR.MATH = PYÖRISTÄ.KERR.ALAS.MATEMAATTINEN +FLOOR.PRECISE = PYÖRISTÄ.KERR.ALAS.TARKKA +GCD = SUURIN.YHT.TEKIJÄ +INT = KOKONAISLUKU +ISO.CEILING = ISO.PYÖRISTÄ.KERR.YLÖS +LCM = PIENIN.YHT.JAETTAVA +LN = LUONNLOG +LOG = LOG +LOG10 = LOG10 +MDETERM = MDETERM +MINVERSE = MKÄÄNTEINEN +MMULT = MKERRO +MOD = JAKOJ +MROUND = PYÖRISTÄ.KERR +MULTINOMIAL = MULTINOMI +MUNIT = YKSIKKÖM +ODD = PARITON +PI = PII +POWER = POTENSSI +PRODUCT = TULO +QUOTIENT = OSAMÄÄRÄ +RADIANS = RADIAANIT +RAND = SATUNNAISLUKU +RANDBETWEEN = SATUNNAISLUKU.VÄLILTÄ +ROMAN = ROMAN +ROUND = PYÖRISTÄ +ROUNDBAHTDOWN = PYÖRISTÄ.BAHT.ALAS +ROUNDBAHTUP = PYÖRISTÄ.BAHT.YLÖS +ROUNDDOWN = PYÖRISTÄ.DES.ALAS +ROUNDUP = PYÖRISTÄ.DES.YLÖS +SEC = SEK +SECH = SEKH +SERIESSUM = SARJA.SUMMA +SIGN = ETUMERKKI +SIN = SIN +SINH = SINH +SQRT = NELIÖJUURI +SQRTPI = NELIÖJUURI.PII +SUBTOTAL = VÄLISUMMA +SUM = SUMMA +SUMIF = SUMMA.JOS +SUMIFS = SUMMA.JOS.JOUKKO +SUMPRODUCT = TULOJEN.SUMMA +SUMSQ = NELIÖSUMMA +SUMX2MY2 = NELIÖSUMMIEN.EROTUS +SUMX2PY2 = NELIÖSUMMIEN.SUMMA +SUMXMY2 = EROTUSTEN.NELIÖSUMMA +TAN = TAN +TANH = TANH +TRUNC = KATKAISE ## -## Math and trigonometry functions Matemaattiset ja trigonometriset funktiot +## Tilastolliset funktiot (Statistical Functions) ## -ABS = ITSEISARVO ## Palauttaa luvun itseisarvon. -ACOS = ACOS ## Palauttaa luvun arkuskosinin. -ACOSH = ACOSH ## Palauttaa luvun käänteisen hyperbolisen kosinin. -ASIN = ASIN ## Palauttaa luvun arkussinin. -ASINH = ASINH ## Palauttaa luvun käänteisen hyperbolisen sinin. -ATAN = ATAN ## Palauttaa luvun arkustangentin. -ATAN2 = ATAN2 ## Palauttaa arkustangentin x- ja y-koordinaatin perusteella. -ATANH = ATANH ## Palauttaa luvun käänteisen hyperbolisen tangentin. -CEILING = PYÖRISTÄ.KERR.YLÖS ## Pyöristää luvun lähimpään kokonaislukuun tai tarkkuusargumentin lähimpään kerrannaiseen. -COMBIN = KOMBINAATIO ## Palauttaa mahdollisten kombinaatioiden määrän annetulle objektien määrälle. -COS = COS ## Palauttaa luvun kosinin. -COSH = COSH ## Palauttaa luvun hyperbolisen kosinin. -DEGREES = ASTEET ## Muuntaa radiaanit asteiksi. -EVEN = PARILLINEN ## Pyöristää luvun ylöspäin lähimpään parilliseen kokonaislukuun. -EXP = EKSPONENTTI ## Palauttaa e:n korotettuna annetun luvun osoittamaan potenssiin. -FACT = KERTOMA ## Palauttaa luvun kertoman. -FACTDOUBLE = KERTOMA.OSA ## Palauttaa luvun osakertoman. -FLOOR = PYÖRISTÄ.KERR.ALAS ## Pyöristää luvun alaspäin (nollaa kohti). -GCD = SUURIN.YHT.TEKIJÄ ## Palauttaa suurimman yhteisen tekijän. -INT = KOKONAISLUKU ## Pyöristää luvun alaspäin lähimpään kokonaislukuun. -LCM = PIENIN.YHT.JAETTAVA ## Palauttaa pienimmän yhteisen tekijän. -LN = LUONNLOG ## Palauttaa luvun luonnollisen logaritmin. -LOG = LOG ## Laskee luvun logaritmin käyttämällä annettua kantalukua. -LOG10 = LOG10 ## Palauttaa luvun kymmenkantaisen logaritmin. -MDETERM = MDETERM ## Palauttaa matriisin matriisideterminantin. -MINVERSE = MKÄÄNTEINEN ## Palauttaa matriisin käänteismatriisin. -MMULT = MKERRO ## Palauttaa kahden matriisin tulon. -MOD = JAKOJ ## Palauttaa jakolaskun jäännöksen. -MROUND = PYÖRISTÄ.KERR ## Palauttaa luvun pyöristettynä annetun luvun kerrannaiseen. -MULTINOMIAL = MULTINOMI ## Palauttaa lukujoukon multinomin. -ODD = PARITON ## Pyöristää luvun ylöspäin lähimpään parittomaan kokonaislukuun. -PI = PII ## Palauttaa piin arvon. -POWER = POTENSSI ## Palauttaa luvun korotettuna haluttuun potenssiin. -PRODUCT = TULO ## Kertoo annetut argumentit. -QUOTIENT = OSAMÄÄRÄ ## Palauttaa osamäärän kokonaislukuosan. -RADIANS = RADIAANIT ## Muuntaa asteet radiaaneiksi. -RAND = SATUNNAISLUKU ## Palauttaa satunnaisluvun väliltä 0–1. -RANDBETWEEN = SATUNNAISLUKU.VÄLILTÄ ## Palauttaa satunnaisluvun määritettyjen lukujen väliltä. -ROMAN = ROMAN ## Muuntaa arabialaisen numeron tekstimuotoiseksi roomalaiseksi numeroksi. -ROUND = PYÖRISTÄ ## Pyöristää luvun annettuun määrään desimaaleja. -ROUNDDOWN = PYÖRISTÄ.DES.ALAS ## Pyöristää luvun alaspäin (nollaa kohti). -ROUNDUP = PYÖRISTÄ.DES.YLÖS ## Pyöristää luvun ylöspäin (poispäin nollasta). -SERIESSUM = SARJA.SUMMA ## Palauttaa kaavaan perustuvan potenssisarjan arvon. -SIGN = ETUMERKKI ## Palauttaa luvun etumerkin. -SIN = SIN ## Palauttaa annetun kulman sinin. -SINH = SINH ## Palauttaa luvun hyperbolisen sinin. -SQRT = NELIÖJUURI ## Palauttaa positiivisen neliöjuuren. -SQRTPI = NELIÖJUURI.PII ## Palauttaa tulon (luku * pii) neliöjuuren. -SUBTOTAL = VÄLISUMMA ## Palauttaa luettelon tai tietokannan välisumman. -SUM = SUMMA ## Laskee yhteen annetut argumentit. -SUMIF = SUMMA.JOS ## Laskee ehdot täyttävien solujen summan. -SUMIFS = SUMMA.JOS.JOUKKO ## Laskee yhteen solualueen useita ehtoja vastaavat solut. -SUMPRODUCT = TULOJEN.SUMMA ## Palauttaa matriisin toisiaan vastaavien osien tulojen summan. -SUMSQ = NELIÖSUMMA ## Palauttaa argumenttien neliöiden summan. -SUMX2MY2 = NELIÖSUMMIEN.EROTUS ## Palauttaa kahden matriisin toisiaan vastaavien arvojen laskettujen neliösummien erotuksen. -SUMX2PY2 = NELIÖSUMMIEN.SUMMA ## Palauttaa kahden matriisin toisiaan vastaavien arvojen neliösummien summan. -SUMXMY2 = EROTUSTEN.NELIÖSUMMA ## Palauttaa kahden matriisin toisiaan vastaavien arvojen erotusten neliösumman. -TAN = TAN ## Palauttaa luvun tangentin. -TANH = TANH ## Palauttaa luvun hyperbolisen tangentin. -TRUNC = KATKAISE ## Katkaisee luvun kokonaisluvuksi. - +AVEDEV = KESKIPOIKKEAMA +AVERAGE = KESKIARVO +AVERAGEA = KESKIARVOA +AVERAGEIF = KESKIARVO.JOS +AVERAGEIFS = KESKIARVO.JOS.JOUKKO +BETA.DIST = BEETA.JAKAUMA +BETA.INV = BEETA.KÄÄNT +BINOM.DIST = BINOMI.JAKAUMA +BINOM.DIST.RANGE = BINOMI.JAKAUMA.ALUE +BINOM.INV = BINOMIJAKAUMA.KÄÄNT +CHISQ.DIST = CHINELIÖ.JAKAUMA +CHISQ.DIST.RT = CHINELIÖ.JAKAUMA.OH +CHISQ.INV = CHINELIÖ.KÄÄNT +CHISQ.INV.RT = CHINELIÖ.KÄÄNT.OH +CHISQ.TEST = CHINELIÖ.TESTI +CONFIDENCE.NORM = LUOTTAMUSVÄLI.NORM +CONFIDENCE.T = LUOTTAMUSVÄLI.T +CORREL = KORRELAATIO +COUNT = LASKE +COUNTA = LASKE.A +COUNTBLANK = LASKE.TYHJÄT +COUNTIF = LASKE.JOS +COUNTIFS = LASKE.JOS.JOUKKO +COVARIANCE.P = KOVARIANSSI.P +COVARIANCE.S = KOVARIANSSI.S +DEVSQ = OIKAISTU.NELIÖSUMMA +EXPON.DIST = EKSPONENTIAALI.JAKAUMA +F.DIST = F.JAKAUMA +F.DIST.RT = F.JAKAUMA.OH +F.INV = F.KÄÄNT +F.INV.RT = F.KÄÄNT.OH +F.TEST = F.TESTI +FISHER = FISHER +FISHERINV = FISHER.KÄÄNT +FORECAST.ETS = ENNUSTE.ETS +FORECAST.ETS.CONFINT = ENNUSTE.ETS.CONFINT +FORECAST.ETS.SEASONALITY = ENNUSTE.ETS.KAUSIVAIHTELU +FORECAST.ETS.STAT = ENNUSTE.ETS.STAT +FORECAST.LINEAR = ENNUSTE.LINEAARINEN +FREQUENCY = TAAJUUS +GAMMA = GAMMA +GAMMA.DIST = GAMMA.JAKAUMA +GAMMA.INV = GAMMA.JAKAUMA.KÄÄNT +GAMMALN = GAMMALN +GAMMALN.PRECISE = GAMMALN.TARKKA +GAUSS = GAUSS +GEOMEAN = KESKIARVO.GEOM +GROWTH = KASVU +HARMEAN = KESKIARVO.HARM +HYPGEOM.DIST = HYPERGEOM_JAKAUMA +INTERCEPT = LEIKKAUSPISTE +KURT = KURT +LARGE = SUURI +LINEST = LINREGR +LOGEST = LOGREGR +LOGNORM.DIST = LOGNORM_JAKAUMA +LOGNORM.INV = LOGNORM.KÄÄNT +MAX = MAKS +MAXA = MAKSA +MAXIFS = MAKS.JOS +MEDIAN = MEDIAANI +MIN = MIN +MINA = MINA +MINIFS = MIN.JOS +MODE.MULT = MOODI.USEA +MODE.SNGL = MOODI.YKSI +NEGBINOM.DIST = BINOMI.JAKAUMA.NEG +NORM.DIST = NORMAALI.JAKAUMA +NORM.INV = NORMAALI.JAKAUMA.KÄÄNT +NORM.S.DIST = NORM_JAKAUMA.NORMIT +NORM.S.INV = NORM_JAKAUMA.KÄÄNT +PEARSON = PEARSON +PERCENTILE.EXC = PROSENTTIPISTE.ULK +PERCENTILE.INC = PROSENTTIPISTE.SIS +PERCENTRANK.EXC = PROSENTTIJÄRJESTYS.ULK +PERCENTRANK.INC = PROSENTTIJÄRJESTYS.SIS +PERMUT = PERMUTAATIO +PERMUTATIONA = PERMUTAATIOA +PHI = FII +POISSON.DIST = POISSON.JAKAUMA +PROB = TODENNÄKÖISYYS +QUARTILE.EXC = NELJÄNNES.ULK +QUARTILE.INC = NELJÄNNES.SIS +RANK.AVG = ARVON.MUKAAN.KESKIARVO +RANK.EQ = ARVON.MUKAAN.TASAN +RSQ = PEARSON.NELIÖ +SKEW = JAKAUMAN.VINOUS +SKEW.P = JAKAUMAN.VINOUS.POP +SLOPE = KULMAKERROIN +SMALL = PIENI +STANDARDIZE = NORMITA +STDEV.P = KESKIHAJONTA.P +STDEV.S = KESKIHAJONTA.S +STDEVA = KESKIHAJONTAA +STDEVPA = KESKIHAJONTAPA +STEYX = KESKIVIRHE +T.DIST = T.JAKAUMA +T.DIST.2T = T.JAKAUMA.2S +T.DIST.RT = T.JAKAUMA.OH +T.INV = T.KÄÄNT +T.INV.2T = T.KÄÄNT.2S +T.TEST = T.TESTI +TREND = SUUNTAUS +TRIMMEAN = KESKIARVO.TASATTU +VAR.P = VAR.P +VAR.S = VAR.S +VARA = VARA +VARPA = VARPA +WEIBULL.DIST = WEIBULL.JAKAUMA +Z.TEST = Z.TESTI ## -## Statistical functions Tilastolliset funktiot +## Tekstifunktiot (Text Functions) ## -AVEDEV = KESKIPOIKKEAMA ## Palauttaa hajontojen itseisarvojen keskiarvon. -AVERAGE = KESKIARVO ## Palauttaa argumenttien keskiarvon. -AVERAGEA = KESKIARVOA ## Palauttaa argumenttien, mukaan lukien lukujen, tekstin ja loogisten arvojen, keskiarvon. -AVERAGEIF = KESKIARVO.JOS ## Palauttaa alueen niiden solujen keskiarvon (aritmeettisen keskiarvon), jotka täyttävät annetut ehdot. -AVERAGEIFS = KESKIARVO.JOS.JOUKKO ## Palauttaa niiden solujen keskiarvon (aritmeettisen keskiarvon), jotka vastaavat useita ehtoja. -BETADIST = BEETAJAKAUMA ## Palauttaa kumulatiivisen beetajakaumafunktion arvon. -BETAINV = BEETAJAKAUMA.KÄÄNT ## Palauttaa määritetyn beetajakauman käänteisen kumulatiivisen jakaumafunktion arvon. -BINOMDIST = BINOMIJAKAUMA ## Palauttaa yksittäisen termin binomijakaumatodennäköisyyden. -CHIDIST = CHIJAKAUMA ## Palauttaa yksisuuntaisen chi-neliön jakauman todennäköisyyden. -CHIINV = CHIJAKAUMA.KÄÄNT ## Palauttaa yksisuuntaisen chi-neliön jakauman todennäköisyyden käänteisarvon. -CHITEST = CHITESTI ## Palauttaa riippumattomuustestin tuloksen. -CONFIDENCE = LUOTTAMUSVÄLI ## Palauttaa luottamusvälin populaation keskiarvolle. -CORREL = KORRELAATIO ## Palauttaa kahden arvojoukon korrelaatiokertoimen. -COUNT = LASKE ## Laskee argumenttiluettelossa olevien lukujen määrän. -COUNTA = LASKE.A ## Laskee argumenttiluettelossa olevien arvojen määrän. -COUNTBLANK = LASKE.TYHJÄT ## Laskee alueella olevien tyhjien solujen määrän. -COUNTIF = LASKE.JOS ## Laskee alueella olevien sellaisten solujen määrän, joiden sisältö vastaa annettuja ehtoja. -COUNTIFS = LASKE.JOS.JOUKKO ## Laskee alueella olevien sellaisten solujen määrän, joiden sisältö vastaa useita ehtoja. -COVAR = KOVARIANSSI ## Palauttaa kovarianssin, joka on keskiarvo havaintoaineiston kunkin pisteparin poikkeamien tuloista. -CRITBINOM = BINOMIJAKAUMA.KRIT ## Palauttaa pienimmän arvon, jossa binomijakauman kertymäfunktion arvo on pienempi tai yhtä suuri kuin vertailuarvo. -DEVSQ = OIKAISTU.NELIÖSUMMA ## Palauttaa keskipoikkeamien neliösumman. -EXPONDIST = EKSPONENTIAALIJAKAUMA ## Palauttaa eksponentiaalijakauman. -FDIST = FJAKAUMA ## Palauttaa F-todennäköisyysjakauman. -FINV = FJAKAUMA.KÄÄNT ## Palauttaa F-todennäköisyysjakauman käänteisfunktion. -FISHER = FISHER ## Palauttaa Fisher-muunnoksen. -FISHERINV = FISHER.KÄÄNT ## Palauttaa käänteisen Fisher-muunnoksen. -FORECAST = ENNUSTE ## Palauttaa lineaarisen trendin arvon. -FREQUENCY = TAAJUUS ## Palauttaa frekvenssijakautuman pystysuuntaisena matriisina. -FTEST = FTESTI ## Palauttaa F-testin tuloksen. -GAMMADIST = GAMMAJAKAUMA ## Palauttaa gammajakauman. -GAMMAINV = GAMMAJAKAUMA.KÄÄNT ## Palauttaa käänteisen gammajakauman kertymäfunktion. -GAMMALN = GAMMALN ## Palauttaa gammafunktion luonnollisen logaritmin G(x). -GEOMEAN = KESKIARVO.GEOM ## Palauttaa geometrisen keskiarvon. -GROWTH = KASVU ## Palauttaa eksponentiaalisen trendin arvon. -HARMEAN = KESKIARVO.HARM ## Palauttaa harmonisen keskiarvon. -HYPGEOMDIST = HYPERGEOM.JAKAUMA ## Palauttaa hypergeometrisen jakauman. -INTERCEPT = LEIKKAUSPISTE ## Palauttaa lineaarisen regressiosuoran leikkauspisteen. -KURT = KURT ## Palauttaa tietoalueen vinous-arvon eli huipukkuuden. -LARGE = SUURI ## Palauttaa tietojoukon k:nneksi suurimman arvon. -LINEST = LINREGR ## Palauttaa lineaarisen trendin parametrit. -LOGEST = LOGREGR ## Palauttaa eksponentiaalisen trendin parametrit. -LOGINV = LOGNORM.JAKAUMA.KÄÄNT ## Palauttaa lognormeeratun jakauman käänteisfunktion. -LOGNORMDIST = LOGNORM.JAKAUMA ## Palauttaa lognormaalisen jakauman kertymäfunktion. -MAX = MAKS ## Palauttaa suurimman arvon argumenttiluettelosta. -MAXA = MAKSA ## Palauttaa argumenttien, mukaan lukien lukujen, tekstin ja loogisten arvojen, suurimman arvon. -MEDIAN = MEDIAANI ## Palauttaa annettujen lukujen mediaanin. -MIN = MIN ## Palauttaa pienimmän arvon argumenttiluettelosta. -MINA = MINA ## Palauttaa argumenttien, mukaan lukien lukujen, tekstin ja loogisten arvojen, pienimmän arvon. -MODE = MOODI ## Palauttaa tietojoukossa useimmin esiintyvän arvon. -NEGBINOMDIST = BINOMIJAKAUMA.NEG ## Palauttaa negatiivisen binomijakauman. -NORMDIST = NORM.JAKAUMA ## Palauttaa normaalijakauman kertymäfunktion. -NORMINV = NORM.JAKAUMA.KÄÄNT ## Palauttaa käänteisen normaalijakauman kertymäfunktion. -NORMSDIST = NORM.JAKAUMA.NORMIT ## Palauttaa normitetun normaalijakauman kertymäfunktion. -NORMSINV = NORM.JAKAUMA.NORMIT.KÄÄNT ## Palauttaa normitetun normaalijakauman kertymäfunktion käänteisarvon. -PEARSON = PEARSON ## Palauttaa Pearsonin tulomomenttikorrelaatiokertoimen. -PERCENTILE = PROSENTTIPISTE ## Palauttaa alueen arvojen k:nnen prosenttipisteen. -PERCENTRANK = PROSENTTIJÄRJESTYS ## Palauttaa tietojoukon arvon prosentuaalisen järjestysluvun. -PERMUT = PERMUTAATIO ## Palauttaa mahdollisten permutaatioiden määrän annetulle objektien määrälle. -POISSON = POISSON ## Palauttaa Poissonin todennäköisyysjakauman. -PROB = TODENNÄKÖISYYS ## Palauttaa todennäköisyyden sille, että arvot ovat tietyltä väliltä. -QUARTILE = NELJÄNNES ## Palauttaa tietoalueen neljänneksen. -RANK = ARVON.MUKAAN ## Palauttaa luvun paikan lukuarvoluettelossa. -RSQ = PEARSON.NELIÖ ## Palauttaa Pearsonin tulomomenttikorrelaatiokertoimen neliön. -SKEW = JAKAUMAN.VINOUS ## Palauttaa jakauman vinouden. -SLOPE = KULMAKERROIN ## Palauttaa lineaarisen regressiosuoran kulmakertoimen. -SMALL = PIENI ## Palauttaa tietojoukon k:nneksi pienimmän arvon. -STANDARDIZE = NORMITA ## Palauttaa normitetun arvon. -STDEV = KESKIHAJONTA ## Laskee populaation keskihajonnan otoksen perusteella. -STDEVA = KESKIHAJONTAA ## Laskee populaation keskihajonnan otoksen perusteella, mukaan lukien luvut, tekstin ja loogiset arvot. -STDEVP = KESKIHAJONTAP ## Laskee normaalijakautuman koko populaation perusteella. -STDEVPA = KESKIHAJONTAPA ## Laskee populaation keskihajonnan koko populaation perusteella, mukaan lukien luvut, tekstin ja totuusarvot. -STEYX = KESKIVIRHE ## Palauttaa regression kutakin x-arvoa vastaavan ennustetun y-arvon keskivirheen. -TDIST = TJAKAUMA ## Palauttaa t-jakautuman. -TINV = TJAKAUMA.KÄÄNT ## Palauttaa käänteisen t-jakauman. -TREND = SUUNTAUS ## Palauttaa lineaarisen trendin arvoja. -TRIMMEAN = KESKIARVO.TASATTU ## Palauttaa tietojoukon tasatun keskiarvon. -TTEST = TTESTI ## Palauttaa t-testiin liittyvän todennäköisyyden. -VAR = VAR ## Arvioi populaation varianssia otoksen perusteella. -VARA = VARA ## Laskee populaation varianssin otoksen perusteella, mukaan lukien luvut, tekstin ja loogiset arvot. -VARP = VARP ## Laskee varianssin koko populaation perusteella. -VARPA = VARPA ## Laskee populaation varianssin koko populaation perusteella, mukaan lukien luvut, tekstin ja totuusarvot. -WEIBULL = WEIBULL ## Palauttaa Weibullin jakauman. -ZTEST = ZTESTI ## Palauttaa z-testin yksisuuntaisen todennäköisyysarvon. - +BAHTTEXT = BAHTTEKSTI +CHAR = MERKKI +CLEAN = SIIVOA +CODE = KOODI +CONCAT = YHDISTÄ +DOLLAR = VALUUTTA +EXACT = VERTAA +FIND = ETSI +FIXED = KIINTEÄ +ISTHAIDIGIT = ON.THAI.NUMERO +LEFT = VASEN +LEN = PITUUS +LOWER = PIENET +MID = POIMI.TEKSTI +NUMBERSTRING = NROMERKKIJONO +NUMBERVALUE = NROARVO +PHONETIC = FONEETTINEN +PROPER = ERISNIMI +REPLACE = KORVAA +REPT = TOISTA +RIGHT = OIKEA +SEARCH = KÄY.LÄPI +SUBSTITUTE = VAIHDA +T = T +TEXT = TEKSTI +TEXTJOIN = TEKSTI.YHDISTÄ +THAIDIGIT = THAI.NUMERO +THAINUMSOUND = THAI.LUKU.ÄÄNI +THAINUMSTRING = THAI.LUKU.MERKKIJONO +THAISTRINGLENGTH = THAI.MERKKIJONON.PITUUS +TRIM = POISTA.VÄLIT +UNICHAR = UNICODEMERKKI +UNICODE = UNICODE +UPPER = ISOT +VALUE = ARVO ## -## Text functions Tekstifunktiot +## Verkkofunktiot (Web Functions) ## -ASC = ASC ## Muuntaa merkkijonossa olevat englanninkieliset DBCS- tai katakana-merkit SBCS-merkeiksi. -BAHTTEXT = BAHTTEKSTI ## Muuntaa luvun tekstiksi ß (baht) -valuuttamuotoa käyttämällä. -CHAR = MERKKI ## Palauttaa koodin lukua vastaavan merkin. -CLEAN = SIIVOA ## Poistaa tekstistä kaikki tulostumattomat merkit. -CODE = KOODI ## Palauttaa tekstimerkkijonon ensimmäisen merkin numerokoodin. -CONCATENATE = KETJUTA ## Yhdistää useat merkkijonot yhdeksi merkkijonoksi. -DOLLAR = VALUUTTA ## Muuntaa luvun tekstiksi $ (dollari) -valuuttamuotoa käyttämällä. -EXACT = VERTAA ## Tarkistaa, ovatko kaksi tekstiarvoa samanlaiset. -FIND = ETSI ## Etsii tekstiarvon toisen tekstin sisältä (tunnistaa isot ja pienet kirjaimet). -FINDB = ETSIB ## Etsii tekstiarvon toisen tekstin sisältä (tunnistaa isot ja pienet kirjaimet). -FIXED = KIINTEÄ ## Muotoilee luvun tekstiksi, jossa on kiinteä määrä desimaaleja. -JIS = JIS ## Muuntaa merkkijonossa olevat englanninkieliset SBCS- tai katakana-merkit DBCS-merkeiksi. -LEFT = VASEN ## Palauttaa tekstiarvon vasemmanpuoliset merkit. -LEFTB = VASENB ## Palauttaa tekstiarvon vasemmanpuoliset merkit. -LEN = PITUUS ## Palauttaa tekstimerkkijonon merkkien määrän. -LENB = PITUUSB ## Palauttaa tekstimerkkijonon merkkien määrän. -LOWER = PIENET ## Muuntaa tekstin pieniksi kirjaimiksi. -MID = POIMI.TEKSTI ## Palauttaa määritetyn määrän merkkejä merkkijonosta alkaen annetusta kohdasta. -MIDB = POIMI.TEKSTIB ## Palauttaa määritetyn määrän merkkejä merkkijonosta alkaen annetusta kohdasta. -PHONETIC = FONEETTINEN ## Hakee foneettiset (furigana) merkit merkkijonosta. -PROPER = ERISNIMI ## Muuttaa merkkijonon kunkin sanan ensimmäisen kirjaimen isoksi. -REPLACE = KORVAA ## Korvaa tekstissä olevat merkit. -REPLACEB = KORVAAB ## Korvaa tekstissä olevat merkit. -REPT = TOISTA ## Toistaa tekstin annetun määrän kertoja. -RIGHT = OIKEA ## Palauttaa tekstiarvon oikeanpuoliset merkit. -RIGHTB = OIKEAB ## Palauttaa tekstiarvon oikeanpuoliset merkit. -SEARCH = KÄY.LÄPI ## Etsii tekstiarvon toisen tekstin sisältä (isot ja pienet kirjaimet tulkitaan samoiksi merkeiksi). -SEARCHB = KÄY.LÄPIB ## Etsii tekstiarvon toisen tekstin sisältä (isot ja pienet kirjaimet tulkitaan samoiksi merkeiksi). -SUBSTITUTE = VAIHDA ## Korvaa merkkijonossa olevan tekstin toisella. -T = T ## Muuntaa argumentit tekstiksi. -TEXT = TEKSTI ## Muotoilee luvun ja muuntaa sen tekstiksi. -TRIM = POISTA.VÄLIT ## Poistaa välilyönnit tekstistä. -UPPER = ISOT ## Muuntaa tekstin isoiksi kirjaimiksi. -VALUE = ARVO ## Muuntaa tekstiargumentin luvuksi. +ENCODEURL = URLKOODAUS +FILTERXML = SUODATA.XML +WEBSERVICE = VERKKOPALVELU + +## +## Yhteensopivuusfunktiot (Compatibility Functions) +## +BETADIST = BEETAJAKAUMA +BETAINV = BEETAJAKAUMA.KÄÄNT +BINOMDIST = BINOMIJAKAUMA +CEILING = PYÖRISTÄ.KERR.YLÖS +CHIDIST = CHIJAKAUMA +CHIINV = CHIJAKAUMA.KÄÄNT +CHITEST = CHITESTI +CONCATENATE = KETJUTA +CONFIDENCE = LUOTTAMUSVÄLI +COVAR = KOVARIANSSI +CRITBINOM = BINOMIJAKAUMA.KRIT +EXPONDIST = EKSPONENTIAALIJAKAUMA +FDIST = FJAKAUMA +FINV = FJAKAUMA.KÄÄNT +FLOOR = PYÖRISTÄ.KERR.ALAS +FORECAST = ENNUSTE +FTEST = FTESTI +GAMMADIST = GAMMAJAKAUMA +GAMMAINV = GAMMAJAKAUMA.KÄÄNT +HYPGEOMDIST = HYPERGEOM.JAKAUMA +LOGINV = LOGNORM.JAKAUMA.KÄÄNT +LOGNORMDIST = LOGNORM.JAKAUMA +MODE = MOODI +NEGBINOMDIST = BINOMIJAKAUMA.NEG +NORMDIST = NORM.JAKAUMA +NORMINV = NORM.JAKAUMA.KÄÄNT +NORMSDIST = NORM.JAKAUMA.NORMIT +NORMSINV = NORM.JAKAUMA.NORMIT.KÄÄNT +PERCENTILE = PROSENTTIPISTE +PERCENTRANK = PROSENTTIJÄRJESTYS +POISSON = POISSON +QUARTILE = NELJÄNNES +RANK = ARVON.MUKAAN +STDEV = KESKIHAJONTA +STDEVP = KESKIHAJONTAP +TDIST = TJAKAUMA +TINV = TJAKAUMA.KÄÄNT +TTEST = TTESTI +VAR = VAR +VARP = VARP +WEIBULL = WEIBULL +ZTEST = ZTESTI diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/fr/config b/PhpOffice/PhpSpreadsheet/Calculation/locale/fr/config old mode 100755 new mode 100644 index 8189598..bdac412 --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/fr/config +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/fr/config @@ -1,24 +1,20 @@ +############################################################ ## -## PhpSpreadsheet +## PhpSpreadsheet - locale settings ## +## Français (French) +## +############################################################ -ArgumentSeparator = ; - +ArgumentSeparator = ; ## -## (For future use) +## Error Codes ## -currencySymbol = € - - -## -## Excel Error Codes (For future use) - -## -NULL = #NUL! -DIV0 = #DIV/0! -VALUE = #VALEUR! -REF = #REF! -NAME = #NOM? -NUM = #NOMBRE! -NA = #N/A +NULL = #NUL! +DIV0 +VALUE = #VALEUR! +REF +NAME = #NOM? +NUM = #NOMBRE! +NA diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/fr/functions b/PhpOffice/PhpSpreadsheet/Calculation/locale/fr/functions old mode 100755 new mode 100644 index 7f40d5f..621cb0d --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/fr/functions +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/fr/functions @@ -1,416 +1,525 @@ +############################################################ ## -## PhpSpreadsheet -## -## Data in this file derived from https://www.excel-function-translation.com/ +## PhpSpreadsheet - function name translations ## +## Français (French) ## +############################################################ ## -## Add-in and Automation functions Fonctions de complément et d’automatisation +## Fonctions Cube (Cube Functions) ## -GETPIVOTDATA = LIREDONNEESTABCROISDYNAMIQUE ## Renvoie les données stockées dans un rapport de tableau croisé dynamique. - +CUBEKPIMEMBER = MEMBREKPICUBE +CUBEMEMBER = MEMBRECUBE +CUBEMEMBERPROPERTY = PROPRIETEMEMBRECUBE +CUBERANKEDMEMBER = RANGMEMBRECUBE +CUBESET = JEUCUBE +CUBESETCOUNT = NBJEUCUBE +CUBEVALUE = VALEURCUBE ## -## Cube functions Fonctions Cube +## Fonctions de base de données (Database Functions) ## -CUBEKPIMEMBER = MEMBREKPICUBE ## Renvoie un nom, une propriété et une mesure d’indicateur de performance clé et affiche le nom et la propriété dans la cellule. Un indicateur de performance clé est une mesure quantifiable, telle que la marge bénéficiaire brute mensuelle ou la rotation trimestrielle du personnel, utilisée pour évaluer les performances d’une entreprise. -CUBEMEMBER = MEMBRECUBE ## Renvoie un membre ou un uplet dans une hiérarchie de cubes. Utilisez cette fonction pour valider l’existence du membre ou de l’uplet dans le cube. -CUBEMEMBERPROPERTY = PROPRIETEMEMBRECUBE ## Renvoie la valeur d’une propriété de membre du cube. Utilisez cette fonction pour valider l’existence d’un nom de membre dans le cube et pour renvoyer la propriété spécifiée pour ce membre. -CUBERANKEDMEMBER = RANGMEMBRECUBE ## Renvoie le nième membre ou le membre placé à un certain rang dans un ensemble. Utilisez cette fonction pour renvoyer un ou plusieurs éléments d’un ensemble, tels que les meilleurs vendeurs ou les 10 meilleurs étudiants. -CUBESET = JEUCUBE ## Définit un ensemble calculé de membres ou d’uplets en envoyant une expression définie au cube sur le serveur qui crée l’ensemble et le renvoie à Microsoft Office Excel. -CUBESETCOUNT = NBJEUCUBE ## Renvoie le nombre d’éléments dans un jeu. -CUBEVALUE = VALEURCUBE ## Renvoie une valeur d’agrégation issue d’un cube. - +DAVERAGE = BDMOYENNE +DCOUNT = BDNB +DCOUNTA = BDNBVAL +DGET = BDLIRE +DMAX = BDMAX +DMIN = BDMIN +DPRODUCT = BDPRODUIT +DSTDEV = BDECARTYPE +DSTDEVP = BDECARTYPEP +DSUM = BDSOMME +DVAR = BDVAR +DVARP = BDVARP ## -## Database functions Fonctions de base de données +## Fonctions de date et d’heure (Date & Time Functions) ## -DAVERAGE = BDMOYENNE ## Renvoie la moyenne des entrées de base de données sélectionnées. -DCOUNT = BCOMPTE ## Compte le nombre de cellules d’une base de données qui contiennent des nombres. -DCOUNTA = BDNBVAL ## Compte les cellules non vides d’une base de données. -DGET = BDLIRE ## Extrait d’une base de données un enregistrement unique répondant aux critères spécifiés. -DMAX = BDMAX ## Renvoie la valeur maximale des entrées de base de données sélectionnées. -DMIN = BDMIN ## Renvoie la valeur minimale des entrées de base de données sélectionnées. -DPRODUCT = BDPRODUIT ## Multiplie les valeurs d’un champ particulier des enregistrements d’une base de données, qui répondent aux critères spécifiés. -DSTDEV = BDECARTYPE ## Calcule l’écart type pour un échantillon d’entrées de base de données sélectionnées. -DSTDEVP = BDECARTYPEP ## Calcule l’écart type pour l’ensemble d’une population d’entrées de base de données sélectionnées. -DSUM = BDSOMME ## Ajoute les nombres dans la colonne de champ des enregistrements de la base de données, qui répondent aux critères. -DVAR = BDVAR ## Calcule la variance pour un échantillon d’entrées de base de données sélectionnées. -DVARP = BDVARP ## Calcule la variance pour l’ensemble d’une population d’entrées de base de données sélectionnées. - +DATE = DATE +DATEVALUE = DATEVAL +DAY = JOUR +DAYS = JOURS +DAYS360 = JOURS360 +EDATE = MOIS.DECALER +EOMONTH = FIN.MOIS +HOUR = HEURE +ISOWEEKNUM = NO.SEMAINE.ISO +MINUTE = MINUTE +MONTH = MOIS +NETWORKDAYS = NB.JOURS.OUVRES +NETWORKDAYS.INTL = NB.JOURS.OUVRES.INTL +NOW = MAINTENANT +SECOND = SECONDE +TIME = TEMPS +TIMEVALUE = TEMPSVAL +TODAY = AUJOURDHUI +WEEKDAY = JOURSEM +WEEKNUM = NO.SEMAINE +WORKDAY = SERIE.JOUR.OUVRE +WORKDAY.INTL = SERIE.JOUR.OUVRE.INTL +YEAR = ANNEE +YEARFRAC = FRACTION.ANNEE ## -## Date and time functions Fonctions de date et d’heure +## Fonctions d’ingénierie (Engineering Functions) ## -DATE = DATE ## Renvoie le numéro de série d’une date précise. -DATEVALUE = DATEVAL ## Convertit une date représentée sous forme de texte en numéro de série. -DAY = JOUR ## Convertit un numéro de série en jour du mois. -DAYS360 = JOURS360 ## Calcule le nombre de jours qui séparent deux dates sur la base d’une année de 360 jours. -EDATE = MOIS.DECALER ## Renvoie le numéro séquentiel de la date qui représente une date spécifiée (l’argument date_départ), corrigée en plus ou en moins du nombre de mois indiqué. -EOMONTH = FIN.MOIS ## Renvoie le numéro séquentiel de la date du dernier jour du mois précédant ou suivant la date_départ du nombre de mois indiqué. -HOUR = HEURE ## Convertit un numéro de série en heure. -MINUTE = MINUTE ## Convertit un numéro de série en minute. -MONTH = MOIS ## Convertit un numéro de série en mois. -NETWORKDAYS = NB.JOURS.OUVRES ## Renvoie le nombre de jours ouvrés entiers compris entre deux dates. -NOW = MAINTENANT ## Renvoie le numéro de série de la date et de l’heure du jour. -SECOND = SECONDE ## Convertit un numéro de série en seconde. -TIME = TEMPS ## Renvoie le numéro de série d’une heure précise. -TIMEVALUE = TEMPSVAL ## Convertit une date représentée sous forme de texte en numéro de série. -TODAY = AUJOURDHUI ## Renvoie le numéro de série de la date du jour. -WEEKDAY = JOURSEM ## Convertit un numéro de série en jour de la semaine. -WEEKNUM = NO.SEMAINE ## Convertit un numéro de série en un numéro représentant l’ordre de la semaine dans l’année. -WORKDAY = SERIE.JOUR.OUVRE ## Renvoie le numéro de série de la date avant ou après le nombre de jours ouvrés spécifiés. -YEAR = ANNEE ## Convertit un numéro de série en année. -YEARFRAC = FRACTION.ANNEE ## Renvoie la fraction de l’année représentant le nombre de jours entre la date de début et la date de fin. - +BESSELI = BESSELI +BESSELJ = BESSELJ +BESSELK = BESSELK +BESSELY = BESSELY +BIN2DEC = BINDEC +BIN2HEX = BINHEX +BIN2OCT = BINOCT +BITAND = BITET +BITLSHIFT = BITDECALG +BITOR = BITOU +BITRSHIFT = BITDECALD +BITXOR = BITOUEXCLUSIF +COMPLEX = COMPLEXE +CONVERT = CONVERT +DEC2BIN = DECBIN +DEC2HEX = DECHEX +DEC2OCT = DECOCT +DELTA = DELTA +ERF = ERF +ERF.PRECISE = ERF.PRECIS +ERFC = ERFC +ERFC.PRECISE = ERFC.PRECIS +GESTEP = SUP.SEUIL +HEX2BIN = HEXBIN +HEX2DEC = HEXDEC +HEX2OCT = HEXOCT +IMABS = COMPLEXE.MODULE +IMAGINARY = COMPLEXE.IMAGINAIRE +IMARGUMENT = COMPLEXE.ARGUMENT +IMCONJUGATE = COMPLEXE.CONJUGUE +IMCOS = COMPLEXE.COS +IMCOSH = COMPLEXE.COSH +IMCOT = COMPLEXE.COT +IMCSC = COMPLEXE.CSC +IMCSCH = COMPLEXE.CSCH +IMDIV = COMPLEXE.DIV +IMEXP = COMPLEXE.EXP +IMLN = COMPLEXE.LN +IMLOG10 = COMPLEXE.LOG10 +IMLOG2 = COMPLEXE.LOG2 +IMPOWER = COMPLEXE.PUISSANCE +IMPRODUCT = COMPLEXE.PRODUIT +IMREAL = COMPLEXE.REEL +IMSEC = COMPLEXE.SEC +IMSECH = COMPLEXE.SECH +IMSIN = COMPLEXE.SIN +IMSINH = COMPLEXE.SINH +IMSQRT = COMPLEXE.RACINE +IMSUB = COMPLEXE.DIFFERENCE +IMSUM = COMPLEXE.SOMME +IMTAN = COMPLEXE.TAN +OCT2BIN = OCTBIN +OCT2DEC = OCTDEC +OCT2HEX = OCTHEX ## -## Engineering functions Fonctions d’ingénierie +## Fonctions financières (Financial Functions) ## -BESSELI = BESSELI ## Renvoie la fonction Bessel modifiée In(x). -BESSELJ = BESSELJ ## Renvoie la fonction Bessel Jn(x). -BESSELK = BESSELK ## Renvoie la fonction Bessel modifiée Kn(x). -BESSELY = BESSELY ## Renvoie la fonction Bessel Yn(x). -BIN2DEC = BINDEC ## Convertit un nombre binaire en nombre décimal. -BIN2HEX = BINHEX ## Convertit un nombre binaire en nombre hexadécimal. -BIN2OCT = BINOCT ## Convertit un nombre binaire en nombre octal. -COMPLEX = COMPLEXE ## Convertit des coefficients réel et imaginaire en un nombre complexe. -CONVERT = CONVERT ## Convertit un nombre d’une unité de mesure à une autre. -DEC2BIN = DECBIN ## Convertit un nombre décimal en nombre binaire. -DEC2HEX = DECHEX ## Convertit un nombre décimal en nombre hexadécimal. -DEC2OCT = DECOCT ## Convertit un nombre décimal en nombre octal. -DELTA = DELTA ## Teste l’égalité de deux nombres. -ERF = ERF ## Renvoie la valeur de la fonction d’erreur. -ERFC = ERFC ## Renvoie la valeur de la fonction d’erreur complémentaire. -GESTEP = SUP.SEUIL ## Teste si un nombre est supérieur à une valeur de seuil. -HEX2BIN = HEXBIN ## Convertit un nombre hexadécimal en nombre binaire. -HEX2DEC = HEXDEC ## Convertit un nombre hexadécimal en nombre décimal. -HEX2OCT = HEXOCT ## Convertit un nombre hexadécimal en nombre octal. -IMABS = COMPLEXE.MODULE ## Renvoie la valeur absolue (module) d’un nombre complexe. -IMAGINARY = COMPLEXE.IMAGINAIRE ## Renvoie le coefficient imaginaire d’un nombre complexe. -IMARGUMENT = COMPLEXE.ARGUMENT ## Renvoie l’argument thêta, un angle exprimé en radians. -IMCONJUGATE = COMPLEXE.CONJUGUE ## Renvoie le nombre complexe conjugué d’un nombre complexe. -IMCOS = IMCOS ## Renvoie le cosinus d’un nombre complexe. -IMDIV = COMPLEXE.DIV ## Renvoie le quotient de deux nombres complexes. -IMEXP = COMPLEXE.EXP ## Renvoie la fonction exponentielle d’un nombre complexe. -IMLN = COMPLEXE.LN ## Renvoie le logarithme népérien d’un nombre complexe. -IMLOG10 = COMPLEXE.LOG10 ## Calcule le logarithme en base 10 d’un nombre complexe. -IMLOG2 = COMPLEXE.LOG2 ## Calcule le logarithme en base 2 d’un nombre complexe. -IMPOWER = COMPLEXE.PUISSANCE ## Renvoie un nombre complexe élevé à une puissance entière. -IMPRODUCT = COMPLEXE.PRODUIT ## Renvoie le produit de plusieurs nombres complexes. -IMREAL = COMPLEXE.REEL ## Renvoie le coefficient réel d’un nombre complexe. -IMSIN = COMPLEXE.SIN ## Renvoie le sinus d’un nombre complexe. -IMSQRT = COMPLEXE.RACINE ## Renvoie la racine carrée d’un nombre complexe. -IMSUB = COMPLEXE.DIFFERENCE ## Renvoie la différence entre deux nombres complexes. -IMSUM = COMPLEXE.SOMME ## Renvoie la somme de plusieurs nombres complexes. -OCT2BIN = OCTBIN ## Convertit un nombre octal en nombre binaire. -OCT2DEC = OCTDEC ## Convertit un nombre octal en nombre décimal. -OCT2HEX = OCTHEX ## Convertit un nombre octal en nombre hexadécimal. - +ACCRINT = INTERET.ACC +ACCRINTM = INTERET.ACC.MAT +AMORDEGRC = AMORDEGRC +AMORLINC = AMORLINC +COUPDAYBS = NB.JOURS.COUPON.PREC +COUPDAYS = NB.JOURS.COUPONS +COUPDAYSNC = NB.JOURS.COUPON.SUIV +COUPNCD = DATE.COUPON.SUIV +COUPNUM = NB.COUPONS +COUPPCD = DATE.COUPON.PREC +CUMIPMT = CUMUL.INTER +CUMPRINC = CUMUL.PRINCPER +DB = DB +DDB = DDB +DISC = TAUX.ESCOMPTE +DOLLARDE = PRIX.DEC +DOLLARFR = PRIX.FRAC +DURATION = DUREE +EFFECT = TAUX.EFFECTIF +FV = VC +FVSCHEDULE = VC.PAIEMENTS +INTRATE = TAUX.INTERET +IPMT = INTPER +IRR = TRI +ISPMT = ISPMT +MDURATION = DUREE.MODIFIEE +MIRR = TRIM +NOMINAL = TAUX.NOMINAL +NPER = NPM +NPV = VAN +ODDFPRICE = PRIX.PCOUPON.IRREG +ODDFYIELD = REND.PCOUPON.IRREG +ODDLPRICE = PRIX.DCOUPON.IRREG +ODDLYIELD = REND.DCOUPON.IRREG +PDURATION = PDUREE +PMT = VPM +PPMT = PRINCPER +PRICE = PRIX.TITRE +PRICEDISC = VALEUR.ENCAISSEMENT +PRICEMAT = PRIX.TITRE.ECHEANCE +PV = VA +RATE = TAUX +RECEIVED = VALEUR.NOMINALE +RRI = TAUX.INT.EQUIV +SLN = AMORLIN +SYD = SYD +TBILLEQ = TAUX.ESCOMPTE.R +TBILLPRICE = PRIX.BON.TRESOR +TBILLYIELD = RENDEMENT.BON.TRESOR +VDB = VDB +XIRR = TRI.PAIEMENTS +XNPV = VAN.PAIEMENTS +YIELD = RENDEMENT.TITRE +YIELDDISC = RENDEMENT.SIMPLE +YIELDMAT = RENDEMENT.TITRE.ECHEANCE ## -## Financial functions Fonctions financières +## Fonctions d’information (Information Functions) ## -ACCRINT = INTERET.ACC ## Renvoie l’intérêt couru non échu d’un titre dont l’intérêt est perçu périodiquement. -ACCRINTM = INTERET.ACC.MAT ## Renvoie l’intérêt couru non échu d’un titre dont l’intérêt est perçu à l’échéance. -AMORDEGRC = AMORDEGRC ## Renvoie l’amortissement correspondant à chaque période comptable en utilisant un coefficient d’amortissement. -AMORLINC = AMORLINC ## Renvoie l’amortissement d’un bien à la fin d’une période fiscale donnée. -COUPDAYBS = NB.JOURS.COUPON.PREC ## Renvoie le nombre de jours entre le début de la période de coupon et la date de liquidation. -COUPDAYS = NB.JOURS.COUPONS ## Renvoie le nombre de jours pour la période du coupon contenant la date de liquidation. -COUPDAYSNC = NB.JOURS.COUPON.SUIV ## Renvoie le nombre de jours entre la date de liquidation et la date du coupon suivant la date de liquidation. -COUPNCD = DATE.COUPON.SUIV ## Renvoie la première date de coupon ultérieure à la date de règlement. -COUPNUM = NB.COUPONS ## Renvoie le nombre de coupons dus entre la date de règlement et la date d’échéance. -COUPPCD = DATE.COUPON.PREC ## Renvoie la date de coupon précédant la date de règlement. -CUMIPMT = CUMUL.INTER ## Renvoie l’intérêt cumulé payé sur un emprunt entre deux périodes. -CUMPRINC = CUMUL.PRINCPER ## Renvoie le montant cumulé des remboursements du capital d’un emprunt effectués entre deux périodes. -DB = DB ## Renvoie l’amortissement d’un bien pour une période spécifiée en utilisant la méthode de l’amortissement dégressif à taux fixe. -DDB = DDB ## Renvoie l’amortissement d’un bien pour toute période spécifiée, en utilisant la méthode de l’amortissement dégressif à taux double ou selon un coefficient à spécifier. -DISC = TAUX.ESCOMPTE ## Calcule le taux d’escompte d’une transaction. -DOLLARDE = PRIX.DEC ## Convertit un prix en euros, exprimé sous forme de fraction, en un prix en euros exprimé sous forme de nombre décimal. -DOLLARFR = PRIX.FRAC ## Convertit un prix en euros, exprimé sous forme de nombre décimal, en un prix en euros exprimé sous forme de fraction. -DURATION = DUREE ## Renvoie la durée, en années, d’un titre dont l’intérêt est perçu périodiquement. -EFFECT = TAUX.EFFECTIF ## Renvoie le taux d’intérêt annuel effectif. -FV = VC ## Renvoie la valeur future d’un investissement. -FVSCHEDULE = VC.PAIEMENTS ## Calcule la valeur future d’un investissement en appliquant une série de taux d’intérêt composites. -INTRATE = TAUX.INTERET ## Affiche le taux d’intérêt d’un titre totalement investi. -IPMT = INTPER ## Calcule le montant des intérêts d’un investissement pour une période donnée. -IRR = TRI ## Calcule le taux de rentabilité interne d’un investissement pour une succession de trésoreries. -ISPMT = ISPMT ## Calcule le montant des intérêts d’un investissement pour une période donnée. -MDURATION = DUREE.MODIFIEE ## Renvoie la durée de Macauley modifiée pour un titre ayant une valeur nominale hypothétique de 100_euros. -MIRR = TRIM ## Calcule le taux de rentabilité interne lorsque les paiements positifs et négatifs sont financés à des taux différents. -NOMINAL = TAUX.NOMINAL ## Calcule le taux d’intérêt nominal annuel. -NPER = NPM ## Renvoie le nombre de versements nécessaires pour rembourser un emprunt. -NPV = VAN ## Calcule la valeur actuelle nette d’un investissement basé sur une série de décaissements et un taux d’escompte. -ODDFPRICE = PRIX.PCOUPON.IRREG ## Renvoie le prix par tranche de valeur nominale de 100 euros d’un titre dont la première période de coupon est irrégulière. -ODDFYIELD = REND.PCOUPON.IRREG ## Renvoie le taux de rendement d’un titre dont la première période de coupon est irrégulière. -ODDLPRICE = PRIX.DCOUPON.IRREG ## Renvoie le prix par tranche de valeur nominale de 100 euros d’un titre dont la première période de coupon est irrégulière. -ODDLYIELD = REND.DCOUPON.IRREG ## Renvoie le taux de rendement d’un titre dont la dernière période de coupon est irrégulière. -PMT = VPM ## Calcule le paiement périodique d’un investissement donné. -PPMT = PRINCPER ## Calcule, pour une période donnée, la part de remboursement du principal d’un investissement. -PRICE = PRIX.TITRE ## Renvoie le prix d’un titre rapportant des intérêts périodiques, pour une valeur nominale de 100 euros. -PRICEDISC = VALEUR.ENCAISSEMENT ## Renvoie la valeur d’encaissement d’un escompte commercial, pour une valeur nominale de 100 euros. -PRICEMAT = PRIX.TITRE.ECHEANCE ## Renvoie le prix d’un titre dont la valeur nominale est 100 euros et qui rapporte des intérêts à l’échéance. -PV = PV ## Calcule la valeur actuelle d’un investissement. -RATE = TAUX ## Calcule le taux d’intérêt par période pour une annuité. -RECEIVED = VALEUR.NOMINALE ## Renvoie la valeur nominale à échéance d’un effet de commerce. -SLN = AMORLIN ## Calcule l’amortissement linéaire d’un bien pour une période donnée. -SYD = SYD ## Calcule l’amortissement d’un bien pour une période donnée sur la base de la méthode américaine Sum-of-Years Digits (amortissement dégressif à taux décroissant appliqué à une valeur constante). -TBILLEQ = TAUX.ESCOMPTE.R ## Renvoie le taux d’escompte rationnel d’un bon du Trésor. -TBILLPRICE = PRIX.BON.TRESOR ## Renvoie le prix d’un bon du Trésor d’une valeur nominale de 100 euros. -TBILLYIELD = RENDEMENT.BON.TRESOR ## Calcule le taux de rendement d’un bon du Trésor. -VDB = VDB ## Renvoie l’amortissement d’un bien pour une période spécifiée ou partielle en utilisant une méthode de l’amortissement dégressif à taux fixe. -XIRR = TRI.PAIEMENTS ## Calcule le taux de rentabilité interne d’un ensemble de paiements non périodiques. -XNPV = VAN.PAIEMENTS ## Renvoie la valeur actuelle nette d’un ensemble de paiements non périodiques. -YIELD = RENDEMENT.TITRE ## Calcule le rendement d’un titre rapportant des intérêts périodiquement. -YIELDDISC = RENDEMENT.SIMPLE ## Calcule le taux de rendement d’un emprunt à intérêt simple (par exemple, un bon du Trésor). -YIELDMAT = RENDEMENT.TITRE.ECHEANCE ## Renvoie le rendement annuel d’un titre qui rapporte des intérêts à l’échéance. - +CELL = CELLULE +ERROR.TYPE = TYPE.ERREUR +INFO = INFORMATIONS +ISBLANK = ESTVIDE +ISERR = ESTERR +ISERROR = ESTERREUR +ISEVEN = EST.PAIR +ISFORMULA = ESTFORMULE +ISLOGICAL = ESTLOGIQUE +ISNA = ESTNA +ISNONTEXT = ESTNONTEXTE +ISNUMBER = ESTNUM +ISODD = EST.IMPAIR +ISREF = ESTREF +ISTEXT = ESTTEXTE +N = N +NA = NA +SHEET = FEUILLE +SHEETS = FEUILLES +TYPE = TYPE ## -## Information functions Fonctions d’information +## Fonctions logiques (Logical Functions) ## -CELL = CELLULE ## Renvoie des informations sur la mise en forme, l’emplacement et le contenu d’une cellule. -ERROR.TYPE = TYPE.ERREUR ## Renvoie un nombre correspondant à un type d’erreur. -INFO = INFORMATIONS ## Renvoie des informations sur l’environnement d’exploitation actuel. -ISBLANK = ESTVIDE ## Renvoie VRAI si l’argument valeur est vide. -ISERR = ESTERR ## Renvoie VRAI si l’argument valeur fait référence à une valeur d’erreur, sauf #N/A. -ISERROR = ESTERREUR ## Renvoie VRAI si l’argument valeur fait référence à une valeur d’erreur. -ISEVEN = EST.PAIR ## Renvoie VRAI si le chiffre est pair. -ISLOGICAL = ESTLOGIQUE ## Renvoie VRAI si l’argument valeur fait référence à une valeur logique. -ISNA = ESTNA ## Renvoie VRAI si l’argument valeur fait référence à la valeur d’erreur #N/A. -ISNONTEXT = ESTNONTEXTE ## Renvoie VRAI si l’argument valeur ne se présente pas sous forme de texte. -ISNUMBER = ESTNUM ## Renvoie VRAI si l’argument valeur représente un nombre. -ISODD = EST.IMPAIR ## Renvoie VRAI si le chiffre est impair. -ISREF = ESTREF ## Renvoie VRAI si l’argument valeur est une référence. -ISTEXT = ESTTEXTE ## Renvoie VRAI si l’argument valeur se présente sous forme de texte. -N = N ## Renvoie une valeur convertie en nombre. -NA = NA ## Renvoie la valeur d’erreur #N/A. -TYPE = TYPE ## Renvoie un nombre indiquant le type de données d’une valeur. - +AND = ET +FALSE = FAUX +IF = SI +IFERROR = SIERREUR +IFNA = SI.NON.DISP +IFS = SI.CONDITIONS +NOT = NON +OR = OU +SWITCH = SI.MULTIPLE +TRUE = VRAI +XOR = OUX ## -## Logical functions Fonctions logiques +## Fonctions de recherche et de référence (Lookup & Reference Functions) ## -AND = ET ## Renvoie VRAI si tous ses arguments sont VRAI. -FALSE = FAUX ## Renvoie la valeur logique FAUX. -IF = SI ## Spécifie un test logique à effectuer. -IFERROR = SIERREUR ## Renvoie une valeur que vous spécifiez si une formule génère une erreur ; sinon, elle renvoie le résultat de la formule. -NOT = NON ## Inverse la logique de cet argument. -OR = OU ## Renvoie VRAI si un des arguments est VRAI. -TRUE = VRAI ## Renvoie la valeur logique VRAI. - +ADDRESS = ADRESSE +AREAS = ZONES +CHOOSE = CHOISIR +COLUMN = COLONNE +COLUMNS = COLONNES +FORMULATEXT = FORMULETEXTE +GETPIVOTDATA = LIREDONNEESTABCROISDYNAMIQUE +HLOOKUP = RECHERCHEH +HYPERLINK = LIEN_HYPERTEXTE +INDEX = INDEX +INDIRECT = INDIRECT +LOOKUP = RECHERCHE +MATCH = EQUIV +OFFSET = DECALER +ROW = LIGNE +ROWS = LIGNES +RTD = RTD +TRANSPOSE = TRANSPOSE +VLOOKUP = RECHERCHEV +*RC = LC ## -## Lookup and reference functions Fonctions de recherche et de référence +## Fonctions mathématiques et trigonométriques (Math & Trig Functions) ## -ADDRESS = ADRESSE ## Renvoie une référence sous forme de texte à une seule cellule d’une feuille de calcul. -AREAS = ZONES ## Renvoie le nombre de zones dans une référence. -CHOOSE = CHOISIR ## Choisit une valeur dans une liste. -COLUMN = COLONNE ## Renvoie le numéro de colonne d’une référence. -COLUMNS = COLONNES ## Renvoie le nombre de colonnes dans une référence. -HLOOKUP = RECHERCHEH ## Effectue une recherche dans la première ligne d’une matrice et renvoie la valeur de la cellule indiquée. -HYPERLINK = LIEN_HYPERTEXTE ## Crée un raccourci ou un renvoi qui ouvre un document stocké sur un serveur réseau, sur un réseau Intranet ou sur Internet. -INDEX = INDEX ## Utilise un index pour choisir une valeur provenant d’une référence ou d’une matrice. -INDIRECT = INDIRECT ## Renvoie une référence indiquée par une valeur de texte. -LOOKUP = RECHERCHE ## Recherche des valeurs dans un vecteur ou une matrice. -MATCH = EQUIV ## Recherche des valeurs dans une référence ou une matrice. -OFFSET = DECALER ## Renvoie une référence décalée par rapport à une référence donnée. -ROW = LIGNE ## Renvoie le numéro de ligne d’une référence. -ROWS = LIGNES ## Renvoie le nombre de lignes dans une référence. -RTD = RTD ## Extrait les données en temps réel à partir d’un programme prenant en charge l’automation COM (Automation : utilisation des objets d'une application à partir d'une autre application ou d'un autre outil de développement. Autrefois appelée OLE Automation, Automation est une norme industrielle et une fonctionnalité du modèle d'objet COM (Component Object Model).). -TRANSPOSE = TRANSPOSE ## Renvoie la transposition d’une matrice. -VLOOKUP = RECHERCHEV ## Effectue une recherche dans la première colonne d’une matrice et se déplace sur la ligne pour renvoyer la valeur d’une cellule. - +ABS = ABS +ACOS = ACOS +ACOSH = ACOSH +ACOT = ACOT +ACOTH = ACOTH +AGGREGATE = AGREGAT +ARABIC = CHIFFRE.ARABE +ASIN = ASIN +ASINH = ASINH +ATAN = ATAN +ATAN2 = ATAN2 +ATANH = ATANH +BASE = BASE +CEILING.MATH = PLAFOND.MATH +CEILING.PRECISE = PLAFOND.PRECIS +COMBIN = COMBIN +COMBINA = COMBINA +COS = COS +COSH = COSH +COT = COT +COTH = COTH +CSC = CSC +CSCH = CSCH +DECIMAL = DECIMAL +DEGREES = DEGRES +ECMA.CEILING = ECMA.PLAFOND +EVEN = PAIR +EXP = EXP +FACT = FACT +FACTDOUBLE = FACTDOUBLE +FLOOR.MATH = PLANCHER.MATH +FLOOR.PRECISE = PLANCHER.PRECIS +GCD = PGCD +INT = ENT +ISO.CEILING = ISO.PLAFOND +LCM = PPCM +LN = LN +LOG = LOG +LOG10 = LOG10 +MDETERM = DETERMAT +MINVERSE = INVERSEMAT +MMULT = PRODUITMAT +MOD = MOD +MROUND = ARRONDI.AU.MULTIPLE +MULTINOMIAL = MULTINOMIALE +MUNIT = MATRICE.UNITAIRE +ODD = IMPAIR +PI = PI +POWER = PUISSANCE +PRODUCT = PRODUIT +QUOTIENT = QUOTIENT +RADIANS = RADIANS +RAND = ALEA +RANDBETWEEN = ALEA.ENTRE.BORNES +ROMAN = ROMAIN +ROUND = ARRONDI +ROUNDDOWN = ARRONDI.INF +ROUNDUP = ARRONDI.SUP +SEC = SEC +SECH = SECH +SERIESSUM = SOMME.SERIES +SIGN = SIGNE +SIN = SIN +SINH = SINH +SQRT = RACINE +SQRTPI = RACINE.PI +SUBTOTAL = SOUS.TOTAL +SUM = SOMME +SUMIF = SOMME.SI +SUMIFS = SOMME.SI.ENS +SUMPRODUCT = SOMMEPROD +SUMSQ = SOMME.CARRES +SUMX2MY2 = SOMME.X2MY2 +SUMX2PY2 = SOMME.X2PY2 +SUMXMY2 = SOMME.XMY2 +TAN = TAN +TANH = TANH +TRUNC = TRONQUE ## -## Math and trigonometry functions Fonctions mathématiques et trigonométriques +## Fonctions statistiques (Statistical Functions) ## -ABS = ABS ## Renvoie la valeur absolue d’un nombre. -ACOS = ACOS ## Renvoie l’arccosinus d’un nombre. -ACOSH = ACOSH ## Renvoie le cosinus hyperbolique inverse d’un nombre. -ASIN = ASIN ## Renvoie l’arcsinus d’un nombre. -ASINH = ASINH ## Renvoie le sinus hyperbolique inverse d’un nombre. -ATAN = ATAN ## Renvoie l’arctangente d’un nombre. -ATAN2 = ATAN2 ## Renvoie l’arctangente des coordonnées x et y. -ATANH = ATANH ## Renvoie la tangente hyperbolique inverse d’un nombre. -CEILING = PLAFOND ## Arrondit un nombre au nombre entier le plus proche ou au multiple le plus proche de l’argument précision en s’éloignant de zéro. -COMBIN = COMBIN ## Renvoie le nombre de combinaisons que l’on peut former avec un nombre donné d’objets. -COS = COS ## Renvoie le cosinus d’un nombre. -COSH = COSH ## Renvoie le cosinus hyperbolique d’un nombre. -DEGREES = DEGRES ## Convertit des radians en degrés. -EVEN = PAIR ## Arrondit un nombre au nombre entier pair le plus proche en s’éloignant de zéro. -EXP = EXP ## Renvoie e élevé à la puissance d’un nombre donné. -FACT = FACT ## Renvoie la factorielle d’un nombre. -FACTDOUBLE = FACTDOUBLE ## Renvoie la factorielle double d’un nombre. -FLOOR = PLANCHER ## Arrondit un nombre en tendant vers 0 (zéro). -GCD = PGCD ## Renvoie le plus grand commun diviseur. -INT = ENT ## Arrondit un nombre à l’entier immédiatement inférieur. -LCM = PPCM ## Renvoie le plus petit commun multiple. -LN = LN ## Renvoie le logarithme népérien d’un nombre. -LOG = LOG ## Renvoie le logarithme d’un nombre dans la base spécifiée. -LOG10 = LOG10 ## Calcule le logarithme en base 10 d’un nombre. -MDETERM = DETERMAT ## Renvoie le déterminant d’une matrice. -MINVERSE = INVERSEMAT ## Renvoie la matrice inverse d’une matrice. -MMULT = PRODUITMAT ## Renvoie le produit de deux matrices. -MOD = MOD ## Renvoie le reste d’une division. -MROUND = ARRONDI.AU.MULTIPLE ## Donne l’arrondi d’un nombre au multiple spécifié. -MULTINOMIAL = MULTINOMIALE ## Calcule la multinomiale d’un ensemble de nombres. -ODD = IMPAIR ## Renvoie le nombre, arrondi à la valeur du nombre entier impair le plus proche en s’éloignant de zéro. -PI = PI ## Renvoie la valeur de pi. -POWER = PUISSANCE ## Renvoie la valeur du nombre élevé à une puissance. -PRODUCT = PRODUIT ## Multiplie ses arguments. -QUOTIENT = QUOTIENT ## Renvoie la partie entière du résultat d’une division. -RADIANS = RADIANS ## Convertit des degrés en radians. -RAND = ALEA ## Renvoie un nombre aléatoire compris entre 0 et 1. -RANDBETWEEN = ALEA.ENTRE.BORNES ## Renvoie un nombre aléatoire entre les nombres que vous spécifiez. -ROMAN = ROMAIN ## Convertit des chiffres arabes en chiffres romains, sous forme de texte. -ROUND = ARRONDI ## Arrondit un nombre au nombre de chiffres indiqué. -ROUNDDOWN = ARRONDI.INF ## Arrondit un nombre en tendant vers 0 (zéro). -ROUNDUP = ARRONDI.SUP ## Arrondit un nombre à l’entier supérieur, en s’éloignant de zéro. -SERIESSUM = SOMME.SERIES ## Renvoie la somme d’une série géométrique en s’appuyant sur la formule suivante : -SIGN = SIGNE ## Renvoie le signe d’un nombre. -SIN = SIN ## Renvoie le sinus d’un angle donné. -SINH = SINH ## Renvoie le sinus hyperbolique d’un nombre. -SQRT = RACINE ## Renvoie la racine carrée d’un nombre. -SQRTPI = RACINE.PI ## Renvoie la racine carrée de (nombre * pi). -SUBTOTAL = SOUS.TOTAL ## Renvoie un sous-total dans une liste ou une base de données. -SUM = SOMME ## Calcule la somme de ses arguments. -SUMIF = SOMME.SI ## Additionne les cellules spécifiées si elles répondent à un critère donné. -SUMIFS = SOMME.SI.ENS ## Ajoute les cellules d’une plage qui répondent à plusieurs critères. -SUMPRODUCT = SOMMEPROD ## Multiplie les valeurs correspondantes des matrices spécifiées et calcule la somme de ces produits. -SUMSQ = SOMME.CARRES ## Renvoie la somme des carrés des arguments. -SUMX2MY2 = SOMME.X2MY2 ## Renvoie la somme de la différence des carrés des valeurs correspondantes de deux matrices. -SUMX2PY2 = SOMME.X2PY2 ## Renvoie la somme de la somme des carrés des valeurs correspondantes de deux matrices. -SUMXMY2 = SOMME.XMY2 ## Renvoie la somme des carrés des différences entre les valeurs correspondantes de deux matrices. -TAN = TAN ## Renvoie la tangente d’un nombre. -TANH = TANH ## Renvoie la tangente hyperbolique d’un nombre. -TRUNC = TRONQUE ## Renvoie la partie entière d’un nombre. - +AVEDEV = ECART.MOYEN +AVERAGE = MOYENNE +AVERAGEA = AVERAGEA +AVERAGEIF = MOYENNE.SI +AVERAGEIFS = MOYENNE.SI.ENS +BETA.DIST = LOI.BETA.N +BETA.INV = BETA.INVERSE.N +BINOM.DIST = LOI.BINOMIALE.N +BINOM.DIST.RANGE = LOI.BINOMIALE.SERIE +BINOM.INV = LOI.BINOMIALE.INVERSE +CHISQ.DIST = LOI.KHIDEUX.N +CHISQ.DIST.RT = LOI.KHIDEUX.DROITE +CHISQ.INV = LOI.KHIDEUX.INVERSE +CHISQ.INV.RT = LOI.KHIDEUX.INVERSE.DROITE +CHISQ.TEST = CHISQ.TEST +CONFIDENCE.NORM = INTERVALLE.CONFIANCE.NORMAL +CONFIDENCE.T = INTERVALLE.CONFIANCE.STUDENT +CORREL = COEFFICIENT.CORRELATION +COUNT = NB +COUNTA = NBVAL +COUNTBLANK = NB.VIDE +COUNTIF = NB.SI +COUNTIFS = NB.SI.ENS +COVARIANCE.P = COVARIANCE.PEARSON +COVARIANCE.S = COVARIANCE.STANDARD +DEVSQ = SOMME.CARRES.ECARTS +EXPON.DIST = LOI.EXPONENTIELLE.N +F.DIST = LOI.F.N +F.DIST.RT = LOI.F.DROITE +F.INV = INVERSE.LOI.F.N +F.INV.RT = INVERSE.LOI.F.DROITE +F.TEST = F.TEST +FISHER = FISHER +FISHERINV = FISHER.INVERSE +FORECAST.ETS = PREVISION.ETS +FORECAST.ETS.CONFINT = PREVISION.ETS.CONFINT +FORECAST.ETS.SEASONALITY = PREVISION.ETS.CARACTERESAISONNIER +FORECAST.ETS.STAT = PREVISION.ETS.STAT +FORECAST.LINEAR = PREVISION.LINEAIRE +FREQUENCY = FREQUENCE +GAMMA = GAMMA +GAMMA.DIST = LOI.GAMMA.N +GAMMA.INV = LOI.GAMMA.INVERSE.N +GAMMALN = LNGAMMA +GAMMALN.PRECISE = LNGAMMA.PRECIS +GAUSS = GAUSS +GEOMEAN = MOYENNE.GEOMETRIQUE +GROWTH = CROISSANCE +HARMEAN = MOYENNE.HARMONIQUE +HYPGEOM.DIST = LOI.HYPERGEOMETRIQUE.N +INTERCEPT = ORDONNEE.ORIGINE +KURT = KURTOSIS +LARGE = GRANDE.VALEUR +LINEST = DROITEREG +LOGEST = LOGREG +LOGNORM.DIST = LOI.LOGNORMALE.N +LOGNORM.INV = LOI.LOGNORMALE.INVERSE.N +MAX = MAX +MAXA = MAXA +MAXIFS = MAX.SI +MEDIAN = MEDIANE +MIN = MIN +MINA = MINA +MINIFS = MIN.SI +MODE.MULT = MODE.MULTIPLE +MODE.SNGL = MODE.SIMPLE +NEGBINOM.DIST = LOI.BINOMIALE.NEG.N +NORM.DIST = LOI.NORMALE.N +NORM.INV = LOI.NORMALE.INVERSE.N +NORM.S.DIST = LOI.NORMALE.STANDARD.N +NORM.S.INV = LOI.NORMALE.STANDARD.INVERSE.N +PEARSON = PEARSON +PERCENTILE.EXC = CENTILE.EXCLURE +PERCENTILE.INC = CENTILE.INCLURE +PERCENTRANK.EXC = RANG.POURCENTAGE.EXCLURE +PERCENTRANK.INC = RANG.POURCENTAGE.INCLURE +PERMUT = PERMUTATION +PERMUTATIONA = PERMUTATIONA +PHI = PHI +POISSON.DIST = LOI.POISSON.N +PROB = PROBABILITE +QUARTILE.EXC = QUARTILE.EXCLURE +QUARTILE.INC = QUARTILE.INCLURE +RANK.AVG = MOYENNE.RANG +RANK.EQ = EQUATION.RANG +RSQ = COEFFICIENT.DETERMINATION +SKEW = COEFFICIENT.ASYMETRIE +SKEW.P = COEFFICIENT.ASYMETRIE.P +SLOPE = PENTE +SMALL = PETITE.VALEUR +STANDARDIZE = CENTREE.REDUITE +STDEV.P = ECARTYPE.PEARSON +STDEV.S = ECARTYPE.STANDARD +STDEVA = STDEVA +STDEVPA = STDEVPA +STEYX = ERREUR.TYPE.XY +T.DIST = LOI.STUDENT.N +T.DIST.2T = LOI.STUDENT.BILATERALE +T.DIST.RT = LOI.STUDENT.DROITE +T.INV = LOI.STUDENT.INVERSE.N +T.INV.2T = LOI.STUDENT.INVERSE.BILATERALE +T.TEST = T.TEST +TREND = TENDANCE +TRIMMEAN = MOYENNE.REDUITE +VAR.P = VAR.P.N +VAR.S = VAR.S +VARA = VARA +VARPA = VARPA +WEIBULL.DIST = LOI.WEIBULL.N +Z.TEST = Z.TEST ## -## Statistical functions Fonctions statistiques +## Fonctions de texte (Text Functions) ## -AVEDEV = ECART.MOYEN ## Renvoie la moyenne des écarts absolus observés dans la moyenne des points de données. -AVERAGE = MOYENNE ## Renvoie la moyenne de ses arguments. -AVERAGEA = AVERAGEA ## Renvoie la moyenne de ses arguments, nombres, texte et valeurs logiques inclus. -AVERAGEIF = MOYENNE.SI ## Renvoie la moyenne (arithmétique) de toutes les cellules d’une plage qui répondent à des critères donnés. -AVERAGEIFS = MOYENNE.SI.ENS ## Renvoie la moyenne (arithmétique) de toutes les cellules qui répondent à plusieurs critères. -BETADIST = LOI.BETA ## Renvoie la fonction de distribution cumulée. -BETAINV = BETA.INVERSE ## Renvoie l’inverse de la fonction de distribution cumulée pour une distribution bêta spécifiée. -BINOMDIST = LOI.BINOMIALE ## Renvoie la probabilité d’une variable aléatoire discrète suivant la loi binomiale. -CHIDIST = LOI.KHIDEUX ## Renvoie la probabilité unilatérale de la distribution khi-deux. -CHIINV = KHIDEUX.INVERSE ## Renvoie l’inverse de la probabilité unilatérale de la distribution khi-deux. -CHITEST = TEST.KHIDEUX ## Renvoie le test d’indépendance. -CONFIDENCE = INTERVALLE.CONFIANCE ## Renvoie l’intervalle de confiance pour une moyenne de population. -CORREL = COEFFICIENT.CORRELATION ## Renvoie le coefficient de corrélation entre deux séries de données. -COUNT = NB ## Détermine les nombres compris dans la liste des arguments. -COUNTA = NBVAL ## Détermine le nombre de valeurs comprises dans la liste des arguments. -COUNTBLANK = NB.VIDE ## Compte le nombre de cellules vides dans une plage. -COUNTIF = NB.SI ## Compte le nombre de cellules qui répondent à un critère donné dans une plage. -COUNTIFS = NB.SI.ENS ## Compte le nombre de cellules à l’intérieur d’une plage qui répondent à plusieurs critères. -COVAR = COVARIANCE ## Renvoie la covariance, moyenne des produits des écarts pour chaque série d’observations. -CRITBINOM = CRITERE.LOI.BINOMIALE ## Renvoie la plus petite valeur pour laquelle la distribution binomiale cumulée est inférieure ou égale à une valeur de critère. -DEVSQ = SOMME.CARRES.ECARTS ## Renvoie la somme des carrés des écarts. -EXPONDIST = LOI.EXPONENTIELLE ## Renvoie la distribution exponentielle. -FDIST = LOI.F ## Renvoie la distribution de probabilité F. -FINV = INVERSE.LOI.F ## Renvoie l’inverse de la distribution de probabilité F. -FISHER = FISHER ## Renvoie la transformation de Fisher. -FISHERINV = FISHER.INVERSE ## Renvoie l’inverse de la transformation de Fisher. -FORECAST = PREVISION ## Calcule une valeur par rapport à une tendance linéaire. -FREQUENCY = FREQUENCE ## Calcule la fréquence d’apparition des valeurs dans une plage de valeurs, puis renvoie des nombres sous forme de matrice verticale. -FTEST = TEST.F ## Renvoie le résultat d’un test F. -GAMMADIST = LOI.GAMMA ## Renvoie la probabilité d’une variable aléatoire suivant une loi Gamma. -GAMMAINV = LOI.GAMMA.INVERSE ## Renvoie, pour une probabilité donnée, la valeur d’une variable aléatoire suivant une loi Gamma. -GAMMALN = LNGAMMA ## Renvoie le logarithme népérien de la fonction Gamma, G(x) -GEOMEAN = MOYENNE.GEOMETRIQUE ## Renvoie la moyenne géométrique. -GROWTH = CROISSANCE ## Calcule des valeurs par rapport à une tendance exponentielle. -HARMEAN = MOYENNE.HARMONIQUE ## Renvoie la moyenne harmonique. -HYPGEOMDIST = LOI.HYPERGEOMETRIQUE ## Renvoie la probabilité d’une variable aléatoire discrète suivant une loi hypergéométrique. -INTERCEPT = ORDONNEE.ORIGINE ## Renvoie l’ordonnée à l’origine d’une droite de régression linéaire. -KURT = KURTOSIS ## Renvoie le kurtosis d’une série de données. -LARGE = GRANDE.VALEUR ## Renvoie la k-ième plus grande valeur d’une série de données. -LINEST = DROITEREG ## Renvoie les paramètres d’une tendance linéaire. -LOGEST = LOGREG ## Renvoie les paramètres d’une tendance exponentielle. -LOGINV = LOI.LOGNORMALE.INVERSE ## Renvoie l’inverse de la probabilité pour une variable aléatoire suivant la loi lognormale. -LOGNORMDIST = LOI.LOGNORMALE ## Renvoie la probabilité d’une variable aléatoire continue suivant une loi lognormale. -MAX = MAX ## Renvoie la valeur maximale contenue dans une liste d’arguments. -MAXA = MAXA ## Renvoie la valeur maximale d’une liste d’arguments, nombres, texte et valeurs logiques inclus. -MEDIAN = MEDIANE ## Renvoie la valeur médiane des nombres donnés. -MIN = MIN ## Renvoie la valeur minimale contenue dans une liste d’arguments. -MINA = MINA ## Renvoie la plus petite valeur d’une liste d’arguments, nombres, texte et valeurs logiques inclus. -MODE = MODE ## Renvoie la valeur la plus courante d’une série de données. -NEGBINOMDIST = LOI.BINOMIALE.NEG ## Renvoie la probabilité d’une variable aléatoire discrète suivant une loi binomiale négative. -NORMDIST = LOI.NORMALE ## Renvoie la probabilité d’une variable aléatoire continue suivant une loi normale. -NORMINV = LOI.NORMALE.INVERSE ## Renvoie, pour une probabilité donnée, la valeur d’une variable aléatoire suivant une loi normale standard. -NORMSDIST = LOI.NORMALE.STANDARD ## Renvoie la probabilité d’une variable aléatoire continue suivant une loi normale standard. -NORMSINV = LOI.NORMALE.STANDARD.INVERSE ## Renvoie l’inverse de la distribution cumulée normale standard. -PEARSON = PEARSON ## Renvoie le coefficient de corrélation d’échantillonnage de Pearson. -PERCENTILE = CENTILE ## Renvoie le k-ième centile des valeurs d’une plage. -PERCENTRANK = RANG.POURCENTAGE ## Renvoie le rang en pourcentage d’une valeur d’une série de données. -PERMUT = PERMUTATION ## Renvoie le nombre de permutations pour un nombre donné d’objets. -POISSON = LOI.POISSON ## Renvoie la probabilité d’une variable aléatoire suivant une loi de Poisson. -PROB = PROBABILITE ## Renvoie la probabilité que des valeurs d’une plage soient comprises entre deux limites. -QUARTILE = QUARTILE ## Renvoie le quartile d’une série de données. -RANK = RANG ## Renvoie le rang d’un nombre contenu dans une liste. -RSQ = COEFFICIENT.DETERMINATION ## Renvoie la valeur du coefficient de détermination R^2 d’une régression linéaire. -SKEW = COEFFICIENT.ASYMETRIE ## Renvoie l’asymétrie d’une distribution. -SLOPE = PENTE ## Renvoie la pente d’une droite de régression linéaire. -SMALL = PETITE.VALEUR ## Renvoie la k-ième plus petite valeur d’une série de données. -STANDARDIZE = CENTREE.REDUITE ## Renvoie une valeur centrée réduite. -STDEV = ECARTYPE ## Évalue l’écart type d’une population en se basant sur un échantillon de cette population. -STDEVA = STDEVA ## Évalue l’écart type d’une population en se basant sur un échantillon de cette population, nombres, texte et valeurs logiques inclus. -STDEVP = ECARTYPEP ## Calcule l’écart type d’une population à partir de la population entière. -STDEVPA = STDEVPA ## Calcule l’écart type d’une population à partir de l’ensemble de la population, nombres, texte et valeurs logiques inclus. -STEYX = ERREUR.TYPE.XY ## Renvoie l’erreur type de la valeur y prévue pour chaque x de la régression. -TDIST = LOI.STUDENT ## Renvoie la probabilité d’une variable aléatoire suivant une loi T de Student. -TINV = LOI.STUDENT.INVERSE ## Renvoie, pour une probabilité donnée, la valeur d’une variable aléatoire suivant une loi T de Student. -TREND = TENDANCE ## Renvoie des valeurs par rapport à une tendance linéaire. -TRIMMEAN = MOYENNE.REDUITE ## Renvoie la moyenne de l’intérieur d’une série de données. -TTEST = TEST.STUDENT ## Renvoie la probabilité associée à un test T de Student. -VAR = VAR ## Calcule la variance sur la base d’un échantillon. -VARA = VARA ## Estime la variance d’une population en se basant sur un échantillon de cette population, nombres, texte et valeurs logiques incluses. -VARP = VAR.P ## Calcule la variance sur la base de l’ensemble de la population. -VARPA = VARPA ## Calcule la variance d’une population en se basant sur la population entière, nombres, texte et valeurs logiques inclus. -WEIBULL = LOI.WEIBULL ## Renvoie la probabilité d’une variable aléatoire suivant une loi de Weibull. -ZTEST = TEST.Z ## Renvoie la valeur de probabilité unilatérale d’un test z. - +BAHTTEXT = BAHTTEXT +CHAR = CAR +CLEAN = EPURAGE +CODE = CODE +CONCAT = CONCAT +DOLLAR = DEVISE +EXACT = EXACT +FIND = TROUVE +FIXED = CTXT +LEFT = GAUCHE +LEN = NBCAR +LOWER = MINUSCULE +MID = STXT +NUMBERVALUE = VALEURNOMBRE +PHONETIC = PHONETIQUE +PROPER = NOMPROPRE +REPLACE = REMPLACER +REPT = REPT +RIGHT = DROITE +SEARCH = CHERCHE +SUBSTITUTE = SUBSTITUE +T = T +TEXT = TEXTE +TEXTJOIN = JOINDRE.TEXTE +TRIM = SUPPRESPACE +UNICHAR = UNICAR +UNICODE = UNICODE +UPPER = MAJUSCULE +VALUE = CNUM ## -## Text functions Fonctions de texte +## Fonctions web (Web Functions) ## -ASC = ASC ## Change les caractères anglais ou katakana à pleine chasse (codés sur deux octets) à l’intérieur d’une chaîne de caractères en caractères à demi-chasse (codés sur un octet). -BAHTTEXT = BAHTTEXT ## Convertit un nombre en texte en utilisant le format monétaire ß (baht). -CHAR = CAR ## Renvoie le caractère spécifié par le code numérique. -CLEAN = EPURAGE ## Supprime tous les caractères de contrôle du texte. -CODE = CODE ## Renvoie le numéro de code du premier caractère du texte. -CONCATENATE = CONCATENER ## Assemble plusieurs éléments textuels de façon à n’en former qu’un seul. -DOLLAR = EURO ## Convertit un nombre en texte en utilisant le format monétaire € (euro). -EXACT = EXACT ## Vérifie si deux valeurs de texte sont identiques. -FIND = TROUVE ## Trouve un valeur textuelle dans une autre, en respectant la casse. -FINDB = TROUVERB ## Trouve un valeur textuelle dans une autre, en respectant la casse. -FIXED = CTXT ## Convertit un nombre au format texte avec un nombre de décimales spécifié. -JIS = JIS ## Change les caractères anglais ou katakana à demi-chasse (codés sur un octet) à l’intérieur d’une chaîne de caractères en caractères à à pleine chasse (codés sur deux octets). -LEFT = GAUCHE ## Renvoie des caractères situés à l’extrême gauche d’une chaîne de caractères. -LEFTB = GAUCHEB ## Renvoie des caractères situés à l’extrême gauche d’une chaîne de caractères. -LEN = NBCAR ## Renvoie le nombre de caractères contenus dans une chaîne de texte. -LENB = LENB ## Renvoie le nombre de caractères contenus dans une chaîne de texte. -LOWER = MINUSCULE ## Convertit le texte en minuscules. -MID = STXT ## Renvoie un nombre déterminé de caractères d’une chaîne de texte à partir de la position que vous indiquez. -MIDB = STXTB ## Renvoie un nombre déterminé de caractères d’une chaîne de texte à partir de la position que vous indiquez. -PHONETIC = PHONETIQUE ## Extrait les caractères phonétiques (furigana) d’une chaîne de texte. -PROPER = NOMPROPRE ## Met en majuscules la première lettre de chaque mot dans une chaîne textuelle. -REPLACE = REMPLACER ## Remplace des caractères dans un texte. -REPLACEB = REMPLACERB ## Remplace des caractères dans un texte. -REPT = REPT ## Répète un texte un certain nombre de fois. -RIGHT = DROITE ## Renvoie des caractères situés à l’extrême droite d’une chaîne de caractères. -RIGHTB = DROITEB ## Renvoie des caractères situés à l’extrême droite d’une chaîne de caractères. -SEARCH = CHERCHE ## Trouve un texte dans un autre texte (sans respecter la casse). -SEARCHB = CHERCHERB ## Trouve un texte dans un autre texte (sans respecter la casse). -SUBSTITUTE = SUBSTITUE ## Remplace l’ancien texte d’une chaîne de caractères par un nouveau. -T = T ## Convertit ses arguments en texte. -TEXT = TEXTE ## Convertit un nombre au format texte. -TRIM = SUPPRESPACE ## Supprime les espaces du texte. -UPPER = MAJUSCULE ## Convertit le texte en majuscules. -VALUE = CNUM ## Convertit un argument textuel en nombre +ENCODEURL = URLENCODAGE +FILTERXML = FILTRE.XML +WEBSERVICE = SERVICEWEB + +## +## Fonctions de compatibilité (Compatibility Functions) +## +BETADIST = LOI.BETA +BETAINV = BETA.INVERSE +BINOMDIST = LOI.BINOMIALE +CEILING = PLAFOND +CHIDIST = LOI.KHIDEUX +CHIINV = KHIDEUX.INVERSE +CHITEST = TEST.KHIDEUX +CONCATENATE = CONCATENER +CONFIDENCE = INTERVALLE.CONFIANCE +COVAR = COVARIANCE +CRITBINOM = CRITERE.LOI.BINOMIALE +EXPONDIST = LOI.EXPONENTIELLE +FDIST = LOI.F +FINV = INVERSE.LOI.F +FLOOR = PLANCHER +FORECAST = PREVISION +FTEST = TEST.F +GAMMADIST = LOI.GAMMA +GAMMAINV = LOI.GAMMA.INVERSE +HYPGEOMDIST = LOI.HYPERGEOMETRIQUE +LOGINV = LOI.LOGNORMALE.INVERSE +LOGNORMDIST = LOI.LOGNORMALE +MODE = MODE +NEGBINOMDIST = LOI.BINOMIALE.NEG +NORMDIST = LOI.NORMALE +NORMINV = LOI.NORMALE.INVERSE +NORMSDIST = LOI.NORMALE.STANDARD +NORMSINV = LOI.NORMALE.STANDARD.INVERSE +PERCENTILE = CENTILE +PERCENTRANK = RANG.POURCENTAGE +POISSON = LOI.POISSON +QUARTILE = QUARTILE +RANK = RANG +STDEV = ECARTYPE +STDEVP = ECARTYPEP +TDIST = LOI.STUDENT +TINV = LOI.STUDENT.INVERSE +TTEST = TEST.STUDENT +VAR = VAR +VARP = VAR.P +WEIBULL = LOI.WEIBULL +ZTEST = TEST.Z diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/hu/config b/PhpOffice/PhpSpreadsheet/Calculation/locale/hu/config old mode 100755 new mode 100644 index db61436..dc585d7 --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/hu/config +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/hu/config @@ -1,23 +1,20 @@ +############################################################ ## -## PhpSpreadsheet +## PhpSpreadsheet - locale settings ## +## Magyar (Hungarian) +## +############################################################ -ArgumentSeparator = ; - +ArgumentSeparator = ; ## -## (For future use) +## Error Codes ## -currencySymbol = Ft - - -## -## Excel Error Codes (For future use) -## -NULL = #NULLA! -DIV0 = #ZÉRÓOSZTÓ! -VALUE = #ÉRTÉK! -REF = #HIV! -NAME = #NÉV? -NUM = #SZÁM! -NA = #HIÁNYZIK +NULL = #NULLA! +DIV0 = #ZÉRÓOSZTÓ! +VALUE = #ÉRTÉK! +REF = #HIV! +NAME = #NÉV? +NUM = #SZÁM! +NA = #HIÁNYZIK diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/hu/functions b/PhpOffice/PhpSpreadsheet/Calculation/locale/hu/functions old mode 100755 new mode 100644 index 3adffeb..4a375ea --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/hu/functions +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/hu/functions @@ -1,416 +1,538 @@ +############################################################ ## -## PhpSpreadsheet -## -## Data in this file derived from https://www.excel-function-translation.com/ +## PhpSpreadsheet - function name translations ## +## Magyar (Hungarian) ## +############################################################ ## -## Add-in and Automation functions Bővítmények és automatizálási függvények +## Kockafüggvények (Cube Functions) ## -GETPIVOTDATA = KIMUTATÁSADATOT.VESZ ## A kimutatásokban tárolt adatok visszaadására használható. - +CUBEKPIMEMBER = KOCKA.FŐTELJMUT +CUBEMEMBER = KOCKA.TAG +CUBEMEMBERPROPERTY = KOCKA.TAG.TUL +CUBERANKEDMEMBER = KOCKA.HALM.ELEM +CUBESET = KOCKA.HALM +CUBESETCOUNT = KOCKA.HALM.DB +CUBEVALUE = KOCKA.ÉRTÉK ## -## Cube functions Kockafüggvények +## Adatbázis-kezelő függvények (Database Functions) ## -CUBEKPIMEMBER = KOCKA.FŐTELJMUT ## Egy fő teljesítménymutató (KPI) nevét, tulajdonságát és mértékegységét adja eredményül, a nevet és a tulajdonságot megjeleníti a cellában. A KPI-k számszerűsíthető mérési lehetőséget jelentenek – ilyen mutató például a havi bruttó nyereség vagy az egy alkalmazottra jutó negyedéves forgalom –, egy szervezet teljesítményének nyomonkövetésére használhatók. -CUBEMEMBER = KOCKA.TAG ## Kockahierachia tagját vagy rekordját adja eredményül. Ellenőrizhető vele, hogy szerepel-e a kockában az adott tag vagy rekord. -CUBEMEMBERPROPERTY = KOCKA.TAG.TUL ## A kocka egyik tagtulajdonságának értékét adja eredményül. Használatával ellenőrizhető, hogy szerepel-e egy tagnév a kockában, eredménye pedig az erre a tagra vonatkozó, megadott tulajdonság. -CUBERANKEDMEMBER = KOCKA.HALM.ELEM ## Egy halmaz rangsor szerinti n-edik tagját adja eredményül. Használatával egy halmaz egy vagy több elemét kaphatja meg, például a legnagyobb teljesítményű üzletkötőt vagy a 10 legjobb tanulót. -CUBESET = KOCKA.HALM ## Számított tagok vagy rekordok halmazát adja eredményül, ehhez egy beállított kifejezést elküld a kiszolgálón található kockának, majd ezt a halmazt adja vissza a Microsoft Office Excel alkalmazásnak. -CUBESETCOUNT = KOCKA.HALM.DB ## Egy halmaz elemszámát adja eredményül. -CUBEVALUE = KOCKA.ÉRTÉK ## Kockából összesített értéket ad eredményül. - +DAVERAGE = AB.ÁTLAG +DCOUNT = AB.DARAB +DCOUNTA = AB.DARAB2 +DGET = AB.MEZŐ +DMAX = AB.MAX +DMIN = AB.MIN +DPRODUCT = AB.SZORZAT +DSTDEV = AB.SZÓRÁS +DSTDEVP = AB.SZÓRÁS2 +DSUM = AB.SZUM +DVAR = AB.VAR +DVARP = AB.VAR2 ## -## Database functions Adatbázis-kezelő függvények +## Dátumfüggvények (Date & Time Functions) ## -DAVERAGE = AB.ÁTLAG ## A kijelölt adatbáziselemek átlagát számítja ki. -DCOUNT = AB.DARAB ## Megszámolja, hogy az adatbázisban hány cella tartalmaz számokat. -DCOUNTA = AB.DARAB2 ## Megszámolja az adatbázisban lévő nem üres cellákat. -DGET = AB.MEZŐ ## Egy adatbázisból egyetlen olyan rekordot ad vissza, amely megfelel a megadott feltételeknek. -DMAX = AB.MAX ## A kiválasztott adatbáziselemek közül a legnagyobb értéket adja eredményül. -DMIN = AB.MIN ## A kijelölt adatbáziselemek közül a legkisebb értéket adja eredményül. -DPRODUCT = AB.SZORZAT ## Az adatbázis megadott feltételeknek eleget tevő rekordjaira összeszorozza a megadott mezőben található számértékeket, és eredményül ezt a szorzatot adja. -DSTDEV = AB.SZÓRÁS ## A kijelölt adatbáziselemek egy mintája alapján megbecsüli a szórást. -DSTDEVP = AB.SZÓRÁS2 ## A kijelölt adatbáziselemek teljes sokasága alapján kiszámítja a szórást. -DSUM = AB.SZUM ## Összeadja a feltételnek megfelelő adatbázisrekordok mezőoszlopában a számokat. -DVAR = AB.VAR ## A kijelölt adatbáziselemek mintája alapján becslést ad a szórásnégyzetre. -DVARP = AB.VAR2 ## A kijelölt adatbáziselemek teljes sokasága alapján kiszámítja a szórásnégyzetet. - +DATE = DÁTUM +DATEDIF = DÁTUMTÓLIG +DATESTRING = DÁTUMSZÖVEG +DATEVALUE = DÁTUMÉRTÉK +DAY = NAP +DAYS = NAPOK +DAYS360 = NAP360 +EDATE = KALK.DÁTUM +EOMONTH = HÓNAP.UTOLSÓ.NAP +HOUR = ÓRA +ISOWEEKNUM = ISO.HÉT.SZÁMA +MINUTE = PERCEK +MONTH = HÓNAP +NETWORKDAYS = ÖSSZ.MUNKANAP +NETWORKDAYS.INTL = ÖSSZ.MUNKANAP.INTL +NOW = MOST +SECOND = MPERC +THAIDAYOFWEEK = THAIHÉTNAPJA +THAIMONTHOFYEAR = THAIHÓNAP +THAIYEAR = THAIÉV +TIME = IDŐ +TIMEVALUE = IDŐÉRTÉK +TODAY = MA +WEEKDAY = HÉT.NAPJA +WEEKNUM = HÉT.SZÁMA +WORKDAY = KALK.MUNKANAP +WORKDAY.INTL = KALK.MUNKANAP.INTL +YEAR = ÉV +YEARFRAC = TÖRTÉV ## -## Date and time functions Dátumfüggvények +## Mérnöki függvények (Engineering Functions) ## -DATE = DÁTUM ## Adott dátum dátumértékét adja eredményül. -DATEVALUE = DÁTUMÉRTÉK ## Szövegként megadott dátumot dátumértékké alakít át. -DAY = NAP ## Dátumértéket a hónap egy napjává (0-31) alakít. -DAYS360 = NAP360 ## Két dátum közé eső napok számát számítja ki a 360 napos év alapján. -EDATE = EDATE ## Adott dátumnál adott számú hónappal korábbi vagy későbbi dátum dátumértékét adja eredményül. -EOMONTH = EOMONTH ## Adott dátumnál adott számú hónappal korábbi vagy későbbi hónap utolsó napjának dátumértékét adja eredményül. -HOUR = ÓRA ## Időértéket órákká alakít. -MINUTE = PERC ## Időértéket percekké alakít. -MONTH = HÓNAP ## Időértéket hónapokká alakít. -NETWORKDAYS = NETWORKDAYS ## Két dátum között a teljes munkanapok számát adja meg. -NOW = MOST ## A napi dátum dátumértékét és a pontos idő időértékét adja eredményül. -SECOND = MPERC ## Időértéket másodpercekké alakít át. -TIME = IDŐ ## Adott időpont időértékét adja meg. -TIMEVALUE = IDŐÉRTÉK ## Szövegként megadott időpontot időértékké alakít át. -TODAY = MA ## A napi dátum dátumértékét adja eredményül. -WEEKDAY = HÉT.NAPJA ## Dátumértéket a hét napjává alakítja át. -WEEKNUM = WEEKNUM ## Visszatérési értéke egy szám, amely azt mutatja meg, hogy a megadott dátum az év hányadik hetére esik. -WORKDAY = WORKDAY ## Adott dátumnál adott munkanappal korábbi vagy későbbi dátum dátumértékét adja eredményül. -YEAR = ÉV ## Sorszámot évvé alakít át. -YEARFRAC = YEARFRAC ## Az adott dátumok közötti teljes napok számát törtévként adja meg. - +BESSELI = BESSELI +BESSELJ = BESSELJ +BESSELK = BESSELK +BESSELY = BESSELY +BIN2DEC = BIN.DEC +BIN2HEX = BIN.HEX +BIN2OCT = BIN.OKT +BITAND = BIT.ÉS +BITLSHIFT = BIT.BAL.ELTOL +BITOR = BIT.VAGY +BITRSHIFT = BIT.JOBB.ELTOL +BITXOR = BIT.XVAGY +COMPLEX = KOMPLEX +CONVERT = KONVERTÁLÁS +DEC2BIN = DEC.BIN +DEC2HEX = DEC.HEX +DEC2OCT = DEC.OKT +DELTA = DELTA +ERF = HIBAF +ERF.PRECISE = HIBAF.PONTOS +ERFC = HIBAF.KOMPLEMENTER +ERFC.PRECISE = HIBAFKOMPLEMENTER.PONTOS +GESTEP = KÜSZÖBNÉL.NAGYOBB +HEX2BIN = HEX.BIN +HEX2DEC = HEX.DEC +HEX2OCT = HEX.OKT +IMABS = KÉPZ.ABSZ +IMAGINARY = KÉPZETES +IMARGUMENT = KÉPZ.ARGUMENT +IMCONJUGATE = KÉPZ.KONJUGÁLT +IMCOS = KÉPZ.COS +IMCOSH = KÉPZ.COSH +IMCOT = KÉPZ.COT +IMCSC = KÉPZ.CSC +IMCSCH = KÉPZ.CSCH +IMDIV = KÉPZ.HÁNYAD +IMEXP = KÉPZ.EXP +IMLN = KÉPZ.LN +IMLOG10 = KÉPZ.LOG10 +IMLOG2 = KÉPZ.LOG2 +IMPOWER = KÉPZ.HATV +IMPRODUCT = KÉPZ.SZORZAT +IMREAL = KÉPZ.VALÓS +IMSEC = KÉPZ.SEC +IMSECH = KÉPZ.SECH +IMSIN = KÉPZ.SIN +IMSINH = KÉPZ.SINH +IMSQRT = KÉPZ.GYÖK +IMSUB = KÉPZ.KÜL +IMSUM = KÉPZ.ÖSSZEG +IMTAN = KÉPZ.TAN +OCT2BIN = OKT.BIN +OCT2DEC = OKT.DEC +OCT2HEX = OKT.HEX ## -## Engineering functions Mérnöki függvények +## Pénzügyi függvények (Financial Functions) ## -BESSELI = BESSELI ## Az In(x) módosított Bessel-függvény értékét adja eredményül. -BESSELJ = BESSELJ ## A Jn(x) Bessel-függvény értékét adja eredményül. -BESSELK = BESSELK ## A Kn(x) módosított Bessel-függvény értékét adja eredményül. -BESSELY = BESSELY ## Az Yn(x) módosított Bessel-függvény értékét adja eredményül. -BIN2DEC = BIN2DEC ## Bináris számot decimálissá alakít át. -BIN2HEX = BIN2HEX ## Bináris számot hexadecimálissá alakít át. -BIN2OCT = BIN2OCT ## Bináris számot oktálissá alakít át. -COMPLEX = COMPLEX ## Valós és képzetes részből komplex számot képez. -CONVERT = CONVERT ## Mértékegységeket vált át. -DEC2BIN = DEC2BIN ## Decimális számot binárissá alakít át. -DEC2HEX = DEC2HEX ## Decimális számot hexadecimálissá alakít át. -DEC2OCT = DEC2OCT ## Decimális számot oktálissá alakít át. -DELTA = DELTA ## Azt vizsgálja, hogy két érték egyenlő-e. -ERF = ERF ## A hibafüggvény értékét adja eredményül. -ERFC = ERFC ## A kiegészített hibafüggvény értékét adja eredményül. -GESTEP = GESTEP ## Azt vizsgálja, hogy egy szám nagyobb-e adott küszöbértéknél. -HEX2BIN = HEX2BIN ## Hexadecimális számot binárissá alakít át. -HEX2DEC = HEX2DEC ## Hexadecimális számot decimálissá alakít át. -HEX2OCT = HEX2OCT ## Hexadecimális számot oktálissá alakít át. -IMABS = IMABS ## Komplex szám abszolút értékét (modulusát) adja eredményül. -IMAGINARY = IMAGINARY ## Komplex szám képzetes részét adja eredményül. -IMARGUMENT = IMARGUMENT ## A komplex szám radiánban kifejezett théta argumentumát adja eredményül. -IMCONJUGATE = IMCONJUGATE ## Komplex szám komplex konjugáltját adja eredményül. -IMCOS = IMCOS ## Komplex szám koszinuszát adja eredményül. -IMDIV = IMDIV ## Két komplex szám hányadosát adja eredményül. -IMEXP = IMEXP ## Az e szám komplex kitevőjű hatványát adja eredményül. -IMLN = IMLN ## Komplex szám természetes logaritmusát adja eredményül. -IMLOG10 = IMLOG10 ## Komplex szám tízes alapú logaritmusát adja eredményül. -IMLOG2 = IMLOG2 ## Komplex szám kettes alapú logaritmusát adja eredményül. -IMPOWER = IMPOWER ## Komplex szám hatványát adja eredményül. -IMPRODUCT = IMPRODUCT ## Komplex számok szorzatát adja eredményül. -IMREAL = IMREAL ## Komplex szám valós részét adja eredményül. -IMSIN = IMSIN ## Komplex szám szinuszát adja eredményül. -IMSQRT = IMSQRT ## Komplex szám négyzetgyökét adja eredményül. -IMSUB = IMSUB ## Két komplex szám különbségét adja eredményül. -IMSUM = IMSUM ## Komplex számok összegét adja eredményül. -OCT2BIN = OCT2BIN ## Oktális számot binárissá alakít át. -OCT2DEC = OCT2DEC ## Oktális számot decimálissá alakít át. -OCT2HEX = OCT2HEX ## Oktális számot hexadecimálissá alakít át. - +ACCRINT = IDŐSZAKI.KAMAT +ACCRINTM = LEJÁRATI.KAMAT +AMORDEGRC = ÉRTÉKCSÖKK.TÉNYEZŐVEL +AMORLINC = ÉRTÉKCSÖKK +COUPDAYBS = SZELVÉNYIDŐ.KEZDETTŐL +COUPDAYS = SZELVÉNYIDŐ +COUPDAYSNC = SZELVÉNYIDŐ.KIFIZETÉSTŐL +COUPNCD = ELSŐ.SZELVÉNYDÁTUM +COUPNUM = SZELVÉNYSZÁM +COUPPCD = UTOLSÓ.SZELVÉNYDÁTUM +CUMIPMT = ÖSSZES.KAMAT +CUMPRINC = ÖSSZES.TŐKERÉSZ +DB = KCS2 +DDB = KCSA +DISC = LESZÁM +DOLLARDE = FORINT.DEC +DOLLARFR = FORINT.TÖRT +DURATION = KAMATÉRZ +EFFECT = TÉNYLEGES +FV = JBÉ +FVSCHEDULE = KJÉ +INTRATE = KAMATRÁTA +IPMT = RRÉSZLET +IRR = BMR +ISPMT = LRÉSZLETKAMAT +MDURATION = MKAMATÉRZ +MIRR = MEGTÉRÜLÉS +NOMINAL = NÉVLEGES +NPER = PER.SZÁM +NPV = NMÉ +ODDFPRICE = ELTÉRŐ.EÁR +ODDFYIELD = ELTÉRŐ.EHOZAM +ODDLPRICE = ELTÉRŐ.UÁR +ODDLYIELD = ELTÉRŐ.UHOZAM +PDURATION = KAMATÉRZ.PER +PMT = RÉSZLET +PPMT = PRÉSZLET +PRICE = ÁR +PRICEDISC = ÁR.LESZÁM +PRICEMAT = ÁR.LEJÁRAT +PV = MÉ +RATE = RÁTA +RECEIVED = KAPOTT +RRI = MR +SLN = LCSA +SYD = ÉSZÖ +TBILLEQ = KJEGY.EGYENÉRT +TBILLPRICE = KJEGY.ÁR +TBILLYIELD = KJEGY.HOZAM +VDB = ÉCSRI +XIRR = XBMR +XNPV = XNJÉ +YIELD = HOZAM +YIELDDISC = HOZAM.LESZÁM +YIELDMAT = HOZAM.LEJÁRAT ## -## Financial functions Pénzügyi függvények +## Információs függvények (Information Functions) ## -ACCRINT = ACCRINT ## Periodikusan kamatozó értékpapír felszaporodott kamatát adja eredményül. -ACCRINTM = ACCRINTM ## Lejáratkor kamatozó értékpapír felszaporodott kamatát adja eredményül. -AMORDEGRC = AMORDEGRC ## Állóeszköz lineáris értékcsökkenését adja meg az egyes könyvelési időszakokra vonatkozóan. -AMORLINC = AMORLINC ## Az egyes könyvelési időszakokban az értékcsökkenést adja meg. -COUPDAYBS = COUPDAYBS ## A szelvényidőszak kezdetétől a kifizetés időpontjáig eltelt napokat adja vissza. -COUPDAYS = COUPDAYS ## A kifizetés időpontját magában foglaló szelvényperiódus hosszát adja meg napokban. -COUPDAYSNC = COUPDAYSNC ## A kifizetés időpontja és a legközelebbi szelvénydátum közötti napok számát adja meg. -COUPNCD = COUPNCD ## A kifizetést követő legelső szelvénydátumot adja eredményül. -COUPNUM = COUPNUM ## A kifizetés és a lejárat időpontja között kifizetendő szelvények számát adja eredményül. -COUPPCD = COUPPCD ## A kifizetés előtti utolsó szelvénydátumot adja eredményül. -CUMIPMT = CUMIPMT ## Két fizetési időszak között kifizetett kamat halmozott értékét adja eredményül. -CUMPRINC = CUMPRINC ## Két fizetési időszak között kifizetett részletek halmozott (kamatot nem tartalmazó) értékét adja eredményül. -DB = KCS2 ## Eszköz adott időszak alatti értékcsökkenését számítja ki a lineáris leírási modell alkalmazásával. -DDB = KCSA ## Eszköz értékcsökkenését számítja ki adott időszakra vonatkozóan a progresszív vagy egyéb megadott leírási modell alkalmazásával. -DISC = DISC ## Értékpapír leszámítolási kamatlábát adja eredményül. -DOLLARDE = DOLLARDE ## Egy közönséges törtként megadott számot tizedes törtté alakít át. -DOLLARFR = DOLLARFR ## Tizedes törtként megadott számot közönséges törtté alakít át. -DURATION = DURATION ## Periodikus kamatfizetésű értékpapír éves kamatérzékenységét adja eredményül. -EFFECT = EFFECT ## Az éves tényleges kamatláb értékét adja eredményül. -FV = JBÉ ## Befektetés jövőbeli értékét számítja ki. -FVSCHEDULE = FVSCHEDULE ## A kezdőtőke adott kamatlábak szerint megnövelt jövőbeli értékét adja eredményül. -INTRATE = INTRATE ## A lejáratig teljesen lekötött értékpapír kamatrátáját adja eredményül. -IPMT = RRÉSZLET ## Hiteltörlesztésen belül a tőketörlesztés nagyságát számítja ki adott időszakra. -IRR = BMR ## A befektetés belső megtérülési rátáját számítja ki pénzáramláshoz. -ISPMT = LRÉSZLETKAMAT ## A befektetés adott időszakára fizetett kamatot számítja ki. -MDURATION = MDURATION ## Egy 100 Ft névértékű értékpapír Macauley-féle módosított kamatérzékenységét adja eredményül. -MIRR = MEGTÉRÜLÉS ## A befektetés belső megtérülési rátáját számítja ki a költségek és a bevételek különböző kamatlába mellett. -NOMINAL = NOMINAL ## Az éves névleges kamatláb értékét adja eredményül. -NPER = PER.SZÁM ## A törlesztési időszakok számát adja meg. -NPV = NMÉ ## Befektetéshez kapcsolódó pénzáramlás nettó jelenértékét számítja ki ismert pénzáramlás és kamatláb mellett. -ODDFPRICE = ODDFPRICE ## Egy 100 Ft névértékű, a futamidő elején töredék-időszakos értékpapír árát adja eredményül. -ODDFYIELD = ODDFYIELD ## A futamidő elején töredék-időszakos értékpapír hozamát adja eredményül. -ODDLPRICE = ODDLPRICE ## Egy 100 Ft névértékű, a futamidő végén töredék-időszakos értékpapír árát adja eredményül. -ODDLYIELD = ODDLYIELD ## A futamidő végén töredék-időszakos értékpapír hozamát adja eredményül. -PMT = RÉSZLET ## A törlesztési időszakra vonatkozó törlesztési összeget számítja ki. -PPMT = PRÉSZLET ## Hiteltörlesztésen belül a tőketörlesztés nagyságát számítja ki adott időszakra. -PRICE = PRICE ## Egy 100 Ft névértékű, periodikusan kamatozó értékpapír árát adja eredményül. -PRICEDISC = PRICEDISC ## Egy 100 Ft névértékű leszámítolt értékpapír árát adja eredményül. -PRICEMAT = PRICEMAT ## Egy 100 Ft névértékű, a lejáratkor kamatozó értékpapír árát adja eredményül. -PV = MÉ ## Befektetés jelenlegi értékét számítja ki. -RATE = RÁTA ## Egy törlesztési időszakban az egy időszakra eső kamatláb nagyságát számítja ki. -RECEIVED = RECEIVED ## A lejáratig teljesen lekötött értékpapír lejáratakor kapott összegét adja eredményül. -SLN = LCSA ## Tárgyi eszköz egy időszakra eső amortizációját adja meg bruttó érték szerinti lineáris leírási kulcsot alkalmazva. -SYD = SYD ## Tárgyi eszköz értékcsökkenését számítja ki adott időszakra az évek számjegyösszegével dolgozó módszer alapján. -TBILLEQ = TBILLEQ ## Kincstárjegy kötvény-egyenértékű hozamát adja eredményül. -TBILLPRICE = TBILLPRICE ## Egy 100 Ft névértékű kincstárjegy árát adja eredményül. -TBILLYIELD = TBILLYIELD ## Kincstárjegy hozamát adja eredményül. -VDB = ÉCSRI ## Tárgyi eszköz amortizációját számítja ki megadott vagy részidőszakra a csökkenő egyenleg módszerének alkalmazásával. -XIRR = XIRR ## Ütemezett készpénzforgalom (cash flow) belső megtérülési kamatrátáját adja eredményül. -XNPV = XNPV ## Ütemezett készpénzforgalom (cash flow) nettó jelenlegi értékét adja eredményül. -YIELD = YIELD ## Periodikusan kamatozó értékpapír hozamát adja eredményül. -YIELDDISC = YIELDDISC ## Leszámítolt értékpapír (például kincstárjegy) éves hozamát adja eredményül. -YIELDMAT = YIELDMAT ## Lejáratkor kamatozó értékpapír éves hozamát adja eredményül. - +CELL = CELLA +ERROR.TYPE = HIBA.TÍPUS +INFO = INFÓ +ISBLANK = ÜRES +ISERR = HIBA.E +ISERROR = HIBÁS +ISEVEN = PÁROSE +ISFORMULA = KÉPLET +ISLOGICAL = LOGIKAI +ISNA = NINCS +ISNONTEXT = NEM.SZÖVEG +ISNUMBER = SZÁM +ISODD = PÁRATLANE +ISREF = HIVATKOZÁS +ISTEXT = SZÖVEG.E +N = S +NA = HIÁNYZIK +SHEET = LAP +SHEETS = LAPOK +TYPE = TÍPUS ## -## Information functions Információs függvények +## Logikai függvények (Logical Functions) ## -CELL = CELLA ## Egy cella formátumára, elhelyezkedésére vagy tartalmára vonatkozó adatokat ad eredményül. -ERROR.TYPE = HIBA.TÍPUS ## Egy hibatípushoz tartozó számot ad eredményül. -INFO = INFÓ ## A rendszer- és munkakörnyezet pillanatnyi állapotáról ad felvilágosítást. -ISBLANK = ÜRES ## Eredménye IGAZ, ha az érték üres. -ISERR = HIBA ## Eredménye IGAZ, ha az érték valamelyik hibaérték a #HIÁNYZIK kivételével. -ISERROR = HIBÁS ## Eredménye IGAZ, ha az érték valamelyik hibaérték. -ISEVEN = ISEVEN ## Eredménye IGAZ, ha argumentuma páros szám. -ISLOGICAL = LOGIKAI ## Eredménye IGAZ, ha az érték logikai érték. -ISNA = NINCS ## Eredménye IGAZ, ha az érték a #HIÁNYZIK hibaérték. -ISNONTEXT = NEM.SZÖVEG ## Eredménye IGAZ, ha az érték nem szöveg. -ISNUMBER = SZÁM ## Eredménye IGAZ, ha az érték szám. -ISODD = ISODD ## Eredménye IGAZ, ha argumentuma páratlan szám. -ISREF = HIVATKOZÁS ## Eredménye IGAZ, ha az érték hivatkozás. -ISTEXT = SZÖVEG.E ## Eredménye IGAZ, ha az érték szöveg. -N = N ## Argumentumának értékét számmá alakítja. -NA = HIÁNYZIK ## Eredménye a #HIÁNYZIK hibaérték. -TYPE = TÍPUS ## Érték adattípusának azonosítószámát adja eredményül. - +AND = ÉS +FALSE = HAMIS +IF = HA +IFERROR = HAHIBA +IFNA = HAHIÁNYZIK +IFS = HAELSŐIGAZ +NOT = NEM +OR = VAGY +SWITCH = ÁTVÁLT +TRUE = IGAZ +XOR = XVAGY ## -## Logical functions Logikai függvények +## Keresési és hivatkozási függvények (Lookup & Reference Functions) ## -AND = ÉS ## Eredménye IGAZ, ha minden argumentuma IGAZ. -FALSE = HAMIS ## A HAMIS logikai értéket adja eredményül. -IF = HA ## Logikai vizsgálatot hajt végre. -IFERROR = HAHIBA ## A megadott értéket adja vissza, ha egy képlet hibához vezet; más esetben a képlet értékét adja eredményül. -NOT = NEM ## Argumentuma értékének ellentettjét adja eredményül. -OR = VAGY ## Eredménye IGAZ, ha bármely argumentuma IGAZ. -TRUE = IGAZ ## Az IGAZ logikai értéket adja eredményül. - +ADDRESS = CÍM +AREAS = TERÜLET +CHOOSE = VÁLASZT +COLUMN = OSZLOP +COLUMNS = OSZLOPOK +FORMULATEXT = KÉPLETSZÖVEG +GETPIVOTDATA = KIMUTATÁSADATOT.VESZ +HLOOKUP = VKERES +HYPERLINK = HIPERHIVATKOZÁS +INDEX = INDEX +INDIRECT = INDIREKT +LOOKUP = KERES +MATCH = HOL.VAN +OFFSET = ELTOLÁS +ROW = SOR +ROWS = SOROK +RTD = VIA +TRANSPOSE = TRANSZPONÁLÁS +VLOOKUP = FKERES +*RC = SO ## -## Lookup and reference functions Keresési és hivatkozási függvények +## Matematikai és trigonometrikus függvények (Math & Trig Functions) ## -ADDRESS = CÍM ## A munkalap egy cellájára való hivatkozást adja szövegként eredményül. -AREAS = TERÜLET ## Hivatkozásban a területek számát adja eredményül. -CHOOSE = VÁLASZT ## Értékek listájából választ ki egy elemet. -COLUMN = OSZLOP ## Egy hivatkozás oszlopszámát adja eredményül. -COLUMNS = OSZLOPOK ## A hivatkozásban található oszlopok számát adja eredményül. -HLOOKUP = VKERES ## A megadott tömb felső sorában adott értékű elemet keres, és a megtalált elem oszlopából adott sorban elhelyezkedő értékkel tér vissza. -HYPERLINK = HIPERHIVATKOZÁS ## Hálózati kiszolgálón, intraneten vagy az interneten tárolt dokumentumot megnyitó parancsikont vagy hivatkozást hoz létre. -INDEX = INDEX ## Tömb- vagy hivatkozás indexszel megadott értékét adja vissza. -INDIRECT = INDIREKT ## Szöveg megadott hivatkozást ad eredményül. -LOOKUP = KERES ## Vektorban vagy tömbben keres meg értékeket. -MATCH = HOL.VAN ## Hivatkozásban vagy tömbben értékeket keres. -OFFSET = OFSZET ## Hivatkozás egy másik hivatkozástól számított távolságát adja meg. -ROW = SOR ## Egy hivatkozás sorának számát adja meg. -ROWS = SOROK ## Egy hivatkozás sorainak számát adja meg. -RTD = RTD ## Valós idejű adatokat keres vissza a COM automatizmust (automatizálás: Egy alkalmazás objektumaival való munka másik alkalmazásból vagy fejlesztőeszközből. A korábban OLE automatizmusnak nevezett automatizálás iparági szabvány, a Component Object Model (COM) szolgáltatása.) támogató programból. -TRANSPOSE = TRANSZPONÁLÁS ## Egy tömb transzponáltját adja eredményül. -VLOOKUP = FKERES ## A megadott tömb bal szélső oszlopában megkeres egy értéket, majd annak sora és a megadott oszlop metszéspontjában levő értéked adja eredményül. - +ABS = ABS +ACOS = ARCCOS +ACOSH = ACOSH +ACOT = ARCCOT +ACOTH = ARCCOTH +AGGREGATE = ÖSSZESÍT +ARABIC = ARAB +ASIN = ARCSIN +ASINH = ASINH +ATAN = ARCTAN +ATAN2 = ARCTAN2 +ATANH = ATANH +BASE = ALAP +CEILING.MATH = PLAFON.MAT +CEILING.PRECISE = PLAFON.PONTOS +COMBIN = KOMBINÁCIÓK +COMBINA = KOMBINÁCIÓK.ISM +COS = COS +COSH = COSH +COT = COT +COTH = COTH +CSC = CSC +CSCH = CSCH +DECIMAL = TIZEDES +DEGREES = FOK +ECMA.CEILING = ECMA.PLAFON +EVEN = PÁROS +EXP = KITEVŐ +FACT = FAKT +FACTDOUBLE = FAKTDUPLA +FLOOR.MATH = PADLÓ.MAT +FLOOR.PRECISE = PADLÓ.PONTOS +GCD = LKO +INT = INT +ISO.CEILING = ISO.PLAFON +LCM = LKT +LN = LN +LOG = LOG +LOG10 = LOG10 +MDETERM = MDETERM +MINVERSE = INVERZ.MÁTRIX +MMULT = MSZORZAT +MOD = MARADÉK +MROUND = TÖBBSZ.KEREKÍT +MULTINOMIAL = SZORHÁNYFAKT +MUNIT = MMÁTRIX +ODD = PÁRATLAN +PI = PI +POWER = HATVÁNY +PRODUCT = SZORZAT +QUOTIENT = KVÓCIENS +RADIANS = RADIÁN +RAND = VÉL +RANDBETWEEN = VÉLETLEN.KÖZÖTT +ROMAN = RÓMAI +ROUND = KEREKÍTÉS +ROUNDBAHTDOWN = BAHTKEREK.LE +ROUNDBAHTUP = BAHTKEREK.FEL +ROUNDDOWN = KEREK.LE +ROUNDUP = KEREK.FEL +SEC = SEC +SECH = SECH +SERIESSUM = SORÖSSZEG +SIGN = ELŐJEL +SIN = SIN +SINH = SINH +SQRT = GYÖK +SQRTPI = GYÖKPI +SUBTOTAL = RÉSZÖSSZEG +SUM = SZUM +SUMIF = SZUMHA +SUMIFS = SZUMHATÖBB +SUMPRODUCT = SZORZATÖSSZEG +SUMSQ = NÉGYZETÖSSZEG +SUMX2MY2 = SZUMX2BŐLY2 +SUMX2PY2 = SZUMX2MEGY2 +SUMXMY2 = SZUMXBŐLY2 +TAN = TAN +TANH = TANH +TRUNC = CSONK ## -## Math and trigonometry functions Matematikai és trigonometrikus függvények +## Statisztikai függvények (Statistical Functions) ## -ABS = ABS ## Egy szám abszolút értékét adja eredményül. -ACOS = ARCCOS ## Egy szám arkusz koszinuszát számítja ki. -ACOSH = ACOSH ## Egy szám inverz koszinusz hiperbolikuszát számítja ki. -ASIN = ARCSIN ## Egy szám arkusz szinuszát számítja ki. -ASINH = ASINH ## Egy szám inverz szinusz hiperbolikuszát számítja ki. -ATAN = ARCTAN ## Egy szám arkusz tangensét számítja ki. -ATAN2 = ARCTAN2 ## X és y koordináták alapján számítja ki az arkusz tangens értéket. -ATANH = ATANH ## A szám inverz tangens hiperbolikuszát számítja ki. -CEILING = PLAFON ## Egy számot a legközelebbi egészre vagy a pontosságként megadott érték legközelebb eső többszörösére kerekít. -COMBIN = KOMBINÁCIÓK ## Adott számú objektum összes lehetséges kombinációinak számát számítja ki. -COS = COS ## Egy szám koszinuszát számítja ki. -COSH = COSH ## Egy szám koszinusz hiperbolikuszát számítja ki. -DEGREES = FOK ## Radiánt fokká alakít át. -EVEN = PÁROS ## Egy számot a legközelebbi páros egész számra kerekít. -EXP = KITEVŐ ## Az e adott kitevőjű hatványát adja eredményül. -FACT = FAKT ## Egy szám faktoriálisát számítja ki. -FACTDOUBLE = FACTDOUBLE ## Egy szám dupla faktoriálisát adja eredményül. -FLOOR = PADLÓ ## Egy számot lefelé, a nulla felé kerekít. -GCD = GCD ## A legnagyobb közös osztót adja eredményül. -INT = INT ## Egy számot lefelé kerekít a legközelebbi egészre. -LCM = LCM ## A legkisebb közös többszöröst adja eredményül. -LN = LN ## Egy szám természetes logaritmusát számítja ki. -LOG = LOG ## Egy szám adott alapú logaritmusát számítja ki. -LOG10 = LOG10 ## Egy szám 10-es alapú logaritmusát számítja ki. -MDETERM = MDETERM ## Egy tömb mátrix-determinánsát számítja ki. -MINVERSE = INVERZ.MÁTRIX ## Egy tömb mátrix inverzét adja eredményül. -MMULT = MSZORZAT ## Két tömb mátrix-szorzatát adja meg. -MOD = MARADÉK ## Egy szám osztási maradékát adja eredményül. -MROUND = MROUND ## A kívánt többszörösére kerekített értéket ad eredményül. -MULTINOMIAL = MULTINOMIAL ## Számhalmaz multinomiálisát adja eredményül. -ODD = PÁRATLAN ## Egy számot a legközelebbi páratlan számra kerekít. -PI = PI ## A pi matematikai állandót adja vissza. -POWER = HATVÁNY ## Egy szám adott kitevőjű hatványát számítja ki. -PRODUCT = SZORZAT ## Argumentumai szorzatát számítja ki. -QUOTIENT = QUOTIENT ## Egy hányados egész részét adja eredményül. -RADIANS = RADIÁN ## Fokot radiánná alakít át. -RAND = VÉL ## Egy 0 és 1 közötti véletlen számot ad eredményül. -RANDBETWEEN = RANDBETWEEN ## Megadott számok közé eső véletlen számot állít elő. -ROMAN = RÓMAI ## Egy számot római számokkal kifejezve szövegként ad eredményül. -ROUND = KEREKÍTÉS ## Egy számot adott számú számjegyre kerekít. -ROUNDDOWN = KEREKÍTÉS.LE ## Egy számot lefelé, a nulla felé kerekít. -ROUNDUP = KEREKÍTÉS.FEL ## Egy számot felfelé, a nullától távolabbra kerekít. -SERIESSUM = SERIESSUM ## Hatványsor összegét adja eredményül. -SIGN = ELŐJEL ## Egy szám előjelét adja meg. -SIN = SIN ## Egy szög szinuszát számítja ki. -SINH = SINH ## Egy szám szinusz hiperbolikuszát számítja ki. -SQRT = GYÖK ## Egy szám pozitív négyzetgyökét számítja ki. -SQRTPI = SQRTPI ## A (szám*pi) négyzetgyökét adja eredményül. -SUBTOTAL = RÉSZÖSSZEG ## Lista vagy adatbázis részösszegét adja eredményül. -SUM = SZUM ## Összeadja az argumentumlistájában lévő számokat. -SUMIF = SZUMHA ## A megadott feltételeknek eleget tevő cellákban található értékeket adja össze. -SUMIFS = SZUMHATÖBB ## Több megadott feltételnek eleget tévő tartománycellák összegét adja eredményül. -SUMPRODUCT = SZORZATÖSSZEG ## A megfelelő tömbelemek szorzatának összegét számítja ki. -SUMSQ = NÉGYZETÖSSZEG ## Argumentumai négyzetének összegét számítja ki. -SUMX2MY2 = SZUMX2BŐLY2 ## Két tömb megfelelő elemei négyzetének különbségét összegzi. -SUMX2PY2 = SZUMX2MEGY2 ## Két tömb megfelelő elemei négyzetének összegét összegzi. -SUMXMY2 = SZUMXBŐLY2 ## Két tömb megfelelő elemei különbségének négyzetösszegét számítja ki. -TAN = TAN ## Egy szám tangensét számítja ki. -TANH = TANH ## Egy szám tangens hiperbolikuszát számítja ki. -TRUNC = CSONK ## Egy számot egésszé csonkít. - +AVEDEV = ÁTL.ELTÉRÉS +AVERAGE = ÁTLAG +AVERAGEA = ÁTLAGA +AVERAGEIF = ÁTLAGHA +AVERAGEIFS = ÁTLAGHATÖBB +BETA.DIST = BÉTA.ELOSZL +BETA.INV = BÉTA.INVERZ +BINOM.DIST = BINOM.ELOSZL +BINOM.DIST.RANGE = BINOM.ELOSZL.TART +BINOM.INV = BINOM.INVERZ +CHISQ.DIST = KHINÉGYZET.ELOSZLÁS +CHISQ.DIST.RT = KHINÉGYZET.ELOSZLÁS.JOBB +CHISQ.INV = KHINÉGYZET.INVERZ +CHISQ.INV.RT = KHINÉGYZET.INVERZ.JOBB +CHISQ.TEST = KHINÉGYZET.PRÓBA +CONFIDENCE.NORM = MEGBÍZHATÓSÁG.NORM +CONFIDENCE.T = MEGBÍZHATÓSÁG.T +CORREL = KORREL +COUNT = DARAB +COUNTA = DARAB2 +COUNTBLANK = DARABÜRES +COUNTIF = DARABTELI +COUNTIFS = DARABHATÖBB +COVARIANCE.P = KOVARIANCIA.S +COVARIANCE.S = KOVARIANCIA.M +DEVSQ = SQ +EXPON.DIST = EXP.ELOSZL +F.DIST = F.ELOSZL +F.DIST.RT = F.ELOSZLÁS.JOBB +F.INV = F.INVERZ +F.INV.RT = F.INVERZ.JOBB +F.TEST = F.PRÓB +FISHER = FISHER +FISHERINV = INVERZ.FISHER +FORECAST.ETS = ELŐREJELZÉS.ESIM +FORECAST.ETS.CONFINT = ELŐREJELZÉS.ESIM.KONFINT +FORECAST.ETS.SEASONALITY = ELŐREJELZÉS.ESIM.SZEZONALITÁS +FORECAST.ETS.STAT = ELŐREJELZÉS.ESIM.STAT +FORECAST.LINEAR = ELŐREJELZÉS.LINEÁRIS +FREQUENCY = GYAKORISÁG +GAMMA = GAMMA +GAMMA.DIST = GAMMA.ELOSZL +GAMMA.INV = GAMMA.INVERZ +GAMMALN = GAMMALN +GAMMALN.PRECISE = GAMMALN.PONTOS +GAUSS = GAUSS +GEOMEAN = MÉRTANI.KÖZÉP +GROWTH = NÖV +HARMEAN = HARM.KÖZÉP +HYPGEOM.DIST = HIPGEOM.ELOSZLÁS +INTERCEPT = METSZ +KURT = CSÚCSOSSÁG +LARGE = NAGY +LINEST = LIN.ILL +LOGEST = LOG.ILL +LOGNORM.DIST = LOGNORM.ELOSZLÁS +LOGNORM.INV = LOGNORM.INVERZ +MAX = MAX +MAXA = MAXA +MAXIFS = MAXHA +MEDIAN = MEDIÁN +MIN = MIN +MINA = MIN2 +MINIFS = MINHA +MODE.MULT = MÓDUSZ.TÖBB +MODE.SNGL = MÓDUSZ.EGY +NEGBINOM.DIST = NEGBINOM.ELOSZLÁS +NORM.DIST = NORM.ELOSZLÁS +NORM.INV = NORM.INVERZ +NORM.S.DIST = NORM.S.ELOSZLÁS +NORM.S.INV = NORM.S.INVERZ +PEARSON = PEARSON +PERCENTILE.EXC = PERCENTILIS.KIZÁR +PERCENTILE.INC = PERCENTILIS.TARTALMAZ +PERCENTRANK.EXC = SZÁZALÉKRANG.KIZÁR +PERCENTRANK.INC = SZÁZALÉKRANG.TARTALMAZ +PERMUT = VARIÁCIÓK +PERMUTATIONA = VARIÁCIÓK.ISM +PHI = FI +POISSON.DIST = POISSON.ELOSZLÁS +PROB = VALÓSZÍNŰSÉG +QUARTILE.EXC = KVARTILIS.KIZÁR +QUARTILE.INC = KVARTILIS.TARTALMAZ +RANK.AVG = RANG.ÁTL +RANK.EQ = RANG.EGY +RSQ = RNÉGYZET +SKEW = FERDESÉG +SKEW.P = FERDESÉG.P +SLOPE = MEREDEKSÉG +SMALL = KICSI +STANDARDIZE = NORMALIZÁLÁS +STDEV.P = SZÓR.S +STDEV.S = SZÓR.M +STDEVA = SZÓRÁSA +STDEVPA = SZÓRÁSPA +STEYX = STHIBAYX +T.DIST = T.ELOSZL +T.DIST.2T = T.ELOSZLÁS.2SZ +T.DIST.RT = T.ELOSZLÁS.JOBB +T.INV = T.INVERZ +T.INV.2T = T.INVERZ.2SZ +T.TEST = T.PRÓB +TREND = TREND +TRIMMEAN = RÉSZÁTLAG +VAR.P = VAR.S +VAR.S = VAR.M +VARA = VARA +VARPA = VARPA +WEIBULL.DIST = WEIBULL.ELOSZLÁS +Z.TEST = Z.PRÓB ## -## Statistical functions Statisztikai függvények +## Szövegműveletekhez használható függvények (Text Functions) ## -AVEDEV = ÁTL.ELTÉRÉS ## Az adatpontoknak átlaguktól való átlagos abszolút eltérését számítja ki. -AVERAGE = ÁTLAG ## Argumentumai átlagát számítja ki. -AVERAGEA = ÁTLAGA ## Argumentumai átlagát számítja ki (beleértve a számokat, szöveget és logikai értékeket). -AVERAGEIF = ÁTLAGHA ## A megadott feltételnek eleget tévő tartomány celláinak átlagát (számtani közepét) adja eredményül. -AVERAGEIFS = ÁTLAGHATÖBB ## A megadott feltételeknek eleget tévő cellák átlagát (számtani közepét) adja eredményül. -BETADIST = BÉTA.ELOSZLÁS ## A béta-eloszlás függvényt számítja ki. -BETAINV = INVERZ.BÉTA ## Adott béta-eloszláshoz kiszámítja a béta eloszlásfüggvény inverzét. -BINOMDIST = BINOM.ELOSZLÁS ## A diszkrét binomiális eloszlás valószínűségértékét számítja ki. -CHIDIST = KHI.ELOSZLÁS ## A khi-négyzet-eloszlás egyszélű valószínűségértékét számítja ki. -CHIINV = INVERZ.KHI ## A khi-négyzet-eloszlás egyszélű valószínűségértékének inverzét számítja ki. -CHITEST = KHI.PRÓBA ## Függetlenségvizsgálatot hajt végre. -CONFIDENCE = MEGBÍZHATÓSÁG ## Egy statisztikai sokaság várható értékének megbízhatósági intervallumát adja eredményül. -CORREL = KORREL ## Két adathalmaz korrelációs együtthatóját számítja ki. -COUNT = DARAB ## Megszámolja, hogy argumentumlistájában hány szám található. -COUNTA = DARAB2 ## Megszámolja, hogy argumentumlistájában hány érték található. -COUNTBLANK = DARABÜRES ## Egy tartományban összeszámolja az üres cellákat. -COUNTIF = DARABTELI ## Egy tartományban összeszámolja azokat a cellákat, amelyek eleget tesznek a megadott feltételnek. -COUNTIFS = DARABHATÖBB ## Egy tartományban összeszámolja azokat a cellákat, amelyek eleget tesznek több feltételnek. -COVAR = KOVAR ## A kovarianciát, azaz a páronkénti eltérések szorzatának átlagát számítja ki. -CRITBINOM = KRITBINOM ## Azt a legkisebb számot adja eredményül, amelyre a binomiális eloszlásfüggvény értéke nem kisebb egy adott határértéknél. -DEVSQ = SQ ## Az átlagtól való eltérések négyzetének összegét számítja ki. -EXPONDIST = EXP.ELOSZLÁS ## Az exponenciális eloszlás értékét számítja ki. -FDIST = F.ELOSZLÁS ## Az F-eloszlás értékét számítja ki. -FINV = INVERZ.F ## Az F-eloszlás inverzének értékét számítja ki. -FISHER = FISHER ## Fisher-transzformációt hajt végre. -FISHERINV = INVERZ.FISHER ## A Fisher-transzformáció inverzét hajtja végre. -FORECAST = ELŐREJELZÉS ## Az ismert értékek alapján lineáris regresszióval becsült értéket ad eredményül. -FREQUENCY = GYAKORISÁG ## A gyakorisági vagy empirikus eloszlás értékét függőleges tömbként adja eredményül. -FTEST = F.PRÓBA ## Az F-próba értékét adja eredményül. -GAMMADIST = GAMMA.ELOSZLÁS ## A gamma-eloszlás értékét számítja ki. -GAMMAINV = INVERZ.GAMMA ## A gamma-eloszlás eloszlásfüggvénye inverzének értékét számítja ki. -GAMMALN = GAMMALN ## A gamma-függvény természetes logaritmusát számítja ki. -GEOMEAN = MÉRTANI.KÖZÉP ## Argumentumai mértani középértékét számítja ki. -GROWTH = NÖV ## Exponenciális regresszió alapján ad becslést. -HARMEAN = HARM.KÖZÉP ## Argumentumai harmonikus átlagát számítja ki. -HYPGEOMDIST = HIPERGEOM.ELOSZLÁS ## A hipergeometriai eloszlás értékét számítja ki. -INTERCEPT = METSZ ## A regressziós egyenes y tengellyel való metszéspontját határozza meg. -KURT = CSÚCSOSSÁG ## Egy adathalmaz csúcsosságát számítja ki. -LARGE = NAGY ## Egy adathalmaz k-adik legnagyobb elemét adja eredményül. -LINEST = LIN.ILL ## A legkisebb négyzetek módszerével az adatokra illesztett egyenes paramétereit határozza meg. -LOGEST = LOG.ILL ## Az adatokra illesztett exponenciális görbe paramétereit határozza meg. -LOGINV = INVERZ.LOG.ELOSZLÁS ## A lognormális eloszlás inverzét számítja ki. -LOGNORMDIST = LOG.ELOSZLÁS ## A lognormális eloszlásfüggvény értékét számítja ki. -MAX = MAX ## Az argumentumai között szereplő legnagyobb számot adja meg. -MAXA = MAX2 ## Az argumentumai között szereplő legnagyobb számot adja meg (beleértve a számokat, szöveget és logikai értékeket). -MEDIAN = MEDIÁN ## Adott számhalmaz mediánját számítja ki. -MIN = MIN ## Az argumentumai között szereplő legkisebb számot adja meg. -MINA = MIN2 ## Az argumentumai között szereplő legkisebb számot adja meg, beleértve a számokat, szöveget és logikai értékeket. -MODE = MÓDUSZ ## Egy adathalmazból kiválasztja a leggyakrabban előforduló számot. -NEGBINOMDIST = NEGBINOM.ELOSZL ## A negatív binomiális eloszlás értékét számítja ki. -NORMDIST = NORM.ELOSZL ## A normális eloszlás értékét számítja ki. -NORMINV = INVERZ.NORM ## A normális eloszlás eloszlásfüggvénye inverzének értékét számítja ki. -NORMSDIST = STNORMELOSZL ## A standard normális eloszlás eloszlásfüggvényének értékét számítja ki. -NORMSINV = INVERZ.STNORM ## A standard normális eloszlás eloszlásfüggvénye inverzének értékét számítja ki. -PEARSON = PEARSON ## A Pearson-féle korrelációs együtthatót számítja ki. -PERCENTILE = PERCENTILIS ## Egy tartományban található értékek k-adik percentilisét, azaz százalékosztályát adja eredményül. -PERCENTRANK = SZÁZALÉKRANG ## Egy értéknek egy adathalmazon belül vett százalékos rangját (elhelyezkedését) számítja ki. -PERMUT = VARIÁCIÓK ## Adott számú objektum k-ad osztályú ismétlés nélküli variációinak számát számítja ki. -POISSON = POISSON ## A Poisson-eloszlás értékét számítja ki. -PROB = VALÓSZÍNŰSÉG ## Annak valószínűségét számítja ki, hogy adott értékek két határérték közé esnek. -QUARTILE = KVARTILIS ## Egy adathalmaz kvartilisét (negyedszintjét) számítja ki. -RANK = SORSZÁM ## Kiszámítja, hogy egy szám hányadik egy számsorozatban. -RSQ = RNÉGYZET ## Kiszámítja a Pearson-féle szorzatmomentum korrelációs együtthatójának négyzetét. -SKEW = FERDESÉG ## Egy eloszlás ferdeségét határozza meg. -SLOPE = MEREDEKSÉG ## Egy lineáris regressziós egyenes meredekségét számítja ki. -SMALL = KICSI ## Egy adathalmaz k-adik legkisebb elemét adja meg. -STANDARDIZE = NORMALIZÁLÁS ## Normalizált értéket ad eredményül. -STDEV = SZÓRÁS ## Egy statisztikai sokaság mintájából kiszámítja annak szórását. -STDEVA = SZÓRÁSA ## Egy statisztikai sokaság mintájából kiszámítja annak szórását (beleértve a számokat, szöveget és logikai értékeket). -STDEVP = SZÓRÁSP ## Egy statisztikai sokaság egészéből kiszámítja annak szórását. -STDEVPA = SZÓRÁSPA ## Egy statisztikai sokaság egészéből kiszámítja annak szórását (beleértve számokat, szöveget és logikai értékeket). -STEYX = STHIBAYX ## Egy regresszió esetén az egyes x-értékek alapján meghatározott y-értékek standard hibáját számítja ki. -TDIST = T.ELOSZLÁS ## A Student-féle t-eloszlás értékét számítja ki. -TINV = INVERZ.T ## A Student-féle t-eloszlás inverzét számítja ki. -TREND = TREND ## Lineáris trend értékeit számítja ki. -TRIMMEAN = RÉSZÁTLAG ## Egy adathalmaz középső részének átlagát számítja ki. -TTEST = T.PRÓBA ## A Student-féle t-próbához tartozó valószínűséget számítja ki. -VAR = VAR ## Minta alapján becslést ad a varianciára. -VARA = VARA ## Minta alapján becslést ad a varianciára (beleértve számokat, szöveget és logikai értékeket). -VARP = VARP ## Egy statisztikai sokaság varianciáját számítja ki. -VARPA = VARPA ## Egy statisztikai sokaság varianciáját számítja ki (beleértve számokat, szöveget és logikai értékeket). -WEIBULL = WEIBULL ## A Weibull-féle eloszlás értékét számítja ki. -ZTEST = Z.PRÓBA ## Az egyszélű z-próbával kapott valószínűségértéket számítja ki. - +BAHTTEXT = BAHTSZÖVEG +CHAR = KARAKTER +CLEAN = TISZTÍT +CODE = KÓD +CONCAT = FŰZ +DOLLAR = FORINT +EXACT = AZONOS +FIND = SZÖVEG.TALÁL +FIXED = FIX +ISTHAIDIGIT = ON.THAI.NUMERO +LEFT = BAL +LEN = HOSSZ +LOWER = KISBETŰ +MID = KÖZÉP +NUMBERSTRING = SZÁM.BETŰVEL +NUMBERVALUE = SZÁMÉRTÉK +PHONETIC = FONETIKUS +PROPER = TNÉV +REPLACE = CSERE +REPT = SOKSZOR +RIGHT = JOBB +SEARCH = SZÖVEG.KERES +SUBSTITUTE = HELYETTE +T = T +TEXT = SZÖVEG +TEXTJOIN = SZÖVEGÖSSZEFŰZÉS +THAIDIGIT = THAISZÁM +THAINUMSOUND = THAISZÁMHANG +THAINUMSTRING = THAISZÁMKAR +THAISTRINGLENGTH = THAIKARHOSSZ +TRIM = KIMETSZ +UNICHAR = UNIKARAKTER +UNICODE = UNICODE +UPPER = NAGYBETŰS +VALUE = ÉRTÉK ## -## Text functions Szövegműveletekhez használható függvények +## Webes függvények (Web Functions) ## -ASC = ASC ## Szöveg teljes szélességű (kétbájtos) latin és katakana karaktereit félszélességű (egybájtos) karakterekké alakítja. -BAHTTEXT = BAHTSZÖVEG ## Számot szöveggé alakít a ß (baht) pénznemformátum használatával. -CHAR = KARAKTER ## A kódszámmal meghatározott karaktert adja eredményül. -CLEAN = TISZTÍT ## A szövegből eltávolítja az összes nem nyomtatható karaktert. -CODE = KÓD ## Karaktersorozat első karakterének numerikus kódját adja eredményül. -CONCATENATE = ÖSSZEFŰZ ## Több szövegelemet egyetlen szöveges elemmé fűz össze. -DOLLAR = FORINT ## Számot pénznem formátumú szöveggé alakít át. -EXACT = AZONOS ## Megvizsgálja, hogy két érték azonos-e. -FIND = SZÖVEG.TALÁL ## Karaktersorozatot keres egy másikban (a kis- és nagybetűk megkülönböztetésével). -FINDB = SZÖVEG.TALÁL2 ## Karaktersorozatot keres egy másikban (a kis- és nagybetűk megkülönböztetésével). -FIXED = FIX ## Számot szöveges formátumúra alakít adott számú tizedesjegyre kerekítve. -JIS = JIS ## A félszélességű (egybájtos) latin és a katakana karaktereket teljes szélességű (kétbájtos) karakterekké alakítja. -LEFT = BAL ## Szöveg bal szélső karaktereit adja eredményül. -LEFTB = BAL2 ## Szöveg bal szélső karaktereit adja eredményül. -LEN = HOSSZ ## Szöveg karakterekben mért hosszát adja eredményül. -LENB = HOSSZ2 ## Szöveg karakterekben mért hosszát adja eredményül. -LOWER = KISBETŰ ## Szöveget kisbetűssé alakít át. -MID = KÖZÉP ## A szöveg adott pozíciójától kezdve megadott számú karaktert ad vissza eredményként. -MIDB = KÖZÉP2 ## A szöveg adott pozíciójától kezdve megadott számú karaktert ad vissza eredményként. -PHONETIC = PHONETIC ## Szöveg furigana (fonetikus) karaktereit adja vissza. -PROPER = TNÉV ## Szöveg minden szavának kezdőbetűjét nagybetűsre cseréli. -REPLACE = CSERE ## A szövegen belül karaktereket cserél. -REPLACEB = CSERE2 ## A szövegen belül karaktereket cserél. -REPT = SOKSZOR ## Megadott számú alkalommal megismétel egy szövegrészt. -RIGHT = JOBB ## Szövegrész jobb szélső karaktereit adja eredményül. -RIGHTB = JOBB2 ## Szövegrész jobb szélső karaktereit adja eredményül. -SEARCH = SZÖVEG.KERES ## Karaktersorozatot keres egy másikban (a kis- és nagybetűk között nem tesz különbséget). -SEARCHB = SZÖVEG.KERES2 ## Karaktersorozatot keres egy másikban (a kis- és nagybetűk között nem tesz különbséget). -SUBSTITUTE = HELYETTE ## Szövegben adott karaktereket másikra cserél. -T = T ## Argumentumát szöveggé alakítja át. -TEXT = SZÖVEG ## Számértéket alakít át adott számformátumú szöveggé. -TRIM = TRIM ## A szövegből eltávolítja a szóközöket. -UPPER = NAGYBETŰS ## Szöveget nagybetűssé alakít át. -VALUE = ÉRTÉK ## Szöveget számmá alakít át. +ENCODEURL = URL.KÓDOL +FILTERXML = XMLSZŰRÉS +WEBSERVICE = WEBSZOLGÁLTATÁS + +## +## Kompatibilitási függvények (Compatibility Functions) +## +BETADIST = BÉTA.ELOSZLÁS +BETAINV = INVERZ.BÉTA +BINOMDIST = BINOM.ELOSZLÁS +CEILING = PLAFON +CHIDIST = KHI.ELOSZLÁS +CHIINV = INVERZ.KHI +CHITEST = KHI.PRÓBA +CONCATENATE = ÖSSZEFŰZ +CONFIDENCE = MEGBÍZHATÓSÁG +COVAR = KOVAR +CRITBINOM = KRITBINOM +EXPONDIST = EXP.ELOSZLÁS +FDIST = F.ELOSZLÁS +FINV = INVERZ.F +FLOOR = PADLÓ +FORECAST = ELŐREJELZÉS +FTEST = F.PRÓBA +GAMMADIST = GAMMA.ELOSZLÁS +GAMMAINV = INVERZ.GAMMA +HYPGEOMDIST = HIPERGEOM.ELOSZLÁS +LOGINV = INVERZ.LOG.ELOSZLÁS +LOGNORMDIST = LOG.ELOSZLÁS +MODE = MÓDUSZ +NEGBINOMDIST = NEGBINOM.ELOSZL +NORMDIST = NORM.ELOSZL +NORMINV = INVERZ.NORM +NORMSDIST = STNORMELOSZL +NORMSINV = INVERZ.STNORM +PERCENTILE = PERCENTILIS +PERCENTRANK = SZÁZALÉKRANG +POISSON = POISSON +QUARTILE = KVARTILIS +RANK = SORSZÁM +STDEV = SZÓRÁS +STDEVP = SZÓRÁSP +TDIST = T.ELOSZLÁS +TINV = INVERZ.T +TTEST = T.PRÓBA +VAR = VAR +VARP = VARP +WEIBULL = WEIBULL +ZTEST = Z.PRÓBA diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/it/config b/PhpOffice/PhpSpreadsheet/Calculation/locale/it/config old mode 100755 new mode 100644 index 6cc013a..5c1e495 --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/it/config +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/it/config @@ -1,24 +1,20 @@ +############################################################ ## -## PhpSpreadsheet +## PhpSpreadsheet - locale settings ## +## Italiano (Italian) +## +############################################################ -ArgumentSeparator = ; - +ArgumentSeparator = ; ## -## (For future use) +## Error Codes ## -currencySymbol = € - - -## -## Excel Error Codes (For future use) - -## -NULL = #NULLO! -DIV0 = #DIV/0! -VALUE = #VALORE! -REF = #RIF! -NAME = #NOME? -NUM = #NUM! -NA = #N/D +NULL +DIV0 +VALUE = #VALORE! +REF = #RIF! +NAME = #NOME? +NUM +NA = #N/D diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/it/functions b/PhpOffice/PhpSpreadsheet/Calculation/locale/it/functions old mode 100755 new mode 100644 index 1901baf..c14ed85 --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/it/functions +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/it/functions @@ -1,416 +1,537 @@ +############################################################ ## -## PhpSpreadsheet -## -## Data in this file derived from https://www.excel-function-translation.com/ +## PhpSpreadsheet - function name translations ## +## Italiano (Italian) ## +############################################################ ## -## Add-in and Automation functions Funzioni di automazione e dei componenti aggiuntivi +## Funzioni cubo (Cube Functions) ## -GETPIVOTDATA = INFO.DATI.TAB.PIVOT ## Restituisce i dati memorizzati in un rapporto di tabella pivot - +CUBEKPIMEMBER = MEMBRO.KPI.CUBO +CUBEMEMBER = MEMBRO.CUBO +CUBEMEMBERPROPERTY = PROPRIETÀ.MEMBRO.CUBO +CUBERANKEDMEMBER = MEMBRO.CUBO.CON.RANGO +CUBESET = SET.CUBO +CUBESETCOUNT = CONTA.SET.CUBO +CUBEVALUE = VALORE.CUBO ## -## Cube functions Funzioni cubo +## Funzioni di database (Database Functions) ## -CUBEKPIMEMBER = MEMBRO.KPI.CUBO ## Restituisce il nome, la proprietà e la misura di un indicatore di prestazioni chiave (KPI) e visualizza il nome e la proprietà nella cella. Un KPI è una misura quantificabile, ad esempio l'utile lordo mensile o il fatturato trimestrale dei dipendenti, utilizzata per il monitoraggio delle prestazioni di un'organizzazione. -CUBEMEMBER = MEMBRO.CUBO ## Restituisce un membro o una tupla in una gerarchia di cubi. Consente di verificare l'esistenza del membro o della tupla nel cubo. -CUBEMEMBERPROPERTY = PROPRIETÀ.MEMBRO.CUBO ## Restituisce il valore di una proprietà di un membro del cubo. Consente di verificare l'esistenza di un nome di membro all'interno del cubo e di restituire la proprietà specificata per tale membro. -CUBERANKEDMEMBER = MEMBRO.CUBO.CON.RANGO ## Restituisce l'n-esimo membro o il membro ordinato di un insieme. Consente di restituire uno o più elementi in un insieme, ad esempio l'agente di vendita migliore o i primi 10 studenti. -CUBESET = SET.CUBO ## Definisce un insieme di tuple o membri calcolati mediante l'invio di un'espressione di insieme al cubo sul server. In questo modo l'insieme viene creato e restituito a Microsoft Office Excel. -CUBESETCOUNT = CONTA.SET.CUBO ## Restituisce il numero di elementi di un insieme. -CUBEVALUE = VALORE.CUBO ## Restituisce un valore aggregato da un cubo. - +DAVERAGE = DB.MEDIA +DCOUNT = DB.CONTA.NUMERI +DCOUNTA = DB.CONTA.VALORI +DGET = DB.VALORI +DMAX = DB.MAX +DMIN = DB.MIN +DPRODUCT = DB.PRODOTTO +DSTDEV = DB.DEV.ST +DSTDEVP = DB.DEV.ST.POP +DSUM = DB.SOMMA +DVAR = DB.VAR +DVARP = DB.VAR.POP ## -## Database functions Funzioni di database +## Funzioni data e ora (Date & Time Functions) ## -DAVERAGE = DB.MEDIA ## Restituisce la media di voci del database selezionate -DCOUNT = DB.CONTA.NUMERI ## Conta le celle di un database contenenti numeri -DCOUNTA = DB.CONTA.VALORI ## Conta le celle non vuote in un database -DGET = DB.VALORI ## Estrae da un database un singolo record che soddisfa i criteri specificati -DMAX = DB.MAX ## Restituisce il valore massimo dalle voci selezionate in un database -DMIN = DB.MIN ## Restituisce il valore minimo dalle voci di un database selezionate -DPRODUCT = DB.PRODOTTO ## Moltiplica i valori in un determinato campo di record che soddisfano i criteri del database -DSTDEV = DB.DEV.ST ## Restituisce una stima della deviazione standard sulla base di un campione di voci di un database selezionate -DSTDEVP = DB.DEV.ST.POP ## Calcola la deviazione standard sulla base di tutte le voci di un database selezionate -DSUM = DB.SOMMA ## Aggiunge i numeri nel campo colonna di record del database che soddisfa determinati criteri -DVAR = DB.VAR ## Restituisce una stima della varianza sulla base di un campione da voci di un database selezionate -DVARP = DB.VAR.POP ## Calcola la varianza sulla base di tutte le voci di un database selezionate - +DATE = DATA +DATEDIF = DATA.DIFF +DATESTRING = DATA.STRINGA +DATEVALUE = DATA.VALORE +DAY = GIORNO +DAYS = GIORNI +DAYS360 = GIORNO360 +EDATE = DATA.MESE +EOMONTH = FINE.MESE +HOUR = ORA +ISOWEEKNUM = NUM.SETTIMANA.ISO +MINUTE = MINUTO +MONTH = MESE +NETWORKDAYS = GIORNI.LAVORATIVI.TOT +NETWORKDAYS.INTL = GIORNI.LAVORATIVI.TOT.INTL +NOW = ADESSO +SECOND = SECONDO +THAIDAYOFWEEK = THAIGIORNODELLASETTIMANA +THAIMONTHOFYEAR = THAIMESEDELLANNO +THAIYEAR = THAIANNO +TIME = ORARIO +TIMEVALUE = ORARIO.VALORE +TODAY = OGGI +WEEKDAY = GIORNO.SETTIMANA +WEEKNUM = NUM.SETTIMANA +WORKDAY = GIORNO.LAVORATIVO +WORKDAY.INTL = GIORNO.LAVORATIVO.INTL +YEAR = ANNO +YEARFRAC = FRAZIONE.ANNO ## -## Date and time functions Funzioni data e ora +## Funzioni ingegneristiche (Engineering Functions) ## -DATE = DATA ## Restituisce il numero seriale di una determinata data -DATEVALUE = DATA.VALORE ## Converte una data sotto forma di testo in un numero seriale -DAY = GIORNO ## Converte un numero seriale in un giorno del mese -DAYS360 = GIORNO360 ## Calcola il numero di giorni compreso tra due date basandosi su un anno di 360 giorni -EDATE = DATA.MESE ## Restituisce il numero seriale della data che rappresenta il numero di mesi prima o dopo la data di inizio -EOMONTH = FINE.MESE ## Restituisce il numero seriale dell'ultimo giorno del mese, prima o dopo un determinato numero di mesi -HOUR = ORA ## Converte un numero seriale in un'ora -MINUTE = MINUTO ## Converte un numero seriale in un minuto -MONTH = MESE ## Converte un numero seriale in un mese -NETWORKDAYS = GIORNI.LAVORATIVI.TOT ## Restituisce il numero di tutti i giorni lavorativi compresi fra due date -NOW = ADESSO ## Restituisce il numero seriale della data e dell'ora corrente -SECOND = SECONDO ## Converte un numero seriale in un secondo -TIME = ORARIO ## Restituisce il numero seriale di una determinata ora -TIMEVALUE = ORARIO.VALORE ## Converte un orario in forma di testo in un numero seriale -TODAY = OGGI ## Restituisce il numero seriale relativo alla data odierna -WEEKDAY = GIORNO.SETTIMANA ## Converte un numero seriale in un giorno della settimana -WEEKNUM = NUM.SETTIMANA ## Converte un numero seriale in un numero che rappresenta la posizione numerica di una settimana nell'anno -WORKDAY = GIORNO.LAVORATIVO ## Restituisce il numero della data prima o dopo un determinato numero di giorni lavorativi -YEAR = ANNO ## Converte un numero seriale in un anno -YEARFRAC = FRAZIONE.ANNO ## Restituisce la frazione dell'anno che rappresenta il numero dei giorni compresi tra una data_ iniziale e una data_finale - +BESSELI = BESSEL.I +BESSELJ = BESSEL.J +BESSELK = BESSEL.K +BESSELY = BESSEL.Y +BIN2DEC = BINARIO.DECIMALE +BIN2HEX = BINARIO.HEX +BIN2OCT = BINARIO.OCT +BITAND = BITAND +BITLSHIFT = BIT.SPOSTA.SX +BITOR = BITOR +BITRSHIFT = BIT.SPOSTA.DX +BITXOR = BITXOR +COMPLEX = COMPLESSO +CONVERT = CONVERTI +DEC2BIN = DECIMALE.BINARIO +DEC2HEX = DECIMALE.HEX +DEC2OCT = DECIMALE.OCT +DELTA = DELTA +ERF = FUNZ.ERRORE +ERF.PRECISE = FUNZ.ERRORE.PRECISA +ERFC = FUNZ.ERRORE.COMP +ERFC.PRECISE = FUNZ.ERRORE.COMP.PRECISA +GESTEP = SOGLIA +HEX2BIN = HEX.BINARIO +HEX2DEC = HEX.DECIMALE +HEX2OCT = HEX.OCT +IMABS = COMP.MODULO +IMAGINARY = COMP.IMMAGINARIO +IMARGUMENT = COMP.ARGOMENTO +IMCONJUGATE = COMP.CONIUGATO +IMCOS = COMP.COS +IMCOSH = COMP.COSH +IMCOT = COMP.COT +IMCSC = COMP.CSC +IMCSCH = COMP.CSCH +IMDIV = COMP.DIV +IMEXP = COMP.EXP +IMLN = COMP.LN +IMLOG10 = COMP.LOG10 +IMLOG2 = COMP.LOG2 +IMPOWER = COMP.POTENZA +IMPRODUCT = COMP.PRODOTTO +IMREAL = COMP.PARTE.REALE +IMSEC = COMP.SEC +IMSECH = COMP.SECH +IMSIN = COMP.SEN +IMSINH = COMP.SENH +IMSQRT = COMP.RADQ +IMSUB = COMP.DIFF +IMSUM = COMP.SOMMA +IMTAN = COMP.TAN +OCT2BIN = OCT.BINARIO +OCT2DEC = OCT.DECIMALE +OCT2HEX = OCT.HEX ## -## Engineering functions Funzioni ingegneristiche +## Funzioni finanziarie (Financial Functions) ## -BESSELI = BESSEL.I ## Restituisce la funzione di Bessel modificata In(x) -BESSELJ = BESSEL.J ## Restituisce la funzione di Bessel Jn(x) -BESSELK = BESSEL.K ## Restituisce la funzione di Bessel modificata Kn(x) -BESSELY = BESSEL.Y ## Restituisce la funzione di Bessel Yn(x) -BIN2DEC = BINARIO.DECIMALE ## Converte un numero binario in decimale -BIN2HEX = BINARIO.HEX ## Converte un numero binario in esadecimale -BIN2OCT = BINARIO.OCT ## Converte un numero binario in ottale -COMPLEX = COMPLESSO ## Converte i coefficienti reali e immaginari in numeri complessi -CONVERT = CONVERTI ## Converte un numero da un sistema di misura in un altro -DEC2BIN = DECIMALE.BINARIO ## Converte un numero decimale in binario -DEC2HEX = DECIMALE.HEX ## Converte un numero decimale in esadecimale -DEC2OCT = DECIMALE.OCT ## Converte un numero decimale in ottale -DELTA = DELTA ## Verifica se due valori sono uguali -ERF = FUNZ.ERRORE ## Restituisce la funzione di errore -ERFC = FUNZ.ERRORE.COMP ## Restituisce la funzione di errore complementare -GESTEP = SOGLIA ## Verifica se un numero è maggiore del valore di soglia -HEX2BIN = HEX.BINARIO ## Converte un numero esadecimale in binario -HEX2DEC = HEX.DECIMALE ## Converte un numero esadecimale in decimale -HEX2OCT = HEX.OCT ## Converte un numero esadecimale in ottale -IMABS = COMP.MODULO ## Restituisce il valore assoluto (modulo) di un numero complesso -IMAGINARY = COMP.IMMAGINARIO ## Restituisce il coefficiente immaginario di un numero complesso -IMARGUMENT = COMP.ARGOMENTO ## Restituisce l'argomento theta, un angolo espresso in radianti -IMCONJUGATE = COMP.CONIUGATO ## Restituisce il complesso coniugato del numero complesso -IMCOS = COMP.COS ## Restituisce il coseno di un numero complesso -IMDIV = COMP.DIV ## Restituisce il quoziente di due numeri complessi -IMEXP = COMP.EXP ## Restituisce il valore esponenziale di un numero complesso -IMLN = COMP.LN ## Restituisce il logaritmo naturale di un numero complesso -IMLOG10 = COMP.LOG10 ## Restituisce il logaritmo in base 10 di un numero complesso -IMLOG2 = COMP.LOG2 ## Restituisce un logaritmo in base 2 di un numero complesso -IMPOWER = COMP.POTENZA ## Restituisce il numero complesso elevato a una potenza intera -IMPRODUCT = COMP.PRODOTTO ## Restituisce il prodotto di numeri complessi compresi tra 2 e 29 -IMREAL = COMP.PARTE.REALE ## Restituisce il coefficiente reale di un numero complesso -IMSIN = COMP.SEN ## Restituisce il seno di un numero complesso -IMSQRT = COMP.RADQ ## Restituisce la radice quadrata di un numero complesso -IMSUB = COMP.DIFF ## Restituisce la differenza fra due numeri complessi -IMSUM = COMP.SOMMA ## Restituisce la somma di numeri complessi -OCT2BIN = OCT.BINARIO ## Converte un numero ottale in binario -OCT2DEC = OCT.DECIMALE ## Converte un numero ottale in decimale -OCT2HEX = OCT.HEX ## Converte un numero ottale in esadecimale - +ACCRINT = INT.MATURATO.PER +ACCRINTM = INT.MATURATO.SCAD +AMORDEGRC = AMMORT.DEGR +AMORLINC = AMMORT.PER +COUPDAYBS = GIORNI.CED.INIZ.LIQ +COUPDAYS = GIORNI.CED +COUPDAYSNC = GIORNI.CED.NUOVA +COUPNCD = DATA.CED.SUCC +COUPNUM = NUM.CED +COUPPCD = DATA.CED.PREC +CUMIPMT = INT.CUMUL +CUMPRINC = CAP.CUM +DB = AMMORT.FISSO +DDB = AMMORT +DISC = TASSO.SCONTO +DOLLARDE = VALUTA.DEC +DOLLARFR = VALUTA.FRAZ +DURATION = DURATA +EFFECT = EFFETTIVO +FV = VAL.FUT +FVSCHEDULE = VAL.FUT.CAPITALE +INTRATE = TASSO.INT +IPMT = INTERESSI +IRR = TIR.COST +ISPMT = INTERESSE.RATA +MDURATION = DURATA.M +MIRR = TIR.VAR +NOMINAL = NOMINALE +NPER = NUM.RATE +NPV = VAN +ODDFPRICE = PREZZO.PRIMO.IRR +ODDFYIELD = REND.PRIMO.IRR +ODDLPRICE = PREZZO.ULTIMO.IRR +ODDLYIELD = REND.ULTIMO.IRR +PDURATION = DURATA.P +PMT = RATA +PPMT = P.RATA +PRICE = PREZZO +PRICEDISC = PREZZO.SCONT +PRICEMAT = PREZZO.SCAD +PV = VA +RATE = TASSO +RECEIVED = RICEV.SCAD +RRI = RIT.INVEST.EFFETT +SLN = AMMORT.COST +SYD = AMMORT.ANNUO +TBILLEQ = BOT.EQUIV +TBILLPRICE = BOT.PREZZO +TBILLYIELD = BOT.REND +VDB = AMMORT.VAR +XIRR = TIR.X +XNPV = VAN.X +YIELD = REND +YIELDDISC = REND.TITOLI.SCONT +YIELDMAT = REND.SCAD ## -## Financial functions Funzioni finanziarie +## Funzioni relative alle informazioni (Information Functions) ## -ACCRINT = INT.MATURATO.PER ## Restituisce l'interesse maturato di un titolo che paga interessi periodici -ACCRINTM = INT.MATURATO.SCAD ## Restituisce l'interesse maturato di un titolo che paga interessi alla scadenza -AMORDEGRC = AMMORT.DEGR ## Restituisce l'ammortamento per ogni periodo contabile utilizzando un coefficiente di ammortamento -AMORLINC = AMMORT.PER ## Restituisce l'ammortamento per ogni periodo contabile -COUPDAYBS = GIORNI.CED.INIZ.LIQ ## Restituisce il numero dei giorni che vanno dall'inizio del periodo di durata della cedola alla data di liquidazione -COUPDAYS = GIORNI.CED ## Restituisce il numero dei giorni relativi al periodo della cedola che contiene la data di liquidazione -COUPDAYSNC = GIORNI.CED.NUOVA ## Restituisce il numero di giorni che vanno dalla data di liquidazione alla data della cedola successiva -COUPNCD = DATA.CED.SUCC ## Restituisce un numero che rappresenta la data della cedola successiva alla data di liquidazione -COUPNUM = NUM.CED ## Restituisce il numero di cedole pagabili fra la data di liquidazione e la data di scadenza -COUPPCD = DATA.CED.PREC ## Restituisce un numero che rappresenta la data della cedola precedente alla data di liquidazione -CUMIPMT = INT.CUMUL ## Restituisce l'interesse cumulativo pagato fra due periodi -CUMPRINC = CAP.CUM ## Restituisce il capitale cumulativo pagato per estinguere un debito fra due periodi -DB = DB ## Restituisce l'ammortamento di un bene per un periodo specificato utilizzando il metodo di ammortamento a quote fisse decrescenti -DDB = AMMORT ## Restituisce l'ammortamento di un bene per un periodo specificato utilizzando il metodo di ammortamento a doppie quote decrescenti o altri metodi specificati -DISC = TASSO.SCONTO ## Restituisce il tasso di sconto per un titolo -DOLLARDE = VALUTA.DEC ## Converte un prezzo valuta, espresso come frazione, in prezzo valuta, espresso come numero decimale -DOLLARFR = VALUTA.FRAZ ## Converte un prezzo valuta, espresso come numero decimale, in prezzo valuta, espresso come frazione -DURATION = DURATA ## Restituisce la durata annuale di un titolo con i pagamenti di interesse periodico -EFFECT = EFFETTIVO ## Restituisce l'effettivo tasso di interesse annuo -FV = VAL.FUT ## Restituisce il valore futuro di un investimento -FVSCHEDULE = VAL.FUT.CAPITALE ## Restituisce il valore futuro di un capitale iniziale dopo aver applicato una serie di tassi di interesse composti -INTRATE = TASSO.INT ## Restituisce il tasso di interesse per un titolo interamente investito -IPMT = INTERESSI ## Restituisce il valore degli interessi per un investimento relativo a un periodo specifico -IRR = TIR.COST ## Restituisce il tasso di rendimento interno per una serie di flussi di cassa -ISPMT = INTERESSE.RATA ## Calcola l'interesse di un investimento pagato durante un periodo specifico -MDURATION = DURATA.M ## Restituisce la durata Macauley modificata per un titolo con un valore presunto di € 100 -MIRR = TIR.VAR ## Restituisce il tasso di rendimento interno in cui i flussi di cassa positivi e negativi sono finanziati a tassi differenti -NOMINAL = NOMINALE ## Restituisce il tasso di interesse nominale annuale -NPER = NUM.RATE ## Restituisce un numero di periodi relativi a un investimento -NPV = VAN ## Restituisce il valore attuale netto di un investimento basato su una serie di flussi di cassa periodici e sul tasso di sconto -ODDFPRICE = PREZZO.PRIMO.IRR ## Restituisce il prezzo di un titolo dal valore nominale di € 100 avente il primo periodo di durata irregolare -ODDFYIELD = REND.PRIMO.IRR ## Restituisce il rendimento di un titolo avente il primo periodo di durata irregolare -ODDLPRICE = PREZZO.ULTIMO.IRR ## Restituisce il prezzo di un titolo dal valore nominale di € 100 avente l'ultimo periodo di durata irregolare -ODDLYIELD = REND.ULTIMO.IRR ## Restituisce il rendimento di un titolo avente l'ultimo periodo di durata irregolare -PMT = RATA ## Restituisce il pagamento periodico di una rendita annua -PPMT = P.RATA ## Restituisce il pagamento sul capitale di un investimento per un dato periodo -PRICE = PREZZO ## Restituisce il prezzo di un titolo dal valore nominale di € 100 che paga interessi periodici -PRICEDISC = PREZZO.SCONT ## Restituisce il prezzo di un titolo scontato dal valore nominale di € 100 -PRICEMAT = PREZZO.SCAD ## Restituisce il prezzo di un titolo dal valore nominale di € 100 che paga gli interessi alla scadenza -PV = VA ## Restituisce il valore attuale di un investimento -RATE = TASSO ## Restituisce il tasso di interesse per un periodo di un'annualità -RECEIVED = RICEV.SCAD ## Restituisce l'ammontare ricevuto alla scadenza di un titolo interamente investito -SLN = AMMORT.COST ## Restituisce l'ammortamento a quote costanti di un bene per un singolo periodo -SYD = AMMORT.ANNUO ## Restituisce l'ammortamento a somma degli anni di un bene per un periodo specificato -TBILLEQ = BOT.EQUIV ## Restituisce il rendimento equivalente ad un'obbligazione per un Buono ordinario del Tesoro -TBILLPRICE = BOT.PREZZO ## Restituisce il prezzo di un Buono del Tesoro dal valore nominale di € 100 -TBILLYIELD = BOT.REND ## Restituisce il rendimento di un Buono del Tesoro -VDB = AMMORT.VAR ## Restituisce l'ammortamento di un bene per un periodo specificato o parziale utilizzando il metodo a doppie quote proporzionali ai valori residui -XIRR = TIR.X ## Restituisce il tasso di rendimento interno di un impiego di flussi di cassa -XNPV = VAN.X ## Restituisce il valore attuale netto di un impiego di flussi di cassa non necessariamente periodici -YIELD = REND ## Restituisce il rendimento di un titolo che frutta interessi periodici -YIELDDISC = REND.TITOLI.SCONT ## Restituisce il rendimento annuale di un titolo scontato, ad esempio un Buono del Tesoro -YIELDMAT = REND.SCAD ## Restituisce il rendimento annuo di un titolo che paga interessi alla scadenza - +CELL = CELLA +ERROR.TYPE = ERRORE.TIPO +INFO = AMBIENTE.INFO +ISBLANK = VAL.VUOTO +ISERR = VAL.ERR +ISERROR = VAL.ERRORE +ISEVEN = VAL.PARI +ISFORMULA = VAL.FORMULA +ISLOGICAL = VAL.LOGICO +ISNA = VAL.NON.DISP +ISNONTEXT = VAL.NON.TESTO +ISNUMBER = VAL.NUMERO +ISODD = VAL.DISPARI +ISREF = VAL.RIF +ISTEXT = VAL.TESTO +N = NUM +NA = NON.DISP +SHEET = FOGLIO +SHEETS = FOGLI +TYPE = TIPO ## -## Information functions Funzioni relative alle informazioni +## Funzioni logiche (Logical Functions) ## -CELL = CELLA ## Restituisce le informazioni sulla formattazione, la posizione o i contenuti di una cella -ERROR.TYPE = ERRORE.TIPO ## Restituisce un numero che corrisponde a un tipo di errore -INFO = INFO ## Restituisce le informazioni sull'ambiente operativo corrente -ISBLANK = VAL.VUOTO ## Restituisce VERO se il valore è vuoto -ISERR = VAL.ERR ## Restituisce VERO se il valore è un valore di errore qualsiasi tranne #N/D -ISERROR = VAL.ERRORE ## Restituisce VERO se il valore è un valore di errore qualsiasi -ISEVEN = VAL.PARI ## Restituisce VERO se il numero è pari -ISLOGICAL = VAL.LOGICO ## Restituisce VERO se il valore è un valore logico -ISNA = VAL.NON.DISP ## Restituisce VERO se il valore è un valore di errore #N/D -ISNONTEXT = VAL.NON.TESTO ## Restituisce VERO se il valore non è in formato testo -ISNUMBER = VAL.NUMERO ## Restituisce VERO se il valore è un numero -ISODD = VAL.DISPARI ## Restituisce VERO se il numero è dispari -ISREF = VAL.RIF ## Restituisce VERO se il valore è un riferimento -ISTEXT = VAL.TESTO ## Restituisce VERO se il valore è in formato testo -N = NUM ## Restituisce un valore convertito in numero -NA = NON.DISP ## Restituisce il valore di errore #N/D -TYPE = TIPO ## Restituisce un numero che indica il tipo di dati relativi a un valore - +AND = E +FALSE = FALSO +IF = SE +IFERROR = SE.ERRORE +IFNA = SE.NON.DISP. +IFS = PIÙ.SE +NOT = NON +OR = O +SWITCH = SWITCH +TRUE = VERO +XOR = XOR ## -## Logical functions Funzioni logiche +## Funzioni di ricerca e di riferimento (Lookup & Reference Functions) ## -AND = E ## Restituisce VERO se tutti gli argomenti sono VERO -FALSE = FALSO ## Restituisce il valore logico FALSO -IF = SE ## Specifica un test logico da eseguire -IFERROR = SE.ERRORE ## Restituisce un valore specificato se una formula fornisce un errore come risultato; in caso contrario, restituisce il risultato della formula -NOT = NON ## Inverte la logica degli argomenti -OR = O ## Restituisce VERO se un argomento qualsiasi è VERO -TRUE = VERO ## Restituisce il valore logico VERO - +ADDRESS = INDIRIZZO +AREAS = AREE +CHOOSE = SCEGLI +COLUMN = RIF.COLONNA +COLUMNS = COLONNE +FORMULATEXT = TESTO.FORMULA +GETPIVOTDATA = INFO.DATI.TAB.PIVOT +HLOOKUP = CERCA.ORIZZ +HYPERLINK = COLLEG.IPERTESTUALE +INDEX = INDICE +INDIRECT = INDIRETTO +LOOKUP = CERCA +MATCH = CONFRONTA +OFFSET = SCARTO +ROW = RIF.RIGA +ROWS = RIGHE +RTD = DATITEMPOREALE +TRANSPOSE = MATR.TRASPOSTA +VLOOKUP = CERCA.VERT ## -## Lookup and reference functions Funzioni di ricerca e di riferimento +## Funzioni matematiche e trigonometriche (Math & Trig Functions) ## -ADDRESS = INDIRIZZO ## Restituisce un riferimento come testo in una singola cella di un foglio di lavoro -AREAS = AREE ## Restituisce il numero di aree in un riferimento -CHOOSE = SCEGLI ## Sceglie un valore da un elenco di valori -COLUMN = RIF.COLONNA ## Restituisce il numero di colonna di un riferimento -COLUMNS = COLONNE ## Restituisce il numero di colonne in un riferimento -HLOOKUP = CERCA.ORIZZ ## Effettua una ricerca nella riga superiore di una matrice e restituisce il valore della cella specificata -HYPERLINK = COLLEG.IPERTESTUALE ## Crea un collegamento che apre un documento memorizzato in un server di rete, una rete Intranet o Internet -INDEX = INDICE ## Utilizza un indice per scegliere un valore da un riferimento o da una matrice -INDIRECT = INDIRETTO ## Restituisce un riferimento specificato da un valore testo -LOOKUP = CERCA ## Ricerca i valori in un vettore o in una matrice -MATCH = CONFRONTA ## Ricerca i valori in un riferimento o in una matrice -OFFSET = SCARTO ## Restituisce uno scarto di riferimento da un riferimento dato -ROW = RIF.RIGA ## Restituisce il numero di riga di un riferimento -ROWS = RIGHE ## Restituisce il numero delle righe in un riferimento -RTD = DATITEMPOREALE ## Recupera dati in tempo reale da un programma che supporta l'automazione COM (automazione: Metodo per utilizzare gli oggetti di un'applicazione da un'altra applicazione o da un altro strumento di sviluppo. Precedentemente nota come automazione OLE, l'automazione è uno standard del settore e una caratteristica del modello COM (Component Object Model).) -TRANSPOSE = MATR.TRASPOSTA ## Restituisce la trasposizione di una matrice -VLOOKUP = CERCA.VERT ## Effettua una ricerca nella prima colonna di una matrice e si sposta attraverso la riga per restituire il valore di una cella - +ABS = ASS +ACOS = ARCCOS +ACOSH = ARCCOSH +ACOT = ARCCOT +ACOTH = ARCCOTH +AGGREGATE = AGGREGA +ARABIC = ARABO +ASIN = ARCSEN +ASINH = ARCSENH +ATAN = ARCTAN +ATAN2 = ARCTAN.2 +ATANH = ARCTANH +BASE = BASE +CEILING.MATH = ARROTONDA.ECCESSO.MAT +CEILING.PRECISE = ARROTONDA.ECCESSO.PRECISA +COMBIN = COMBINAZIONE +COMBINA = COMBINAZIONE.VALORI +COS = COS +COSH = COSH +COT = COT +COTH = COTH +CSC = CSC +CSCH = CSCH +DECIMAL = DECIMALE +DEGREES = GRADI +ECMA.CEILING = ECMA.ARROTONDA.ECCESSO +EVEN = PARI +EXP = EXP +FACT = FATTORIALE +FACTDOUBLE = FATT.DOPPIO +FLOOR.MATH = ARROTONDA.DIFETTO.MAT +FLOOR.PRECISE = ARROTONDA.DIFETTO.PRECISA +GCD = MCD +INT = INT +ISO.CEILING = ISO.ARROTONDA.ECCESSO +LCM = MCM +LN = LN +LOG = LOG +LOG10 = LOG10 +MDETERM = MATR.DETERM +MINVERSE = MATR.INVERSA +MMULT = MATR.PRODOTTO +MOD = RESTO +MROUND = ARROTONDA.MULTIPLO +MULTINOMIAL = MULTINOMIALE +MUNIT = MATR.UNIT +ODD = DISPARI +PI = PI.GRECO +POWER = POTENZA +PRODUCT = PRODOTTO +QUOTIENT = QUOZIENTE +RADIANS = RADIANTI +RAND = CASUALE +RANDBETWEEN = CASUALE.TRA +ROMAN = ROMANO +ROUND = ARROTONDA +ROUNDBAHTDOWN = ARROTBAHTGIU +ROUNDBAHTUP = ARROTBAHTSU +ROUNDDOWN = ARROTONDA.PER.DIF +ROUNDUP = ARROTONDA.PER.ECC +SEC = SEC +SECH = SECH +SERIESSUM = SOMMA.SERIE +SIGN = SEGNO +SIN = SEN +SINH = SENH +SQRT = RADQ +SQRTPI = RADQ.PI.GRECO +SUBTOTAL = SUBTOTALE +SUM = SOMMA +SUMIF = SOMMA.SE +SUMIFS = SOMMA.PIÙ.SE +SUMPRODUCT = MATR.SOMMA.PRODOTTO +SUMSQ = SOMMA.Q +SUMX2MY2 = SOMMA.DIFF.Q +SUMX2PY2 = SOMMA.SOMMA.Q +SUMXMY2 = SOMMA.Q.DIFF +TAN = TAN +TANH = TANH +TRUNC = TRONCA ## -## Math and trigonometry functions Funzioni matematiche e trigonometriche +## Funzioni statistiche (Statistical Functions) ## -ABS = ASS ## Restituisce il valore assoluto di un numero. -ACOS = ARCCOS ## Restituisce l'arcocoseno di un numero -ACOSH = ARCCOSH ## Restituisce l'inverso del coseno iperbolico di un numero -ASIN = ARCSEN ## Restituisce l'arcoseno di un numero -ASINH = ARCSENH ## Restituisce l'inverso del seno iperbolico di un numero -ATAN = ARCTAN ## Restituisce l'arcotangente di un numero -ATAN2 = ARCTAN.2 ## Restituisce l'arcotangente delle coordinate x e y specificate -ATANH = ARCTANH ## Restituisce l'inverso della tangente iperbolica di un numero -CEILING = ARROTONDA.ECCESSO ## Arrotonda un numero per eccesso all'intero più vicino o al multiplo più vicino a peso -COMBIN = COMBINAZIONE ## Restituisce il numero di combinazioni possibili per un numero assegnato di elementi -COS = COS ## Restituisce il coseno dell'angolo specificato -COSH = COSH ## Restituisce il coseno iperbolico di un numero -DEGREES = GRADI ## Converte i radianti in gradi -EVEN = PARI ## Arrotonda il valore assoluto di un numero per eccesso al più vicino intero pari -EXP = ESP ## Restituisce il numero e elevato alla potenza di num -FACT = FATTORIALE ## Restituisce il fattoriale di un numero -FACTDOUBLE = FATT.DOPPIO ## Restituisce il fattoriale doppio di un numero -FLOOR = ARROTONDA.DIFETTO ## Arrotonda un numero per difetto al multiplo più vicino a zero -GCD = MCD ## Restituisce il massimo comune divisore -INT = INT ## Arrotonda un numero per difetto al numero intero più vicino -LCM = MCM ## Restituisce il minimo comune multiplo -LN = LN ## Restituisce il logaritmo naturale di un numero -LOG = LOG ## Restituisce il logaritmo di un numero in una specificata base -LOG10 = LOG10 ## Restituisce il logaritmo in base 10 di un numero -MDETERM = MATR.DETERM ## Restituisce il determinante di una matrice -MINVERSE = MATR.INVERSA ## Restituisce l'inverso di una matrice -MMULT = MATR.PRODOTTO ## Restituisce il prodotto di due matrici -MOD = RESTO ## Restituisce il resto della divisione -MROUND = ARROTONDA.MULTIPLO ## Restituisce un numero arrotondato al multiplo desiderato -MULTINOMIAL = MULTINOMIALE ## Restituisce il multinomiale di un insieme di numeri -ODD = DISPARI ## Arrotonda un numero per eccesso al più vicino intero dispari -PI = PI.GRECO ## Restituisce il valore di pi greco -POWER = POTENZA ## Restituisce il risultato di un numero elevato a potenza -PRODUCT = PRODOTTO ## Moltiplica i suoi argomenti -QUOTIENT = QUOZIENTE ## Restituisce la parte intera di una divisione -RADIANS = RADIANTI ## Converte i gradi in radianti -RAND = CASUALE ## Restituisce un numero casuale compreso tra 0 e 1 -RANDBETWEEN = CASUALE.TRA ## Restituisce un numero casuale compreso tra i numeri specificati -ROMAN = ROMANO ## Restituisce il numero come numero romano sotto forma di testo -ROUND = ARROTONDA ## Arrotonda il numero al numero di cifre specificato -ROUNDDOWN = ARROTONDA.PER.DIF ## Arrotonda il valore assoluto di un numero per difetto -ROUNDUP = ARROTONDA.PER.ECC ## Arrotonda il valore assoluto di un numero per eccesso -SERIESSUM = SOMMA.SERIE ## Restituisce la somma di una serie di potenze in base alla formula -SIGN = SEGNO ## Restituisce il segno di un numero -SIN = SEN ## Restituisce il seno di un dato angolo -SINH = SENH ## Restituisce il seno iperbolico di un numero -SQRT = RADQ ## Restituisce una radice quadrata -SQRTPI = RADQ.PI.GRECO ## Restituisce la radice quadrata di un numero (numero * pi greco) -SUBTOTAL = SUBTOTALE ## Restituisce un subtotale in un elenco o in un database -SUM = SOMMA ## Somma i suoi argomenti -SUMIF = SOMMA.SE ## Somma le celle specificate da un dato criterio -SUMIFS = SOMMA.PIÙ.SE ## Somma le celle in un intervallo che soddisfano più criteri -SUMPRODUCT = MATR.SOMMA.PRODOTTO ## Restituisce la somma dei prodotti dei componenti corrispondenti della matrice -SUMSQ = SOMMA.Q ## Restituisce la somma dei quadrati degli argomenti -SUMX2MY2 = SOMMA.DIFF.Q ## Restituisce la somma della differenza dei quadrati dei corrispondenti elementi in due matrici -SUMX2PY2 = SOMMA.SOMMA.Q ## Restituisce la somma della somma dei quadrati dei corrispondenti elementi in due matrici -SUMXMY2 = SOMMA.Q.DIFF ## Restituisce la somma dei quadrati delle differenze dei corrispondenti elementi in due matrici -TAN = TAN ## Restituisce la tangente di un numero -TANH = TANH ## Restituisce la tangente iperbolica di un numero -TRUNC = TRONCA ## Tronca la parte decimale di un numero - +AVEDEV = MEDIA.DEV +AVERAGE = MEDIA +AVERAGEA = MEDIA.VALORI +AVERAGEIF = MEDIA.SE +AVERAGEIFS = MEDIA.PIÙ.SE +BETA.DIST = DISTRIB.BETA.N +BETA.INV = INV.BETA.N +BINOM.DIST = DISTRIB.BINOM.N +BINOM.DIST.RANGE = INTERVALLO.DISTRIB.BINOM.N. +BINOM.INV = INV.BINOM +CHISQ.DIST = DISTRIB.CHI.QUAD +CHISQ.DIST.RT = DISTRIB.CHI.QUAD.DS +CHISQ.INV = INV.CHI.QUAD +CHISQ.INV.RT = INV.CHI.QUAD.DS +CHISQ.TEST = TEST.CHI.QUAD +CONFIDENCE.NORM = CONFIDENZA.NORM +CONFIDENCE.T = CONFIDENZA.T +CORREL = CORRELAZIONE +COUNT = CONTA.NUMERI +COUNTA = CONTA.VALORI +COUNTBLANK = CONTA.VUOTE +COUNTIF = CONTA.SE +COUNTIFS = CONTA.PIÙ.SE +COVARIANCE.P = COVARIANZA.P +COVARIANCE.S = COVARIANZA.C +DEVSQ = DEV.Q +EXPON.DIST = DISTRIB.EXP.N +F.DIST = DISTRIBF +F.DIST.RT = DISTRIB.F.DS +F.INV = INVF +F.INV.RT = INV.F.DS +F.TEST = TESTF +FISHER = FISHER +FISHERINV = INV.FISHER +FORECAST.ETS = PREVISIONE.ETS +FORECAST.ETS.CONFINT = PREVISIONE.ETS.INTCONF +FORECAST.ETS.SEASONALITY = PREVISIONE.ETS.STAGIONALITÀ +FORECAST.ETS.STAT = PREVISIONE.ETS.STAT +FORECAST.LINEAR = PREVISIONE.LINEARE +FREQUENCY = FREQUENZA +GAMMA = GAMMA +GAMMA.DIST = DISTRIB.GAMMA.N +GAMMA.INV = INV.GAMMA.N +GAMMALN = LN.GAMMA +GAMMALN.PRECISE = LN.GAMMA.PRECISA +GAUSS = GAUSS +GEOMEAN = MEDIA.GEOMETRICA +GROWTH = CRESCITA +HARMEAN = MEDIA.ARMONICA +HYPGEOM.DIST = DISTRIB.IPERGEOM.N +INTERCEPT = INTERCETTA +KURT = CURTOSI +LARGE = GRANDE +LINEST = REGR.LIN +LOGEST = REGR.LOG +LOGNORM.DIST = DISTRIB.LOGNORM.N +LOGNORM.INV = INV.LOGNORM.N +MAX = MAX +MAXA = MAX.VALORI +MAXIFS = MAX.PIÙ.SE +MEDIAN = MEDIANA +MIN = MIN +MINA = MIN.VALORI +MINIFS = MIN.PIÙ.SE +MODE.MULT = MODA.MULT +MODE.SNGL = MODA.SNGL +NEGBINOM.DIST = DISTRIB.BINOM.NEG.N +NORM.DIST = DISTRIB.NORM.N +NORM.INV = INV.NORM.N +NORM.S.DIST = DISTRIB.NORM.ST.N +NORM.S.INV = INV.NORM.S +PEARSON = PEARSON +PERCENTILE.EXC = ESC.PERCENTILE +PERCENTILE.INC = INC.PERCENTILE +PERCENTRANK.EXC = ESC.PERCENT.RANGO +PERCENTRANK.INC = INC.PERCENT.RANGO +PERMUT = PERMUTAZIONE +PERMUTATIONA = PERMUTAZIONE.VALORI +PHI = PHI +POISSON.DIST = DISTRIB.POISSON +PROB = PROBABILITÀ +QUARTILE.EXC = ESC.QUARTILE +QUARTILE.INC = INC.QUARTILE +RANK.AVG = RANGO.MEDIA +RANK.EQ = RANGO.UG +RSQ = RQ +SKEW = ASIMMETRIA +SKEW.P = ASIMMETRIA.P +SLOPE = PENDENZA +SMALL = PICCOLO +STANDARDIZE = NORMALIZZA +STDEV.P = DEV.ST.P +STDEV.S = DEV.ST.C +STDEVA = DEV.ST.VALORI +STDEVPA = DEV.ST.POP.VALORI +STEYX = ERR.STD.YX +T.DIST = DISTRIB.T.N +T.DIST.2T = DISTRIB.T.2T +T.DIST.RT = DISTRIB.T.DS +T.INV = INVT +T.INV.2T = INV.T.2T +T.TEST = TESTT +TREND = TENDENZA +TRIMMEAN = MEDIA.TRONCATA +VAR.P = VAR.P +VAR.S = VAR.C +VARA = VAR.VALORI +VARPA = VAR.POP.VALORI +WEIBULL.DIST = DISTRIB.WEIBULL +Z.TEST = TESTZ ## -## Statistical functions Funzioni statistiche +## Funzioni di testo (Text Functions) ## -AVEDEV = MEDIA.DEV ## Restituisce la media delle deviazioni assolute delle coordinate rispetto alla loro media -AVERAGE = MEDIA ## Restituisce la media degli argomenti -AVERAGEA = MEDIA.VALORI ## Restituisce la media degli argomenti, inclusi i numeri, il testo e i valori logici -AVERAGEIF = MEDIA.SE ## Restituisce la media aritmetica di tutte le celle in un intervallo che soddisfano un determinato criterio -AVERAGEIFS = MEDIA.PIÙ.SE ## Restituisce la media aritmetica di tutte le celle che soddisfano più criteri -BETADIST = DISTRIB.BETA ## Restituisce la funzione di distribuzione cumulativa beta -BETAINV = INV.BETA ## Restituisce l'inverso della funzione di distribuzione cumulativa per una distribuzione beta specificata -BINOMDIST = DISTRIB.BINOM ## Restituisce la distribuzione binomiale per il termine individuale -CHIDIST = DISTRIB.CHI ## Restituisce la probabilità a una coda per la distribuzione del chi quadrato -CHIINV = INV.CHI ## Restituisce l'inverso della probabilità ad una coda per la distribuzione del chi quadrato -CHITEST = TEST.CHI ## Restituisce il test per l'indipendenza -CONFIDENCE = CONFIDENZA ## Restituisce l'intervallo di confidenza per una popolazione -CORREL = CORRELAZIONE ## Restituisce il coefficiente di correlazione tra due insiemi di dati -COUNT = CONTA.NUMERI ## Conta la quantità di numeri nell'elenco di argomenti -COUNTA = CONTA.VALORI ## Conta il numero di valori nell'elenco di argomenti -COUNTBLANK = CONTA.VUOTE ## Conta il numero di celle vuote all'interno di un intervallo -COUNTIF = CONTA.SE ## Conta il numero di celle all'interno di un intervallo che soddisfa i criteri specificati -COUNTIFS = CONTA.PIÙ.SE ## Conta il numero di celle in un intervallo che soddisfano più criteri. -COVAR = COVARIANZA ## Calcola la covarianza, la media dei prodotti delle deviazioni accoppiate -CRITBINOM = CRIT.BINOM ## Restituisce il più piccolo valore per il quale la distribuzione cumulativa binomiale risulta maggiore o uguale ad un valore di criterio -DEVSQ = DEV.Q ## Restituisce la somma dei quadrati delle deviazioni -EXPONDIST = DISTRIB.EXP ## Restituisce la distribuzione esponenziale -FDIST = DISTRIB.F ## Restituisce la distribuzione di probabilità F -FINV = INV.F ## Restituisce l'inverso della distribuzione della probabilità F -FISHER = FISHER ## Restituisce la trasformazione di Fisher -FISHERINV = INV.FISHER ## Restituisce l'inverso della trasformazione di Fisher -FORECAST = PREVISIONE ## Restituisce i valori lungo una tendenza lineare -FREQUENCY = FREQUENZA ## Restituisce la distribuzione di frequenza come matrice verticale -FTEST = TEST.F ## Restituisce il risultato di un test F -GAMMADIST = DISTRIB.GAMMA ## Restituisce la distribuzione gamma -GAMMAINV = INV.GAMMA ## Restituisce l'inverso della distribuzione cumulativa gamma -GAMMALN = LN.GAMMA ## Restituisce il logaritmo naturale della funzione gamma, G(x) -GEOMEAN = MEDIA.GEOMETRICA ## Restituisce la media geometrica -GROWTH = CRESCITA ## Restituisce i valori lungo una linea di tendenza esponenziale -HARMEAN = MEDIA.ARMONICA ## Restituisce la media armonica -HYPGEOMDIST = DISTRIB.IPERGEOM ## Restituisce la distribuzione ipergeometrica -INTERCEPT = INTERCETTA ## Restituisce l'intercetta della retta di regressione lineare -KURT = CURTOSI ## Restituisce la curtosi di un insieme di dati -LARGE = GRANDE ## Restituisce il k-esimo valore più grande in un insieme di dati -LINEST = REGR.LIN ## Restituisce i parametri di una tendenza lineare -LOGEST = REGR.LOG ## Restituisce i parametri di una linea di tendenza esponenziale -LOGINV = INV.LOGNORM ## Restituisce l'inverso di una distribuzione lognormale -LOGNORMDIST = DISTRIB.LOGNORM ## Restituisce la distribuzione lognormale cumulativa -MAX = MAX ## Restituisce il valore massimo in un elenco di argomenti -MAXA = MAX.VALORI ## Restituisce il valore massimo in un elenco di argomenti, inclusi i numeri, il testo e i valori logici -MEDIAN = MEDIANA ## Restituisce la mediana dei numeri specificati -MIN = MIN ## Restituisce il valore minimo in un elenco di argomenti -MINA = MIN.VALORI ## Restituisce il più piccolo valore in un elenco di argomenti, inclusi i numeri, il testo e i valori logici -MODE = MODA ## Restituisce il valore più comune in un insieme di dati -NEGBINOMDIST = DISTRIB.BINOM.NEG ## Restituisce la distribuzione binomiale negativa -NORMDIST = DISTRIB.NORM ## Restituisce la distribuzione cumulativa normale -NORMINV = INV.NORM ## Restituisce l'inverso della distribuzione cumulativa normale standard -NORMSDIST = DISTRIB.NORM.ST ## Restituisce la distribuzione cumulativa normale standard -NORMSINV = INV.NORM.ST ## Restituisce l'inverso della distribuzione cumulativa normale -PEARSON = PEARSON ## Restituisce il coefficiente del momento di correlazione di Pearson -PERCENTILE = PERCENTILE ## Restituisce il k-esimo dato percentile di valori in un intervallo -PERCENTRANK = PERCENT.RANGO ## Restituisce il rango di un valore in un insieme di dati come percentuale -PERMUT = PERMUTAZIONE ## Restituisce il numero delle permutazioni per un determinato numero di oggetti -POISSON = POISSON ## Restituisce la distribuzione di Poisson -PROB = PROBABILITÀ ## Calcola la probabilità che dei valori in un intervallo siano compresi tra due limiti -QUARTILE = QUARTILE ## Restituisce il quartile di un insieme di dati -RANK = RANGO ## Restituisce il rango di un numero in un elenco di numeri -RSQ = RQ ## Restituisce la radice quadrata del coefficiente di momento di correlazione di Pearson -SKEW = ASIMMETRIA ## Restituisce il grado di asimmetria di una distribuzione -SLOPE = PENDENZA ## Restituisce la pendenza di una retta di regressione lineare -SMALL = PICCOLO ## Restituisce il k-esimo valore più piccolo in un insieme di dati -STANDARDIZE = NORMALIZZA ## Restituisce un valore normalizzato -STDEV = DEV.ST ## Restituisce una stima della deviazione standard sulla base di un campione -STDEVA = DEV.ST.VALORI ## Restituisce una stima della deviazione standard sulla base di un campione, inclusi i numeri, il testo e i valori logici -STDEVP = DEV.ST.POP ## Calcola la deviazione standard sulla base di un'intera popolazione -STDEVPA = DEV.ST.POP.VALORI ## Calcola la deviazione standard sulla base sull'intera popolazione, inclusi i numeri, il testo e i valori logici -STEYX = ERR.STD.YX ## Restituisce l'errore standard del valore previsto per y per ogni valore x nella regressione -TDIST = DISTRIB.T ## Restituisce la distribuzione t di Student -TINV = INV.T ## Restituisce l'inversa della distribuzione t di Student -TREND = TENDENZA ## Restituisce i valori lungo una linea di tendenza lineare -TRIMMEAN = MEDIA.TRONCATA ## Restituisce la media della parte interna di un insieme di dati -TTEST = TEST.T ## Restituisce la probabilità associata ad un test t di Student -VAR = VAR ## Stima la varianza sulla base di un campione -VARA = VAR.VALORI ## Stima la varianza sulla base di un campione, inclusi i numeri, il testo e i valori logici -VARP = VAR.POP ## Calcola la varianza sulla base dell'intera popolazione -VARPA = VAR.POP.VALORI ## Calcola la deviazione standard sulla base sull'intera popolazione, inclusi i numeri, il testo e i valori logici -WEIBULL = WEIBULL ## Restituisce la distribuzione di Weibull -ZTEST = TEST.Z ## Restituisce il valore di probabilità a una coda per un test z - +BAHTTEXT = BAHTTESTO +CHAR = CODICE.CARATT +CLEAN = LIBERA +CODE = CODICE +CONCAT = CONCAT +DOLLAR = VALUTA +EXACT = IDENTICO +FIND = TROVA +FIXED = FISSO +ISTHAIDIGIT = ÈTHAICIFRA +LEFT = SINISTRA +LEN = LUNGHEZZA +LOWER = MINUSC +MID = STRINGA.ESTRAI +NUMBERSTRING = NUMERO.STRINGA +NUMBERVALUE = NUMERO.VALORE +PHONETIC = FURIGANA +PROPER = MAIUSC.INIZ +REPLACE = RIMPIAZZA +REPT = RIPETI +RIGHT = DESTRA +SEARCH = RICERCA +SUBSTITUTE = SOSTITUISCI +T = T +TEXT = TESTO +TEXTJOIN = TESTO.UNISCI +THAIDIGIT = THAICIFRA +THAINUMSOUND = THAINUMSUONO +THAINUMSTRING = THAISZÁMKAR +THAISTRINGLENGTH = THAILUNGSTRINGA +TRIM = ANNULLA.SPAZI +UNICHAR = CARATT.UNI +UNICODE = UNICODE +UPPER = MAIUSC +VALUE = VALORE ## -## Text functions Funzioni di testo +## Funzioni Web (Web Functions) ## -ASC = ASC ## Modifica le lettere inglesi o il katakana a doppio byte all'interno di una stringa di caratteri in caratteri a singolo byte -BAHTTEXT = BAHTTESTO ## Converte un numero in testo, utilizzando il formato valuta ß (baht) -CHAR = CODICE.CARATT ## Restituisce il carattere specificato dal numero di codice -CLEAN = LIBERA ## Elimina dal testo tutti i caratteri che non è possibile stampare -CODE = CODICE ## Restituisce il codice numerico del primo carattere di una stringa di testo -CONCATENATE = CONCATENA ## Unisce diversi elementi di testo in un unico elemento di testo -DOLLAR = VALUTA ## Converte un numero in testo, utilizzando il formato valuta € (euro) -EXACT = IDENTICO ## Verifica se due valori di testo sono uguali -FIND = TROVA ## Rileva un valore di testo all'interno di un altro (distinzione tra maiuscole e minuscole) -FINDB = TROVA.B ## Rileva un valore di testo all'interno di un altro (distinzione tra maiuscole e minuscole) -FIXED = FISSO ## Formatta un numero come testo con un numero fisso di decimali -JIS = ORDINAMENTO.JIS ## Modifica le lettere inglesi o i caratteri katakana a byte singolo all'interno di una stringa di caratteri in caratteri a byte doppio. -LEFT = SINISTRA ## Restituisce il carattere più a sinistra di un valore di testo -LEFTB = SINISTRA.B ## Restituisce il carattere più a sinistra di un valore di testo -LEN = LUNGHEZZA ## Restituisce il numero di caratteri di una stringa di testo -LENB = LUNB ## Restituisce il numero di caratteri di una stringa di testo -LOWER = MINUSC ## Converte il testo in lettere minuscole -MID = MEDIA ## Restituisce un numero specifico di caratteri di una stringa di testo a partire dalla posizione specificata -MIDB = MEDIA.B ## Restituisce un numero specifico di caratteri di una stringa di testo a partire dalla posizione specificata -PHONETIC = FURIGANA ## Estrae i caratteri fonetici (furigana) da una stringa di testo. -PROPER = MAIUSC.INIZ ## Converte in maiuscolo la prima lettera di ogni parola di un valore di testo -REPLACE = RIMPIAZZA ## Sostituisce i caratteri all'interno di un testo -REPLACEB = SOSTITUISCI.B ## Sostituisce i caratteri all'interno di un testo -REPT = RIPETI ## Ripete un testo per un dato numero di volte -RIGHT = DESTRA ## Restituisce il carattere più a destra di un valore di testo -RIGHTB = DESTRA.B ## Restituisce il carattere più a destra di un valore di testo -SEARCH = RICERCA ## Rileva un valore di testo all'interno di un altro (non è sensibile alle maiuscole e minuscole) -SEARCHB = CERCA.B ## Rileva un valore di testo all'interno di un altro (non è sensibile alle maiuscole e minuscole) -SUBSTITUTE = SOSTITUISCI ## Sostituisce il nuovo testo al testo contenuto in una stringa -T = T ## Converte gli argomenti in testo -TEXT = TESTO ## Formatta un numero e lo converte in testo -TRIM = ANNULLA.SPAZI ## Elimina gli spazi dal testo -UPPER = MAIUSC ## Converte il testo in lettere maiuscole -VALUE = VALORE ## Converte un argomento di testo in numero +ENCODEURL = CODIFICA.URL +FILTERXML = FILTRO.XML +WEBSERVICE = SERVIZIO.WEB + +## +## Funzioni di compatibilità (Compatibility Functions) +## +BETADIST = DISTRIB.BETA +BETAINV = INV.BETA +BINOMDIST = DISTRIB.BINOM +CEILING = ARROTONDA.ECCESSO +CHIDIST = DISTRIB.CHI +CHIINV = INV.CHI +CHITEST = TEST.CHI +CONCATENATE = CONCATENA +CONFIDENCE = CONFIDENZA +COVAR = COVARIANZA +CRITBINOM = CRIT.BINOM +EXPONDIST = DISTRIB.EXP +FDIST = DISTRIB.F +FINV = INV.F +FLOOR = ARROTONDA.DIFETTO +FORECAST = PREVISIONE +FTEST = TEST.F +GAMMADIST = DISTRIB.GAMMA +GAMMAINV = INV.GAMMA +HYPGEOMDIST = DISTRIB.IPERGEOM +LOGINV = INV.LOGNORM +LOGNORMDIST = DISTRIB.LOGNORM +MODE = MODA +NEGBINOMDIST = DISTRIB.BINOM.NEG +NORMDIST = DISTRIB.NORM +NORMINV = INV.NORM +NORMSDIST = DISTRIB.NORM.ST +NORMSINV = INV.NORM.ST +PERCENTILE = PERCENTILE +PERCENTRANK = PERCENT.RANGO +POISSON = POISSON +QUARTILE = QUARTILE +RANK = RANGO +STDEV = DEV.ST +STDEVP = DEV.ST.POP +TDIST = DISTRIB.T +TINV = INV.T +TTEST = TEST.T +VAR = VAR +VARP = VAR.POP +WEIBULL = WEIBULL +ZTEST = TEST.Z diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/nb/config b/PhpOffice/PhpSpreadsheet/Calculation/locale/nb/config new file mode 100644 index 0000000..a7f3be1 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/nb/config @@ -0,0 +1,20 @@ +############################################################ +## +## PhpSpreadsheet - locale settings +## +## Norsk Bokmål (Norwegian Bokmål) +## +############################################################ + +ArgumentSeparator = ; + +## +## Error Codes +## +NULL +DIV0 +VALUE = #VERDI! +REF +NAME = #NAVN? +NUM +NA = #N/D diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/nb/functions b/PhpOffice/PhpSpreadsheet/Calculation/locale/nb/functions new file mode 100644 index 0000000..d352e1f --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/nb/functions @@ -0,0 +1,539 @@ +############################################################ +## +## PhpSpreadsheet - function name translations +## +## Norsk Bokmål (Norwegian Bokmål) +## +############################################################ + + +## +## Kubefunksjoner (Cube Functions) +## +CUBEKPIMEMBER = KUBEKPIMEDLEM +CUBEMEMBER = KUBEMEDLEM +CUBEMEMBERPROPERTY = KUBEMEDLEMEGENSKAP +CUBERANKEDMEMBER = KUBERANGERTMEDLEM +CUBESET = KUBESETT +CUBESETCOUNT = KUBESETTANTALL +CUBEVALUE = KUBEVERDI + +## +## Databasefunksjoner (Database Functions) +## +DAVERAGE = DGJENNOMSNITT +DCOUNT = DANTALL +DCOUNTA = DANTALLA +DGET = DHENT +DMAX = DMAKS +DMIN = DMIN +DPRODUCT = DPRODUKT +DSTDEV = DSTDAV +DSTDEVP = DSTDAVP +DSUM = DSUMMER +DVAR = DVARIANS +DVARP = DVARIANSP + +## +## Dato- og tidsfunksjoner (Date & Time Functions) +## +DATE = DATO +DATEDIF = DATODIFF +DATESTRING = DATOSTRENG +DATEVALUE = DATOVERDI +DAY = DAG +DAYS = DAGER +DAYS360 = DAGER360 +EDATE = DAG.ETTER +EOMONTH = MÅNEDSSLUTT +HOUR = TIME +ISOWEEKNUM = ISOUKENR +MINUTE = MINUTT +MONTH = MÅNED +NETWORKDAYS = NETT.ARBEIDSDAGER +NETWORKDAYS.INTL = NETT.ARBEIDSDAGER.INTL +NOW = NÅ +SECOND = SEKUND +THAIDAYOFWEEK = THAIUKEDAG +THAIMONTHOFYEAR = THAIMÅNED +THAIYEAR = THAIÅR +TIME = TID +TIMEVALUE = TIDSVERDI +TODAY = IDAG +WEEKDAY = UKEDAG +WEEKNUM = UKENR +WORKDAY = ARBEIDSDAG +WORKDAY.INTL = ARBEIDSDAG.INTL +YEAR = ÅR +YEARFRAC = ÅRDEL + +## +## Tekniske funksjoner (Engineering Functions) +## +BESSELI = BESSELI +BESSELJ = BESSELJ +BESSELK = BESSELK +BESSELY = BESSELY +BIN2DEC = BINTILDES +BIN2HEX = BINTILHEKS +BIN2OCT = BINTILOKT +BITAND = BITOG +BITLSHIFT = BITVFORSKYV +BITOR = BITELLER +BITRSHIFT = BITHFORSKYV +BITXOR = BITEKSKLUSIVELLER +COMPLEX = KOMPLEKS +CONVERT = KONVERTER +DEC2BIN = DESTILBIN +DEC2HEX = DESTILHEKS +DEC2OCT = DESTILOKT +DELTA = DELTA +ERF = FEILF +ERF.PRECISE = FEILF.PRESIS +ERFC = FEILFK +ERFC.PRECISE = FEILFK.PRESIS +GESTEP = GRENSEVERDI +HEX2BIN = HEKSTILBIN +HEX2DEC = HEKSTILDES +HEX2OCT = HEKSTILOKT +IMABS = IMABS +IMAGINARY = IMAGINÆR +IMARGUMENT = IMARGUMENT +IMCONJUGATE = IMKONJUGERT +IMCOS = IMCOS +IMCOSH = IMCOSH +IMCOT = IMCOT +IMCSC = IMCSC +IMCSCH = IMCSCH +IMDIV = IMDIV +IMEXP = IMEKSP +IMLN = IMLN +IMLOG10 = IMLOG10 +IMLOG2 = IMLOG2 +IMPOWER = IMOPPHØY +IMPRODUCT = IMPRODUKT +IMREAL = IMREELL +IMSEC = IMSEC +IMSECH = IMSECH +IMSIN = IMSIN +IMSINH = IMSINH +IMSQRT = IMROT +IMSUB = IMSUB +IMSUM = IMSUMMER +IMTAN = IMTAN +OCT2BIN = OKTTILBIN +OCT2DEC = OKTTILDES +OCT2HEX = OKTTILHEKS + +## +## Økonomiske funksjoner (Financial Functions) +## +ACCRINT = PÅLØPT.PERIODISK.RENTE +ACCRINTM = PÅLØPT.FORFALLSRENTE +AMORDEGRC = AMORDEGRC +AMORLINC = AMORLINC +COUPDAYBS = OBLIG.DAGER.FF +COUPDAYS = OBLIG.DAGER +COUPDAYSNC = OBLIG.DAGER.NF +COUPNCD = OBLIG.DAGER.EF +COUPNUM = OBLIG.ANTALL +COUPPCD = OBLIG.DAG.FORRIGE +CUMIPMT = SAMLET.RENTE +CUMPRINC = SAMLET.HOVEDSTOL +DB = DAVSKR +DDB = DEGRAVS +DISC = DISKONTERT +DOLLARDE = DOLLARDE +DOLLARFR = DOLLARBR +DURATION = VARIGHET +EFFECT = EFFEKTIV.RENTE +FV = SLUTTVERDI +FVSCHEDULE = SVPLAN +INTRATE = RENTESATS +IPMT = RAVDRAG +IRR = IR +ISPMT = ER.AVDRAG +MDURATION = MVARIGHET +MIRR = MODIR +NOMINAL = NOMINELL +NPER = PERIODER +NPV = NNV +ODDFPRICE = AVVIKFP.PRIS +ODDFYIELD = AVVIKFP.AVKASTNING +ODDLPRICE = AVVIKSP.PRIS +ODDLYIELD = AVVIKSP.AVKASTNING +PDURATION = PVARIGHET +PMT = AVDRAG +PPMT = AMORT +PRICE = PRIS +PRICEDISC = PRIS.DISKONTERT +PRICEMAT = PRIS.FORFALL +PV = NÅVERDI +RATE = RENTE +RECEIVED = MOTTATT.AVKAST +RRI = REALISERT.AVKASTNING +SLN = LINAVS +SYD = ÅRSAVS +TBILLEQ = TBILLEKV +TBILLPRICE = TBILLPRIS +TBILLYIELD = TBILLAVKASTNING +VDB = VERDIAVS +XIRR = XIR +XNPV = XNNV +YIELD = AVKAST +YIELDDISC = AVKAST.DISKONTERT +YIELDMAT = AVKAST.FORFALL + +## +## Informasjonsfunksjoner (Information Functions) +## +CELL = CELLE +ERROR.TYPE = FEIL.TYPE +INFO = INFO +ISBLANK = ERTOM +ISERR = ERF +ISERROR = ERFEIL +ISEVEN = ERPARTALL +ISFORMULA = ERFORMEL +ISLOGICAL = ERLOGISK +ISNA = ERIT +ISNONTEXT = ERIKKETEKST +ISNUMBER = ERTALL +ISODD = ERODDE +ISREF = ERREF +ISTEXT = ERTEKST +N = N +NA = IT +SHEET = ARK +SHEETS = ANTALL.ARK +TYPE = VERDITYPE + +## +## Logiske funksjoner (Logical Functions) +## +AND = OG +FALSE = USANN +IF = HVIS +IFERROR = HVISFEIL +IFNA = HVIS.IT +IFS = HVIS.SETT +NOT = IKKE +OR = ELLER +SWITCH = BRYTER +TRUE = SANN +XOR = EKSKLUSIVELLER + +## +## Oppslag- og referansefunksjoner (Lookup & Reference Functions) +## +ADDRESS = ADRESSE +AREAS = OMRÅDER +CHOOSE = VELG +COLUMN = KOLONNE +COLUMNS = KOLONNER +FORMULATEXT = FORMELTEKST +GETPIVOTDATA = HENTPIVOTDATA +HLOOKUP = FINN.KOLONNE +HYPERLINK = HYPERKOBLING +INDEX = INDEKS +INDIRECT = INDIREKTE +LOOKUP = SLÅ.OPP +MATCH = SAMMENLIGNE +OFFSET = FORSKYVNING +ROW = RAD +ROWS = RADER +RTD = RTD +TRANSPOSE = TRANSPONER +VLOOKUP = FINN.RAD +*RC = RK + +## +## Matematikk- og trigonometrifunksjoner (Math & Trig Functions) +## +ABS = ABS +ACOS = ARCCOS +ACOSH = ARCCOSH +ACOT = ACOT +ACOTH = ACOTH +AGGREGATE = MENGDE +ARABIC = ARABISK +ASIN = ARCSIN +ASINH = ARCSINH +ATAN = ARCTAN +ATAN2 = ARCTAN2 +ATANH = ARCTANH +BASE = GRUNNTALL +CEILING.MATH = AVRUND.GJELDENDE.MULTIPLUM.OPP.MATEMATISK +CEILING.PRECISE = AVRUND.GJELDENDE.MULTIPLUM.PRESIS +COMBIN = KOMBINASJON +COMBINA = KOMBINASJONA +COS = COS +COSH = COSH +COT = COT +COTH = COTH +CSC = CSC +CSCH = CSCH +DECIMAL = DESIMAL +DEGREES = GRADER +ECMA.CEILING = ECMA.AVRUND.GJELDENDE.MULTIPLUM +EVEN = AVRUND.TIL.PARTALL +EXP = EKSP +FACT = FAKULTET +FACTDOUBLE = DOBBELFAKT +FLOOR.MATH = AVRUND.GJELDENDE.MULTIPLUM.NED.MATEMATISK +FLOOR.PRECISE = AVRUND.GJELDENDE.MULTIPLUM.NED.PRESIS +GCD = SFF +INT = HELTALL +ISO.CEILING = ISO.AVRUND.GJELDENDE.MULTIPLUM +LCM = MFM +LN = LN +LOG = LOG +LOG10 = LOG10 +MDETERM = MDETERM +MINVERSE = MINVERS +MMULT = MMULT +MOD = REST +MROUND = MRUND +MULTINOMIAL = MULTINOMINELL +MUNIT = MENHET +ODD = AVRUND.TIL.ODDETALL +PI = PI +POWER = OPPHØYD.I +PRODUCT = PRODUKT +QUOTIENT = KVOTIENT +RADIANS = RADIANER +RAND = TILFELDIG +RANDBETWEEN = TILFELDIGMELLOM +ROMAN = ROMERTALL +ROUND = AVRUND +ROUNDBAHTDOWN = RUNDAVBAHTNEDOVER +ROUNDBAHTUP = RUNDAVBAHTOPPOVER +ROUNDDOWN = AVRUND.NED +ROUNDUP = AVRUND.OPP +SEC = SEC +SECH = SECH +SERIESSUM = SUMMER.REKKE +SIGN = FORTEGN +SIN = SIN +SINH = SINH +SQRT = ROT +SQRTPI = ROTPI +SUBTOTAL = DELSUM +SUM = SUMMER +SUMIF = SUMMERHVIS +SUMIFS = SUMMER.HVIS.SETT +SUMPRODUCT = SUMMERPRODUKT +SUMSQ = SUMMERKVADRAT +SUMX2MY2 = SUMMERX2MY2 +SUMX2PY2 = SUMMERX2PY2 +SUMXMY2 = SUMMERXMY2 +TAN = TAN +TANH = TANH +TRUNC = AVKORT + +## +## Statistiske funksjoner (Statistical Functions) +## +AVEDEV = GJENNOMSNITTSAVVIK +AVERAGE = GJENNOMSNITT +AVERAGEA = GJENNOMSNITTA +AVERAGEIF = GJENNOMSNITTHVIS +AVERAGEIFS = GJENNOMSNITT.HVIS.SETT +BETA.DIST = BETA.FORDELING.N +BETA.INV = BETA.INV +BINOM.DIST = BINOM.FORDELING.N +BINOM.DIST.RANGE = BINOM.FORDELING.OMRÅDE +BINOM.INV = BINOM.INV +CHISQ.DIST = KJIKVADRAT.FORDELING +CHISQ.DIST.RT = KJIKVADRAT.FORDELING.H +CHISQ.INV = KJIKVADRAT.INV +CHISQ.INV.RT = KJIKVADRAT.INV.H +CHISQ.TEST = KJIKVADRAT.TEST +CONFIDENCE.NORM = KONFIDENS.NORM +CONFIDENCE.T = KONFIDENS.T +CORREL = KORRELASJON +COUNT = ANTALL +COUNTA = ANTALLA +COUNTBLANK = TELLBLANKE +COUNTIF = ANTALL.HVIS +COUNTIFS = ANTALL.HVIS.SETT +COVARIANCE.P = KOVARIANS.P +COVARIANCE.S = KOVARIANS.S +DEVSQ = AVVIK.KVADRERT +EXPON.DIST = EKSP.FORDELING.N +F.DIST = F.FORDELING +F.DIST.RT = F.FORDELING.H +F.INV = F.INV +F.INV.RT = F.INV.H +F.TEST = F.TEST +FISHER = FISHER +FISHERINV = FISHERINV +FORECAST.ETS = PROGNOSE.ETS +FORECAST.ETS.CONFINT = PROGNOSE.ETS.CONFINT +FORECAST.ETS.SEASONALITY = PROGNOSE.ETS.SESONGAVHENGIGHET +FORECAST.ETS.STAT = PROGNOSE.ETS.STAT +FORECAST.LINEAR = PROGNOSE.LINEÆR +FREQUENCY = FREKVENS +GAMMA = GAMMA +GAMMA.DIST = GAMMA.FORDELING +GAMMA.INV = GAMMA.INV +GAMMALN = GAMMALN +GAMMALN.PRECISE = GAMMALN.PRESIS +GAUSS = GAUSS +GEOMEAN = GJENNOMSNITT.GEOMETRISK +GROWTH = VEKST +HARMEAN = GJENNOMSNITT.HARMONISK +HYPGEOM.DIST = HYPGEOM.FORDELING.N +INTERCEPT = SKJÆRINGSPUNKT +KURT = KURT +LARGE = N.STØRST +LINEST = RETTLINJE +LOGEST = KURVE +LOGNORM.DIST = LOGNORM.FORDELING +LOGNORM.INV = LOGNORM.INV +MAX = STØRST +MAXA = MAKSA +MAXIFS = MAKS.HVIS.SETT +MEDIAN = MEDIAN +MIN = MIN +MINA = MINA +MINIFS = MIN.HVIS.SETT +MODE.MULT = MODUS.MULT +MODE.SNGL = MODUS.SNGL +NEGBINOM.DIST = NEGBINOM.FORDELING.N +NORM.DIST = NORM.FORDELING +NORM.INV = NORM.INV +NORM.S.DIST = NORM.S.FORDELING +NORM.S.INV = NORM.S.INV +PEARSON = PEARSON +PERCENTILE.EXC = PERSENTIL.EKS +PERCENTILE.INC = PERSENTIL.INK +PERCENTRANK.EXC = PROSENTDEL.EKS +PERCENTRANK.INC = PROSENTDEL.INK +PERMUT = PERMUTER +PERMUTATIONA = PERMUTASJONA +PHI = PHI +POISSON.DIST = POISSON.FORDELING +PROB = SANNSYNLIG +QUARTILE.EXC = KVARTIL.EKS +QUARTILE.INC = KVARTIL.INK +RANK.AVG = RANG.GJSN +RANK.EQ = RANG.EKV +RSQ = RKVADRAT +SKEW = SKJEVFORDELING +SKEW.P = SKJEVFORDELING.P +SLOPE = STIGNINGSTALL +SMALL = N.MINST +STANDARDIZE = NORMALISER +STDEV.P = STDAV.P +STDEV.S = STDAV.S +STDEVA = STDAVVIKA +STDEVPA = STDAVVIKPA +STEYX = STANDARDFEIL +T.DIST = T.FORDELING +T.DIST.2T = T.FORDELING.2T +T.DIST.RT = T.FORDELING.H +T.INV = T.INV +T.INV.2T = T.INV.2T +T.TEST = T.TEST +TREND = TREND +TRIMMEAN = TRIMMET.GJENNOMSNITT +VAR.P = VARIANS.P +VAR.S = VARIANS.S +VARA = VARIANSA +VARPA = VARIANSPA +WEIBULL.DIST = WEIBULL.DIST.N +Z.TEST = Z.TEST + +## +## Tekstfunksjoner (Text Functions) +## +ASC = STIGENDE +BAHTTEXT = BAHTTEKST +CHAR = TEGNKODE +CLEAN = RENSK +CODE = KODE +CONCAT = KJED.SAMMEN +DOLLAR = VALUTA +EXACT = EKSAKT +FIND = FINN +FIXED = FASTSATT +ISTHAIDIGIT = ERTHAISIFFER +LEFT = VENSTRE +LEN = LENGDE +LOWER = SMÅ +MID = DELTEKST +NUMBERSTRING = TALLSTRENG +NUMBERVALUE = TALLVERDI +PHONETIC = FURIGANA +PROPER = STOR.FORBOKSTAV +REPLACE = ERSTATT +REPT = GJENTA +RIGHT = HØYRE +SEARCH = SØK +SUBSTITUTE = BYTT.UT +T = T +TEXT = TEKST +TEXTJOIN = TEKST.KOMBINER +THAIDIGIT = THAISIFFER +THAINUMSOUND = THAINUMLYD +THAINUMSTRING = THAINUMSTRENG +THAISTRINGLENGTH = THAISTRENGLENGDE +TRIM = TRIMME +UNICHAR = UNICODETEGN +UNICODE = UNICODE +UPPER = STORE +VALUE = VERDI + +## +## Nettfunksjoner (Web Functions) +## +ENCODEURL = URL.KODE +FILTERXML = FILTRERXML +WEBSERVICE = NETTJENESTE + +## +## Kompatibilitetsfunksjoner (Compatibility Functions) +## +BETADIST = BETA.FORDELING +BETAINV = INVERS.BETA.FORDELING +BINOMDIST = BINOM.FORDELING +CEILING = AVRUND.GJELDENDE.MULTIPLUM +CHIDIST = KJI.FORDELING +CHIINV = INVERS.KJI.FORDELING +CHITEST = KJI.TEST +CONCATENATE = KJEDE.SAMMEN +CONFIDENCE = KONFIDENS +COVAR = KOVARIANS +CRITBINOM = GRENSE.BINOM +EXPONDIST = EKSP.FORDELING +FDIST = FFORDELING +FINV = FFORDELING.INVERS +FLOOR = AVRUND.GJELDENDE.MULTIPLUM.NED +FORECAST = PROGNOSE +FTEST = FTEST +GAMMADIST = GAMMAFORDELING +GAMMAINV = GAMMAINV +HYPGEOMDIST = HYPGEOM.FORDELING +LOGINV = LOGINV +LOGNORMDIST = LOGNORMFORD +MODE = MODUS +NEGBINOMDIST = NEGBINOM.FORDELING +NORMDIST = NORMALFORDELING +NORMINV = NORMINV +NORMSDIST = NORMSFORDELING +NORMSINV = NORMSINV +PERCENTILE = PERSENTIL +PERCENTRANK = PROSENTDEL +POISSON = POISSON +QUARTILE = KVARTIL +RANK = RANG +STDEV = STDAV +STDEVP = STDAVP +TDIST = TFORDELING +TINV = TINV +TTEST = TTEST +VAR = VARIANS +VARP = VARIANSP +WEIBULL = WEIBULL.FORDELING +ZTEST = ZTEST diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/nl/config b/PhpOffice/PhpSpreadsheet/Calculation/locale/nl/config old mode 100755 new mode 100644 index 8376022..370567a --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/nl/config +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/nl/config @@ -1,24 +1,20 @@ +############################################################ ## -## PhpSpreadsheet +## PhpSpreadsheet - locale settings ## +## Nederlands (Dutch) +## +############################################################ -ArgumentSeparator = ; - +ArgumentSeparator = ; ## -## (For future use) +## Error Codes ## -currencySymbol = € - - -## -## Excel Error Codes (For future use) - -## -NULL = #LEEG! -DIV0 = #DEEL/0! -VALUE = #WAARDE! -REF = #VERW! -NAME = #NAAM? -NUM = #GETAL! -NA = #N/B +NULL = #LEEG! +DIV0 = #DEEL/0! +VALUE = #WAARDE! +REF = #VERW! +NAME = #NAAM? +NUM = #GETAL! +NA = #N/B diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/nl/functions b/PhpOffice/PhpSpreadsheet/Calculation/locale/nl/functions old mode 100755 new mode 100644 index 2518f42..ce0b30c --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/nl/functions +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/nl/functions @@ -1,416 +1,537 @@ +############################################################ ## -## PhpSpreadsheet -## -## Data in this file derived from https://www.excel-function-translation.com/ +## PhpSpreadsheet - function name translations ## +## Nederlands (Dutch) ## +############################################################ ## -## Add-in and Automation functions Automatiseringsfuncties en functies in invoegtoepassingen +## Kubusfuncties (Cube Functions) ## -GETPIVOTDATA = DRAAITABEL.OPHALEN ## Geeft gegevens uit een draaitabelrapport als resultaat - +CUBEKPIMEMBER = KUBUSKPILID +CUBEMEMBER = KUBUSLID +CUBEMEMBERPROPERTY = KUBUSLIDEIGENSCHAP +CUBERANKEDMEMBER = KUBUSGERANGSCHIKTLID +CUBESET = KUBUSSET +CUBESETCOUNT = KUBUSSETAANTAL +CUBEVALUE = KUBUSWAARDE ## -## Cube functions Kubusfuncties +## Databasefuncties (Database Functions) ## -CUBEKPIMEMBER = KUBUSKPILID ## Retourneert de naam, eigenschap en waarde van een KPI (prestatie-indicator) en geeft de naam en de eigenschap in de cel weer. Een KPI is een meetbare waarde, zoals de maandelijkse brutowinst of de omzet per kwartaal per werknemer, die wordt gebruikt om de prestaties van een organisatie te bewaken -CUBEMEMBER = KUBUSLID ## Retourneert een lid of tupel in een kubushiërarchie. Wordt gebruikt om te controleren of het lid of de tupel in de kubus aanwezig is -CUBEMEMBERPROPERTY = KUBUSLIDEIGENSCHAP ## Retourneert de waarde van een lideigenschap in de kubus. Wordt gebruikt om te controleren of de lidnaam in de kubus bestaat en retourneert de opgegeven eigenschap voor dit lid -CUBERANKEDMEMBER = KUBUSGERANGCHIKTLID ## Retourneert het zoveelste, gerangschikte lid in een set. Wordt gebruikt om een of meer elementen in een set te retourneren, zoals de tien beste verkopers of de tien beste studenten -CUBESET = KUBUSSET ## Definieert een berekende set leden of tupels door een ingestelde expressie naar de kubus op de server te sturen, alwaar de set wordt gemaakt en vervolgens wordt geretourneerd naar Microsoft Office Excel -CUBESETCOUNT = KUBUSSETAANTAL ## Retourneert het aantal onderdelen in een set -CUBEVALUE = KUBUSWAARDE ## Retourneert een samengestelde waarde van een kubus - +DAVERAGE = DBGEMIDDELDE +DCOUNT = DBAANTAL +DCOUNTA = DBAANTALC +DGET = DBLEZEN +DMAX = DBMAX +DMIN = DBMIN +DPRODUCT = DBPRODUCT +DSTDEV = DBSTDEV +DSTDEVP = DBSTDEVP +DSUM = DBSOM +DVAR = DBVAR +DVARP = DBVARP ## -## Database functions Databasefuncties +## Datum- en tijdfuncties (Date & Time Functions) ## -DAVERAGE = DBGEMIDDELDE ## Berekent de gemiddelde waarde in geselecteerde databasegegevens -DCOUNT = DBAANTAL ## Telt de cellen met getallen in een database -DCOUNTA = DBAANTALC ## Telt de niet-lege cellen in een database -DGET = DBLEZEN ## Retourneert één record dat voldoet aan de opgegeven criteria uit een database -DMAX = DBMAX ## Retourneert de maximumwaarde in de geselecteerde databasegegevens -DMIN = DBMIN ## Retourneert de minimumwaarde in de geselecteerde databasegegevens -DPRODUCT = DBPRODUCT ## Vermenigvuldigt de waarden in een bepaald veld van de records die voldoen aan de criteria in een database -DSTDEV = DBSTDEV ## Maakt een schatting van de standaarddeviatie op basis van een steekproef uit geselecteerde databasegegevens -DSTDEVP = DBSTDEVP ## Berekent de standaarddeviatie op basis van de volledige populatie van geselecteerde databasegegevens -DSUM = DBSOM ## Telt de getallen uit een kolom records in de database op die voldoen aan de criteria -DVAR = DBVAR ## Maakt een schatting van de variantie op basis van een steekproef uit geselecteerde databasegegevens -DVARP = DBVARP ## Berekent de variantie op basis van de volledige populatie van geselecteerde databasegegevens - +DATE = DATUM +DATESTRING = DATUMNOTATIE +DATEVALUE = DATUMWAARDE +DAY = DAG +DAYS = DAGEN +DAYS360 = DAGEN360 +EDATE = ZELFDE.DAG +EOMONTH = LAATSTE.DAG +HOUR = UUR +ISOWEEKNUM = ISO.WEEKNUMMER +MINUTE = MINUUT +MONTH = MAAND +NETWORKDAYS = NETTO.WERKDAGEN +NETWORKDAYS.INTL = NETWERKDAGEN.INTL +NOW = NU +SECOND = SECONDE +THAIDAYOFWEEK = THAIS.WEEKDAG +THAIMONTHOFYEAR = THAIS.MAAND.VAN.JAAR +THAIYEAR = THAIS.JAAR +TIME = TIJD +TIMEVALUE = TIJDWAARDE +TODAY = VANDAAG +WEEKDAY = WEEKDAG +WEEKNUM = WEEKNUMMER +WORKDAY = WERKDAG +WORKDAY.INTL = WERKDAG.INTL +YEAR = JAAR +YEARFRAC = JAAR.DEEL ## -## Date and time functions Datum- en tijdfuncties +## Technische functies (Engineering Functions) ## -DATE = DATUM ## Geeft als resultaat het seriële getal van een opgegeven datum -DATEVALUE = DATUMWAARDE ## Converteert een datum in de vorm van tekst naar een serieel getal -DAY = DAG ## Converteert een serieel getal naar een dag van de maand -DAYS360 = DAGEN360 ## Berekent het aantal dagen tussen twee datums op basis van een jaar met 360 dagen -EDATE = ZELFDE.DAG ## Geeft als resultaat het seriële getal van een datum die het opgegeven aantal maanden voor of na de begindatum ligt -EOMONTH = LAATSTE.DAG ## Geeft als resultaat het seriële getal van de laatste dag van de maand voor of na het opgegeven aantal maanden -HOUR = UUR ## Converteert een serieel getal naar uren -MINUTE = MINUUT ## Converteert een serieel naar getal minuten -MONTH = MAAND ## Converteert een serieel getal naar een maand -NETWORKDAYS = NETTO.WERKDAGEN ## Geeft als resultaat het aantal hele werkdagen tussen twee datums -NOW = NU ## Geeft als resultaat het seriële getal van de huidige datum en tijd -SECOND = SECONDE ## Converteert een serieel getal naar seconden -TIME = TIJD ## Geeft als resultaat het seriële getal van een bepaald tijdstip -TIMEVALUE = TIJDWAARDE ## Converteert de tijd in de vorm van tekst naar een serieel getal -TODAY = VANDAAG ## Geeft als resultaat het seriële getal van de huidige datum -WEEKDAY = WEEKDAG ## Converteert een serieel getal naar een weekdag -WEEKNUM = WEEKNUMMER ## Converteert een serieel getal naar een weeknummer -WORKDAY = WERKDAG ## Geeft als resultaat het seriële getal van de datum voor of na een bepaald aantal werkdagen -YEAR = JAAR ## Converteert een serieel getal naar een jaar -YEARFRAC = JAAR.DEEL ## Geeft als resultaat het gedeelte van het jaar, uitgedrukt in het aantal hele dagen tussen begindatum en einddatum - +BESSELI = BESSEL.I +BESSELJ = BESSEL.J +BESSELK = BESSEL.K +BESSELY = BESSEL.Y +BIN2DEC = BIN.N.DEC +BIN2HEX = BIN.N.HEX +BIN2OCT = BIN.N.OCT +BITAND = BIT.EN +BITLSHIFT = BIT.VERSCHUIF.LINKS +BITOR = BIT.OF +BITRSHIFT = BIT.VERSCHUIF.RECHTS +BITXOR = BIT.EX.OF +COMPLEX = COMPLEX +CONVERT = CONVERTEREN +DEC2BIN = DEC.N.BIN +DEC2HEX = DEC.N.HEX +DEC2OCT = DEC.N.OCT +DELTA = DELTA +ERF = FOUTFUNCTIE +ERF.PRECISE = FOUTFUNCTIE.NAUWKEURIG +ERFC = FOUT.COMPLEMENT +ERFC.PRECISE = FOUT.COMPLEMENT.NAUWKEURIG +GESTEP = GROTER.DAN +HEX2BIN = HEX.N.BIN +HEX2DEC = HEX.N.DEC +HEX2OCT = HEX.N.OCT +IMABS = C.ABS +IMAGINARY = C.IM.DEEL +IMARGUMENT = C.ARGUMENT +IMCONJUGATE = C.TOEGEVOEGD +IMCOS = C.COS +IMCOSH = C.COSH +IMCOT = C.COT +IMCSC = C.COSEC +IMCSCH = C.COSECH +IMDIV = C.QUOTIENT +IMEXP = C.EXP +IMLN = C.LN +IMLOG10 = C.LOG10 +IMLOG2 = C.LOG2 +IMPOWER = C.MACHT +IMPRODUCT = C.PRODUCT +IMREAL = C.REEEL.DEEL +IMSEC = C.SEC +IMSECH = C.SECH +IMSIN = C.SIN +IMSINH = C.SINH +IMSQRT = C.WORTEL +IMSUB = C.VERSCHIL +IMSUM = C.SOM +IMTAN = C.TAN +OCT2BIN = OCT.N.BIN +OCT2DEC = OCT.N.DEC +OCT2HEX = OCT.N.HEX ## -## Engineering functions Technische functies +## Financiële functies (Financial Functions) ## -BESSELI = BESSEL.Y ## Geeft als resultaat de gewijzigde Bessel-functie In(x) -BESSELJ = BESSEL.J ## Geeft als resultaat de Bessel-functie Jn(x) -BESSELK = BESSEL.K ## Geeft als resultaat de gewijzigde Bessel-functie Kn(x) -BESSELY = BESSEL.Y ## Geeft als resultaat de gewijzigde Bessel-functie Yn(x) -BIN2DEC = BIN.N.DEC ## Converteert een binair getal naar een decimaal getal -BIN2HEX = BIN.N.HEX ## Converteert een binair getal naar een hexadecimaal getal -BIN2OCT = BIN.N.OCT ## Converteert een binair getal naar een octaal getal -COMPLEX = COMPLEX ## Converteert reële en imaginaire coëfficiënten naar een complex getal -CONVERT = CONVERTEREN ## Converteert een getal in de ene maateenheid naar een getal in een andere maateenheid -DEC2BIN = DEC.N.BIN ## Converteert een decimaal getal naar een binair getal -DEC2HEX = DEC.N.HEX ## Converteert een decimaal getal naar een hexadecimaal getal -DEC2OCT = DEC.N.OCT ## Converteert een decimaal getal naar een octaal getal -DELTA = DELTA ## Test of twee waarden gelijk zijn -ERF = FOUTFUNCTIE ## Geeft als resultaat de foutfunctie -ERFC = FOUT.COMPLEMENT ## Geeft als resultaat de complementaire foutfunctie -GESTEP = GROTER.DAN ## Test of een getal groter is dan de drempelwaarde -HEX2BIN = HEX.N.BIN ## Converteert een hexadecimaal getal naar een binair getal -HEX2DEC = HEX.N.DEC ## Converteert een hexadecimaal getal naar een decimaal getal -HEX2OCT = HEX.N.OCT ## Converteert een hexadecimaal getal naar een octaal getal -IMABS = C.ABS ## Geeft als resultaat de absolute waarde (modulus) van een complex getal -IMAGINARY = C.IM.DEEL ## Geeft als resultaat de imaginaire coëfficiënt van een complex getal -IMARGUMENT = C.ARGUMENT ## Geeft als resultaat het argument thèta, een hoek uitgedrukt in radialen -IMCONJUGATE = C.TOEGEVOEGD ## Geeft als resultaat het complexe toegevoegde getal van een complex getal -IMCOS = C.COS ## Geeft als resultaat de cosinus van een complex getal -IMDIV = C.QUOTIENT ## Geeft als resultaat het quotiënt van twee complexe getallen -IMEXP = C.EXP ## Geeft als resultaat de exponent van een complex getal -IMLN = C.LN ## Geeft als resultaat de natuurlijke logaritme van een complex getal -IMLOG10 = C.LOG10 ## Geeft als resultaat de logaritme met grondtal 10 van een complex getal -IMLOG2 = C.LOG2 ## Geeft als resultaat de logaritme met grondtal 2 van een complex getal -IMPOWER = C.MACHT ## Geeft als resultaat een complex getal dat is verheven tot de macht van een geheel getal -IMPRODUCT = C.PRODUCT ## Geeft als resultaat het product van complexe getallen -IMREAL = C.REEEL.DEEL ## Geeft als resultaat de reële coëfficiënt van een complex getal -IMSIN = C.SIN ## Geeft als resultaat de sinus van een complex getal -IMSQRT = C.WORTEL ## Geeft als resultaat de vierkantswortel van een complex getal -IMSUB = C.VERSCHIL ## Geeft als resultaat het verschil tussen twee complexe getallen -IMSUM = C.SOM ## Geeft als resultaat de som van complexe getallen -OCT2BIN = OCT.N.BIN ## Converteert een octaal getal naar een binair getal -OCT2DEC = OCT.N.DEC ## Converteert een octaal getal naar een decimaal getal -OCT2HEX = OCT.N.HEX ## Converteert een octaal getal naar een hexadecimaal getal - +ACCRINT = SAMENG.RENTE +ACCRINTM = SAMENG.RENTE.V +AMORDEGRC = AMORDEGRC +AMORLINC = AMORLINC +COUPDAYBS = COUP.DAGEN.BB +COUPDAYS = COUP.DAGEN +COUPDAYSNC = COUP.DAGEN.VV +COUPNCD = COUP.DATUM.NB +COUPNUM = COUP.AANTAL +COUPPCD = COUP.DATUM.VB +CUMIPMT = CUM.RENTE +CUMPRINC = CUM.HOOFDSOM +DB = DB +DDB = DDB +DISC = DISCONTO +DOLLARDE = EURO.DE +DOLLARFR = EURO.BR +DURATION = DUUR +EFFECT = EFFECT.RENTE +FV = TW +FVSCHEDULE = TOEK.WAARDE2 +INTRATE = RENTEPERCENTAGE +IPMT = IBET +IRR = IR +ISPMT = ISBET +MDURATION = AANG.DUUR +MIRR = GIR +NOMINAL = NOMINALE.RENTE +NPER = NPER +NPV = NHW +ODDFPRICE = AFW.ET.PRIJS +ODDFYIELD = AFW.ET.REND +ODDLPRICE = AFW.LT.PRIJS +ODDLYIELD = AFW.LT.REND +PDURATION = PDUUR +PMT = BET +PPMT = PBET +PRICE = PRIJS.NOM +PRICEDISC = PRIJS.DISCONTO +PRICEMAT = PRIJS.VERVALDAG +PV = HW +RATE = RENTE +RECEIVED = OPBRENGST +RRI = RRI +SLN = LIN.AFSCHR +SYD = SYD +TBILLEQ = SCHATK.OBL +TBILLPRICE = SCHATK.PRIJS +TBILLYIELD = SCHATK.REND +VDB = VDB +XIRR = IR.SCHEMA +XNPV = NHW2 +YIELD = RENDEMENT +YIELDDISC = REND.DISCONTO +YIELDMAT = REND.VERVAL ## -## Financial functions Financiële functies +## Informatiefuncties (Information Functions) ## -ACCRINT = SAMENG.RENTE ## Berekent de opgelopen rente voor een waardepapier waarvan de rente periodiek wordt uitgekeerd -ACCRINTM = SAMENG.RENTE.V ## Berekent de opgelopen rente voor een waardepapier waarvan de rente op de vervaldatum wordt uitgekeerd -AMORDEGRC = AMORDEGRC ## Geeft als resultaat de afschrijving voor elke boekingsperiode door een afschrijvingscoëfficiënt toe te passen -AMORLINC = AMORLINC ## Berekent de afschrijving voor elke boekingsperiode -COUPDAYBS = COUP.DAGEN.BB ## Berekent het aantal dagen vanaf het begin van de coupontermijn tot de stortingsdatum -COUPDAYS = COUP.DAGEN ## Geeft als resultaat het aantal dagen in de coupontermijn waarin de stortingsdatum valt -COUPDAYSNC = COUP.DAGEN.VV ## Geeft als resultaat het aantal dagen vanaf de stortingsdatum tot de volgende couponvervaldatum -COUPNCD = COUP.DATUM.NB ## Geeft als resultaat de volgende coupondatum na de stortingsdatum -COUPNUM = COUP.AANTAL ## Geeft als resultaat het aantal coupons dat nog moet worden uitbetaald tussen de stortingsdatum en de vervaldatum -COUPPCD = COUP.DATUM.VB ## Geeft als resultaat de vorige couponvervaldatum vóór de stortingsdatum -CUMIPMT = CUM.RENTE ## Geeft als resultaat de cumulatieve rente die tussen twee termijnen is uitgekeerd -CUMPRINC = CUM.HOOFDSOM ## Geeft als resultaat de cumulatieve hoofdsom van een lening die tussen twee termijnen is terugbetaald -DB = DB ## Geeft als resultaat de afschrijving van activa voor een bepaalde periode met behulp van de 'fixed declining balance'-methode -DDB = DDB ## Geeft als resultaat de afschrijving van activa over een bepaalde termijn met behulp van de 'double declining balance'-methode of een andere methode die u opgeeft -DISC = DISCONTO ## Geeft als resultaat het discontopercentage voor een waardepapier -DOLLARDE = EURO.DE ## Converteert een prijs in euro's, uitgedrukt in een breuk, naar een prijs in euro's, uitgedrukt in een decimaal getal -DOLLARFR = EURO.BR ## Converteert een prijs in euro's, uitgedrukt in een decimaal getal, naar een prijs in euro's, uitgedrukt in een breuk -DURATION = DUUR ## Geeft als resultaat de gewogen gemiddelde looptijd voor een waardepapier met periodieke rentebetalingen -EFFECT = EFFECT.RENTE ## Geeft als resultaat het effectieve jaarlijkse rentepercentage -FV = TW ## Geeft als resultaat de toekomstige waarde van een investering -FVSCHEDULE = TOEK.WAARDE2 ## Geeft als resultaat de toekomstige waarde van een bepaalde hoofdsom na het toepassen van een reeks samengestelde rentepercentages -INTRATE = RENTEPERCENTAGE ## Geeft als resultaat het rentepercentage voor een volgestort waardepapier -IPMT = IBET ## Geeft als resultaat de te betalen rente voor een investering over een bepaalde termijn -IRR = IR ## Geeft als resultaat de interne rentabiliteit voor een reeks cashflows -ISPMT = ISBET ## Geeft als resultaat de rente die is betaald tijdens een bepaalde termijn van een investering -MDURATION = AANG.DUUR ## Geeft als resultaat de aangepaste Macauley-looptijd voor een waardepapier, aangenomen dat de nominale waarde € 100 bedraagt -MIRR = GIR ## Geeft als resultaat de interne rentabiliteit voor een serie cashflows, waarbij voor betalingen een ander rentepercentage geldt dan voor inkomsten -NOMINAL = NOMINALE.RENTE ## Geeft als resultaat het nominale jaarlijkse rentepercentage -NPER = NPER ## Geeft als resultaat het aantal termijnen van een investering -NPV = NHW ## Geeft als resultaat de netto huidige waarde van een investering op basis van een reeks periodieke cashflows en een discontopercentage -ODDFPRICE = AFW.ET.PRIJS ## Geeft als resultaat de prijs per € 100 nominale waarde voor een waardepapier met een afwijkende eerste termijn -ODDFYIELD = AFW.ET.REND ## Geeft als resultaat het rendement voor een waardepapier met een afwijkende eerste termijn -ODDLPRICE = AFW.LT.PRIJS ## Geeft als resultaat de prijs per € 100 nominale waarde voor een waardepapier met een afwijkende laatste termijn -ODDLYIELD = AFW.LT.REND ## Geeft als resultaat het rendement voor een waardepapier met een afwijkende laatste termijn -PMT = BET ## Geeft als resultaat de periodieke betaling voor een annuïteit -PPMT = PBET ## Geeft als resultaat de afbetaling op de hoofdsom voor een bepaalde termijn -PRICE = PRIJS.NOM ## Geeft als resultaat de prijs per € 100 nominale waarde voor een waardepapier waarvan de rente periodiek wordt uitgekeerd -PRICEDISC = PRIJS.DISCONTO ## Geeft als resultaat de prijs per € 100 nominale waarde voor een verdisconteerd waardepapier -PRICEMAT = PRIJS.VERVALDAG ## Geeft als resultaat de prijs per € 100 nominale waarde voor een waardepapier waarvan de rente wordt uitgekeerd op de vervaldatum -PV = HW ## Geeft als resultaat de huidige waarde van een investering -RATE = RENTE ## Geeft als resultaat het periodieke rentepercentage voor een annuïteit -RECEIVED = OPBRENGST ## Geeft als resultaat het bedrag dat op de vervaldatum wordt uitgekeerd voor een volgestort waardepapier -SLN = LIN.AFSCHR ## Geeft als resultaat de lineaire afschrijving van activa over één termijn -SYD = SYD ## Geeft als resultaat de afschrijving van activa over een bepaalde termijn met behulp van de 'Sum-Of-Years-Digits'-methode -TBILLEQ = SCHATK.OBL ## Geeft als resultaat het rendement op schatkistpapier, dat op dezelfde manier wordt berekend als het rendement op obligaties -TBILLPRICE = SCHATK.PRIJS ## Bepaalt de prijs per € 100 nominale waarde voor schatkistpapier -TBILLYIELD = SCHATK.REND ## Berekent het rendement voor schatkistpapier -VDB = VDB ## Geeft als resultaat de afschrijving van activa over een gehele of gedeeltelijke termijn met behulp van de 'declining balance'-methode -XIRR = IR.SCHEMA ## Berekent de interne rentabiliteit voor een betalingsschema van cashflows -XNPV = NHW2 ## Berekent de huidige nettowaarde voor een betalingsschema van cashflows -YIELD = RENDEMENT ## Geeft als resultaat het rendement voor een waardepapier waarvan de rente periodiek wordt uitgekeerd -YIELDDISC = REND.DISCONTO ## Geeft als resultaat het jaarlijkse rendement voor een verdisconteerd waardepapier, bijvoorbeeld schatkistpapier -YIELDMAT = REND.VERVAL ## Geeft als resultaat het jaarlijkse rendement voor een waardepapier waarvan de rente wordt uitgekeerd op de vervaldatum - +CELL = CEL +ERROR.TYPE = TYPE.FOUT +INFO = INFO +ISBLANK = ISLEEG +ISERR = ISFOUT2 +ISERROR = ISFOUT +ISEVEN = IS.EVEN +ISFORMULA = ISFORMULE +ISLOGICAL = ISLOGISCH +ISNA = ISNB +ISNONTEXT = ISGEENTEKST +ISNUMBER = ISGETAL +ISODD = IS.ONEVEN +ISREF = ISVERWIJZING +ISTEXT = ISTEKST +N = N +NA = NB +SHEET = BLAD +SHEETS = BLADEN +TYPE = TYPE ## -## Information functions Informatiefuncties +## Logische functies (Logical Functions) ## -CELL = CEL ## Geeft als resultaat informatie over de opmaak, locatie of inhoud van een cel -ERROR.TYPE = TYPE.FOUT ## Geeft als resultaat een getal dat overeenkomt met een van de foutwaarden van Microsoft Excel -INFO = INFO ## Geeft als resultaat informatie over de huidige besturingsomgeving -ISBLANK = ISLEEG ## Geeft als resultaat WAAR als de waarde leeg is -ISERR = ISFOUT2 ## Geeft als resultaat WAAR als de waarde een foutwaarde is, met uitzondering van #N/B -ISERROR = ISFOUT ## Geeft als resultaat WAAR als de waarde een foutwaarde is -ISEVEN = IS.EVEN ## Geeft als resultaat WAAR als het getal even is -ISLOGICAL = ISLOGISCH ## Geeft als resultaat WAAR als de waarde een logische waarde is -ISNA = ISNB ## Geeft als resultaat WAAR als de waarde de foutwaarde #N/B is -ISNONTEXT = ISGEENTEKST ## Geeft als resultaat WAAR als de waarde geen tekst is -ISNUMBER = ISGETAL ## Geeft als resultaat WAAR als de waarde een getal is -ISODD = IS.ONEVEN ## Geeft als resultaat WAAR als het getal oneven is -ISREF = ISVERWIJZING ## Geeft als resultaat WAAR als de waarde een verwijzing is -ISTEXT = ISTEKST ## Geeft als resultaat WAAR als de waarde tekst is -N = N ## Geeft als resultaat een waarde die is geconverteerd naar een getal -NA = NB ## Geeft als resultaat de foutwaarde #N/B -TYPE = TYPE ## Geeft als resultaat een getal dat het gegevenstype van een waarde aangeeft - +AND = EN +FALSE = ONWAAR +IF = ALS +IFERROR = ALS.FOUT +IFNA = ALS.NB +IFS = ALS.VOORWAARDEN +NOT = NIET +OR = OF +SWITCH = SCHAKELEN +TRUE = WAAR +XOR = EX.OF ## -## Logical functions Logische functies +## Zoek- en verwijzingsfuncties (Lookup & Reference Functions) ## -AND = EN ## Geeft als resultaat WAAR als alle argumenten WAAR zijn -FALSE = ONWAAR ## Geeft als resultaat de logische waarde ONWAAR -IF = ALS ## Geeft een logische test aan -IFERROR = ALS.FOUT ## Retourneert een waarde die u opgeeft als een formule een fout oplevert, anders wordt het resultaat van de formule geretourneerd -NOT = NIET ## Keert de logische waarde van het argument om -OR = OF ## Geeft als resultaat WAAR als minimaal een van de argumenten WAAR is -TRUE = WAAR ## Geeft als resultaat de logische waarde WAAR - +ADDRESS = ADRES +AREAS = BEREIKEN +CHOOSE = KIEZEN +COLUMN = KOLOM +COLUMNS = KOLOMMEN +FORMULATEXT = FORMULETEKST +GETPIVOTDATA = DRAAITABEL.OPHALEN +HLOOKUP = HORIZ.ZOEKEN +HYPERLINK = HYPERLINK +INDEX = INDEX +INDIRECT = INDIRECT +LOOKUP = ZOEKEN +MATCH = VERGELIJKEN +OFFSET = VERSCHUIVING +ROW = RIJ +ROWS = RIJEN +RTD = RTG +TRANSPOSE = TRANSPONEREN +VLOOKUP = VERT.ZOEKEN +*RC = RK ## -## Lookup and reference functions Zoek- en verwijzingsfuncties +## Wiskundige en trigonometrische functies (Math & Trig Functions) ## -ADDRESS = ADRES ## Geeft als resultaat een verwijzing, in de vorm van tekst, naar één bepaalde cel in een werkblad -AREAS = BEREIKEN ## Geeft als resultaat het aantal bereiken in een verwijzing -CHOOSE = KIEZEN ## Kiest een waarde uit een lijst met waarden -COLUMN = KOLOM ## Geeft als resultaat het kolomnummer van een verwijzing -COLUMNS = KOLOMMEN ## Geeft als resultaat het aantal kolommen in een verwijzing -HLOOKUP = HORIZ.ZOEKEN ## Zoekt in de bovenste rij van een matrix naar een bepaalde waarde en geeft als resultaat de gevonden waarde in de opgegeven cel -HYPERLINK = HYPERLINK ## Maakt een snelkoppeling of een sprong waarmee een document wordt geopend dat is opgeslagen op een netwerkserver, een intranet of op internet -INDEX = INDEX ## Kiest met een index een waarde uit een verwijzing of een matrix -INDIRECT = INDIRECT ## Geeft als resultaat een verwijzing die wordt aangegeven met een tekstwaarde -LOOKUP = ZOEKEN ## Zoekt naar bepaalde waarden in een vector of een matrix -MATCH = VERGELIJKEN ## Zoekt naar bepaalde waarden in een verwijzing of een matrix -OFFSET = VERSCHUIVING ## Geeft als resultaat een nieuwe verwijzing die is verschoven ten opzichte van een bepaalde verwijzing -ROW = RIJ ## Geeft als resultaat het rijnummer van een verwijzing -ROWS = RIJEN ## Geeft als resultaat het aantal rijen in een verwijzing -RTD = RTG ## Haalt realtimegegevens op uit een programma dat COM-automatisering (automatisering: een methode waarmee de ene toepassing objecten van een andere toepassing of ontwikkelprogramma kan besturen. Automatisering werd vroeger OLE-automatisering genoemd. Automatisering is een industrienorm die deel uitmaakt van het Component Object Model (COM).) ondersteunt -TRANSPOSE = TRANSPONEREN ## Geeft als resultaat de getransponeerde van een matrix -VLOOKUP = VERT.ZOEKEN ## Zoekt in de meest linkse kolom van een matrix naar een bepaalde waarde en geeft als resultaat de waarde in de opgegeven cel - +ABS = ABS +ACOS = BOOGCOS +ACOSH = BOOGCOSH +ACOT = BOOGCOT +ACOTH = BOOGCOTH +AGGREGATE = AGGREGAAT +ARABIC = ARABISCH +ASIN = BOOGSIN +ASINH = BOOGSINH +ATAN = BOOGTAN +ATAN2 = BOOGTAN2 +ATANH = BOOGTANH +BASE = BASIS +CEILING.MATH = AFRONDEN.BOVEN.WISK +CEILING.PRECISE = AFRONDEN.BOVEN.NAUWKEURIG +COMBIN = COMBINATIES +COMBINA = COMBIN.A +COS = COS +COSH = COSH +COT = COT +COTH = COTH +CSC = COSEC +CSCH = COSECH +DECIMAL = DECIMAAL +DEGREES = GRADEN +ECMA.CEILING = ECMA.AFRONDEN.BOVEN +EVEN = EVEN +EXP = EXP +FACT = FACULTEIT +FACTDOUBLE = DUBBELE.FACULTEIT +FLOOR.MATH = AFRONDEN.BENEDEN.WISK +FLOOR.PRECISE = AFRONDEN.BENEDEN.NAUWKEURIG +GCD = GGD +INT = INTEGER +ISO.CEILING = ISO.AFRONDEN.BOVEN +LCM = KGV +LN = LN +LOG = LOG +LOG10 = LOG10 +MDETERM = DETERMINANTMAT +MINVERSE = INVERSEMAT +MMULT = PRODUCTMAT +MOD = REST +MROUND = AFRONDEN.N.VEELVOUD +MULTINOMIAL = MULTINOMIAAL +MUNIT = EENHEIDMAT +ODD = ONEVEN +PI = PI +POWER = MACHT +PRODUCT = PRODUCT +QUOTIENT = QUOTIENT +RADIANS = RADIALEN +RAND = ASELECT +RANDBETWEEN = ASELECTTUSSEN +ROMAN = ROMEINS +ROUND = AFRONDEN +ROUNDBAHTDOWN = BAHT.AFR.NAAR.BENEDEN +ROUNDBAHTUP = BAHT.AFR.NAAR.BOVEN +ROUNDDOWN = AFRONDEN.NAAR.BENEDEN +ROUNDUP = AFRONDEN.NAAR.BOVEN +SEC = SEC +SECH = SECH +SERIESSUM = SOM.MACHTREEKS +SIGN = POS.NEG +SIN = SIN +SINH = SINH +SQRT = WORTEL +SQRTPI = WORTEL.PI +SUBTOTAL = SUBTOTAAL +SUM = SOM +SUMIF = SOM.ALS +SUMIFS = SOMMEN.ALS +SUMPRODUCT = SOMPRODUCT +SUMSQ = KWADRATENSOM +SUMX2MY2 = SOM.X2MINY2 +SUMX2PY2 = SOM.X2PLUSY2 +SUMXMY2 = SOM.XMINY.2 +TAN = TAN +TANH = TANH +TRUNC = GEHEEL ## -## Math and trigonometry functions Wiskundige en trigonometrische functies +## Statistische functies (Statistical Functions) ## -ABS = ABS ## Geeft als resultaat de absolute waarde van een getal -ACOS = BOOGCOS ## Geeft als resultaat de boogcosinus van een getal -ACOSH = BOOGCOSH ## Geeft als resultaat de inverse cosinus hyperbolicus van een getal -ASIN = BOOGSIN ## Geeft als resultaat de boogsinus van een getal -ASINH = BOOGSINH ## Geeft als resultaat de inverse sinus hyperbolicus van een getal -ATAN = BOOGTAN ## Geeft als resultaat de boogtangens van een getal -ATAN2 = BOOGTAN2 ## Geeft als resultaat de boogtangens van de x- en y-coördinaten -ATANH = BOOGTANH ## Geeft als resultaat de inverse tangens hyperbolicus van een getal -CEILING = AFRONDEN.BOVEN ## Rondt de absolute waarde van een getal naar boven af op het dichtstbijzijnde gehele getal of het dichtstbijzijnde significante veelvoud -COMBIN = COMBINATIES ## Geeft als resultaat het aantal combinaties voor een bepaald aantal objecten -COS = COS ## Geeft als resultaat de cosinus van een getal -COSH = COSH ## Geeft als resultaat de cosinus hyperbolicus van een getal -DEGREES = GRADEN ## Converteert radialen naar graden -EVEN = EVEN ## Rondt het getal af op het dichtstbijzijnde gehele even getal -EXP = EXP ## Verheft e tot de macht van een bepaald getal -FACT = FACULTEIT ## Geeft als resultaat de faculteit van een getal -FACTDOUBLE = DUBBELE.FACULTEIT ## Geeft als resultaat de dubbele faculteit van een getal -FLOOR = AFRONDEN.BENEDEN ## Rondt de absolute waarde van een getal naar beneden af -GCD = GGD ## Geeft als resultaat de grootste gemene deler -INT = INTEGER ## Rondt een getal naar beneden af op het dichtstbijzijnde gehele getal -LCM = KGV ## Geeft als resultaat het kleinste gemene veelvoud -LN = LN ## Geeft als resultaat de natuurlijke logaritme van een getal -LOG = LOG ## Geeft als resultaat de logaritme met het opgegeven grondtal van een getal -LOG10 = LOG10 ## Geeft als resultaat de logaritme met grondtal 10 van een getal -MDETERM = DETERMINANTMAT ## Geeft als resultaat de determinant van een matrix -MINVERSE = INVERSEMAT ## Geeft als resultaat de inverse van een matrix -MMULT = PRODUCTMAT ## Geeft als resultaat het product van twee matrices -MOD = REST ## Geeft als resultaat het restgetal van een deling -MROUND = AFRONDEN.N.VEELVOUD ## Geeft als resultaat een getal afgerond op het gewenste veelvoud -MULTINOMIAL = MULTINOMIAAL ## Geeft als resultaat de multinomiaalcoëfficiënt van een reeks getallen -ODD = ONEVEN ## Rondt de absolute waarde van het getal naar boven af op het dichtstbijzijnde gehele oneven getal -PI = PI ## Geeft als resultaat de waarde van pi -POWER = MACHT ## Verheft een getal tot een macht -PRODUCT = PRODUCT ## Vermenigvuldigt de argumenten met elkaar -QUOTIENT = QUOTIENT ## Geeft als resultaat de uitkomst van een deling als geheel getal -RADIANS = RADIALEN ## Converteert graden naar radialen -RAND = ASELECT ## Geeft als resultaat een willekeurig getal tussen 0 en 1 -RANDBETWEEN = ASELECTTUSSEN ## Geeft een willekeurig getal tussen de getallen die u hebt opgegeven -ROMAN = ROMEINS ## Converteert een Arabisch getal naar een Romeins getal en geeft het resultaat weer in de vorm van tekst -ROUND = AFRONDEN ## Rondt een getal af op het opgegeven aantal decimalen -ROUNDDOWN = AFRONDEN.NAAR.BENEDEN ## Rondt de absolute waarde van een getal naar beneden af -ROUNDUP = AFRONDEN.NAAR.BOVEN ## Rondt de absolute waarde van een getal naar boven af -SERIESSUM = SOM.MACHTREEKS ## Geeft als resultaat de som van een machtreeks die is gebaseerd op de formule -SIGN = POS.NEG ## Geeft als resultaat het teken van een getal -SIN = SIN ## Geeft als resultaat de sinus van de opgegeven hoek -SINH = SINH ## Geeft als resultaat de sinus hyperbolicus van een getal -SQRT = WORTEL ## Geeft als resultaat de positieve vierkantswortel van een getal -SQRTPI = WORTEL.PI ## Geeft als resultaat de vierkantswortel van (getal * pi) -SUBTOTAL = SUBTOTAAL ## Geeft als resultaat een subtotaal voor een bereik -SUM = SOM ## Telt de argumenten op -SUMIF = SOM.ALS ## Telt de getallen bij elkaar op die voldoen aan een bepaald criterium -SUMIFS = SOMMEN.ALS ## Telt de cellen in een bereik op die aan meerdere criteria voldoen -SUMPRODUCT = SOMPRODUCT ## Geeft als resultaat de som van de producten van de corresponderende matrixelementen -SUMSQ = KWADRATENSOM ## Geeft als resultaat de som van de kwadraten van de argumenten -SUMX2MY2 = SOM.X2MINY2 ## Geeft als resultaat de som van het verschil tussen de kwadraten van corresponderende waarden in twee matrices -SUMX2PY2 = SOM.X2PLUSY2 ## Geeft als resultaat de som van de kwadratensom van corresponderende waarden in twee matrices -SUMXMY2 = SOM.XMINY.2 ## Geeft als resultaat de som van de kwadraten van de verschillen tussen de corresponderende waarden in twee matrices -TAN = TAN ## Geeft als resultaat de tangens van een getal -TANH = TANH ## Geeft als resultaat de tangens hyperbolicus van een getal -TRUNC = GEHEEL ## Kapt een getal af tot een geheel getal - +AVEDEV = GEM.DEVIATIE +AVERAGE = GEMIDDELDE +AVERAGEA = GEMIDDELDEA +AVERAGEIF = GEMIDDELDE.ALS +AVERAGEIFS = GEMIDDELDEN.ALS +BETA.DIST = BETA.VERD +BETA.INV = BETA.INV +BINOM.DIST = BINOM.VERD +BINOM.DIST.RANGE = BINOM.VERD.BEREIK +BINOM.INV = BINOMIALE.INV +CHISQ.DIST = CHIKW.VERD +CHISQ.DIST.RT = CHIKW.VERD.RECHTS +CHISQ.INV = CHIKW.INV +CHISQ.INV.RT = CHIKW.INV.RECHTS +CHISQ.TEST = CHIKW.TEST +CONFIDENCE.NORM = VERTROUWELIJKHEID.NORM +CONFIDENCE.T = VERTROUWELIJKHEID.T +CORREL = CORRELATIE +COUNT = AANTAL +COUNTA = AANTALARG +COUNTBLANK = AANTAL.LEGE.CELLEN +COUNTIF = AANTAL.ALS +COUNTIFS = AANTALLEN.ALS +COVARIANCE.P = COVARIANTIE.P +COVARIANCE.S = COVARIANTIE.S +DEVSQ = DEV.KWAD +EXPON.DIST = EXPON.VERD.N +F.DIST = F.VERD +F.DIST.RT = F.VERD.RECHTS +F.INV = F.INV +F.INV.RT = F.INV.RECHTS +F.TEST = F.TEST +FISHER = FISHER +FISHERINV = FISHER.INV +FORECAST.ETS = VOORSPELLEN.ETS +FORECAST.ETS.CONFINT = VOORSPELLEN.ETS.CONFINT +FORECAST.ETS.SEASONALITY = VOORSPELLEN.ETS.SEASONALITY +FORECAST.ETS.STAT = FORECAST.ETS.STAT +FORECAST.LINEAR = VOORSPELLEN.LINEAR +FREQUENCY = INTERVAL +GAMMA = GAMMA +GAMMA.DIST = GAMMA.VERD.N +GAMMA.INV = GAMMA.INV.N +GAMMALN = GAMMA.LN +GAMMALN.PRECISE = GAMMA.LN.NAUWKEURIG +GAUSS = GAUSS +GEOMEAN = MEETK.GEM +GROWTH = GROEI +HARMEAN = HARM.GEM +HYPGEOM.DIST = HYPGEOM.VERD +INTERCEPT = SNIJPUNT +KURT = KURTOSIS +LARGE = GROOTSTE +LINEST = LIJNSCH +LOGEST = LOGSCH +LOGNORM.DIST = LOGNORM.VERD +LOGNORM.INV = LOGNORM.INV +MAX = MAX +MAXA = MAXA +MAXIFS = MAX.ALS.VOORWAARDEN +MEDIAN = MEDIAAN +MIN = MIN +MINA = MINA +MINIFS = MIN.ALS.VOORWAARDEN +MODE.MULT = MODUS.MEERV +MODE.SNGL = MODUS.ENKELV +NEGBINOM.DIST = NEGBINOM.VERD +NORM.DIST = NORM.VERD.N +NORM.INV = NORM.INV.N +NORM.S.DIST = NORM.S.VERD +NORM.S.INV = NORM.S.INV +PEARSON = PEARSON +PERCENTILE.EXC = PERCENTIEL.EXC +PERCENTILE.INC = PERCENTIEL.INC +PERCENTRANK.EXC = PROCENTRANG.EXC +PERCENTRANK.INC = PROCENTRANG.INC +PERMUT = PERMUTATIES +PERMUTATIONA = PERMUTATIE.A +PHI = PHI +POISSON.DIST = POISSON.VERD +PROB = KANS +QUARTILE.EXC = KWARTIEL.EXC +QUARTILE.INC = KWARTIEL.INC +RANK.AVG = RANG.GEMIDDELDE +RANK.EQ = RANG.GELIJK +RSQ = R.KWADRAAT +SKEW = SCHEEFHEID +SKEW.P = SCHEEFHEID.P +SLOPE = RICHTING +SMALL = KLEINSTE +STANDARDIZE = NORMALISEREN +STDEV.P = STDEV.P +STDEV.S = STDEV.S +STDEVA = STDEVA +STDEVPA = STDEVPA +STEYX = STAND.FOUT.YX +T.DIST = T.DIST +T.DIST.2T = T.VERD.2T +T.DIST.RT = T.VERD.RECHTS +T.INV = T.INV +T.INV.2T = T.INV.2T +T.TEST = T.TEST +TREND = TREND +TRIMMEAN = GETRIMD.GEM +VAR.P = VAR.P +VAR.S = VAR.S +VARA = VARA +VARPA = VARPA +WEIBULL.DIST = WEIBULL.VERD +Z.TEST = Z.TEST ## -## Statistical functions Statistische functies +## Tekstfuncties (Text Functions) ## -AVEDEV = GEM.DEVIATIE ## Geeft als resultaat het gemiddelde van de absolute deviaties van gegevenspunten ten opzichte van hun gemiddelde waarde -AVERAGE = GEMIDDELDE ## Geeft als resultaat het gemiddelde van de argumenten -AVERAGEA = GEMIDDELDEA ## Geeft als resultaat het gemiddelde van de argumenten, inclusief getallen, tekst en logische waarden -AVERAGEIF = GEMIDDELDE.ALS ## Geeft het gemiddelde (rekenkundig gemiddelde) als resultaat van alle cellen in een bereik die voldoen aan de opgegeven criteria -AVERAGEIFS = GEMIDDELDEN.ALS ## Geeft het gemiddelde (rekenkundig gemiddelde) als resultaat van alle cellen die aan meerdere criteria voldoen -BETADIST = BETA.VERD ## Geeft als resultaat de cumulatieve bèta-verdelingsfunctie -BETAINV = BETA.INV ## Geeft als resultaat de inverse van de cumulatieve verdelingsfunctie voor een gegeven bèta-verdeling -BINOMDIST = BINOMIALE.VERD ## Geeft als resultaat de binomiale verdeling -CHIDIST = CHI.KWADRAAT ## Geeft als resultaat de eenzijdige kans van de chi-kwadraatverdeling -CHIINV = CHI.KWADRAAT.INV ## Geeft als resultaat de inverse van een eenzijdige kans van de chi-kwadraatverdeling -CHITEST = CHI.TOETS ## Geeft als resultaat de onafhankelijkheidstoets -CONFIDENCE = BETROUWBAARHEID ## Geeft als resultaat het betrouwbaarheidsinterval van een gemiddelde waarde voor de elementen van een populatie -CORREL = CORRELATIE ## Geeft als resultaat de correlatiecoëfficiënt van twee gegevensverzamelingen -COUNT = AANTAL ## Telt het aantal getallen in de argumentenlijst -COUNTA = AANTALARG ## Telt het aantal waarden in de argumentenlijst -COUNTBLANK = AANTAL.LEGE.CELLEN ## Telt het aantal lege cellen in een bereik -COUNTIF = AANTAL.ALS ## Telt in een bereik het aantal cellen die voldoen aan een bepaald criterium -COUNTIFS = AANTALLEN.ALS ## Telt in een bereik het aantal cellen die voldoen aan meerdere criteria -COVAR = COVARIANTIE ## Geeft als resultaat de covariantie, het gemiddelde van de producten van de gepaarde deviaties -CRITBINOM = CRIT.BINOM ## Geeft als resultaat de kleinste waarde waarvoor de binomiale verdeling kleiner is dan of gelijk is aan het criterium -DEVSQ = DEV.KWAD ## Geeft als resultaat de som van de deviaties in het kwadraat -EXPONDIST = EXPON.VERD ## Geeft als resultaat de exponentiële verdeling -FDIST = F.VERDELING ## Geeft als resultaat de F-verdeling -FINV = F.INVERSE ## Geeft als resultaat de inverse van de F-verdeling -FISHER = FISHER ## Geeft als resultaat de Fisher-transformatie -FISHERINV = FISHER.INV ## Geeft als resultaat de inverse van de Fisher-transformatie -FORECAST = VOORSPELLEN ## Geeft als resultaat een waarde op basis van een lineaire trend -FREQUENCY = FREQUENTIE ## Geeft als resultaat een frequentieverdeling in de vorm van een verticale matrix -FTEST = F.TOETS ## Geeft als resultaat een F-toets -GAMMADIST = GAMMA.VERD ## Geeft als resultaat de gamma-verdeling -GAMMAINV = GAMMA.INV ## Geeft als resultaat de inverse van de cumulatieve gamma-verdeling -GAMMALN = GAMMA.LN ## Geeft als resultaat de natuurlijke logaritme van de gamma-functie, G(x) -GEOMEAN = MEETK.GEM ## Geeft als resultaat het meetkundige gemiddelde -GROWTH = GROEI ## Geeft als resultaat de waarden voor een exponentiële trend -HARMEAN = HARM.GEM ## Geeft als resultaat het harmonische gemiddelde -HYPGEOMDIST = HYPERGEO.VERD ## Geeft als resultaat de hypergeometrische verdeling -INTERCEPT = SNIJPUNT ## Geeft als resultaat het snijpunt van de lineaire regressielijn met de y-as -KURT = KURTOSIS ## Geeft als resultaat de kurtosis van een gegevensverzameling -LARGE = GROOTSTE ## Geeft als resultaat de op k-1 na grootste waarde in een gegevensverzameling -LINEST = LIJNSCH ## Geeft als resultaat de parameters van een lineaire trend -LOGEST = LOGSCH ## Geeft als resultaat de parameters van een exponentiële trend -LOGINV = LOG.NORM.INV ## Geeft als resultaat de inverse van de logaritmische normale verdeling -LOGNORMDIST = LOG.NORM.VERD ## Geeft als resultaat de cumulatieve logaritmische normale verdeling -MAX = MAX ## Geeft als resultaat de maximumwaarde in een lijst met argumenten -MAXA = MAXA ## Geeft als resultaat de maximumwaarde in een lijst met argumenten, inclusief getallen, tekst en logische waarden -MEDIAN = MEDIAAN ## Geeft als resultaat de mediaan van de opgegeven getallen -MIN = MIN ## Geeft als resultaat de minimumwaarde in een lijst met argumenten -MINA = MINA ## Geeft als resultaat de minimumwaarde in een lijst met argumenten, inclusief getallen, tekst en logische waarden -MODE = MODUS ## Geeft als resultaat de meest voorkomende waarde in een gegevensverzameling -NEGBINOMDIST = NEG.BINOM.VERD ## Geeft als resultaat de negatieve binomiaalverdeling -NORMDIST = NORM.VERD ## Geeft als resultaat de cumulatieve normale verdeling -NORMINV = NORM.INV ## Geeft als resultaat de inverse van de cumulatieve standaardnormale verdeling -NORMSDIST = STAND.NORM.VERD ## Geeft als resultaat de cumulatieve standaardnormale verdeling -NORMSINV = STAND.NORM.INV ## Geeft als resultaat de inverse van de cumulatieve normale verdeling -PEARSON = PEARSON ## Geeft als resultaat de correlatiecoëfficiënt van Pearson -PERCENTILE = PERCENTIEL ## Geeft als resultaat het k-de percentiel van waarden in een bereik -PERCENTRANK = PERCENT.RANG ## Geeft als resultaat de positie, in procenten uitgedrukt, van een waarde in de rangorde van een gegevensverzameling -PERMUT = PERMUTATIES ## Geeft als resultaat het aantal permutaties voor een gegeven aantal objecten -POISSON = POISSON ## Geeft als resultaat de Poisson-verdeling -PROB = KANS ## Geeft als resultaat de kans dat waarden zich tussen twee grenzen bevinden -QUARTILE = KWARTIEL ## Geeft als resultaat het kwartiel van een gegevensverzameling -RANK = RANG ## Geeft als resultaat het rangnummer van een getal in een lijst getallen -RSQ = R.KWADRAAT ## Geeft als resultaat het kwadraat van de Pearson-correlatiecoëfficiënt -SKEW = SCHEEFHEID ## Geeft als resultaat de mate van asymmetrie van een verdeling -SLOPE = RICHTING ## Geeft als resultaat de richtingscoëfficiënt van een lineaire regressielijn -SMALL = KLEINSTE ## Geeft als resultaat de op k-1 na kleinste waarde in een gegevensverzameling -STANDARDIZE = NORMALISEREN ## Geeft als resultaat een genormaliseerde waarde -STDEV = STDEV ## Maakt een schatting van de standaarddeviatie op basis van een steekproef -STDEVA = STDEVA ## Maakt een schatting van de standaarddeviatie op basis van een steekproef, inclusief getallen, tekst en logische waarden -STDEVP = STDEVP ## Berekent de standaarddeviatie op basis van de volledige populatie -STDEVPA = STDEVPA ## Berekent de standaarddeviatie op basis van de volledige populatie, inclusief getallen, tekst en logische waarden -STEYX = STAND.FOUT.YX ## Geeft als resultaat de standaardfout in de voorspelde y-waarde voor elke x in een regressie -TDIST = T.VERD ## Geeft als resultaat de Student T-verdeling -TINV = T.INV ## Geeft als resultaat de inverse van de Student T-verdeling -TREND = TREND ## Geeft als resultaat de waarden voor een lineaire trend -TRIMMEAN = GETRIMD.GEM ## Geeft als resultaat het gemiddelde van waarden in een gegevensverzameling -TTEST = T.TOETS ## Geeft als resultaat de kans met behulp van de Student T-toets -VAR = VAR ## Maakt een schatting van de variantie op basis van een steekproef -VARA = VARA ## Maakt een schatting van de variantie op basis van een steekproef, inclusief getallen, tekst en logische waarden -VARP = VARP ## Berekent de variantie op basis van de volledige populatie -VARPA = VARPA ## Berekent de standaarddeviatie op basis van de volledige populatie, inclusief getallen, tekst en logische waarden -WEIBULL = WEIBULL ## Geeft als resultaat de Weibull-verdeling -ZTEST = Z.TOETS ## Geeft als resultaat de eenzijdige kanswaarde van een Z-toets - +BAHTTEXT = BAHT.TEKST +CHAR = TEKEN +CLEAN = WISSEN.CONTROL +CODE = CODE +CONCAT = TEKST.SAMENV +DOLLAR = EURO +EXACT = GELIJK +FIND = VIND.ALLES +FIXED = VAST +ISTHAIDIGIT = IS.THAIS.CIJFER +LEFT = LINKS +LEN = LENGTE +LOWER = KLEINE.LETTERS +MID = DEEL +NUMBERSTRING = GETALNOTATIE +NUMBERVALUE = NUMERIEKE.WAARDE +PHONETIC = FONETISCH +PROPER = BEGINLETTERS +REPLACE = VERVANGEN +REPT = HERHALING +RIGHT = RECHTS +SEARCH = VIND.SPEC +SUBSTITUTE = SUBSTITUEREN +T = T +TEXT = TEKST +TEXTJOIN = TEKST.COMBINEREN +THAIDIGIT = THAIS.CIJFER +THAINUMSOUND = THAIS.GETAL.GELUID +THAINUMSTRING = THAIS.GETAL.REEKS +THAISTRINGLENGTH = THAIS.REEKS.LENGTE +TRIM = SPATIES.WISSEN +UNICHAR = UNITEKEN +UNICODE = UNICODE +UPPER = HOOFDLETTERS +VALUE = WAARDE ## -## Text functions Tekstfuncties +## Webfuncties (Web Functions) ## -ASC = ASC ## Wijzigt Nederlandse letters of katakanatekens over de volle breedte (dubbel-bytetekens) binnen een tekenreeks in tekens over de halve breedte (enkel-bytetekens) -BAHTTEXT = BAHT.TEKST ## Converteert een getal naar tekst met de valutanotatie ß (baht) -CHAR = TEKEN ## Geeft als resultaat het teken dat hoort bij de opgegeven code -CLEAN = WISSEN.CONTROL ## Verwijdert alle niet-afdrukbare tekens uit een tekst -CODE = CODE ## Geeft als resultaat de numerieke code voor het eerste teken in een tekenreeks -CONCATENATE = TEKST.SAMENVOEGEN ## Voegt verschillende tekstfragmenten samen tot één tekstfragment -DOLLAR = EURO ## Converteert een getal naar tekst met de valutanotatie € (euro) -EXACT = GELIJK ## Controleert of twee tekenreeksen identiek zijn -FIND = VIND.ALLES ## Zoekt een bepaalde tekenreeks in een tekst (waarbij onderscheid wordt gemaakt tussen hoofdletters en kleine letters) -FINDB = VIND.ALLES.B ## Zoekt een bepaalde tekenreeks in een tekst (waarbij onderscheid wordt gemaakt tussen hoofdletters en kleine letters) -FIXED = VAST ## Maakt een getal als tekst met een vast aantal decimalen op -JIS = JIS ## Wijzigt Nederlandse letters of katakanatekens over de halve breedte (enkel-bytetekens) binnen een tekenreeks in tekens over de volle breedte (dubbel-bytetekens) -LEFT = LINKS ## Geeft als resultaat de meest linkse tekens in een tekenreeks -LEFTB = LINKSB ## Geeft als resultaat de meest linkse tekens in een tekenreeks -LEN = LENGTE ## Geeft als resultaat het aantal tekens in een tekenreeks -LENB = LENGTEB ## Geeft als resultaat het aantal tekens in een tekenreeks -LOWER = KLEINE.LETTERS ## Zet tekst om in kleine letters -MID = MIDDEN ## Geeft als resultaat een bepaald aantal tekens van een tekenreeks vanaf de positie die u opgeeft -MIDB = DEELB ## Geeft als resultaat een bepaald aantal tekens van een tekenreeks vanaf de positie die u opgeeft -PHONETIC = FONETISCH ## Haalt de fonetische tekens (furigana) uit een tekenreeks op -PROPER = BEGINLETTERS ## Zet de eerste letter van elk woord in een tekst om in een hoofdletter -REPLACE = VERVANG ## Vervangt tekens binnen een tekst -REPLACEB = VERVANGENB ## Vervangt tekens binnen een tekst -REPT = HERHALING ## Herhaalt een tekst een aantal malen -RIGHT = RECHTS ## Geeft als resultaat de meest rechtse tekens in een tekenreeks -RIGHTB = RECHTSB ## Geeft als resultaat de meest rechtse tekens in een tekenreeks -SEARCH = VIND.SPEC ## Zoekt een bepaalde tekenreeks in een tekst (waarbij geen onderscheid wordt gemaakt tussen hoofdletters en kleine letters) -SEARCHB = VIND.SPEC.B ## Zoekt een bepaalde tekenreeks in een tekst (waarbij geen onderscheid wordt gemaakt tussen hoofdletters en kleine letters) -SUBSTITUTE = SUBSTITUEREN ## Vervangt oude tekst door nieuwe tekst in een tekenreeks -T = T ## Converteert de argumenten naar tekst -TEXT = TEKST ## Maakt een getal op en converteert het getal naar tekst -TRIM = SPATIES.WISSEN ## Verwijdert de spaties uit een tekst -UPPER = HOOFDLETTERS ## Zet tekst om in hoofdletters -VALUE = WAARDE ## Converteert tekst naar een getal +ENCODEURL = URL.CODEREN +FILTERXML = XML.FILTEREN +WEBSERVICE = WEBSERVICE + +## +## Compatibiliteitsfuncties (Compatibility Functions) +## +BETADIST = BETAVERD +BETAINV = BETAINV +BINOMDIST = BINOMIALE.VERD +CEILING = AFRONDEN.BOVEN +CHIDIST = CHI.KWADRAAT +CHIINV = CHI.KWADRAAT.INV +CHITEST = CHI.TOETS +CONCATENATE = TEKST.SAMENVOEGEN +CONFIDENCE = BETROUWBAARHEID +COVAR = COVARIANTIE +CRITBINOM = CRIT.BINOM +EXPONDIST = EXPON.VERD +FDIST = F.VERDELING +FINV = F.INVERSE +FLOOR = AFRONDEN.BENEDEN +FORECAST = VOORSPELLEN +FTEST = F.TOETS +GAMMADIST = GAMMA.VERD +GAMMAINV = GAMMA.INV +HYPGEOMDIST = HYPERGEO.VERD +LOGINV = LOG.NORM.INV +LOGNORMDIST = LOG.NORM.VERD +MODE = MODUS +NEGBINOMDIST = NEG.BINOM.VERD +NORMDIST = NORM.VERD +NORMINV = NORM.INV +NORMSDIST = STAND.NORM.VERD +NORMSINV = STAND.NORM.INV +PERCENTILE = PERCENTIEL +PERCENTRANK = PERCENT.RANG +POISSON = POISSON +QUARTILE = KWARTIEL +RANK = RANG +STDEV = STDEV +STDEVP = STDEVP +TDIST = T.VERD +TINV = TINV +TTEST = T.TOETS +VAR = VAR +VARP = VARP +WEIBULL = WEIBULL +ZTEST = Z.TOETS diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/no/config b/PhpOffice/PhpSpreadsheet/Calculation/locale/no/config deleted file mode 100755 index c7f4152..0000000 --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/no/config +++ /dev/null @@ -1,24 +0,0 @@ -## -## PhpSpreadsheet -## - -ArgumentSeparator = ; - - -## -## (For future use) -## -currencySymbol = kr - - -## -## Excel Error Codes (For future use) - -## -NULL = #NULL! -DIV0 = #DIV/0! -VALUE = #VERDI! -REF = #REF! -NAME = #NAVN? -NUM = #NUM! -NA = #I/T diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/no/functions b/PhpOffice/PhpSpreadsheet/Calculation/locale/no/functions deleted file mode 100755 index ab2a379..0000000 --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/no/functions +++ /dev/null @@ -1,416 +0,0 @@ -## -## PhpSpreadsheet -## -## Data in this file derived from https://www.excel-function-translation.com/ -## -## - - -## -## Add-in and Automation functions Funksjonene Tillegg og Automatisering -## -GETPIVOTDATA = HENTPIVOTDATA ## Returnerer data som er lagret i en pivottabellrapport - - -## -## Cube functions Kubefunksjoner -## -CUBEKPIMEMBER = KUBEKPIMEDLEM ## Returnerer navnet, egenskapen og målet for en viktig ytelsesindikator (KPI), og viser navnet og egenskapen i cellen. En KPI er en målbar enhet, for eksempel månedlig bruttoinntjening eller kvartalsvis inntjening per ansatt, og brukes til å overvåke ytelsen i en organisasjon. -CUBEMEMBER = KUBEMEDLEM ## Returnerer et medlem eller en tuppel i et kubehierarki. Brukes til å validere at medlemmet eller tuppelen finnes i kuben. -CUBEMEMBERPROPERTY = KUBEMEDLEMEGENSKAP ## Returnerer verdien til en medlemsegenskap i kuben. Brukes til å validere at et medlemsnavn finnes i kuben, og til å returnere den angitte egenskapen for dette medlemmet. -CUBERANKEDMEMBER = KUBERANGERTMEDLEM ## Returnerer det n-te, eller rangerte, medlemmet i et sett. Brukes til å returnere ett eller flere elementer i et sett, for eksempel de 10 beste studentene. -CUBESET = KUBESETT ## Definerer et beregnet sett av medlemmer eller tuppeler ved å sende et settuttrykk til kuben på serveren, noe som oppretter settet og deretter returnerer dette settet til Microsoft Office Excel. -CUBESETCOUNT = KUBESETTANTALL ## Returnerer antallet elementer i et sett. -CUBEVALUE = KUBEVERDI ## Returnerer en aggregert verdi fra en kube. - - -## -## Database functions Databasefunksjoner -## -DAVERAGE = DGJENNOMSNITT ## Returnerer gjennomsnittet av merkede databaseposter -DCOUNT = DANTALL ## Teller celler som inneholder tall i en database -DCOUNTA = DANTALLA ## Teller celler som ikke er tomme i en database -DGET = DHENT ## Trekker ut fra en database en post som oppfyller angitte vilkår -DMAX = DMAKS ## Returnerer maksimumsverdien fra merkede databaseposter -DMIN = DMIN ## Returnerer minimumsverdien fra merkede databaseposter -DPRODUCT = DPRODUKT ## Multipliserer verdiene i et bestemt felt med poster som oppfyller vilkårene i en database -DSTDEV = DSTDAV ## Estimerer standardavviket basert på et utvalg av merkede databaseposter -DSTDEVP = DSTAVP ## Beregner standardavviket basert på at merkede databaseposter utgjør hele populasjonen -DSUM = DSUMMER ## Legger til tallene i feltkolonnen med poster, i databasen som oppfyller vilkårene -DVAR = DVARIANS ## Estimerer variansen basert på et utvalg av merkede databaseposter -DVARP = DVARIANSP ## Beregner variansen basert på at merkede databaseposter utgjør hele populasjonen - - -## -## Date and time functions Dato- og tidsfunksjoner -## -DATE = DATO ## Returnerer serienummeret som svarer til en bestemt dato -DATEVALUE = DATOVERDI ## Konverterer en dato med tekstformat til et serienummer -DAY = DAG ## Konverterer et serienummer til en dag i måneden -DAYS360 = DAGER360 ## Beregner antall dager mellom to datoer basert på et år med 360 dager -EDATE = DAG.ETTER ## Returnerer serienummeret som svarer til datoen som er det indikerte antall måneder før eller etter startdatoen -EOMONTH = MÅNEDSSLUTT ## Returnerer serienummeret som svarer til siste dag i måneden, før eller etter et angitt antall måneder -HOUR = TIME ## Konverterer et serienummer til en time -MINUTE = MINUTT ## Konverterer et serienummer til et minutt -MONTH = MÅNED ## Konverterer et serienummer til en måned -NETWORKDAYS = NETT.ARBEIDSDAGER ## Returnerer antall hele arbeidsdager mellom to datoer -NOW = NÅ ## Returnerer serienummeret som svarer til gjeldende dato og klokkeslett -SECOND = SEKUND ## Konverterer et serienummer til et sekund -TIME = TID ## Returnerer serienummeret som svarer til et bestemt klokkeslett -TIMEVALUE = TIDSVERDI ## Konverterer et klokkeslett i tekstformat til et serienummer -TODAY = IDAG ## Returnerer serienummeret som svarer til dagens dato -WEEKDAY = UKEDAG ## Konverterer et serienummer til en ukedag -WEEKNUM = UKENR ## Konverterer et serienummer til et tall som representerer hvilket nummer uken har i et år -WORKDAY = ARBEIDSDAG ## Returnerer serienummeret som svarer til datoen før eller etter et angitt antall arbeidsdager -YEAR = ÅR ## Konverterer et serienummer til et år -YEARFRAC = ÅRDEL ## Returnerer brøkdelen for året, som svarer til antall hele dager mellom startdato og sluttdato - - -## -## Engineering functions Tekniske funksjoner -## -BESSELI = BESSELI ## Returnerer den endrede Bessel-funksjonen In(x) -BESSELJ = BESSELJ ## Returnerer Bessel-funksjonen Jn(x) -BESSELK = BESSELK ## Returnerer den endrede Bessel-funksjonen Kn(x) -BESSELY = BESSELY ## Returnerer Bessel-funksjonen Yn(x) -BIN2DEC = BINTILDES ## Konverterer et binært tall til et desimaltall -BIN2HEX = BINTILHEKS ## Konverterer et binært tall til et heksadesimaltall -BIN2OCT = BINTILOKT ## Konverterer et binært tall til et oktaltall -COMPLEX = KOMPLEKS ## Konverterer reelle og imaginære koeffisienter til et komplekst tall -CONVERT = KONVERTER ## Konverterer et tall fra ett målsystem til et annet -DEC2BIN = DESTILBIN ## Konverterer et desimaltall til et binærtall -DEC2HEX = DESTILHEKS ## Konverterer et heltall i 10-tallsystemet til et heksadesimalt tall -DEC2OCT = DESTILOKT ## Konverterer et heltall i 10-tallsystemet til et oktaltall -DELTA = DELTA ## Undersøker om to verdier er like -ERF = FEILF ## Returnerer feilfunksjonen -ERFC = FEILFK ## Returnerer den komplementære feilfunksjonen -GESTEP = GRENSEVERDI ## Tester om et tall er større enn en terskelverdi -HEX2BIN = HEKSTILBIN ## Konverterer et heksadesimaltall til et binært tall -HEX2DEC = HEKSTILDES ## Konverterer et heksadesimalt tall til et heltall i 10-tallsystemet -HEX2OCT = HEKSTILOKT ## Konverterer et heksadesimalt tall til et oktaltall -IMABS = IMABS ## Returnerer absoluttverdien (koeffisienten) til et komplekst tall -IMAGINARY = IMAGINÆR ## Returnerer den imaginære koeffisienten til et komplekst tall -IMARGUMENT = IMARGUMENT ## Returnerer argumentet theta, som er en vinkel uttrykt i radianer -IMCONJUGATE = IMKONJUGERT ## Returnerer den komplekse konjugaten til et komplekst tall -IMCOS = IMCOS ## Returnerer cosinus til et komplekst tall -IMDIV = IMDIV ## Returnerer kvotienten til to komplekse tall -IMEXP = IMEKSP ## Returnerer eksponenten til et komplekst tall -IMLN = IMLN ## Returnerer den naturlige logaritmen for et komplekst tall -IMLOG10 = IMLOG10 ## Returnerer logaritmen med grunntall 10 for et komplekst tall -IMLOG2 = IMLOG2 ## Returnerer logaritmen med grunntall 2 for et komplekst tall -IMPOWER = IMOPPHØY ## Returnerer et komplekst tall opphøyd til en heltallspotens -IMPRODUCT = IMPRODUKT ## Returnerer produktet av komplekse tall -IMREAL = IMREELL ## Returnerer den reelle koeffisienten til et komplekst tall -IMSIN = IMSIN ## Returnerer sinus til et komplekst tall -IMSQRT = IMROT ## Returnerer kvadratroten av et komplekst tall -IMSUB = IMSUB ## Returnerer differansen mellom to komplekse tall -IMSUM = IMSUMMER ## Returnerer summen av komplekse tall -OCT2BIN = OKTTILBIN ## Konverterer et oktaltall til et binært tall -OCT2DEC = OKTTILDES ## Konverterer et oktaltall til et desimaltall -OCT2HEX = OKTTILHEKS ## Konverterer et oktaltall til et heksadesimaltall - - -## -## Financial functions Økonomiske funksjoner -## -ACCRINT = PÅLØPT.PERIODISK.RENTE ## Returnerer påløpte renter for et verdipapir som betaler periodisk rente -ACCRINTM = PÅLØPT.FORFALLSRENTE ## Returnerer den påløpte renten for et verdipapir som betaler rente ved forfall -AMORDEGRC = AMORDEGRC ## Returnerer avskrivningen for hver regnskapsperiode ved hjelp av en avskrivingskoeffisient -AMORLINC = AMORLINC ## Returnerer avskrivingen for hver regnskapsperiode -COUPDAYBS = OBLIG.DAGER.FF ## Returnerer antall dager fra begynnelsen av den rentebærende perioden til innløsningsdatoen -COUPDAYS = OBLIG.DAGER ## Returnerer antall dager i den rentebærende perioden som inneholder innløsningsdatoen -COUPDAYSNC = OBLIG.DAGER.NF ## Returnerer antall dager fra betalingsdato til neste renteinnbetalingsdato -COUPNCD = OBLIG.DAGER.EF ## Returnerer obligasjonsdatoen som kommer etter oppgjørsdatoen -COUPNUM = OBLIG.ANTALL ## Returnerer antall obligasjoner som skal betales mellom oppgjørsdatoen og forfallsdatoen -COUPPCD = OBLIG.DAG.FORRIGE ## Returnerer obligasjonsdatoen som kommer før oppgjørsdatoen -CUMIPMT = SAMLET.RENTE ## Returnerer den kumulative renten som er betalt mellom to perioder -CUMPRINC = SAMLET.HOVEDSTOL ## Returnerer den kumulative hovedstolen som er betalt for et lån mellom to perioder -DB = DAVSKR ## Returnerer avskrivningen for et aktivum i en angitt periode, foretatt med fast degressiv avskrivning -DDB = DEGRAVS ## Returnerer avskrivningen for et aktivum for en gitt periode, ved hjelp av dobbel degressiv avskrivning eller en metode som du selv angir -DISC = DISKONTERT ## Returnerer diskonteringsraten for et verdipapir -DOLLARDE = DOLLARDE ## Konverterer en valutapris uttrykt som en brøk, til en valutapris uttrykt som et desimaltall -DOLLARFR = DOLLARBR ## Konverterer en valutapris uttrykt som et desimaltall, til en valutapris uttrykt som en brøk -DURATION = VARIGHET ## Returnerer årlig varighet for et verdipapir med renter som betales periodisk -EFFECT = EFFEKTIV.RENTE ## Returnerer den effektive årlige rentesatsen -FV = SLUTTVERDI ## Returnerer fremtidig verdi for en investering -FVSCHEDULE = SVPLAN ## Returnerer den fremtidige verdien av en inngående hovedstol etter å ha anvendt en serie med sammensatte rentesatser -INTRATE = RENTESATS ## Returnerer rentefoten av et fullfinansiert verdipapir -IPMT = RAVDRAG ## Returnerer betalte renter på en investering for en gitt periode -IRR = IR ## Returnerer internrenten for en serie kontantstrømmer -ISPMT = ER.AVDRAG ## Beregner renten som er betalt for en investering i løpet av en bestemt periode -MDURATION = MVARIGHET ## Returnerer Macauleys modifiserte varighet for et verdipapir med en antatt pålydende verdi på kr 100,00 -MIRR = MODIR ## Returnerer internrenten der positive og negative kontantstrømmer finansieres med forskjellige satser -NOMINAL = NOMINELL ## Returnerer årlig nominell rentesats -NPER = PERIODER ## Returnerer antall perioder for en investering -NPV = NNV ## Returnerer netto nåverdi for en investering, basert på en serie periodiske kontantstrømmer og en rentesats -ODDFPRICE = AVVIKFP.PRIS ## Returnerer pris pålydende kr 100 for et verdipapir med en odde første periode -ODDFYIELD = AVVIKFP.AVKASTNING ## Returnerer avkastingen for et verdipapir med en odde første periode -ODDLPRICE = AVVIKSP.PRIS ## Returnerer pris pålydende kr 100 for et verdipapir med en odde siste periode -ODDLYIELD = AVVIKSP.AVKASTNING ## Returnerer avkastingen for et verdipapir med en odde siste periode -PMT = AVDRAG ## Returnerer periodisk betaling for en annuitet -PPMT = AMORT ## Returnerer betalingen på hovedstolen for en investering i en gitt periode -PRICE = PRIS ## Returnerer prisen per pålydende kr 100 for et verdipapir som gir periodisk avkastning -PRICEDISC = PRIS.DISKONTERT ## Returnerer prisen per pålydende kr 100 for et diskontert verdipapir -PRICEMAT = PRIS.FORFALL ## Returnerer prisen per pålydende kr 100 av et verdipapir som betaler rente ved forfall -PV = NÅVERDI ## Returnerer nåverdien av en investering -RATE = RENTE ## Returnerer rentesatsen per periode for en annuitet -RECEIVED = MOTTATT.AVKAST ## Returnerer summen som mottas ved forfallsdato for et fullinvestert verdipapir -SLN = LINAVS ## Returnerer den lineære avskrivningen for et aktivum i én periode -SYD = ÅRSAVS ## Returnerer årsavskrivningen for et aktivum i en angitt periode -TBILLEQ = TBILLEKV ## Returnerer den obligasjonsekvivalente avkastningen for en statsobligasjon -TBILLPRICE = TBILLPRIS ## Returnerer prisen per pålydende kr 100 for en statsobligasjon -TBILLYIELD = TBILLAVKASTNING ## Returnerer avkastningen til en statsobligasjon -VDB = VERDIAVS ## Returnerer avskrivningen for et aktivum i en angitt periode eller delperiode, ved hjelp av degressiv avskrivning -XIRR = XIR ## Returnerer internrenten for en serie kontantstrømmer som ikke nødvendigvis er periodiske -XNPV = XNNV ## Returnerer netto nåverdi for en serie kontantstrømmer som ikke nødvendigvis er periodiske -YIELD = AVKAST ## Returnerer avkastningen på et verdipapir som betaler periodisk rente -YIELDDISC = AVKAST.DISKONTERT ## Returnerer årlig avkastning for et diskontert verdipapir, for eksempel en statskasseveksel -YIELDMAT = AVKAST.FORFALL ## Returnerer den årlige avkastningen for et verdipapir som betaler rente ved forfallsdato - - -## -## Information functions Informasjonsfunksjoner -## -CELL = CELLE ## Returnerer informasjon om formatering, plassering eller innholdet til en celle -ERROR.TYPE = FEIL.TYPE ## Returnerer et tall som svarer til en feiltype -INFO = INFO ## Returnerer informasjon om gjeldende operativmiljø -ISBLANK = ERTOM ## Returnerer SANN hvis verdien er tom -ISERR = ERFEIL ## Returnerer SANN hvis verdien er en hvilken som helst annen feilverdi enn #I/T -ISERROR = ERFEIL ## Returnerer SANN hvis verdien er en hvilken som helst feilverdi -ISEVEN = ERPARTALL ## Returnerer SANN hvis tallet er et partall -ISLOGICAL = ERLOGISK ## Returnerer SANN hvis verdien er en logisk verdi -ISNA = ERIT ## Returnerer SANN hvis verdien er feilverdien #I/T -ISNONTEXT = ERIKKETEKST ## Returnerer SANN hvis verdien ikke er tekst -ISNUMBER = ERTALL ## Returnerer SANN hvis verdien er et tall -ISODD = ERODDETALL ## Returnerer SANN hvis tallet er et oddetall -ISREF = ERREF ## Returnerer SANN hvis verdien er en referanse -ISTEXT = ERTEKST ## Returnerer SANN hvis verdien er tekst -N = N ## Returnerer en verdi som er konvertert til et tall -NA = IT ## Returnerer feilverdien #I/T -TYPE = VERDITYPE ## Returnerer et tall som indikerer datatypen til en verdi - - -## -## Logical functions Logiske funksjoner -## -AND = OG ## Returnerer SANN hvis alle argumentene er lik SANN -FALSE = USANN ## Returnerer den logiske verdien USANN -IF = HVIS ## Angir en logisk test som skal utføres -IFERROR = HVISFEIL ## Returnerer en verdi du angir hvis en formel evaluerer til en feil. Ellers returnerer den resultatet av formelen. -NOT = IKKE ## Reverserer logikken til argumentet -OR = ELLER ## Returnerer SANN hvis ett eller flere argumenter er lik SANN -TRUE = SANN ## Returnerer den logiske verdien SANN - - -## -## Lookup and reference functions Oppslag- og referansefunksjoner -## -ADDRESS = ADRESSE ## Returnerer en referanse som tekst til en enkelt celle i et regneark -AREAS = OMRÅDER ## Returnerer antall områder i en referanse -CHOOSE = VELG ## Velger en verdi fra en liste med verdier -COLUMN = KOLONNE ## Returnerer kolonnenummeret for en referanse -COLUMNS = KOLONNER ## Returnerer antall kolonner i en referanse -HLOOKUP = FINN.KOLONNE ## Leter i den øverste raden i en matrise og returnerer verdien for den angitte cellen -HYPERLINK = HYPERKOBLING ## Oppretter en snarvei eller et hopp som åpner et dokument som er lagret på en nettverksserver, et intranett eller Internett -INDEX = INDEKS ## Bruker en indeks til å velge en verdi fra en referanse eller matrise -INDIRECT = INDIREKTE ## Returnerer en referanse angitt av en tekstverdi -LOOKUP = SLÅ.OPP ## Slår opp verdier i en vektor eller matrise -MATCH = SAMMENLIGNE ## Slår opp verdier i en referanse eller matrise -OFFSET = FORSKYVNING ## Returnerer en referanseforskyvning fra en gitt referanse -ROW = RAD ## Returnerer radnummeret for en referanse -ROWS = RADER ## Returnerer antall rader i en referanse -RTD = RTD ## Henter sanntidsdata fra et program som støtter COM-automatisering (automatisering: En måte å arbeide på med programobjekter fra et annet program- eller utviklingsverktøy. Tidligere kalt OLE-automatisering. Automatisering er en bransjestandard og en funksjon i Component Object Model (COM).) -TRANSPOSE = TRANSPONER ## Returnerer transponeringen av en matrise -VLOOKUP = FINN.RAD ## Leter i den første kolonnen i en matrise og flytter bortover raden for å returnere verdien til en celle - - -## -## Math and trigonometry functions Matematikk- og trigonometrifunksjoner -## -ABS = ABS ## Returnerer absoluttverdien til et tall -ACOS = ARCCOS ## Returnerer arcus cosinus til et tall -ACOSH = ARCCOSH ## Returnerer den inverse hyperbolske cosinus til et tall -ASIN = ARCSIN ## Returnerer arcus sinus til et tall -ASINH = ARCSINH ## Returnerer den inverse hyperbolske sinus til et tall -ATAN = ARCTAN ## Returnerer arcus tangens til et tall -ATAN2 = ARCTAN2 ## Returnerer arcus tangens fra x- og y-koordinater -ATANH = ARCTANH ## Returnerer den inverse hyperbolske tangens til et tall -CEILING = AVRUND.GJELDENDE.MULTIPLUM ## Runder av et tall til nærmeste heltall eller til nærmeste signifikante multiplum -COMBIN = KOMBINASJON ## Returnerer antall kombinasjoner for ett gitt antall objekter -COS = COS ## Returnerer cosinus til et tall -COSH = COSH ## Returnerer den hyperbolske cosinus til et tall -DEGREES = GRADER ## Konverterer radianer til grader -EVEN = AVRUND.TIL.PARTALL ## Runder av et tall oppover til nærmeste heltall som er et partall -EXP = EKSP ## Returnerer e opphøyd i en angitt potens -FACT = FAKULTET ## Returnerer fakultet til et tall -FACTDOUBLE = DOBBELFAKT ## Returnerer et talls doble fakultet -FLOOR = AVRUND.GJELDENDE.MULTIPLUM.NED ## Avrunder et tall nedover, mot null -GCD = SFF ## Returnerer høyeste felles divisor -INT = HELTALL ## Avrunder et tall nedover til nærmeste heltall -LCM = MFM ## Returnerer minste felles multiplum -LN = LN ## Returnerer den naturlige logaritmen til et tall -LOG = LOG ## Returnerer logaritmen for et tall til et angitt grunntall -LOG10 = LOG10 ## Returnerer logaritmen med grunntall 10 for et tall -MDETERM = MDETERM ## Returnerer matrisedeterminanten til en matrise -MINVERSE = MINVERS ## Returnerer den inverse matrisen til en matrise -MMULT = MMULT ## Returnerer matriseproduktet av to matriser -MOD = REST ## Returnerer resten fra en divisjon -MROUND = MRUND ## Returnerer et tall avrundet til det ønskede multiplum -MULTINOMIAL = MULTINOMINELL ## Returnerer det multinominelle for et sett med tall -ODD = AVRUND.TIL.ODDETALL ## Runder av et tall oppover til nærmeste heltall som er et oddetall -PI = PI ## Returnerer verdien av pi -POWER = OPPHØYD.I ## Returnerer resultatet av et tall opphøyd i en potens -PRODUCT = PRODUKT ## Multipliserer argumentene -QUOTIENT = KVOTIENT ## Returnerer heltallsdelen av en divisjon -RADIANS = RADIANER ## Konverterer grader til radianer -RAND = TILFELDIG ## Returnerer et tilfeldig tall mellom 0 og 1 -RANDBETWEEN = TILFELDIGMELLOM ## Returnerer et tilfeldig tall innenfor et angitt område -ROMAN = ROMERTALL ## Konverterer vanlige tall til romertall, som tekst -ROUND = AVRUND ## Avrunder et tall til et angitt antall sifre -ROUNDDOWN = AVRUND.NED ## Avrunder et tall nedover, mot null -ROUNDUP = AVRUND.OPP ## Runder av et tall oppover, bort fra null -SERIESSUM = SUMMER.REKKE ## Returnerer summen av en geometrisk rekke, basert på formelen -SIGN = FORTEGN ## Returnerer fortegnet for et tall -SIN = SIN ## Returnerer sinus til en gitt vinkel -SINH = SINH ## Returnerer den hyperbolske sinus til et tall -SQRT = ROT ## Returnerer en positiv kvadratrot -SQRTPI = ROTPI ## Returnerer kvadratroten av (tall * pi) -SUBTOTAL = DELSUM ## Returnerer en delsum i en liste eller database -SUM = SUMMER ## Legger sammen argumentene -SUMIF = SUMMERHVIS ## Legger sammen cellene angitt ved et gitt vilkår -SUMIFS = SUMMER.HVIS.SETT ## Legger sammen cellene i et område som oppfyller flere vilkår -SUMPRODUCT = SUMMERPRODUKT ## Returnerer summen av produktene av tilsvarende matrisekomponenter -SUMSQ = SUMMERKVADRAT ## Returnerer kvadratsummen av argumentene -SUMX2MY2 = SUMMERX2MY2 ## Returnerer summen av differansen av kvadratene for tilsvarende verdier i to matriser -SUMX2PY2 = SUMMERX2PY2 ## Returnerer summen av kvadratsummene for tilsvarende verdier i to matriser -SUMXMY2 = SUMMERXMY2 ## Returnerer summen av kvadratene av differansen for tilsvarende verdier i to matriser -TAN = TAN ## Returnerer tangens for et tall -TANH = TANH ## Returnerer den hyperbolske tangens for et tall -TRUNC = AVKORT ## Korter av et tall til et heltall - - -## -## Statistical functions Statistiske funksjoner -## -AVEDEV = GJENNOMSNITTSAVVIK ## Returnerer datapunktenes gjennomsnittlige absoluttavvik fra middelverdien -AVERAGE = GJENNOMSNITT ## Returnerer gjennomsnittet for argumentene -AVERAGEA = GJENNOMSNITTA ## Returnerer gjennomsnittet for argumentene, inkludert tall, tekst og logiske verdier -AVERAGEIF = GJENNOMSNITTHVIS ## Returnerer gjennomsnittet (aritmetisk gjennomsnitt) av alle cellene i et område som oppfyller et bestemt vilkår -AVERAGEIFS = GJENNOMSNITT.HVIS.SETT ## Returnerer gjennomsnittet (aritmetisk middelverdi) av alle celler som oppfyller flere vilkår. -BETADIST = BETA.FORDELING ## Returnerer den kumulative betafordelingsfunksjonen -BETAINV = INVERS.BETA.FORDELING ## Returnerer den inverse verdien til fordelingsfunksjonen for en angitt betafordeling -BINOMDIST = BINOM.FORDELING ## Returnerer den individuelle binomiske sannsynlighetsfordelingen -CHIDIST = KJI.FORDELING ## Returnerer den ensidige sannsynligheten for en kjikvadrert fordeling -CHIINV = INVERS.KJI.FORDELING ## Returnerer den inverse av den ensidige sannsynligheten for den kjikvadrerte fordelingen -CHITEST = KJI.TEST ## Utfører testen for uavhengighet -CONFIDENCE = KONFIDENS ## Returnerer konfidensintervallet til gjennomsnittet for en populasjon -CORREL = KORRELASJON ## Returnerer korrelasjonskoeffisienten mellom to datasett -COUNT = ANTALL ## Teller hvor mange tall som er i argumentlisten -COUNTA = ANTALLA ## Teller hvor mange verdier som er i argumentlisten -COUNTBLANK = TELLBLANKE ## Teller antall tomme celler i et område. -COUNTIF = ANTALL.HVIS ## Teller antall celler i et område som oppfyller gitte vilkår -COUNTIFS = ANTALL.HVIS.SETT ## Teller antallet ikke-tomme celler i et område som oppfyller flere vilkår -COVAR = KOVARIANS ## Returnerer kovariansen, gjennomsnittet av produktene av parvise avvik -CRITBINOM = GRENSE.BINOM ## Returnerer den minste verdien der den kumulative binomiske fordelingen er mindre enn eller lik en vilkårsverdi -DEVSQ = AVVIK.KVADRERT ## Returnerer summen av kvadrerte avvik -EXPONDIST = EKSP.FORDELING ## Returnerer eksponentialfordelingen -FDIST = FFORDELING ## Returnerer F-sannsynlighetsfordelingen -FINV = FFORDELING.INVERS ## Returnerer den inverse av den sannsynlige F-fordelingen -FISHER = FISHER ## Returnerer Fisher-transformasjonen -FISHERINV = FISHERINV ## Returnerer den inverse av Fisher-transformasjonen -FORECAST = PROGNOSE ## Returnerer en verdi langs en lineær trend -FREQUENCY = FREKVENS ## Returnerer en frekvensdistribusjon som en loddrett matrise -FTEST = FTEST ## Returnerer resultatet av en F-test -GAMMADIST = GAMMAFORDELING ## Returnerer gammafordelingen -GAMMAINV = GAMMAINV ## Returnerer den inverse av den gammakumulative fordelingen -GAMMALN = GAMMALN ## Returnerer den naturlige logaritmen til gammafunksjonen G(x) -GEOMEAN = GJENNOMSNITT.GEOMETRISK ## Returnerer den geometriske middelverdien -GROWTH = VEKST ## Returnerer verdier langs en eksponentiell trend -HARMEAN = GJENNOMSNITT.HARMONISK ## Returnerer den harmoniske middelverdien -HYPGEOMDIST = HYPGEOM.FORDELING ## Returnerer den hypergeometriske fordelingen -INTERCEPT = SKJÆRINGSPUNKT ## Returnerer skjæringspunktet til den lineære regresjonslinjen -KURT = KURT ## Returnerer kurtosen til et datasett -LARGE = N.STØRST ## Returnerer den n-te største verdien i et datasett -LINEST = RETTLINJE ## Returnerer parameterne til en lineær trend -LOGEST = KURVE ## Returnerer parameterne til en eksponentiell trend -LOGINV = LOGINV ## Returnerer den inverse lognormale fordelingen -LOGNORMDIST = LOGNORMFORD ## Returnerer den kumulative lognormale fordelingen -MAX = STØRST ## Returnerer maksimumsverdien i en argumentliste -MAXA = MAKSA ## Returnerer maksimumsverdien i en argumentliste, inkludert tall, tekst og logiske verdier -MEDIAN = MEDIAN ## Returnerer medianen til tallene som er gitt -MIN = MIN ## Returnerer minimumsverdien i en argumentliste -MINA = MINA ## Returnerer den minste verdien i en argumentliste, inkludert tall, tekst og logiske verdier -MODE = MODUS ## Returnerer den vanligste verdien i et datasett -NEGBINOMDIST = NEGBINOM.FORDELING ## Returnerer den negative binomiske fordelingen -NORMDIST = NORMALFORDELING ## Returnerer den kumulative normalfordelingen -NORMINV = NORMINV ## Returnerer den inverse kumulative normalfordelingen -NORMSDIST = NORMSFORDELING ## Returnerer standard kumulativ normalfordeling -NORMSINV = NORMSINV ## Returnerer den inverse av den den kumulative standard normalfordelingen -PEARSON = PEARSON ## Returnerer produktmomentkorrelasjonskoeffisienten, Pearson -PERCENTILE = PERSENTIL ## Returnerer den n-te persentil av verdiene i et område -PERCENTRANK = PROSENTDEL ## Returnerer prosentrangeringen av en verdi i et datasett -PERMUT = PERMUTER ## Returnerer antall permutasjoner for et gitt antall objekter -POISSON = POISSON ## Returnerer Poissons sannsynlighetsfordeling -PROB = SANNSYNLIG ## Returnerer sannsynligheten for at verdier i et område ligger mellom to grenser -QUARTILE = KVARTIL ## Returnerer kvartilen til et datasett -RANK = RANG ## Returnerer rangeringen av et tall, eller plassen tallet har i en rekke -RSQ = RKVADRAT ## Returnerer kvadratet av produktmomentkorrelasjonskoeffisienten (Pearsons r) -SKEW = SKJEVFORDELING ## Returnerer skjevheten i en fordeling -SLOPE = STIGNINGSTALL ## Returnerer stigningtallet for den lineære regresjonslinjen -SMALL = N.MINST ## Returnerer den n-te minste verdien i et datasett -STANDARDIZE = NORMALISER ## Returnerer en normalisert verdi -STDEV = STDAV ## Estimere standardavvik på grunnlag av et utvalg -STDEVA = STDAVVIKA ## Estimerer standardavvik basert på et utvalg, inkludert tall, tekst og logiske verdier -STDEVP = STDAVP ## Beregner standardavvik basert på hele populasjonen -STDEVPA = STDAVVIKPA ## Beregner standardavvik basert på hele populasjonen, inkludert tall, tekst og logiske verdier -STEYX = STANDARDFEIL ## Returnerer standardfeilen for den predikerte y-verdien for hver x i regresjonen -TDIST = TFORDELING ## Returnerer en Student t-fordeling -TINV = TINV ## Returnerer den inverse Student t-fordelingen -TREND = TREND ## Returnerer verdier langs en lineær trend -TRIMMEAN = TRIMMET.GJENNOMSNITT ## Returnerer den interne middelverdien til et datasett -TTEST = TTEST ## Returnerer sannsynligheten assosiert med en Student t-test -VAR = VARIANS ## Estimerer varians basert på et utvalg -VARA = VARIANSA ## Estimerer varians basert på et utvalg, inkludert tall, tekst og logiske verdier -VARP = VARIANSP ## Beregner varians basert på hele populasjonen -VARPA = VARIANSPA ## Beregner varians basert på hele populasjonen, inkludert tall, tekst og logiske verdier -WEIBULL = WEIBULL.FORDELING ## Returnerer Weibull-fordelingen -ZTEST = ZTEST ## Returnerer den ensidige sannsynlighetsverdien for en z-test - - -## -## Text functions Tekstfunksjoner -## -ASC = STIGENDE ## Endrer fullbreddes (dobbeltbyte) engelske bokstaver eller katakana i en tegnstreng, til halvbreddes (enkeltbyte) tegn -BAHTTEXT = BAHTTEKST ## Konverterer et tall til tekst, og bruker valutaformatet ß (baht) -CHAR = TEGNKODE ## Returnerer tegnet som svarer til kodenummeret -CLEAN = RENSK ## Fjerner alle tegn som ikke kan skrives ut, fra teksten -CODE = KODE ## Returnerer en numerisk kode for det første tegnet i en tekststreng -CONCATENATE = KJEDE.SAMMEN ## Slår sammen flere tekstelementer til ett tekstelement -DOLLAR = VALUTA ## Konverterer et tall til tekst, og bruker valutaformatet $ (dollar) -EXACT = EKSAKT ## Kontrollerer om to tekstverdier er like -FIND = FINN ## Finner en tekstverdi inne i en annen (skiller mellom store og små bokstaver) -FINDB = FINNB ## Finner en tekstverdi inne i en annen (skiller mellom store og små bokstaver) -FIXED = FASTSATT ## Formaterer et tall som tekst med et bestemt antall desimaler -JIS = JIS ## Endrer halvbreddes (enkeltbyte) engelske bokstaver eller katakana i en tegnstreng, til fullbreddes (dobbeltbyte) tegn -LEFT = VENSTRE ## Returnerer tegnene lengst til venstre i en tekstverdi -LEFTB = VENSTREB ## Returnerer tegnene lengst til venstre i en tekstverdi -LEN = LENGDE ## Returnerer antall tegn i en tekststreng -LENB = LENGDEB ## Returnerer antall tegn i en tekststreng -LOWER = SMÅ ## Konverterer tekst til små bokstaver -MID = DELTEKST ## Returnerer et angitt antall tegn fra en tekststreng, og begynner fra posisjonen du angir -MIDB = DELTEKSTB ## Returnerer et angitt antall tegn fra en tekststreng, og begynner fra posisjonen du angir -PHONETIC = FURIGANA ## Trekker ut fonetiske tegn (furigana) fra en tekststreng -PROPER = STOR.FORBOKSTAV ## Gir den første bokstaven i hvert ord i en tekstverdi stor forbokstav -REPLACE = ERSTATT ## Erstatter tegn i en tekst -REPLACEB = ERSTATTB ## Erstatter tegn i en tekst -REPT = GJENTA ## Gjentar tekst et gitt antall ganger -RIGHT = HØYRE ## Returnerer tegnene lengst til høyre i en tekstverdi -RIGHTB = HØYREB ## Returnerer tegnene lengst til høyre i en tekstverdi -SEARCH = SØK ## Finner en tekstverdi inne i en annen (skiller ikke mellom store og små bokstaver) -SEARCHB = SØKB ## Finner en tekstverdi inne i en annen (skiller ikke mellom store og små bokstaver) -SUBSTITUTE = BYTT.UT ## Bytter ut gammel tekst med ny tekst i en tekststreng -T = T ## Konverterer argumentene til tekst -TEXT = TEKST ## Formaterer et tall og konverterer det til tekst -TRIM = TRIMME ## Fjerner mellomrom fra tekst -UPPER = STORE ## Konverterer tekst til store bokstaver -VALUE = VERDI ## Konverterer et tekstargument til et tall diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/pl/config b/PhpOffice/PhpSpreadsheet/Calculation/locale/pl/config old mode 100755 new mode 100644 index 00f8b9a..1d8468b --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/pl/config +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/pl/config @@ -1,24 +1,20 @@ +############################################################ ## -## PhpSpreadsheet +## PhpSpreadsheet - locale settings ## +## Jezyk polski (Polish) +## +############################################################ -ArgumentSeparator = ; - +ArgumentSeparator = ; ## -## (For future use) +## Error Codes ## -currencySymbol = zł - - -## -## Excel Error Codes (For future use) - -## -NULL = #ZERO! -DIV0 = #DZIEL/0! -VALUE = #ARG! -REF = #ADR! -NAME = #NAZWA? -NUM = #LICZBA! -NA = #N/D! +NULL = #ZERO! +DIV0 = #DZIEL/0! +VALUE = #ARG! +REF = #ADR! +NAME = #NAZWA? +NUM = #LICZBA! +NA = #N/D! diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/pl/functions b/PhpOffice/PhpSpreadsheet/Calculation/locale/pl/functions old mode 100755 new mode 100644 index 907a4ff..d1b43b2 --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/pl/functions +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/pl/functions @@ -1,416 +1,536 @@ +############################################################ ## -## PhpSpreadsheet -## -## Data in this file derived from https://www.excel-function-translation.com/ +## PhpSpreadsheet - function name translations ## +## Jezyk polski (Polish) ## +############################################################ ## -## Add-in and Automation functions Funkcje dodatków i automatyzacji +## Funkcje baz danych (Cube Functions) ## -GETPIVOTDATA = WEŹDANETABELI ## Zwraca dane przechowywane w raporcie tabeli przestawnej. - +CUBEKPIMEMBER = ELEMENT.KPI.MODUŁU +CUBEMEMBER = ELEMENT.MODUŁU +CUBEMEMBERPROPERTY = WŁAŚCIWOŚĆ.ELEMENTU.MODUŁU +CUBERANKEDMEMBER = USZEREGOWANY.ELEMENT.MODUŁU +CUBESET = ZESTAW.MODUŁÓW +CUBESETCOUNT = LICZNIK.MODUŁÓW.ZESTAWU +CUBEVALUE = WARTOŚĆ.MODUŁU ## -## Cube functions Funkcje modułów +## Funkcje baz danych (Database Functions) ## -CUBEKPIMEMBER = ELEMENT.KPI.MODUŁU ## Zwraca nazwę, właściwość i miarę kluczowego wskaźnika wydajności (KPI) oraz wyświetla nazwę i właściwość w komórce. Wskaźnik KPI jest miarą ilościową, taką jak miesięczny zysk brutto lub kwartalna fluktuacja pracowników, używaną do monitorowania wydajności organizacji. -CUBEMEMBER = ELEMENT.MODUŁU ## Zwraca element lub krotkę z hierarchii modułu. Służy do sprawdzania, czy element lub krotka istnieje w module. -CUBEMEMBERPROPERTY = WŁAŚCIWOŚĆ.ELEMENTU.MODUŁU ## Zwraca wartość właściwości elementu w module. Służy do sprawdzania, czy nazwa elementu istnieje w module, i zwracania określonej właściwości dla tego elementu. -CUBERANKEDMEMBER = USZEREGOWANY.ELEMENT.MODUŁU ## Zwraca n-ty (albo uszeregowany) element zestawu. Służy do zwracania elementu lub elementów zestawu, na przykład najlepszego sprzedawcy lub 10 najlepszych studentów. -CUBESET = ZESTAW.MODUŁÓW ## Definiuje obliczony zestaw elementów lub krotek, wysyłając wyrażenie zestawu do serwera modułu, który tworzy zestaw i zwraca go do programu Microsoft Office Excel. -CUBESETCOUNT = LICZNIK.MODUŁÓW.ZESTAWU ## Zwraca liczbę elementów zestawu. -CUBEVALUE = WARTOŚĆ.MODUŁU ## Zwraca zagregowaną wartość z modułu. - +DAVERAGE = BD.ŚREDNIA +DCOUNT = BD.ILE.REKORDÓW +DCOUNTA = BD.ILE.REKORDÓW.A +DGET = BD.POLE +DMAX = BD.MAX +DMIN = BD.MIN +DPRODUCT = BD.ILOCZYN +DSTDEV = BD.ODCH.STANDARD +DSTDEVP = BD.ODCH.STANDARD.POPUL +DSUM = BD.SUMA +DVAR = BD.WARIANCJA +DVARP = BD.WARIANCJA.POPUL ## -## Database functions Funkcje baz danych +## Funkcje daty i godziny (Date & Time Functions) ## -DAVERAGE = BD.ŚREDNIA ## Zwraca wartość średniej wybranych wpisów bazy danych. -DCOUNT = BD.ILE.REKORDÓW ## Zlicza komórki zawierające liczby w bazie danych. -DCOUNTA = BD.ILE.REKORDÓW.A ## Zlicza niepuste komórki w bazie danych. -DGET = BD.POLE ## Wyodrębnia z bazy danych jeden rekord spełniający określone kryteria. -DMAX = BD.MAX ## Zwraca wartość maksymalną z wybranych wpisów bazy danych. -DMIN = BD.MIN ## Zwraca wartość minimalną z wybranych wpisów bazy danych. -DPRODUCT = BD.ILOCZYN ## Mnoży wartości w konkretnym, spełniającym kryteria polu rekordów bazy danych. -DSTDEV = BD.ODCH.STANDARD ## Szacuje odchylenie standardowe na podstawie próbki z wybranych wpisów bazy danych. -DSTDEVP = BD.ODCH.STANDARD.POPUL ## Oblicza odchylenie standardowe na podstawie całej populacji wybranych wpisów bazy danych. -DSUM = BD.SUMA ## Dodaje liczby w kolumnie pól rekordów bazy danych, które spełniają kryteria. -DVAR = BD.WARIANCJA ## Szacuje wariancję na podstawie próbki z wybranych wpisów bazy danych. -DVARP = BD.WARIANCJA.POPUL ## Oblicza wariancję na podstawie całej populacji wybranych wpisów bazy danych. - +DATE = DATA +DATEDIF = DATA.RÓŻNICA +DATESTRING = DATA.CIĄG.ZNAK +DATEVALUE = DATA.WARTOŚĆ +DAY = DZIEŃ +DAYS = DNI +DAYS360 = DNI.360 +EDATE = NR.SER.DATY +EOMONTH = NR.SER.OST.DN.MIES +HOUR = GODZINA +ISOWEEKNUM = ISO.NUM.TYG +MINUTE = MINUTA +MONTH = MIESIĄC +NETWORKDAYS = DNI.ROBOCZE +NETWORKDAYS.INTL = DNI.ROBOCZE.NIESTAND +NOW = TERAZ +SECOND = SEKUNDA +THAIDAYOFWEEK = TAJ.DZIEŃ.TYGODNIA +THAIMONTHOFYEAR = TAJ.MIESIĄC.ROKU +THAIYEAR = TAJ.ROK +TIME = CZAS +TIMEVALUE = CZAS.WARTOŚĆ +TODAY = DZIŚ +WEEKDAY = DZIEŃ.TYG +WEEKNUM = NUM.TYG +WORKDAY = DZIEŃ.ROBOCZY +WORKDAY.INTL = DZIEŃ.ROBOCZY.NIESTAND +YEAR = ROK +YEARFRAC = CZĘŚĆ.ROKU ## -## Date and time functions Funkcje dat, godzin i czasu +## Funkcje inżynierskie (Engineering Functions) ## -DATE = DATA ## Zwraca liczbę seryjną dla wybranej daty. -DATEVALUE = DATA.WARTOŚĆ ## Konwertuje datę w formie tekstu na liczbę seryjną. -DAY = DZIEŃ ## Konwertuje liczbę seryjną na dzień miesiąca. -DAYS360 = DNI.360 ## Oblicza liczbę dni między dwiema datami na podstawie roku 360-dniowego. -EDATE = UPŁDNI ## Zwraca liczbę seryjną daty jako wskazaną liczbę miesięcy przed określoną datą początkową lub po niej. -EOMONTH = EOMONTH ## Zwraca liczbę seryjną ostatniego dnia miesiąca przed określoną liczbą miesięcy lub po niej. -HOUR = GODZINA ## Konwertuje liczbę seryjną na godzinę. -MINUTE = MINUTA ## Konwertuje liczbę seryjną na minutę. -MONTH = MIESIĄC ## Konwertuje liczbę seryjną na miesiąc. -NETWORKDAYS = NETWORKDAYS ## Zwraca liczbę pełnych dni roboczych między dwiema datami. -NOW = TERAZ ## Zwraca liczbę seryjną bieżącej daty i godziny. -SECOND = SEKUNDA ## Konwertuje liczbę seryjną na sekundę. -TIME = CZAS ## Zwraca liczbę seryjną określonego czasu. -TIMEVALUE = CZAS.WARTOŚĆ ## Konwertuje czas w formie tekstu na liczbę seryjną. -TODAY = DZIŚ ## Zwraca liczbę seryjną dla daty bieżącej. -WEEKDAY = DZIEŃ.TYG ## Konwertuje liczbę seryjną na dzień tygodnia. -WEEKNUM = WEEKNUM ## Konwertuje liczbę seryjną na liczbę reprezentującą numer tygodnia w roku. -WORKDAY = WORKDAY ## Zwraca liczbę seryjną dla daty przed określoną liczbą dni roboczych lub po niej. -YEAR = ROK ## Konwertuje liczbę seryjną na rok. -YEARFRAC = YEARFRAC ## Zwraca część roku reprezentowaną przez pełną liczbę dni między datą początkową a datą końcową. - +BESSELI = BESSEL.I +BESSELJ = BESSEL.J +BESSELK = BESSEL.K +BESSELY = BESSEL.Y +BIN2DEC = DWÓJK.NA.DZIES +BIN2HEX = DWÓJK.NA.SZESN +BIN2OCT = DWÓJK.NA.ÓSM +BITAND = BITAND +BITLSHIFT = BIT.PRZESUNIĘCIE.W.LEWO +BITOR = BITOR +BITRSHIFT = BIT.PRZESUNIĘCIE.W.PRAWO +BITXOR = BITXOR +COMPLEX = LICZBA.ZESP +CONVERT = KONWERTUJ +DEC2BIN = DZIES.NA.DWÓJK +DEC2HEX = DZIES.NA.SZESN +DEC2OCT = DZIES.NA.ÓSM +DELTA = CZY.RÓWNE +ERF = FUNKCJA.BŁ +ERF.PRECISE = FUNKCJA.BŁ.DOKŁ +ERFC = KOMP.FUNKCJA.BŁ +ERFC.PRECISE = KOMP.FUNKCJA.BŁ.DOKŁ +GESTEP = SPRAWDŹ.PRÓG +HEX2BIN = SZESN.NA.DWÓJK +HEX2DEC = SZESN.NA.DZIES +HEX2OCT = SZESN.NA.ÓSM +IMABS = MODUŁ.LICZBY.ZESP +IMAGINARY = CZ.UROJ.LICZBY.ZESP +IMARGUMENT = ARG.LICZBY.ZESP +IMCONJUGATE = SPRZĘŻ.LICZBY.ZESP +IMCOS = COS.LICZBY.ZESP +IMCOSH = COSH.LICZBY.ZESP +IMCOT = COT.LICZBY.ZESP +IMCSC = CSC.LICZBY.ZESP +IMCSCH = CSCH.LICZBY.ZESP +IMDIV = ILORAZ.LICZB.ZESP +IMEXP = EXP.LICZBY.ZESP +IMLN = LN.LICZBY.ZESP +IMLOG10 = LOG10.LICZBY.ZESP +IMLOG2 = LOG2.LICZBY.ZESP +IMPOWER = POTĘGA.LICZBY.ZESP +IMPRODUCT = ILOCZYN.LICZB.ZESP +IMREAL = CZ.RZECZ.LICZBY.ZESP +IMSEC = SEC.LICZBY.ZESP +IMSECH = SECH.LICZBY.ZESP +IMSIN = SIN.LICZBY.ZESP +IMSINH = SINH.LICZBY.ZESP +IMSQRT = PIERWIASTEK.LICZBY.ZESP +IMSUB = RÓŻN.LICZB.ZESP +IMSUM = SUMA.LICZB.ZESP +IMTAN = TAN.LICZBY.ZESP +OCT2BIN = ÓSM.NA.DWÓJK +OCT2DEC = ÓSM.NA.DZIES +OCT2HEX = ÓSM.NA.SZESN ## -## Engineering functions Funkcje inżynierskie +## Funkcje finansowe (Financial Functions) ## -BESSELI = BESSELI ## Zwraca wartość zmodyfikowanej funkcji Bessela In(x). -BESSELJ = BESSELJ ## Zwraca wartość funkcji Bessela Jn(x). -BESSELK = BESSELK ## Zwraca wartość zmodyfikowanej funkcji Bessela Kn(x). -BESSELY = BESSELY ## Zwraca wartość funkcji Bessela Yn(x). -BIN2DEC = BIN2DEC ## Konwertuje liczbę w postaci dwójkowej na liczbę w postaci dziesiętnej. -BIN2HEX = BIN2HEX ## Konwertuje liczbę w postaci dwójkowej na liczbę w postaci szesnastkowej. -BIN2OCT = BIN2OCT ## Konwertuje liczbę w postaci dwójkowej na liczbę w postaci ósemkowej. -COMPLEX = COMPLEX ## Konwertuje część rzeczywistą i urojoną na liczbę zespoloną. -CONVERT = CONVERT ## Konwertuje liczbę z jednego systemu miar na inny. -DEC2BIN = DEC2BIN ## Konwertuje liczbę w postaci dziesiętnej na postać dwójkową. -DEC2HEX = DEC2HEX ## Konwertuje liczbę w postaci dziesiętnej na liczbę w postaci szesnastkowej. -DEC2OCT = DEC2OCT ## Konwertuje liczbę w postaci dziesiętnej na liczbę w postaci ósemkowej. -DELTA = DELTA ## Sprawdza, czy dwie wartości są równe. -ERF = ERF ## Zwraca wartość funkcji błędu. -ERFC = ERFC ## Zwraca wartość komplementarnej funkcji błędu. -GESTEP = GESTEP ## Sprawdza, czy liczba jest większa niż wartość progowa. -HEX2BIN = HEX2BIN ## Konwertuje liczbę w postaci szesnastkowej na liczbę w postaci dwójkowej. -HEX2DEC = HEX2DEC ## Konwertuje liczbę w postaci szesnastkowej na liczbę w postaci dziesiętnej. -HEX2OCT = HEX2OCT ## Konwertuje liczbę w postaci szesnastkowej na liczbę w postaci ósemkowej. -IMABS = IMABS ## Zwraca wartość bezwzględną (moduł) liczby zespolonej. -IMAGINARY = IMAGINARY ## Zwraca wartość części urojonej liczby zespolonej. -IMARGUMENT = IMARGUMENT ## Zwraca wartość argumentu liczby zespolonej, przy czym kąt wyrażony jest w radianach. -IMCONJUGATE = IMCONJUGATE ## Zwraca wartość liczby sprzężonej danej liczby zespolonej. -IMCOS = IMCOS ## Zwraca wartość cosinusa liczby zespolonej. -IMDIV = IMDIV ## Zwraca wartość ilorazu dwóch liczb zespolonych. -IMEXP = IMEXP ## Zwraca postać wykładniczą liczby zespolonej. -IMLN = IMLN ## Zwraca wartość logarytmu naturalnego liczby zespolonej. -IMLOG10 = IMLOG10 ## Zwraca wartość logarytmu dziesiętnego liczby zespolonej. -IMLOG2 = IMLOG2 ## Zwraca wartość logarytmu liczby zespolonej przy podstawie 2. -IMPOWER = IMPOWER ## Zwraca wartość liczby zespolonej podniesionej do potęgi całkowitej. -IMPRODUCT = IMPRODUCT ## Zwraca wartość iloczynu liczb zespolonych. -IMREAL = IMREAL ## Zwraca wartość części rzeczywistej liczby zespolonej. -IMSIN = IMSIN ## Zwraca wartość sinusa liczby zespolonej. -IMSQRT = IMSQRT ## Zwraca wartość pierwiastka kwadratowego z liczby zespolonej. -IMSUB = IMSUB ## Zwraca wartość różnicy dwóch liczb zespolonych. -IMSUM = IMSUM ## Zwraca wartość sumy liczb zespolonych. -OCT2BIN = OCT2BIN ## Konwertuje liczbę w postaci ósemkowej na liczbę w postaci dwójkowej. -OCT2DEC = OCT2DEC ## Konwertuje liczbę w postaci ósemkowej na liczbę w postaci dziesiętnej. -OCT2HEX = OCT2HEX ## Konwertuje liczbę w postaci ósemkowej na liczbę w postaci szesnastkowej. - +ACCRINT = NAL.ODS +ACCRINTM = NAL.ODS.WYKUP +AMORDEGRC = AMORT.NIELIN +AMORLINC = AMORT.LIN +COUPDAYBS = WYPŁ.DNI.OD.POCZ +COUPDAYS = WYPŁ.DNI +COUPDAYSNC = WYPŁ.DNI.NAST +COUPNCD = WYPŁ.DATA.NAST +COUPNUM = WYPŁ.LICZBA +COUPPCD = WYPŁ.DATA.POPRZ +CUMIPMT = SPŁAC.ODS +CUMPRINC = SPŁAC.KAPIT +DB = DB +DDB = DDB +DISC = STOPA.DYSK +DOLLARDE = CENA.DZIES +DOLLARFR = CENA.UŁAM +DURATION = ROCZ.PRZYCH +EFFECT = EFEKTYWNA +FV = FV +FVSCHEDULE = WART.PRZYSZŁ.KAP +INTRATE = STOPA.PROC +IPMT = IPMT +IRR = IRR +ISPMT = ISPMT +MDURATION = ROCZ.PRZYCH.M +MIRR = MIRR +NOMINAL = NOMINALNA +NPER = NPER +NPV = NPV +ODDFPRICE = CENA.PIERW.OKR +ODDFYIELD = RENT.PIERW.OKR +ODDLPRICE = CENA.OST.OKR +ODDLYIELD = RENT.OST.OKR +PDURATION = O.CZAS.TRWANIA +PMT = PMT +PPMT = PPMT +PRICE = CENA +PRICEDISC = CENA.DYSK +PRICEMAT = CENA.WYKUP +PV = PV +RATE = RATE +RECEIVED = KWOTA.WYKUP +RRI = RÓWNOW.STOPA.PROC +SLN = SLN +SYD = SYD +TBILLEQ = RENT.EKW.BS +TBILLPRICE = CENA.BS +TBILLYIELD = RENT.BS +VDB = VDB +XIRR = XIRR +XNPV = XNPV +YIELD = RENTOWNOŚĆ +YIELDDISC = RENT.DYSK +YIELDMAT = RENT.WYKUP ## -## Financial functions Funkcje finansowe +## Funkcje informacyjne (Information Functions) ## -ACCRINT = ACCRINT ## Zwraca narosłe odsetki dla papieru wartościowego z oprocentowaniem okresowym. -ACCRINTM = ACCRINTM ## Zwraca narosłe odsetki dla papieru wartościowego z oprocentowaniem w terminie wykupu. -AMORDEGRC = AMORDEGRC ## Zwraca amortyzację dla każdego okresu rozliczeniowego z wykorzystaniem współczynnika amortyzacji. -AMORLINC = AMORLINC ## Zwraca amortyzację dla każdego okresu rozliczeniowego. -COUPDAYBS = COUPDAYBS ## Zwraca liczbę dni od początku okresu dywidendy do dnia rozliczeniowego. -COUPDAYS = COUPDAYS ## Zwraca liczbę dni w okresie dywidendy, z uwzględnieniem dnia rozliczeniowego. -COUPDAYSNC = COUPDAYSNC ## Zwraca liczbę dni od dnia rozliczeniowego do daty następnego dnia dywidendy. -COUPNCD = COUPNCD ## Zwraca dzień następnej dywidendy po dniu rozliczeniowym. -COUPNUM = COUPNUM ## Zwraca liczbę dywidend płatnych między dniem rozliczeniowym a dniem wykupu. -COUPPCD = COUPPCD ## Zwraca dzień poprzedniej dywidendy przed dniem rozliczeniowym. -CUMIPMT = CUMIPMT ## Zwraca wartość procentu składanego płatnego między dwoma okresami. -CUMPRINC = CUMPRINC ## Zwraca wartość kapitału skumulowanego spłaty pożyczki między dwoma okresami. -DB = DB ## Zwraca amortyzację środka trwałego w danym okresie metodą degresywną z zastosowaniem stałej bazowej. -DDB = DDB ## Zwraca amortyzację środka trwałego za podany okres metodą degresywną z zastosowaniem podwójnej bazowej lub metodą określoną przez użytkownika. -DISC = DISC ## Zwraca wartość stopy dyskontowej papieru wartościowego. -DOLLARDE = DOLLARDE ## Konwertuje cenę w postaci ułamkowej na cenę wyrażoną w postaci dziesiętnej. -DOLLARFR = DOLLARFR ## Konwertuje cenę wyrażoną w postaci dziesiętnej na cenę wyrażoną w postaci ułamkowej. -DURATION = DURATION ## Zwraca wartość rocznego przychodu z papieru wartościowego o okresowych wypłatach oprocentowania. -EFFECT = EFFECT ## Zwraca wartość efektywnej rocznej stopy procentowej. -FV = FV ## Zwraca przyszłą wartość lokaty. -FVSCHEDULE = FVSCHEDULE ## Zwraca przyszłą wartość kapitału początkowego wraz z szeregiem procentów składanych. -INTRATE = INTRATE ## Zwraca wartość stopy procentowej papieru wartościowego całkowicie ulokowanego. -IPMT = IPMT ## Zwraca wysokość spłaty oprocentowania lokaty za dany okres. -IRR = IRR ## Zwraca wartość wewnętrznej stopy zwrotu dla serii przepływów gotówkowych. -ISPMT = ISPMT ## Oblicza wysokość spłaty oprocentowania za dany okres lokaty. -MDURATION = MDURATION ## Zwraca wartość zmodyfikowanego okresu Macauleya dla papieru wartościowego o założonej wartości nominalnej 100 zł. -MIRR = MIRR ## Zwraca wartość wewnętrznej stopy zwrotu dla przypadku, gdy dodatnie i ujemne przepływy gotówkowe mają różne stopy. -NOMINAL = NOMINAL ## Zwraca wysokość nominalnej rocznej stopy procentowej. -NPER = NPER ## Zwraca liczbę okresów dla lokaty. -NPV = NPV ## Zwraca wartość bieżącą netto lokaty na podstawie szeregu okresowych przepływów gotówkowych i stopy dyskontowej. -ODDFPRICE = ODDFPRICE ## Zwraca cenę za 100 zł wartości nominalnej papieru wartościowego z nietypowym pierwszym okresem. -ODDFYIELD = ODDFYIELD ## Zwraca rentowność papieru wartościowego z nietypowym pierwszym okresem. -ODDLPRICE = ODDLPRICE ## Zwraca cenę za 100 zł wartości nominalnej papieru wartościowego z nietypowym ostatnim okresem. -ODDLYIELD = ODDLYIELD ## Zwraca rentowność papieru wartościowego z nietypowym ostatnim okresem. -PMT = PMT ## Zwraca wartość okresowej płatności raty rocznej. -PPMT = PPMT ## Zwraca wysokość spłaty kapitału w przypadku lokaty dla danego okresu. -PRICE = PRICE ## Zwraca cenę za 100 zł wartości nominalnej papieru wartościowego z oprocentowaniem okresowym. -PRICEDISC = PRICEDISC ## Zwraca cenę za 100 zł wartości nominalnej papieru wartościowego zdyskontowanego. -PRICEMAT = PRICEMAT ## Zwraca cenę za 100 zł wartości nominalnej papieru wartościowego z oprocentowaniem w terminie wykupu. -PV = PV ## Zwraca wartość bieżącą lokaty. -RATE = RATE ## Zwraca wysokość stopy procentowej w okresie raty rocznej. -RECEIVED = RECEIVED ## Zwraca wartość kapitału otrzymanego przy wykupie papieru wartościowego całkowicie ulokowanego. -SLN = SLN ## Zwraca amortyzację środka trwałego za jeden okres metodą liniową. -SYD = SYD ## Zwraca amortyzację środka trwałego za dany okres metodą sumy cyfr lat amortyzacji. -TBILLEQ = TBILLEQ ## Zwraca rentowność ekwiwalentu obligacji dla bonu skarbowego. -TBILLPRICE = TBILLPRICE ## Zwraca cenę za 100 zł wartości nominalnej bonu skarbowego. -TBILLYIELD = TBILLYIELD ## Zwraca rentowność bonu skarbowego. -VDB = VDB ## Oblicza amortyzację środka trwałego w danym okresie lub jego części metodą degresywną. -XIRR = XIRR ## Zwraca wartość wewnętrznej stopy zwrotu dla serii rozłożonych w czasie przepływów gotówkowych, niekoniecznie okresowych. -XNPV = XNPV ## Zwraca wartość bieżącą netto dla serii rozłożonych w czasie przepływów gotówkowych, niekoniecznie okresowych. -YIELD = YIELD ## Zwraca rentowność papieru wartościowego z oprocentowaniem okresowym. -YIELDDISC = YIELDDISC ## Zwraca roczną rentowność zdyskontowanego papieru wartościowego, na przykład bonu skarbowego. -YIELDMAT = YIELDMAT ## Zwraca roczną rentowność papieru wartościowego oprocentowanego przy wykupie. - +CELL = KOMÓRKA +ERROR.TYPE = NR.BŁĘDU +INFO = INFO +ISBLANK = CZY.PUSTA +ISERR = CZY.BŁ +ISERROR = CZY.BŁĄD +ISEVEN = CZY.PARZYSTE +ISFORMULA = CZY.FORMUŁA +ISLOGICAL = CZY.LOGICZNA +ISNA = CZY.BRAK +ISNONTEXT = CZY.NIE.TEKST +ISNUMBER = CZY.LICZBA +ISODD = CZY.NIEPARZYSTE +ISREF = CZY.ADR +ISTEXT = CZY.TEKST +N = N +NA = BRAK +SHEET = ARKUSZ +SHEETS = ARKUSZE +TYPE = TYP ## -## Information functions Funkcje informacyjne +## Funkcje logiczne (Logical Functions) ## -CELL = KOMÓRKA ## Zwraca informacje o formacie, położeniu lub zawartości komórki. -ERROR.TYPE = NR.BŁĘDU ## Zwraca liczbę odpowiadającą typowi błędu. -INFO = INFO ## Zwraca informację o aktualnym środowisku pracy. -ISBLANK = CZY.PUSTA ## Zwraca wartość PRAWDA, jeśli wartość jest pusta. -ISERR = CZY.BŁ ## Zwraca wartość PRAWDA, jeśli wartość jest dowolną wartością błędu, z wyjątkiem #N/D!. -ISERROR = CZY.BŁĄD ## Zwraca wartość PRAWDA, jeśli wartość jest dowolną wartością błędu. -ISEVEN = ISEVEN ## Zwraca wartość PRAWDA, jeśli liczba jest parzysta. -ISLOGICAL = CZY.LOGICZNA ## Zwraca wartość PRAWDA, jeśli wartość jest wartością logiczną. -ISNA = CZY.BRAK ## Zwraca wartość PRAWDA, jeśli wartość jest wartością błędu #N/D!. -ISNONTEXT = CZY.NIE.TEKST ## Zwraca wartość PRAWDA, jeśli wartość nie jest tekstem. -ISNUMBER = CZY.LICZBA ## Zwraca wartość PRAWDA, jeśli wartość jest liczbą. -ISODD = ISODD ## Zwraca wartość PRAWDA, jeśli liczba jest nieparzysta. -ISREF = CZY.ADR ## Zwraca wartość PRAWDA, jeśli wartość jest odwołaniem. -ISTEXT = CZY.TEKST ## Zwraca wartość PRAWDA, jeśli wartość jest tekstem. -N = L ## Zwraca wartość przekonwertowaną na postać liczbową. -NA = BRAK ## Zwraca wartość błędu #N/D!. -TYPE = TYP ## Zwraca liczbę wskazującą typ danych wartości. - +AND = ORAZ +FALSE = FAŁSZ +IF = JEŻELI +IFERROR = JEŻELI.BŁĄD +IFNA = JEŻELI.ND +IFS = WARUNKI +NOT = NIE +OR = LUB +SWITCH = PRZEŁĄCZ +TRUE = PRAWDA +XOR = XOR ## -## Logical functions Funkcje logiczne +## Funkcje wyszukiwania i odwołań (Lookup & Reference Functions) ## -AND = ORAZ ## Zwraca wartość PRAWDA, jeśli wszystkie argumenty mają wartość PRAWDA. -FALSE = FAŁSZ ## Zwraca wartość logiczną FAŁSZ. -IF = JEŻELI ## Określa warunek logiczny do sprawdzenia. -IFERROR = JEŻELI.BŁĄD ## Zwraca określoną wartość, jeśli wynikiem obliczenia formuły jest błąd; w przeciwnym przypadku zwraca wynik formuły. -NOT = NIE ## Odwraca wartość logiczną argumentu. -OR = LUB ## Zwraca wartość PRAWDA, jeśli co najmniej jeden z argumentów ma wartość PRAWDA. -TRUE = PRAWDA ## Zwraca wartość logiczną PRAWDA. - +ADDRESS = ADRES +AREAS = OBSZARY +CHOOSE = WYBIERZ +COLUMN = NR.KOLUMNY +COLUMNS = LICZBA.KOLUMN +FORMULATEXT = FORMUŁA.TEKST +GETPIVOTDATA = WEŹDANETABELI +HLOOKUP = WYSZUKAJ.POZIOMO +HYPERLINK = HIPERŁĄCZE +INDEX = INDEKS +INDIRECT = ADR.POŚR +LOOKUP = WYSZUKAJ +MATCH = PODAJ.POZYCJĘ +OFFSET = PRZESUNIĘCIE +ROW = WIERSZ +ROWS = ILE.WIERSZY +RTD = DANE.CZASU.RZECZ +TRANSPOSE = TRANSPONUJ +VLOOKUP = WYSZUKAJ.PIONOWO ## -## Lookup and reference functions Funkcje wyszukiwania i odwołań +## Funkcje matematyczne i trygonometryczne (Math & Trig Functions) ## -ADDRESS = ADRES ## Zwraca odwołanie do jednej komórki w arkuszu jako wartość tekstową. -AREAS = OBSZARY ## Zwraca liczbę obszarów występujących w odwołaniu. -CHOOSE = WYBIERZ ## Wybiera wartość z listy wartości. -COLUMN = NR.KOLUMNY ## Zwraca numer kolumny z odwołania. -COLUMNS = LICZBA.KOLUMN ## Zwraca liczbę kolumn dla danego odwołania. -HLOOKUP = WYSZUKAJ.POZIOMO ## Przegląda górny wiersz tablicy i zwraca wartość wskazanej komórki. -HYPERLINK = HIPERŁĄCZE ## Tworzy skrót lub skok, który pozwala otwierać dokument przechowywany na serwerze sieciowym, w sieci intranet lub w Internecie. -INDEX = INDEKS ## Używa indeksu do wybierania wartości z odwołania lub tablicy. -INDIRECT = ADR.POŚR ## Zwraca odwołanie określone przez wartość tekstową. -LOOKUP = WYSZUKAJ ## Wyszukuje wartości w wektorze lub tablicy. -MATCH = PODAJ.POZYCJĘ ## Wyszukuje wartości w odwołaniu lub w tablicy. -OFFSET = PRZESUNIĘCIE ## Zwraca adres przesunięty od danego odwołania. -ROW = WIERSZ ## Zwraca numer wiersza odwołania. -ROWS = ILE.WIERSZY ## Zwraca liczbę wierszy dla danego odwołania. -RTD = RTD ## Pobiera dane w czasie rzeczywistym z programu obsługującego automatyzację COM (Automatyzacja: Sposób pracy z obiektami aplikacji pochodzącymi z innej aplikacji lub narzędzia projektowania. Nazywana wcześniej Automatyzacją OLE, Automatyzacja jest standardem przemysłowym i funkcją obiektowego modelu składników (COM, Component Object Model).). -TRANSPOSE = TRANSPONUJ ## Zwraca transponowaną tablicę. -VLOOKUP = WYSZUKAJ.PIONOWO ## Przeszukuje pierwszą kolumnę tablicy i przechodzi wzdłuż wiersza, aby zwrócić wartość komórki. - +ABS = MODUŁ.LICZBY +ACOS = ACOS +ACOSH = ACOSH +ACOT = ACOT +ACOTH = ACOTH +AGGREGATE = AGREGUJ +ARABIC = ARABSKIE +ASIN = ASIN +ASINH = ASINH +ATAN = ATAN +ATAN2 = ATAN2 +ATANH = ATANH +BASE = PODSTAWA +CEILING.MATH = ZAOKR.W.GÓRĘ.MATEMATYCZNE +CEILING.PRECISE = ZAOKR.W.GÓRĘ.DOKŁ +COMBIN = KOMBINACJE +COMBINA = KOMBINACJE.A +COS = COS +COSH = COSH +COT = COT +COTH = COTH +CSC = CSC +CSCH = CSCH +DECIMAL = DZIESIĘTNA +DEGREES = STOPNIE +ECMA.CEILING = ECMA.ZAOKR.W.GÓRĘ +EVEN = ZAOKR.DO.PARZ +EXP = EXP +FACT = SILNIA +FACTDOUBLE = SILNIA.DWUKR +FLOOR.MATH = ZAOKR.W.DÓŁ.MATEMATYCZNE +FLOOR.PRECISE = ZAOKR.W.DÓŁ.DOKŁ +GCD = NAJW.WSP.DZIEL +INT = ZAOKR.DO.CAŁK +ISO.CEILING = ISO.ZAOKR.W.GÓRĘ +LCM = NAJMN.WSP.WIEL +LN = LN +LOG = LOG +LOG10 = LOG10 +MDETERM = WYZNACZNIK.MACIERZY +MINVERSE = MACIERZ.ODW +MMULT = MACIERZ.ILOCZYN +MOD = MOD +MROUND = ZAOKR.DO.WIELOKR +MULTINOMIAL = WIELOMIAN +MUNIT = MACIERZ.JEDNOSTKOWA +ODD = ZAOKR.DO.NPARZ +PI = PI +POWER = POTĘGA +PRODUCT = ILOCZYN +QUOTIENT = CZ.CAŁK.DZIELENIA +RADIANS = RADIANY +RAND = LOS +RANDBETWEEN = LOS.ZAKR +ROMAN = RZYMSKIE +ROUND = ZAOKR +ROUNDBAHTDOWN = ZAOKR.DÓŁ.BAT +ROUNDBAHTUP = ZAOKR.GÓRA.BAT +ROUNDDOWN = ZAOKR.DÓŁ +ROUNDUP = ZAOKR.GÓRA +SEC = SEC +SECH = SECH +SERIESSUM = SUMA.SZER.POT +SIGN = ZNAK.LICZBY +SIN = SIN +SINH = SINH +SQRT = PIERWIASTEK +SQRTPI = PIERW.PI +SUBTOTAL = SUMY.CZĘŚCIOWE +SUM = SUMA +SUMIF = SUMA.JEŻELI +SUMIFS = SUMA.WARUNKÓW +SUMPRODUCT = SUMA.ILOCZYNÓW +SUMSQ = SUMA.KWADRATÓW +SUMX2MY2 = SUMA.X2.M.Y2 +SUMX2PY2 = SUMA.X2.P.Y2 +SUMXMY2 = SUMA.XMY.2 +TAN = TAN +TANH = TANH +TRUNC = LICZBA.CAŁK ## -## Math and trigonometry functions Funkcje matematyczne i trygonometryczne +## Funkcje statystyczne (Statistical Functions) ## -ABS = MODUŁ.LICZBY ## Zwraca wartość absolutną liczby. -ACOS = ACOS ## Zwraca arcus cosinus liczby. -ACOSH = ACOSH ## Zwraca arcus cosinus hiperboliczny liczby. -ASIN = ASIN ## Zwraca arcus sinus liczby. -ASINH = ASINH ## Zwraca arcus sinus hiperboliczny liczby. -ATAN = ATAN ## Zwraca arcus tangens liczby. -ATAN2 = ATAN2 ## Zwraca arcus tangens liczby na podstawie współrzędnych x i y. -ATANH = ATANH ## Zwraca arcus tangens hiperboliczny liczby. -CEILING = ZAOKR.W.GÓRĘ ## Zaokrągla liczbę do najbliższej liczby całkowitej lub do najbliższej wielokrotności dokładności. -COMBIN = KOMBINACJE ## Zwraca liczbę kombinacji dla danej liczby obiektów. -COS = COS ## Zwraca cosinus liczby. -COSH = COSH ## Zwraca cosinus hiperboliczny liczby. -DEGREES = STOPNIE ## Konwertuje radiany na stopnie. -EVEN = ZAOKR.DO.PARZ ## Zaokrągla liczbę w górę do najbliższej liczby parzystej. -EXP = EXP ## Zwraca wartość liczby e podniesionej do potęgi określonej przez podaną liczbę. -FACT = SILNIA ## Zwraca silnię liczby. -FACTDOUBLE = FACTDOUBLE ## Zwraca podwójną silnię liczby. -FLOOR = ZAOKR.W.DÓŁ ## Zaokrągla liczbę w dół, w kierunku zera. -GCD = GCD ## Zwraca największy wspólny dzielnik. -INT = ZAOKR.DO.CAŁK ## Zaokrągla liczbę w dół do najbliższej liczby całkowitej. -LCM = LCM ## Zwraca najmniejszą wspólną wielokrotność. -LN = LN ## Zwraca logarytm naturalny podanej liczby. -LOG = LOG ## Zwraca logarytm danej liczby przy zadanej podstawie. -LOG10 = LOG10 ## Zwraca logarytm dziesiętny liczby. -MDETERM = WYZNACZNIK.MACIERZY ## Zwraca wyznacznik macierzy tablicy. -MINVERSE = MACIERZ.ODW ## Zwraca odwrotność macierzy tablicy. -MMULT = MACIERZ.ILOCZYN ## Zwraca iloczyn macierzy dwóch tablic. -MOD = MOD ## Zwraca resztę z dzielenia. -MROUND = MROUND ## Zwraca liczbę zaokrągloną do żądanej wielokrotności. -MULTINOMIAL = MULTINOMIAL ## Zwraca wielomian dla zbioru liczb. -ODD = ZAOKR.DO.NPARZ ## Zaokrągla liczbę w górę do najbliższej liczby nieparzystej. -PI = PI ## Zwraca wartość liczby Pi. -POWER = POTĘGA ## Zwraca liczbę podniesioną do potęgi. -PRODUCT = ILOCZYN ## Mnoży argumenty. -QUOTIENT = QUOTIENT ## Zwraca iloraz (całkowity). -RADIANS = RADIANY ## Konwertuje stopnie na radiany. -RAND = LOS ## Zwraca liczbę pseudolosową z zakresu od 0 do 1. -RANDBETWEEN = RANDBETWEEN ## Zwraca liczbę pseudolosową z zakresu określonego przez podane argumenty. -ROMAN = RZYMSKIE ## Konwertuje liczbę arabską na rzymską jako tekst. -ROUND = ZAOKR ## Zaokrągla liczbę do określonej liczby cyfr. -ROUNDDOWN = ZAOKR.DÓŁ ## Zaokrągla liczbę w dół, w kierunku zera. -ROUNDUP = ZAOKR.GÓRA ## Zaokrągla liczbę w górę, w kierunku od zera. -SERIESSUM = SERIESSUM ## Zwraca sumę szeregu potęgowego na podstawie wzoru. -SIGN = ZNAK.LICZBY ## Zwraca znak liczby. -SIN = SIN ## Zwraca sinus danego kąta. -SINH = SINH ## Zwraca sinus hiperboliczny liczby. -SQRT = PIERWIASTEK ## Zwraca dodatni pierwiastek kwadratowy. -SQRTPI = SQRTPI ## Zwraca pierwiastek kwadratowy iloczynu (liczba * Pi). -SUBTOTAL = SUMY.POŚREDNIE ## Zwraca sumę częściową listy lub bazy danych. -SUM = SUMA ## Dodaje argumenty. -SUMIF = SUMA.JEŻELI ## Dodaje komórki określone przez podane kryterium. -SUMIFS = SUMA.WARUNKÓW ## Dodaje komórki w zakresie, które spełniają wiele kryteriów. -SUMPRODUCT = SUMA.ILOCZYNÓW ## Zwraca sumę iloczynów odpowiednich elementów tablicy. -SUMSQ = SUMA.KWADRATÓW ## Zwraca sumę kwadratów argumentów. -SUMX2MY2 = SUMA.X2.M.Y2 ## Zwraca sumę różnic kwadratów odpowiednich wartości w dwóch tablicach. -SUMX2PY2 = SUMA.X2.P.Y2 ## Zwraca sumę sum kwadratów odpowiednich wartości w dwóch tablicach. -SUMXMY2 = SUMA.XMY.2 ## Zwraca sumę kwadratów różnic odpowiednich wartości w dwóch tablicach. -TAN = TAN ## Zwraca tangens liczby. -TANH = TANH ## Zwraca tangens hiperboliczny liczby. -TRUNC = LICZBA.CAŁK ## Przycina liczbę do wartości całkowitej. - +AVEDEV = ODCH.ŚREDNIE +AVERAGE = ŚREDNIA +AVERAGEA = ŚREDNIA.A +AVERAGEIF = ŚREDNIA.JEŻELI +AVERAGEIFS = ŚREDNIA.WARUNKÓW +BETA.DIST = ROZKŁ.BETA +BETA.INV = ROZKŁ.BETA.ODWR +BINOM.DIST = ROZKŁ.DWUM +BINOM.DIST.RANGE = ROZKŁ.DWUM.ZAKRES +BINOM.INV = ROZKŁ.DWUM.ODWR +CHISQ.DIST = ROZKŁ.CHI +CHISQ.DIST.RT = ROZKŁ.CHI.PS +CHISQ.INV = ROZKŁ.CHI.ODWR +CHISQ.INV.RT = ROZKŁ.CHI.ODWR.PS +CHISQ.TEST = CHI.TEST +CONFIDENCE.NORM = UFNOŚĆ.NORM +CONFIDENCE.T = UFNOŚĆ.T +CORREL = WSP.KORELACJI +COUNT = ILE.LICZB +COUNTA = ILE.NIEPUSTYCH +COUNTBLANK = LICZ.PUSTE +COUNTIF = LICZ.JEŻELI +COUNTIFS = LICZ.WARUNKI +COVARIANCE.P = KOWARIANCJA.POPUL +COVARIANCE.S = KOWARIANCJA.PRÓBKI +DEVSQ = ODCH.KWADRATOWE +EXPON.DIST = ROZKŁ.EXP +F.DIST = ROZKŁ.F +F.DIST.RT = ROZKŁ.F.PS +F.INV = ROZKŁ.F.ODWR +F.INV.RT = ROZKŁ.F.ODWR.PS +F.TEST = F.TEST +FISHER = ROZKŁAD.FISHER +FISHERINV = ROZKŁAD.FISHER.ODW +FORECAST.ETS = REGLINX.ETS +FORECAST.ETS.CONFINT = REGLINX.ETS.CONFINT +FORECAST.ETS.SEASONALITY = REGLINX.ETS.SEZONOWOŚĆ +FORECAST.ETS.STAT = REGLINX.ETS.STATYSTYKA +FORECAST.LINEAR = REGLINX.LINIOWA +FREQUENCY = CZĘSTOŚĆ +GAMMA = GAMMA +GAMMA.DIST = ROZKŁ.GAMMA +GAMMA.INV = ROZKŁ.GAMMA.ODWR +GAMMALN = ROZKŁAD.LIN.GAMMA +GAMMALN.PRECISE = ROZKŁAD.LIN.GAMMA.DOKŁ +GAUSS = GAUSS +GEOMEAN = ŚREDNIA.GEOMETRYCZNA +GROWTH = REGEXPW +HARMEAN = ŚREDNIA.HARMONICZNA +HYPGEOM.DIST = ROZKŁ.HIPERGEOM +INTERCEPT = ODCIĘTA +KURT = KURTOZA +LARGE = MAX.K +LINEST = REGLINP +LOGEST = REGEXPP +LOGNORM.DIST = ROZKŁ.LOG +LOGNORM.INV = ROZKŁ.LOG.ODWR +MAX = MAX +MAXA = MAX.A +MAXIFS = MAKS.WARUNKÓW +MEDIAN = MEDIANA +MIN = MIN +MINA = MIN.A +MINIFS = MIN.WARUNKÓW +MODE.MULT = WYST.NAJCZĘŚCIEJ.TABL +MODE.SNGL = WYST.NAJCZĘŚCIEJ.WART +NEGBINOM.DIST = ROZKŁ.DWUM.PRZEC +NORM.DIST = ROZKŁ.NORMALNY +NORM.INV = ROZKŁ.NORMALNY.ODWR +NORM.S.DIST = ROZKŁ.NORMALNY.S +NORM.S.INV = ROZKŁ.NORMALNY.S.ODWR +PEARSON = PEARSON +PERCENTILE.EXC = PERCENTYL.PRZEDZ.OTW +PERCENTILE.INC = PERCENTYL.PRZEDZ.ZAMK +PERCENTRANK.EXC = PROC.POZ.PRZEDZ.OTW +PERCENTRANK.INC = PROC.POZ.PRZEDZ.ZAMK +PERMUT = PERMUTACJE +PERMUTATIONA = PERMUTACJE.A +PHI = PHI +POISSON.DIST = ROZKŁ.POISSON +PROB = PRAWDPD +QUARTILE.EXC = KWARTYL.PRZEDZ.OTW +QUARTILE.INC = KWARTYL.PRZEDZ.ZAMK +RANK.AVG = POZYCJA.ŚR +RANK.EQ = POZYCJA.NAJW +RSQ = R.KWADRAT +SKEW = SKOŚNOŚĆ +SKEW.P = SKOŚNOŚĆ.P +SLOPE = NACHYLENIE +SMALL = MIN.K +STANDARDIZE = NORMALIZUJ +STDEV.P = ODCH.STAND.POPUL +STDEV.S = ODCH.STANDARD.PRÓBKI +STDEVA = ODCH.STANDARDOWE.A +STDEVPA = ODCH.STANDARD.POPUL.A +STEYX = REGBŁSTD +T.DIST = ROZKŁ.T +T.DIST.2T = ROZKŁ.T.DS +T.DIST.RT = ROZKŁ.T.PS +T.INV = ROZKŁ.T.ODWR +T.INV.2T = ROZKŁ.T.ODWR.DS +T.TEST = T.TEST +TREND = REGLINW +TRIMMEAN = ŚREDNIA.WEWN +VAR.P = WARIANCJA.POP +VAR.S = WARIANCJA.PRÓBKI +VARA = WARIANCJA.A +VARPA = WARIANCJA.POPUL.A +WEIBULL.DIST = ROZKŁ.WEIBULL +Z.TEST = Z.TEST ## -## Statistical functions Funkcje statystyczne +## Funkcje tekstowe (Text Functions) ## -AVEDEV = ODCH.ŚREDNIE ## Zwraca średnią wartość odchyleń absolutnych punktów danych od ich wartości średniej. -AVERAGE = ŚREDNIA ## Zwraca wartość średnią argumentów. -AVERAGEA = ŚREDNIA.A ## Zwraca wartość średnią argumentów, z uwzględnieniem liczb, tekstów i wartości logicznych. -AVERAGEIF = ŚREDNIA.JEŻELI ## Zwraca średnią (średnią arytmetyczną) wszystkich komórek w zakresie, które spełniają podane kryteria. -AVERAGEIFS = ŚREDNIA.WARUNKÓW ## Zwraca średnią (średnią arytmetyczną) wszystkich komórek, które spełniają jedno lub więcej kryteriów. -BETADIST = ROZKŁAD.BETA ## Zwraca skumulowaną funkcję gęstości prawdopodobieństwa beta. -BETAINV = ROZKŁAD.BETA.ODW ## Zwraca odwrotność skumulowanej funkcji gęstości prawdopodobieństwa beta. -BINOMDIST = ROZKŁAD.DWUM ## Zwraca pojedynczy składnik dwumianowego rozkładu prawdopodobieństwa. -CHIDIST = ROZKŁAD.CHI ## Zwraca wartość jednostronnego prawdopodobieństwa rozkładu chi-kwadrat. -CHIINV = ROZKŁAD.CHI.ODW ## Zwraca odwrotność wartości jednostronnego prawdopodobieństwa rozkładu chi-kwadrat. -CHITEST = TEST.CHI ## Zwraca test niezależności. -CONFIDENCE = UFNOŚĆ ## Zwraca interwał ufności dla średniej populacji. -CORREL = WSP.KORELACJI ## Zwraca współczynnik korelacji dwóch zbiorów danych. -COUNT = ILE.LICZB ## Zlicza liczby znajdujące się na liście argumentów. -COUNTA = ILE.NIEPUSTYCH ## Zlicza wartości znajdujące się na liście argumentów. -COUNTBLANK = LICZ.PUSTE ## Zwraca liczbę pustych komórek w pewnym zakresie. -COUNTIF = LICZ.JEŻELI ## Zlicza komórki wewnątrz zakresu, które spełniają podane kryteria. -COUNTIFS = LICZ.WARUNKI ## Zlicza komórki wewnątrz zakresu, które spełniają wiele kryteriów. -COVAR = KOWARIANCJA ## Zwraca kowariancję, czyli średnią wartość iloczynów odpowiednich odchyleń. -CRITBINOM = PRÓG.ROZKŁAD.DWUM ## Zwraca najmniejszą wartość, dla której skumulowany rozkład dwumianowy jest mniejszy niż wartość kryterium lub równy jej. -DEVSQ = ODCH.KWADRATOWE ## Zwraca sumę kwadratów odchyleń. -EXPONDIST = ROZKŁAD.EXP ## Zwraca rozkład wykładniczy. -FDIST = ROZKŁAD.F ## Zwraca rozkład prawdopodobieństwa F. -FINV = ROZKŁAD.F.ODW ## Zwraca odwrotność rozkładu prawdopodobieństwa F. -FISHER = ROZKŁAD.FISHER ## Zwraca transformację Fishera. -FISHERINV = ROZKŁAD.FISHER.ODW ## Zwraca odwrotność transformacji Fishera. -FORECAST = REGLINX ## Zwraca wartość trendu liniowego. -FREQUENCY = CZĘSTOŚĆ ## Zwraca rozkład częstotliwości jako tablicę pionową. -FTEST = TEST.F ## Zwraca wynik testu F. -GAMMADIST = ROZKŁAD.GAMMA ## Zwraca rozkład gamma. -GAMMAINV = ROZKŁAD.GAMMA.ODW ## Zwraca odwrotność skumulowanego rozkładu gamma. -GAMMALN = ROZKŁAD.LIN.GAMMA ## Zwraca logarytm naturalny funkcji gamma, Γ(x). -GEOMEAN = ŚREDNIA.GEOMETRYCZNA ## Zwraca średnią geometryczną. -GROWTH = REGEXPW ## Zwraca wartości trendu wykładniczego. -HARMEAN = ŚREDNIA.HARMONICZNA ## Zwraca średnią harmoniczną. -HYPGEOMDIST = ROZKŁAD.HIPERGEOM ## Zwraca rozkład hipergeometryczny. -INTERCEPT = ODCIĘTA ## Zwraca punkt przecięcia osi pionowej z linią regresji liniowej. -KURT = KURTOZA ## Zwraca kurtozę zbioru danych. -LARGE = MAX.K ## Zwraca k-tą największą wartość ze zbioru danych. -LINEST = REGLINP ## Zwraca parametry trendu liniowego. -LOGEST = REGEXPP ## Zwraca parametry trendu wykładniczego. -LOGINV = ROZKŁAD.LOG.ODW ## Zwraca odwrotność rozkładu logarytmu naturalnego. -LOGNORMDIST = ROZKŁAD.LOG ## Zwraca skumulowany rozkład logarytmu naturalnego. -MAX = MAX ## Zwraca maksymalną wartość listy argumentów. -MAXA = MAX.A ## Zwraca maksymalną wartość listy argumentów, z uwzględnieniem liczb, tekstów i wartości logicznych. -MEDIAN = MEDIANA ## Zwraca medianę podanych liczb. -MIN = MIN ## Zwraca minimalną wartość listy argumentów. -MINA = MIN.A ## Zwraca najmniejszą wartość listy argumentów, z uwzględnieniem liczb, tekstów i wartości logicznych. -MODE = WYST.NAJCZĘŚCIEJ ## Zwraca wartość najczęściej występującą w zbiorze danych. -NEGBINOMDIST = ROZKŁAD.DWUM.PRZEC ## Zwraca ujemny rozkład dwumianowy. -NORMDIST = ROZKŁAD.NORMALNY ## Zwraca rozkład normalny skumulowany. -NORMINV = ROZKŁAD.NORMALNY.ODW ## Zwraca odwrotność rozkładu normalnego skumulowanego. -NORMSDIST = ROZKŁAD.NORMALNY.S ## Zwraca standardowy rozkład normalny skumulowany. -NORMSINV = ROZKŁAD.NORMALNY.S.ODW ## Zwraca odwrotność standardowego rozkładu normalnego skumulowanego. -PEARSON = PEARSON ## Zwraca współczynnik korelacji momentu iloczynu Pearsona. -PERCENTILE = PERCENTYL ## Wyznacza k-ty percentyl wartości w zakresie. -PERCENTRANK = PROCENT.POZYCJA ## Zwraca procentową pozycję wartości w zbiorze danych. -PERMUT = PERMUTACJE ## Zwraca liczbę permutacji dla danej liczby obiektów. -POISSON = ROZKŁAD.POISSON ## Zwraca rozkład Poissona. -PROB = PRAWDPD ## Zwraca prawdopodobieństwo, że wartości w zakresie leżą pomiędzy dwiema granicami. -QUARTILE = KWARTYL ## Wyznacza kwartyl zbioru danych. -RANK = POZYCJA ## Zwraca pozycję liczby na liście liczb. -RSQ = R.KWADRAT ## Zwraca kwadrat współczynnika korelacji momentu iloczynu Pearsona. -SKEW = SKOŚNOŚĆ ## Zwraca skośność rozkładu. -SLOPE = NACHYLENIE ## Zwraca nachylenie linii regresji liniowej. -SMALL = MIN.K ## Zwraca k-tą najmniejszą wartość ze zbioru danych. -STANDARDIZE = NORMALIZUJ ## Zwraca wartość znormalizowaną. -STDEV = ODCH.STANDARDOWE ## Szacuje odchylenie standardowe na podstawie próbki. -STDEVA = ODCH.STANDARDOWE.A ## Szacuje odchylenie standardowe na podstawie próbki, z uwzględnieniem liczb, tekstów i wartości logicznych. -STDEVP = ODCH.STANDARD.POPUL ## Oblicza odchylenie standardowe na podstawie całej populacji. -STDEVPA = ODCH.STANDARD.POPUL.A ## Oblicza odchylenie standardowe na podstawie całej populacji, z uwzględnieniem liczb, teksów i wartości logicznych. -STEYX = REGBŁSTD ## Zwraca błąd standardowy przewidzianej wartości y dla każdej wartości x w regresji. -TDIST = ROZKŁAD.T ## Zwraca rozkład t-Studenta. -TINV = ROZKŁAD.T.ODW ## Zwraca odwrotność rozkładu t-Studenta. -TREND = REGLINW ## Zwraca wartości trendu liniowego. -TRIMMEAN = ŚREDNIA.WEWN ## Zwraca średnią wartość dla wnętrza zbioru danych. -TTEST = TEST.T ## Zwraca prawdopodobieństwo związane z testem t-Studenta. -VAR = WARIANCJA ## Szacuje wariancję na podstawie próbki. -VARA = WARIANCJA.A ## Szacuje wariancję na podstawie próbki, z uwzględnieniem liczb, tekstów i wartości logicznych. -VARP = WARIANCJA.POPUL ## Oblicza wariancję na podstawie całej populacji. -VARPA = WARIANCJA.POPUL.A ## Oblicza wariancję na podstawie całej populacji, z uwzględnieniem liczb, tekstów i wartości logicznych. -WEIBULL = ROZKŁAD.WEIBULL ## Zwraca rozkład Weibulla. -ZTEST = TEST.Z ## Zwraca wartość jednostronnego prawdopodobieństwa testu z. - +BAHTTEXT = BAT.TEKST +CHAR = ZNAK +CLEAN = OCZYŚĆ +CODE = KOD +CONCAT = ZŁĄCZ.TEKST +DOLLAR = KWOTA +EXACT = PORÓWNAJ +FIND = ZNAJDŹ +FIXED = ZAOKR.DO.TEKST +ISTHAIDIGIT = CZY.CYFRA.TAJ +LEFT = LEWY +LEN = DŁ +LOWER = LITERY.MAŁE +MID = FRAGMENT.TEKSTU +NUMBERSTRING = LICZBA.CIĄG.ZNAK +NUMBERVALUE = WARTOŚĆ.LICZBOWA +PROPER = Z.WIELKIEJ.LITERY +REPLACE = ZASTĄP +REPT = POWT +RIGHT = PRAWY +SEARCH = SZUKAJ.TEKST +SUBSTITUTE = PODSTAW +T = T +TEXT = TEKST +TEXTJOIN = POŁĄCZ.TEKSTY +THAIDIGIT = TAJ.CYFRA +THAINUMSOUND = TAJ.DŹWIĘK.NUM +THAINUMSTRING = TAJ.CIĄG.NUM +THAISTRINGLENGTH = TAJ.DŁUGOŚĆ.CIĄGU +TRIM = USUŃ.ZBĘDNE.ODSTĘPY +UNICHAR = ZNAK.UNICODE +UNICODE = UNICODE +UPPER = LITERY.WIELKIE +VALUE = WARTOŚĆ ## -## Text functions Funkcje tekstowe +## Funkcje sieci Web (Web Functions) ## -ASC = ASC ## Zamienia litery angielskie lub katakana o pełnej szerokości (dwubajtowe) w ciągu znaków na znaki o szerokości połówkowej (jednobajtowe). -BAHTTEXT = BAHTTEXT ## Konwertuje liczbę na tekst, stosując format walutowy ß (baht). -CHAR = ZNAK ## Zwraca znak o podanym numerze kodu. -CLEAN = OCZYŚĆ ## Usuwa z tekstu wszystkie znaki, które nie mogą być drukowane. -CODE = KOD ## Zwraca kod numeryczny pierwszego znaku w ciągu tekstowym. -CONCATENATE = ZŁĄCZ.TEKSTY ## Łączy kilka oddzielnych tekstów w jeden tekst. -DOLLAR = KWOTA ## Konwertuje liczbę na tekst, stosując format walutowy $ (dolar). -EXACT = PORÓWNAJ ## Sprawdza identyczność dwóch wartości tekstowych. -FIND = ZNAJDŹ ## Znajduje jedną wartość tekstową wewnątrz innej (z uwzględnieniem wielkich i małych liter). -FINDB = ZNAJDŹB ## Znajduje jedną wartość tekstową wewnątrz innej (z uwzględnieniem wielkich i małych liter). -FIXED = ZAOKR.DO.TEKST ## Formatuje liczbę jako tekst przy stałej liczbie miejsc dziesiętnych. -JIS = JIS ## Zmienia litery angielskie lub katakana o szerokości połówkowej (jednobajtowe) w ciągu znaków na znaki o pełnej szerokości (dwubajtowe). -LEFT = LEWY ## Zwraca skrajne lewe znaki z wartości tekstowej. -LEFTB = LEWYB ## Zwraca skrajne lewe znaki z wartości tekstowej. -LEN = DŁ ## Zwraca liczbę znaków ciągu tekstowego. -LENB = DŁ.B ## Zwraca liczbę znaków ciągu tekstowego. -LOWER = LITERY.MAŁE ## Konwertuje wielkie litery tekstu na małe litery. -MID = FRAGMENT.TEKSTU ## Zwraca określoną liczbę znaków z ciągu tekstowego, zaczynając od zadanej pozycji. -MIDB = FRAGMENT.TEKSTU.B ## Zwraca określoną liczbę znaków z ciągu tekstowego, zaczynając od zadanej pozycji. -PHONETIC = PHONETIC ## Wybiera znaki fonetyczne (furigana) z ciągu tekstowego. -PROPER = Z.WIELKIEJ.LITERY ## Zastępuje pierwszą literę każdego wyrazu tekstu wielką literą. -REPLACE = ZASTĄP ## Zastępuje znaki w tekście. -REPLACEB = ZASTĄP.B ## Zastępuje znaki w tekście. -REPT = POWT ## Powiela tekst daną liczbę razy. -RIGHT = PRAWY ## Zwraca skrajne prawe znaki z wartości tekstowej. -RIGHTB = PRAWYB ## Zwraca skrajne prawe znaki z wartości tekstowej. -SEARCH = SZUKAJ.TEKST ## Wyszukuje jedną wartość tekstową wewnątrz innej (bez uwzględniania wielkości liter). -SEARCHB = SZUKAJ.TEKST.B ## Wyszukuje jedną wartość tekstową wewnątrz innej (bez uwzględniania wielkości liter). -SUBSTITUTE = PODSTAW ## Podstawia nowy tekst w miejsce poprzedniego tekstu w ciągu tekstowym. -T = T ## Konwertuje argumenty na tekst. -TEXT = TEKST ## Formatuje liczbę i konwertuje ją na tekst. -TRIM = USUŃ.ZBĘDNE.ODSTĘPY ## Usuwa spacje z tekstu. -UPPER = LITERY.WIELKIE ## Konwertuje znaki tekstu na wielkie litery. -VALUE = WARTOŚĆ ## Konwertuje argument tekstowy na liczbę. +ENCODEURL = ENCODEURL +FILTERXML = FILTERXML +WEBSERVICE = WEBSERVICE + +## +## Funkcje zgodności (Compatibility Functions) +## +BETADIST = ROZKŁAD.BETA +BETAINV = ROZKŁAD.BETA.ODW +BINOMDIST = ROZKŁAD.DWUM +CEILING = ZAOKR.W.GÓRĘ +CHIDIST = ROZKŁAD.CHI +CHIINV = ROZKŁAD.CHI.ODW +CHITEST = TEST.CHI +CONCATENATE = ZŁĄCZ.TEKSTY +CONFIDENCE = UFNOŚĆ +COVAR = KOWARIANCJA +CRITBINOM = PRÓG.ROZKŁAD.DWUM +EXPONDIST = ROZKŁAD.EXP +FDIST = ROZKŁAD.F +FINV = ROZKŁAD.F.ODW +FLOOR = ZAOKR.W.DÓŁ +FORECAST = REGLINX +FTEST = TEST.F +GAMMADIST = ROZKŁAD.GAMMA +GAMMAINV = ROZKŁAD.GAMMA.ODW +HYPGEOMDIST = ROZKŁAD.HIPERGEOM +LOGINV = ROZKŁAD.LOG.ODW +LOGNORMDIST = ROZKŁAD.LOG +MODE = WYST.NAJCZĘŚCIEJ +NEGBINOMDIST = ROZKŁAD.DWUM.PRZEC +NORMDIST = ROZKŁAD.NORMALNY +NORMINV = ROZKŁAD.NORMALNY.ODW +NORMSDIST = ROZKŁAD.NORMALNY.S +NORMSINV = ROZKŁAD.NORMALNY.S.ODW +PERCENTILE = PERCENTYL +PERCENTRANK = PROCENT.POZYCJA +POISSON = ROZKŁAD.POISSON +QUARTILE = KWARTYL +RANK = POZYCJA +STDEV = ODCH.STANDARDOWE +STDEVP = ODCH.STANDARD.POPUL +TDIST = ROZKŁAD.T +TINV = ROZKŁAD.T.ODW +TTEST = TEST.T +VAR = WARIANCJA +VARP = WARIANCJA.POPUL +WEIBULL = ROZKŁAD.WEIBULL +ZTEST = TEST.Z diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/pt/br/config b/PhpOffice/PhpSpreadsheet/Calculation/locale/pt/br/config old mode 100755 new mode 100644 index 904f99f..c39057c --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/pt/br/config +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/pt/br/config @@ -1,24 +1,20 @@ +############################################################ ## -## PhpSpreadsheet +## PhpSpreadsheet - locale settings ## +## Português Brasileiro (Brazilian Portuguese) +## +############################################################ -ArgumentSeparator = ; - +ArgumentSeparator = ; ## -## (For future use) +## Error Codes ## -currencySymbol = R$ - - -## -## Excel Error Codes (For future use) - -## -NULL = #NULO! -DIV0 = #DIV/0! -VALUE = #VALOR! -REF = #REF! -NAME = #NOME? -NUM = #NÚM! -NA = #N/D +NULL = #NULO! +DIV0 +VALUE = #VALOR! +REF +NAME = #NOME? +NUM = #NÚM! +NA = #N/D diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/pt/br/functions b/PhpOffice/PhpSpreadsheet/Calculation/locale/pt/br/functions old mode 100755 new mode 100644 index a062a7f..5781b0c --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/pt/br/functions +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/pt/br/functions @@ -1,408 +1,528 @@ +############################################################ ## -## Add-in and Automation functions Funções Suplemento e Automação +## PhpSpreadsheet - function name translations ## -GETPIVOTDATA = INFODADOSTABELADINÂMICA ## Retorna os dados armazenados em um relatório de tabela dinâmica +## Português Brasileiro (Brazilian Portuguese) +## +############################################################ ## -## Cube functions Funções de Cubo +## Funções de cubo (Cube Functions) ## -CUBEKPIMEMBER = MEMBROKPICUBO ## Retorna o nome de um KPI (indicador de desempenho-chave), uma propriedade e uma medida e exibe o nome e a propriedade na célula. Um KPI é uma medida quantificável, como o lucro bruto mensal ou a rotatividade trimestral dos funcionários, usada para monitorar o desempenho de uma organização. -CUBEMEMBER = MEMBROCUBO ## Retorna um membro ou tupla em uma hierarquia de cubo. Use para validar se o membro ou tupla existe no cubo. -CUBEMEMBERPROPERTY = PROPRIEDADEMEMBROCUBO ## Retorna o valor da propriedade de um membro no cubo. Usada para validar a existência do nome do membro no cubo e para retornar a propriedade especificada para esse membro. -CUBERANKEDMEMBER = MEMBROCLASSIFICADOCUBO ## Retorna o enésimo membro, ou o membro ordenado, em um conjunto. Use para retornar um ou mais elementos em um conjunto, assim como o melhor vendedor ou os dez melhores alunos. -CUBESET = CONJUNTOCUBO ## Define um conjunto calculado de membros ou tuplas enviando uma expressão do conjunto para o cubo no servidor, que cria o conjunto e o retorna para o Microsoft Office Excel. -CUBESETCOUNT = CONTAGEMCONJUNTOCUBO ## Retorna o número de itens em um conjunto. -CUBEVALUE = VALORCUBO ## Retorna um valor agregado de um cubo. - +CUBEKPIMEMBER = MEMBROKPICUBO +CUBEMEMBER = MEMBROCUBO +CUBEMEMBERPROPERTY = PROPRIEDADEMEMBROCUBO +CUBERANKEDMEMBER = MEMBROCLASSIFICADOCUBO +CUBESET = CONJUNTOCUBO +CUBESETCOUNT = CONTAGEMCONJUNTOCUBO +CUBEVALUE = VALORCUBO ## -## Database functions Funções de banco de dados +## Funções de banco de dados (Database Functions) ## -DAVERAGE = BDMÉDIA ## Retorna a média das entradas selecionadas de um banco de dados -DCOUNT = BDCONTAR ## Conta as células que contêm números em um banco de dados -DCOUNTA = BDCONTARA ## Conta células não vazias em um banco de dados -DGET = BDEXTRAIR ## Extrai de um banco de dados um único registro que corresponde a um critério específico -DMAX = BDMÁX ## Retorna o valor máximo de entradas selecionadas de um banco de dados -DMIN = BDMÍN ## Retorna o valor mínimo de entradas selecionadas de um banco de dados -DPRODUCT = BDMULTIPL ## Multiplica os valores em um campo específico de registros que correspondem ao critério em um banco de dados -DSTDEV = BDEST ## Estima o desvio padrão com base em uma amostra de entradas selecionadas de um banco de dados -DSTDEVP = BDDESVPA ## Calcula o desvio padrão com base na população inteira de entradas selecionadas de um banco de dados -DSUM = BDSOMA ## Adiciona os números à coluna de campos de registros do banco de dados que correspondem ao critério -DVAR = BDVAREST ## Estima a variância com base em uma amostra de entradas selecionadas de um banco de dados -DVARP = BDVARP ## Calcula a variância com base na população inteira de entradas selecionadas de um banco de dados - +DAVERAGE = BDMÉDIA +DCOUNT = BDCONTAR +DCOUNTA = BDCONTARA +DGET = BDEXTRAIR +DMAX = BDMÁX +DMIN = BDMÍN +DPRODUCT = BDMULTIPL +DSTDEV = BDEST +DSTDEVP = BDDESVPA +DSUM = BDSOMA +DVAR = BDVAREST +DVARP = BDVARP ## -## Date and time functions Funções de data e hora +## Funções de data e hora (Date & Time Functions) ## -DATE = DATA ## Retorna o número de série de uma data específica -DATEVALUE = DATA.VALOR ## Converte uma data na forma de texto para um número de série -DAY = DIA ## Converte um número de série em um dia do mês -DAYS360 = DIAS360 ## Calcula o número de dias entre duas datas com base em um ano de 360 dias -EDATE = DATAM ## Retorna o número de série da data que é o número indicado de meses antes ou depois da data inicial -EOMONTH = FIMMÊS ## Retorna o número de série do último dia do mês antes ou depois de um número especificado de meses -HOUR = HORA ## Converte um número de série em uma hora -MINUTE = MINUTO ## Converte um número de série em um minuto -MONTH = MÊS ## Converte um número de série em um mês -NETWORKDAYS = DIATRABALHOTOTAL ## Retorna o número de dias úteis inteiros entre duas datas -NOW = AGORA ## Retorna o número de série seqüencial da data e hora atuais -SECOND = SEGUNDO ## Converte um número de série em um segundo -TIME = HORA ## Retorna o número de série de uma hora específica -TIMEVALUE = VALOR.TEMPO ## Converte um horário na forma de texto para um número de série -TODAY = HOJE ## Retorna o número de série da data de hoje -WEEKDAY = DIA.DA.SEMANA ## Converte um número de série em um dia da semana -WEEKNUM = NÚMSEMANA ## Converte um número de série em um número que representa onde a semana cai numericamente em um ano -WORKDAY = DIATRABALHO ## Retorna o número de série da data antes ou depois de um número específico de dias úteis -YEAR = ANO ## Converte um número de série em um ano -YEARFRAC = FRAÇÃOANO ## Retorna a fração do ano que representa o número de dias entre data_inicial e data_final - +DATE = DATA +DATEDIF = DATADIF +DATESTRING = DATA.SÉRIE +DATEVALUE = DATA.VALOR +DAY = DIA +DAYS = DIAS +DAYS360 = DIAS360 +EDATE = DATAM +EOMONTH = FIMMÊS +HOUR = HORA +ISOWEEKNUM = NÚMSEMANAISO +MINUTE = MINUTO +MONTH = MÊS +NETWORKDAYS = DIATRABALHOTOTAL +NETWORKDAYS.INTL = DIATRABALHOTOTAL.INTL +NOW = AGORA +SECOND = SEGUNDO +TIME = TEMPO +TIMEVALUE = VALOR.TEMPO +TODAY = HOJE +WEEKDAY = DIA.DA.SEMANA +WEEKNUM = NÚMSEMANA +WORKDAY = DIATRABALHO +WORKDAY.INTL = DIATRABALHO.INTL +YEAR = ANO +YEARFRAC = FRAÇÃOANO ## -## Engineering functions Funções de engenharia +## Funções de engenharia (Engineering Functions) ## -BESSELI = BESSELI ## Retorna a função de Bessel In(x) modificada -BESSELJ = BESSELJ ## Retorna a função de Bessel Jn(x) -BESSELK = BESSELK ## Retorna a função de Bessel Kn(x) modificada -BESSELY = BESSELY ## Retorna a função de Bessel Yn(x) -BIN2DEC = BIN2DEC ## Converte um número binário em decimal -BIN2HEX = BIN2HEX ## Converte um número binário em hexadecimal -BIN2OCT = BIN2OCT ## Converte um número binário em octal -COMPLEX = COMPLEX ## Converte coeficientes reais e imaginários e um número complexo -CONVERT = CONVERTER ## Converte um número de um sistema de medida para outro -DEC2BIN = DECABIN ## Converte um número decimal em binário -DEC2HEX = DECAHEX ## Converte um número decimal em hexadecimal -DEC2OCT = DECAOCT ## Converte um número decimal em octal -DELTA = DELTA ## Testa se dois valores são iguais -ERF = FUNERRO ## Retorna a função de erro -ERFC = FUNERROCOMPL ## Retorna a função de erro complementar -GESTEP = DEGRAU ## Testa se um número é maior do que um valor limite -HEX2BIN = HEXABIN ## Converte um número hexadecimal em binário -HEX2DEC = HEXADEC ## Converte um número hexadecimal em decimal -HEX2OCT = HEXAOCT ## Converte um número hexadecimal em octal -IMABS = IMABS ## Retorna o valor absoluto (módulo) de um número complexo -IMAGINARY = IMAGINÁRIO ## Retorna o coeficiente imaginário de um número complexo -IMARGUMENT = IMARG ## Retorna o argumento teta, um ângulo expresso em radianos -IMCONJUGATE = IMCONJ ## Retorna o conjugado complexo de um número complexo -IMCOS = IMCOS ## Retorna o cosseno de um número complexo -IMDIV = IMDIV ## Retorna o quociente de dois números complexos -IMEXP = IMEXP ## Retorna o exponencial de um número complexo -IMLN = IMLN ## Retorna o logaritmo natural de um número complexo -IMLOG10 = IMLOG10 ## Retorna o logaritmo de base 10 de um número complexo -IMLOG2 = IMLOG2 ## Retorna o logaritmo de base 2 de um número complexo -IMPOWER = IMPOT ## Retorna um número complexo elevado a uma potência inteira -IMPRODUCT = IMPROD ## Retorna o produto de números complexos -IMREAL = IMREAL ## Retorna o coeficiente real de um número complexo -IMSIN = IMSENO ## Retorna o seno de um número complexo -IMSQRT = IMRAIZ ## Retorna a raiz quadrada de um número complexo -IMSUB = IMSUBTR ## Retorna a diferença entre dois números complexos -IMSUM = IMSOMA ## Retorna a soma de números complexos -OCT2BIN = OCTABIN ## Converte um número octal em binário -OCT2DEC = OCTADEC ## Converte um número octal em decimal -OCT2HEX = OCTAHEX ## Converte um número octal em hexadecimal - +BESSELI = BESSELI +BESSELJ = BESSELJ +BESSELK = BESSELK +BESSELY = BESSELY +BIN2DEC = BINADEC +BIN2HEX = BINAHEX +BIN2OCT = BINAOCT +BITAND = BITAND +BITLSHIFT = DESLOCESQBIT +BITOR = BITOR +BITRSHIFT = DESLOCDIRBIT +BITXOR = BITXOR +COMPLEX = COMPLEXO +CONVERT = CONVERTER +DEC2BIN = DECABIN +DEC2HEX = DECAHEX +DEC2OCT = DECAOCT +DELTA = DELTA +ERF = FUNERRO +ERF.PRECISE = FUNERRO.PRECISO +ERFC = FUNERROCOMPL +ERFC.PRECISE = FUNERROCOMPL.PRECISO +GESTEP = DEGRAU +HEX2BIN = HEXABIN +HEX2DEC = HEXADEC +HEX2OCT = HEXAOCT +IMABS = IMABS +IMAGINARY = IMAGINÁRIO +IMARGUMENT = IMARG +IMCONJUGATE = IMCONJ +IMCOS = IMCOS +IMCOSH = IMCOSH +IMCOT = IMCOT +IMCSC = IMCOSEC +IMCSCH = IMCOSECH +IMDIV = IMDIV +IMEXP = IMEXP +IMLN = IMLN +IMLOG10 = IMLOG10 +IMLOG2 = IMLOG2 +IMPOWER = IMPOT +IMPRODUCT = IMPROD +IMREAL = IMREAL +IMSEC = IMSEC +IMSECH = IMSECH +IMSIN = IMSENO +IMSINH = IMSENH +IMSQRT = IMRAIZ +IMSUB = IMSUBTR +IMSUM = IMSOMA +IMTAN = IMTAN +OCT2BIN = OCTABIN +OCT2DEC = OCTADEC +OCT2HEX = OCTAHEX ## -## Financial functions Funções financeiras +## Funções financeiras (Financial Functions) ## -ACCRINT = JUROSACUM ## Retorna a taxa de juros acumulados de um título que paga uma taxa periódica de juros -ACCRINTM = JUROSACUMV ## Retorna os juros acumulados de um título que paga juros no vencimento -AMORDEGRC = AMORDEGRC ## Retorna a depreciação para cada período contábil usando o coeficiente de depreciação -AMORLINC = AMORLINC ## Retorna a depreciação para cada período contábil -COUPDAYBS = CUPDIASINLIQ ## Retorna o número de dias do início do período de cupom até a data de liquidação -COUPDAYS = CUPDIAS ## Retorna o número de dias no período de cupom que contém a data de quitação -COUPDAYSNC = CUPDIASPRÓX ## Retorna o número de dias da data de liquidação até a data do próximo cupom -COUPNCD = CUPDATAPRÓX ## Retorna a próxima data de cupom após a data de quitação -COUPNUM = CUPNÚM ## Retorna o número de cupons pagáveis entre as datas de quitação e vencimento -COUPPCD = CUPDATAANT ## Retorna a data de cupom anterior à data de quitação -CUMIPMT = PGTOJURACUM ## Retorna os juros acumulados pagos entre dois períodos -CUMPRINC = PGTOCAPACUM ## Retorna o capital acumulado pago sobre um empréstimo entre dois períodos -DB = BD ## Retorna a depreciação de um ativo para um período especificado, usando o método de balanço de declínio fixo -DDB = BDD ## Retorna a depreciação de um ativo com relação a um período especificado usando o método de saldos decrescentes duplos ou qualquer outro método especificado por você -DISC = DESC ## Retorna a taxa de desconto de um título -DOLLARDE = MOEDADEC ## Converte um preço em formato de moeda, na forma fracionária, em um preço na forma decimal -DOLLARFR = MOEDAFRA ## Converte um preço, apresentado na forma decimal, em um preço apresentado na forma fracionária -DURATION = DURAÇÃO ## Retorna a duração anual de um título com pagamentos de juros periódicos -EFFECT = EFETIVA ## Retorna a taxa de juros anual efetiva -FV = VF ## Retorna o valor futuro de um investimento -FVSCHEDULE = VFPLANO ## Retorna o valor futuro de um capital inicial após a aplicação de uma série de taxas de juros compostas -INTRATE = TAXAJUROS ## Retorna a taxa de juros de um título totalmente investido -IPMT = IPGTO ## Retorna o pagamento de juros para um investimento em um determinado período -IRR = TIR ## Retorna a taxa interna de retorno de uma série de fluxos de caixa -ISPMT = ÉPGTO ## Calcula os juros pagos durante um período específico de um investimento -MDURATION = MDURAÇÃO ## Retorna a duração de Macauley modificada para um título com um valor de paridade equivalente a R$ 100 -MIRR = MTIR ## Calcula a taxa interna de retorno em que fluxos de caixa positivos e negativos são financiados com diferentes taxas -NOMINAL = NOMINAL ## Retorna a taxa de juros nominal anual -NPER = NPER ## Retorna o número de períodos de um investimento -NPV = VPL ## Retorna o valor líquido atual de um investimento com base em uma série de fluxos de caixa periódicos e em uma taxa de desconto -ODDFPRICE = PREÇOPRIMINC ## Retorna o preço por R$ 100 de valor nominal de um título com um primeiro período indefinido -ODDFYIELD = LUCROPRIMINC ## Retorna o rendimento de um título com um primeiro período indefinido -ODDLPRICE = PREÇOÚLTINC ## Retorna o preço por R$ 100 de valor nominal de um título com um último período de cupom indefinido -ODDLYIELD = LUCROÚLTINC ## Retorna o rendimento de um título com um último período indefinido -PMT = PGTO ## Retorna o pagamento periódico de uma anuidade -PPMT = PPGTO ## Retorna o pagamento de capital para determinado período de investimento -PRICE = PREÇO ## Retorna a preço por R$ 100,00 de valor nominal de um título que paga juros periódicos -PRICEDISC = PREÇODESC ## Retorna o preço por R$ 100,00 de valor nominal de um título descontado -PRICEMAT = PREÇOVENC ## Retorna o preço por R$ 100,00 de valor nominal de um título que paga juros no vencimento -PV = VP ## Retorna o valor presente de um investimento -RATE = TAXA ## Retorna a taxa de juros por período de uma anuidade -RECEIVED = RECEBER ## Retorna a quantia recebida no vencimento de um título totalmente investido -SLN = DPD ## Retorna a depreciação em linha reta de um ativo durante um período -SYD = SDA ## Retorna a depreciação dos dígitos da soma dos anos de um ativo para um período especificado -TBILLEQ = OTN ## Retorna o rendimento de um título equivalente a uma obrigação do Tesouro -TBILLPRICE = OTNVALOR ## Retorna o preço por R$ 100,00 de valor nominal de uma obrigação do Tesouro -TBILLYIELD = OTNLUCRO ## Retorna o rendimento de uma obrigação do Tesouro -VDB = BDV ## Retorna a depreciação de um ativo para um período especificado ou parcial usando um método de balanço declinante -XIRR = XTIR ## Fornece a taxa interna de retorno para um programa de fluxos de caixa que não é necessariamente periódico -XNPV = XVPL ## Retorna o valor presente líquido de um programa de fluxos de caixa que não é necessariamente periódico -YIELD = LUCRO ## Retorna o lucro de um título que paga juros periódicos -YIELDDISC = LUCRODESC ## Retorna o rendimento anual de um título descontado. Por exemplo, uma obrigação do Tesouro -YIELDMAT = LUCROVENC ## Retorna o lucro anual de um título que paga juros no vencimento - +ACCRINT = JUROSACUM +ACCRINTM = JUROSACUMV +AMORDEGRC = AMORDEGRC +AMORLINC = AMORLINC +COUPDAYBS = CUPDIASINLIQ +COUPDAYS = CUPDIAS +COUPDAYSNC = CUPDIASPRÓX +COUPNCD = CUPDATAPRÓX +COUPNUM = CUPNÚM +COUPPCD = CUPDATAANT +CUMIPMT = PGTOJURACUM +CUMPRINC = PGTOCAPACUM +DB = BD +DDB = BDD +DISC = DESC +DOLLARDE = MOEDADEC +DOLLARFR = MOEDAFRA +DURATION = DURAÇÃO +EFFECT = EFETIVA +FV = VF +FVSCHEDULE = VFPLANO +INTRATE = TAXAJUROS +IPMT = IPGTO +IRR = TIR +ISPMT = ÉPGTO +MDURATION = MDURAÇÃO +MIRR = MTIR +NOMINAL = NOMINAL +NPER = NPER +NPV = VPL +ODDFPRICE = PREÇOPRIMINC +ODDFYIELD = LUCROPRIMINC +ODDLPRICE = PREÇOÚLTINC +ODDLYIELD = LUCROÚLTINC +PDURATION = DURAÇÃOP +PMT = PGTO +PPMT = PPGTO +PRICE = PREÇO +PRICEDISC = PREÇODESC +PRICEMAT = PREÇOVENC +PV = VP +RATE = TAXA +RECEIVED = RECEBER +RRI = TAXAJURO +SLN = DPD +SYD = SDA +TBILLEQ = OTN +TBILLPRICE = OTNVALOR +TBILLYIELD = OTNLUCRO +VDB = BDV +XIRR = XTIR +XNPV = XVPL +YIELD = LUCRO +YIELDDISC = LUCRODESC +YIELDMAT = LUCROVENC ## -## Information functions Funções de informação +## Funções de informação (Information Functions) ## -CELL = CÉL ## Retorna informações sobre formatação, localização ou conteúdo de uma célula -ERROR.TYPE = TIPO.ERRO ## Retorna um número correspondente a um tipo de erro -INFO = INFORMAÇÃO ## Retorna informações sobre o ambiente operacional atual -ISBLANK = ÉCÉL.VAZIA ## Retorna VERDADEIRO se o valor for vazio -ISERR = ÉERRO ## Retorna VERDADEIRO se o valor for um valor de erro diferente de #N/D -ISERROR = ÉERROS ## Retorna VERDADEIRO se o valor for um valor de erro -ISEVEN = ÉPAR ## Retorna VERDADEIRO se o número for par -ISLOGICAL = ÉLÓGICO ## Retorna VERDADEIRO se o valor for um valor lógico -ISNA = É.NÃO.DISP ## Retorna VERDADEIRO se o valor for o valor de erro #N/D -ISNONTEXT = É.NÃO.TEXTO ## Retorna VERDADEIRO se o valor for diferente de texto -ISNUMBER = ÉNÚM ## Retorna VERDADEIRO se o valor for um número -ISODD = ÉIMPAR ## Retorna VERDADEIRO se o número for ímpar -ISREF = ÉREF ## Retorna VERDADEIRO se o valor for uma referência -ISTEXT = ÉTEXTO ## Retorna VERDADEIRO se o valor for texto -N = N ## Retorna um valor convertido em um número -NA = NÃO.DISP ## Retorna o valor de erro #N/D -TYPE = TIPO ## Retorna um número indicando o tipo de dados de um valor - +CELL = CÉL +ERROR.TYPE = TIPO.ERRO +INFO = INFORMAÇÃO +ISBLANK = ÉCÉL.VAZIA +ISERR = ÉERRO +ISERROR = ÉERROS +ISEVEN = ÉPAR +ISFORMULA = ÉFÓRMULA +ISLOGICAL = ÉLÓGICO +ISNA = É.NÃO.DISP +ISNONTEXT = É.NÃO.TEXTO +ISNUMBER = ÉNÚM +ISODD = ÉIMPAR +ISREF = ÉREF +ISTEXT = ÉTEXTO +N = N +NA = NÃO.DISP +SHEET = PLAN +SHEETS = PLANS +TYPE = TIPO ## -## Logical functions Funções lógicas +## Funções lógicas (Logical Functions) ## -AND = E ## Retorna VERDADEIRO se todos os seus argumentos forem VERDADEIROS -FALSE = FALSO ## Retorna o valor lógico FALSO -IF = SE ## Especifica um teste lógico a ser executado -IFERROR = SEERRO ## Retornará um valor que você especifica se uma fórmula for avaliada para um erro; do contrário, retornará o resultado da fórmula -NOT = NÃO ## Inverte o valor lógico do argumento -OR = OU ## Retorna VERDADEIRO se um dos argumentos for VERDADEIRO -TRUE = VERDADEIRO ## Retorna o valor lógico VERDADEIRO - +AND = E +FALSE = FALSO +IF = SE +IFERROR = SEERRO +IFNA = SENÃODISP +IFS = SES +NOT = NÃO +OR = OU +SWITCH = PARÂMETRO +TRUE = VERDADEIRO +XOR = XOR ## -## Lookup and reference functions Funções de pesquisa e referência +## Funções de pesquisa e referência (Lookup & Reference Functions) ## -ADDRESS = ENDEREÇO ## Retorna uma referência como texto para uma única célula em uma planilha -AREAS = ÁREAS ## Retorna o número de áreas em uma referência -CHOOSE = ESCOLHER ## Escolhe um valor a partir de uma lista de valores -COLUMN = COL ## Retorna o número da coluna de uma referência -COLUMNS = COLS ## Retorna o número de colunas em uma referência -HLOOKUP = PROCH ## Procura na linha superior de uma matriz e retorna o valor da célula especificada -HYPERLINK = HYPERLINK ## Cria um atalho ou salto que abre um documento armazenado em um servidor de rede, uma intranet ou na Internet -INDEX = ÍNDICE ## Usa um índice para escolher um valor de uma referência ou matriz -INDIRECT = INDIRETO ## Retorna uma referência indicada por um valor de texto -LOOKUP = PROC ## Procura valores em um vetor ou em uma matriz -MATCH = CORRESP ## Procura valores em uma referência ou em uma matriz -OFFSET = DESLOC ## Retorna um deslocamento de referência com base em uma determinada referência -ROW = LIN ## Retorna o número da linha de uma referência -ROWS = LINS ## Retorna o número de linhas em uma referência -RTD = RTD ## Recupera dados em tempo real de um programa que ofereça suporte a automação COM (automação: uma forma de trabalhar com objetos de um aplicativo a partir de outro aplicativo ou ferramenta de desenvolvimento. Chamada inicialmente de automação OLE, a automação é um padrão industrial e um recurso do modelo de objeto componente (COM).) -TRANSPOSE = TRANSPOR ## Retorna a transposição de uma matriz -VLOOKUP = PROCV ## Procura na primeira coluna de uma matriz e move ao longo da linha para retornar o valor de uma célula - +ADDRESS = ENDEREÇO +AREAS = ÁREAS +CHOOSE = ESCOLHER +COLUMN = COL +COLUMNS = COLS +FORMULATEXT = FÓRMULATEXTO +GETPIVOTDATA = INFODADOSTABELADINÂMICA +HLOOKUP = PROCH +HYPERLINK = HIPERLINK +INDEX = ÍNDICE +INDIRECT = INDIRETO +LOOKUP = PROC +MATCH = CORRESP +OFFSET = DESLOC +ROW = LIN +ROWS = LINS +RTD = RTD +TRANSPOSE = TRANSPOR +VLOOKUP = PROCV +*RC = LC ## -## Math and trigonometry functions Funções matemáticas e trigonométricas +## Funções matemáticas e trigonométricas (Math & Trig Functions) ## -ABS = ABS ## Retorna o valor absoluto de um número -ACOS = ACOS ## Retorna o arco cosseno de um número -ACOSH = ACOSH ## Retorna o cosseno hiperbólico inverso de um número -ASIN = ASEN ## Retorna o arco seno de um número -ASINH = ASENH ## Retorna o seno hiperbólico inverso de um número -ATAN = ATAN ## Retorna o arco tangente de um número -ATAN2 = ATAN2 ## Retorna o arco tangente das coordenadas x e y especificadas -ATANH = ATANH ## Retorna a tangente hiperbólica inversa de um número -CEILING = TETO ## Arredonda um número para o inteiro mais próximo ou para o múltiplo mais próximo de significância -COMBIN = COMBIN ## Retorna o número de combinações de um determinado número de objetos -COS = COS ## Retorna o cosseno de um número -COSH = COSH ## Retorna o cosseno hiperbólico de um número -DEGREES = GRAUS ## Converte radianos em graus -EVEN = PAR ## Arredonda um número para cima até o inteiro par mais próximo -EXP = EXP ## Retorna e elevado à potência de um número especificado -FACT = FATORIAL ## Retorna o fatorial de um número -FACTDOUBLE = FATDUPLO ## Retorna o fatorial duplo de um número -FLOOR = ARREDMULTB ## Arredonda um número para baixo até zero -GCD = MDC ## Retorna o máximo divisor comum -INT = INT ## Arredonda um número para baixo até o número inteiro mais próximo -LCM = MMC ## Retorna o mínimo múltiplo comum -LN = LN ## Retorna o logaritmo natural de um número -LOG = LOG ## Retorna o logaritmo de um número de uma base especificada -LOG10 = LOG10 ## Retorna o logaritmo de base 10 de um número -MDETERM = MATRIZ.DETERM ## Retorna o determinante de uma matriz de uma variável do tipo matriz -MINVERSE = MATRIZ.INVERSO ## Retorna a matriz inversa de uma matriz -MMULT = MATRIZ.MULT ## Retorna o produto de duas matrizes -MOD = RESTO ## Retorna o resto da divisão -MROUND = MARRED ## Retorna um número arredondado ao múltiplo desejado -MULTINOMIAL = MULTINOMIAL ## Retorna o multinomial de um conjunto de números -ODD = ÍMPAR ## Arredonda um número para cima até o inteiro ímpar mais próximo -PI = PI ## Retorna o valor de Pi -POWER = POTÊNCIA ## Fornece o resultado de um número elevado a uma potência -PRODUCT = MULT ## Multiplica seus argumentos -QUOTIENT = QUOCIENTE ## Retorna a parte inteira de uma divisão -RADIANS = RADIANOS ## Converte graus em radianos -RAND = ALEATÓRIO ## Retorna um número aleatório entre 0 e 1 -RANDBETWEEN = ALEATÓRIOENTRE ## Retorna um número aleatório entre os números especificados -ROMAN = ROMANO ## Converte um algarismo arábico em romano, como texto -ROUND = ARRED ## Arredonda um número até uma quantidade especificada de dígitos -ROUNDDOWN = ARREDONDAR.PARA.BAIXO ## Arredonda um número para baixo até zero -ROUNDUP = ARREDONDAR.PARA.CIMA ## Arredonda um número para cima, afastando-o de zero -SERIESSUM = SOMASEQÜÊNCIA ## Retorna a soma de uma série polinomial baseada na fórmula -SIGN = SINAL ## Retorna o sinal de um número -SIN = SEN ## Retorna o seno de um ângulo dado -SINH = SENH ## Retorna o seno hiperbólico de um número -SQRT = RAIZ ## Retorna uma raiz quadrada positiva -SQRTPI = RAIZPI ## Retorna a raiz quadrada de (núm* pi) -SUBTOTAL = SUBTOTAL ## Retorna um subtotal em uma lista ou em um banco de dados -SUM = SOMA ## Soma seus argumentos -SUMIF = SOMASE ## Adiciona as células especificadas por um determinado critério -SUMIFS = SOMASE ## Adiciona as células em um intervalo que atende a vários critérios -SUMPRODUCT = SOMARPRODUTO ## Retorna a soma dos produtos de componentes correspondentes de matrizes -SUMSQ = SOMAQUAD ## Retorna a soma dos quadrados dos argumentos -SUMX2MY2 = SOMAX2DY2 ## Retorna a soma da diferença dos quadrados dos valores correspondentes em duas matrizes -SUMX2PY2 = SOMAX2SY2 ## Retorna a soma da soma dos quadrados dos valores correspondentes em duas matrizes -SUMXMY2 = SOMAXMY2 ## Retorna a soma dos quadrados das diferenças dos valores correspondentes em duas matrizes -TAN = TAN ## Retorna a tangente de um número -TANH = TANH ## Retorna a tangente hiperbólica de um número -TRUNC = TRUNCAR ## Trunca um número para um inteiro - +ABS = ABS +ACOS = ACOS +ACOSH = ACOSH +ACOT = ACOT +ACOTH = ACOTH +AGGREGATE = AGREGAR +ARABIC = ARÁBICO +ASIN = ASEN +ASINH = ASENH +ATAN = ATAN +ATAN2 = ATAN2 +ATANH = ATANH +BASE = BASE +CEILING.MATH = TETO.MAT +CEILING.PRECISE = TETO.PRECISO +COMBIN = COMBIN +COMBINA = COMBINA +COS = COS +COSH = COSH +COT = COT +COTH = COTH +CSC = COSEC +CSCH = COSECH +DECIMAL = DECIMAL +DEGREES = GRAUS +ECMA.CEILING = ECMA.TETO +EVEN = PAR +EXP = EXP +FACT = FATORIAL +FACTDOUBLE = FATDUPLO +FLOOR.MATH = ARREDMULTB.MAT +FLOOR.PRECISE = ARREDMULTB.PRECISO +GCD = MDC +INT = INT +ISO.CEILING = ISO.TETO +LCM = MMC +LN = LN +LOG = LOG +LOG10 = LOG10 +MDETERM = MATRIZ.DETERM +MINVERSE = MATRIZ.INVERSO +MMULT = MATRIZ.MULT +MOD = MOD +MROUND = MARRED +MULTINOMIAL = MULTINOMIAL +MUNIT = MUNIT +ODD = ÍMPAR +PI = PI +POWER = POTÊNCIA +PRODUCT = MULT +QUOTIENT = QUOCIENTE +RADIANS = RADIANOS +RAND = ALEATÓRIO +RANDBETWEEN = ALEATÓRIOENTRE +ROMAN = ROMANO +ROUND = ARRED +ROUNDDOWN = ARREDONDAR.PARA.BAIXO +ROUNDUP = ARREDONDAR.PARA.CIMA +SEC = SEC +SECH = SECH +SERIESSUM = SOMASEQÜÊNCIA +SIGN = SINAL +SIN = SEN +SINH = SENH +SQRT = RAIZ +SQRTPI = RAIZPI +SUBTOTAL = SUBTOTAL +SUM = SOMA +SUMIF = SOMASE +SUMIFS = SOMASES +SUMPRODUCT = SOMARPRODUTO +SUMSQ = SOMAQUAD +SUMX2MY2 = SOMAX2DY2 +SUMX2PY2 = SOMAX2SY2 +SUMXMY2 = SOMAXMY2 +TAN = TAN +TANH = TANH +TRUNC = TRUNCAR ## -## Statistical functions Funções estatísticas +## Funções estatísticas (Statistical Functions) ## -AVEDEV = DESV.MÉDIO ## Retorna a média aritmética dos desvios médios dos pontos de dados a partir de sua média -AVERAGE = MÉDIA ## Retorna a média dos argumentos -AVERAGEA = MÉDIAA ## Retorna a média dos argumentos, inclusive números, texto e valores lógicos -AVERAGEIF = MÉDIASE ## Retorna a média (média aritmética) de todas as células em um intervalo que atendem a um determinado critério -AVERAGEIFS = MÉDIASES ## Retorna a média (média aritmética) de todas as células que atendem a múltiplos critérios. -BETADIST = DISTBETA ## Retorna a função de distribuição cumulativa beta -BETAINV = BETA.ACUM.INV ## Retorna o inverso da função de distribuição cumulativa para uma distribuição beta especificada -BINOMDIST = DISTRBINOM ## Retorna a probabilidade de distribuição binomial do termo individual -CHIDIST = DIST.QUI ## Retorna a probabilidade unicaudal da distribuição qui-quadrada -CHIINV = INV.QUI ## Retorna o inverso da probabilidade uni-caudal da distribuição qui-quadrada -CHITEST = TESTE.QUI ## Retorna o teste para independência -CONFIDENCE = INT.CONFIANÇA ## Retorna o intervalo de confiança para uma média da população -CORREL = CORREL ## Retorna o coeficiente de correlação entre dois conjuntos de dados -COUNT = CONT.NÚM ## Calcula quantos números há na lista de argumentos -COUNTA = CONT.VALORES ## Calcula quantos valores há na lista de argumentos -COUNTBLANK = CONTAR.VAZIO ## Conta o número de células vazias no intervalo especificado -COUNTIF = CONT.SE ## Calcula o número de células não vazias em um intervalo que corresponde a determinados critérios -COUNTIFS = CONT.SES ## Conta o número de células dentro de um intervalo que atende a múltiplos critérios -COVAR = COVAR ## Retorna a covariância, a média dos produtos dos desvios pares -CRITBINOM = CRIT.BINOM ## Retorna o menor valor para o qual a distribuição binomial cumulativa é menor ou igual ao valor padrão -DEVSQ = DESVQ ## Retorna a soma dos quadrados dos desvios -EXPONDIST = DISTEXPON ## Retorna a distribuição exponencial -FDIST = DISTF ## Retorna a distribuição de probabilidade F -FINV = INVF ## Retorna o inverso da distribuição de probabilidades F -FISHER = FISHER ## Retorna a transformação Fisher -FISHERINV = FISHERINV ## Retorna o inverso da transformação Fisher -FORECAST = PREVISÃO ## Retorna um valor ao longo de uma linha reta -FREQUENCY = FREQÜÊNCIA ## Retorna uma distribuição de freqüência como uma matriz vertical -FTEST = TESTEF ## Retorna o resultado de um teste F -GAMMADIST = DISTGAMA ## Retorna a distribuição gama -GAMMAINV = INVGAMA ## Retorna o inverso da distribuição cumulativa gama -GAMMALN = LNGAMA ## Retorna o logaritmo natural da função gama, G(x) -GEOMEAN = MÉDIA.GEOMÉTRICA ## Retorna a média geométrica -GROWTH = CRESCIMENTO ## Retorna valores ao longo de uma tendência exponencial -HARMEAN = MÉDIA.HARMÔNICA ## Retorna a média harmônica -HYPGEOMDIST = DIST.HIPERGEOM ## Retorna a distribuição hipergeométrica -INTERCEPT = INTERCEPÇÃO ## Retorna a intercepção da linha de regressão linear -KURT = CURT ## Retorna a curtose de um conjunto de dados -LARGE = MAIOR ## Retorna o maior valor k-ésimo de um conjunto de dados -LINEST = PROJ.LIN ## Retorna os parâmetros de uma tendência linear -LOGEST = PROJ.LOG ## Retorna os parâmetros de uma tendência exponencial -LOGINV = INVLOG ## Retorna o inverso da distribuição lognormal -LOGNORMDIST = DIST.LOGNORMAL ## Retorna a distribuição lognormal cumulativa -MAX = MÁXIMO ## Retorna o valor máximo em uma lista de argumentos -MAXA = MÁXIMOA ## Retorna o maior valor em uma lista de argumentos, inclusive números, texto e valores lógicos -MEDIAN = MED ## Retorna a mediana dos números indicados -MIN = MÍNIMO ## Retorna o valor mínimo em uma lista de argumentos -MINA = MÍNIMOA ## Retorna o menor valor em uma lista de argumentos, inclusive números, texto e valores lógicos -MODE = MODO ## Retorna o valor mais comum em um conjunto de dados -NEGBINOMDIST = DIST.BIN.NEG ## Retorna a distribuição binomial negativa -NORMDIST = DIST.NORM ## Retorna a distribuição cumulativa normal -NORMINV = INV.NORM ## Retorna o inverso da distribuição cumulativa normal -NORMSDIST = DIST.NORMP ## Retorna a distribuição cumulativa normal padrão -NORMSINV = INV.NORMP ## Retorna o inverso da distribuição cumulativa normal padrão -PEARSON = PEARSON ## Retorna o coeficiente de correlação do momento do produto Pearson -PERCENTILE = PERCENTIL ## Retorna o k-ésimo percentil de valores em um intervalo -PERCENTRANK = ORDEM.PORCENTUAL ## Retorna a ordem percentual de um valor em um conjunto de dados -PERMUT = PERMUT ## Retorna o número de permutações de um determinado número de objetos -POISSON = POISSON ## Retorna a distribuição Poisson -PROB = PROB ## Retorna a probabilidade de valores em um intervalo estarem entre dois limites -QUARTILE = QUARTIL ## Retorna o quartil do conjunto de dados -RANK = ORDEM ## Retorna a posição de um número em uma lista de números -RSQ = RQUAD ## Retorna o quadrado do coeficiente de correlação do momento do produto de Pearson -SKEW = DISTORÇÃO ## Retorna a distorção de uma distribuição -SLOPE = INCLINAÇÃO ## Retorna a inclinação da linha de regressão linear -SMALL = MENOR ## Retorna o menor valor k-ésimo do conjunto de dados -STANDARDIZE = PADRONIZAR ## Retorna um valor normalizado -STDEV = DESVPAD ## Estima o desvio padrão com base em uma amostra -STDEVA = DESVPADA ## Estima o desvio padrão com base em uma amostra, inclusive números, texto e valores lógicos -STDEVP = DESVPADP ## Calcula o desvio padrão com base na população total -STDEVPA = DESVPADPA ## Calcula o desvio padrão com base na população total, inclusive números, texto e valores lógicos -STEYX = EPADYX ## Retorna o erro padrão do valor-y previsto para cada x da regressão -TDIST = DISTT ## Retorna a distribuição t de Student -TINV = INVT ## Retorna o inverso da distribuição t de Student -TREND = TENDÊNCIA ## Retorna valores ao longo de uma tendência linear -TRIMMEAN = MÉDIA.INTERNA ## Retorna a média do interior de um conjunto de dados -TTEST = TESTET ## Retorna a probabilidade associada ao teste t de Student -VAR = VAR ## Estima a variância com base em uma amostra -VARA = VARA ## Estima a variância com base em uma amostra, inclusive números, texto e valores lógicos -VARP = VARP ## Calcula a variância com base na população inteira -VARPA = VARPA ## Calcula a variância com base na população total, inclusive números, texto e valores lógicos -WEIBULL = WEIBULL ## Retorna a distribuição Weibull -ZTEST = TESTEZ ## Retorna o valor de probabilidade uni-caudal de um teste-z - +AVEDEV = DESV.MÉDIO +AVERAGE = MÉDIA +AVERAGEA = MÉDIAA +AVERAGEIF = MÉDIASE +AVERAGEIFS = MÉDIASES +BETA.DIST = DIST.BETA +BETA.INV = INV.BETA +BINOM.DIST = DISTR.BINOM +BINOM.DIST.RANGE = INTERV.DISTR.BINOM +BINOM.INV = INV.BINOM +CHISQ.DIST = DIST.QUIQUA +CHISQ.DIST.RT = DIST.QUIQUA.CD +CHISQ.INV = INV.QUIQUA +CHISQ.INV.RT = INV.QUIQUA.CD +CHISQ.TEST = TESTE.QUIQUA +CONFIDENCE.NORM = INT.CONFIANÇA.NORM +CONFIDENCE.T = INT.CONFIANÇA.T +CORREL = CORREL +COUNT = CONT.NÚM +COUNTA = CONT.VALORES +COUNTBLANK = CONTAR.VAZIO +COUNTIF = CONT.SE +COUNTIFS = CONT.SES +COVARIANCE.P = COVARIAÇÃO.P +COVARIANCE.S = COVARIAÇÃO.S +DEVSQ = DESVQ +EXPON.DIST = DISTR.EXPON +F.DIST = DIST.F +F.DIST.RT = DIST.F.CD +F.INV = INV.F +F.INV.RT = INV.F.CD +F.TEST = TESTE.F +FISHER = FISHER +FISHERINV = FISHERINV +FORECAST.ETS = PREVISÃO.ETS +FORECAST.ETS.CONFINT = PREVISÃO.ETS.CONFINT +FORECAST.ETS.SEASONALITY = PREVISÃO.ETS.SAZONALIDADE +FORECAST.ETS.STAT = PREVISÃO.ETS.STAT +FORECAST.LINEAR = PREVISÃO.LINEAR +FREQUENCY = FREQÜÊNCIA +GAMMA = GAMA +GAMMA.DIST = DIST.GAMA +GAMMA.INV = INV.GAMA +GAMMALN = LNGAMA +GAMMALN.PRECISE = LNGAMA.PRECISO +GAUSS = GAUSS +GEOMEAN = MÉDIA.GEOMÉTRICA +GROWTH = CRESCIMENTO +HARMEAN = MÉDIA.HARMÔNICA +HYPGEOM.DIST = DIST.HIPERGEOM.N +INTERCEPT = INTERCEPÇÃO +KURT = CURT +LARGE = MAIOR +LINEST = PROJ.LIN +LOGEST = PROJ.LOG +LOGNORM.DIST = DIST.LOGNORMAL.N +LOGNORM.INV = INV.LOGNORMAL +MAX = MÁXIMO +MAXA = MÁXIMOA +MAXIFS = MÁXIMOSES +MEDIAN = MED +MIN = MÍNIMO +MINA = MÍNIMOA +MINIFS = MÍNIMOSES +MODE.MULT = MODO.MULT +MODE.SNGL = MODO.ÚNICO +NEGBINOM.DIST = DIST.BIN.NEG.N +NORM.DIST = DIST.NORM.N +NORM.INV = INV.NORM.N +NORM.S.DIST = DIST.NORMP.N +NORM.S.INV = INV.NORMP.N +PEARSON = PEARSON +PERCENTILE.EXC = PERCENTIL.EXC +PERCENTILE.INC = PERCENTIL.INC +PERCENTRANK.EXC = ORDEM.PORCENTUAL.EXC +PERCENTRANK.INC = ORDEM.PORCENTUAL.INC +PERMUT = PERMUT +PERMUTATIONA = PERMUTAS +PHI = PHI +POISSON.DIST = DIST.POISSON +PROB = PROB +QUARTILE.EXC = QUARTIL.EXC +QUARTILE.INC = QUARTIL.INC +RANK.AVG = ORDEM.MÉD +RANK.EQ = ORDEM.EQ +RSQ = RQUAD +SKEW = DISTORÇÃO +SKEW.P = DISTORÇÃO.P +SLOPE = INCLINAÇÃO +SMALL = MENOR +STANDARDIZE = PADRONIZAR +STDEV.P = DESVPAD.P +STDEV.S = DESVPAD.A +STDEVA = DESVPADA +STDEVPA = DESVPADPA +STEYX = EPADYX +T.DIST = DIST.T +T.DIST.2T = DIST.T.BC +T.DIST.RT = DIST.T.CD +T.INV = INV.T +T.INV.2T = INV.T.BC +T.TEST = TESTE.T +TREND = TENDÊNCIA +TRIMMEAN = MÉDIA.INTERNA +VAR.P = VAR.P +VAR.S = VAR.A +VARA = VARA +VARPA = VARPA +WEIBULL.DIST = DIST.WEIBULL +Z.TEST = TESTE.Z ## -## Text functions Funções de texto +## Funções de texto (Text Functions) ## -ASC = ASC ## Altera letras do inglês ou katakana de largura total (bytes duplos) dentro de uma seqüência de caracteres para caracteres de meia largura (byte único) -BAHTTEXT = BAHTTEXT ## Converte um número em um texto, usando o formato de moeda ß (baht) -CHAR = CARACT ## Retorna o caractere especificado pelo número de código -CLEAN = TIRAR ## Remove todos os caracteres do texto que não podem ser impressos -CODE = CÓDIGO ## Retorna um código numérico para o primeiro caractere de uma seqüência de caracteres de texto -CONCATENATE = CONCATENAR ## Agrupa vários itens de texto em um único item de texto -DOLLAR = MOEDA ## Converte um número em texto, usando o formato de moeda $ (dólar) -EXACT = EXATO ## Verifica se dois valores de texto são idênticos -FIND = PROCURAR ## Procura um valor de texto dentro de outro (diferencia maiúsculas de minúsculas) -FINDB = PROCURARB ## Procura um valor de texto dentro de outro (diferencia maiúsculas de minúsculas) -FIXED = DEF.NÚM.DEC ## Formata um número como texto com um número fixo de decimais -JIS = JIS ## Altera letras do inglês ou katakana de meia largura (byte único) dentro de uma seqüência de caracteres para caracteres de largura total (bytes duplos) -LEFT = ESQUERDA ## Retorna os caracteres mais à esquerda de um valor de texto -LEFTB = ESQUERDAB ## Retorna os caracteres mais à esquerda de um valor de texto -LEN = NÚM.CARACT ## Retorna o número de caracteres em uma seqüência de texto -LENB = NÚM.CARACTB ## Retorna o número de caracteres em uma seqüência de texto -LOWER = MINÚSCULA ## Converte texto para minúsculas -MID = EXT.TEXTO ## Retorna um número específico de caracteres de uma seqüência de texto começando na posição especificada -MIDB = EXT.TEXTOB ## Retorna um número específico de caracteres de uma seqüência de texto começando na posição especificada -PHONETIC = FONÉTICA ## Extrai os caracteres fonéticos (furigana) de uma seqüência de caracteres de texto -PROPER = PRI.MAIÚSCULA ## Coloca a primeira letra de cada palavra em maiúscula em um valor de texto -REPLACE = MUDAR ## Muda os caracteres dentro do texto -REPLACEB = MUDARB ## Muda os caracteres dentro do texto -REPT = REPT ## Repete o texto um determinado número de vezes -RIGHT = DIREITA ## Retorna os caracteres mais à direita de um valor de texto -RIGHTB = DIREITAB ## Retorna os caracteres mais à direita de um valor de texto -SEARCH = LOCALIZAR ## Localiza um valor de texto dentro de outro (não diferencia maiúsculas de minúsculas) -SEARCHB = LOCALIZARB ## Localiza um valor de texto dentro de outro (não diferencia maiúsculas de minúsculas) -SUBSTITUTE = SUBSTITUIR ## Substitui um novo texto por um texto antigo em uma seqüência de texto -T = T ## Converte os argumentos em texto -TEXT = TEXTO ## Formata um número e o converte em texto -TRIM = ARRUMAR ## Remove espaços do texto -UPPER = MAIÚSCULA ## Converte o texto em maiúsculas -VALUE = VALOR ## Converte um argumento de texto em um número +BAHTTEXT = BAHTTEXT +CHAR = CARACT +CLEAN = TIRAR +CODE = CÓDIGO +CONCAT = CONCAT +DOLLAR = MOEDA +EXACT = EXATO +FIND = PROCURAR +FIXED = DEF.NÚM.DEC +LEFT = ESQUERDA +LEN = NÚM.CARACT +LOWER = MINÚSCULA +MID = EXT.TEXTO +NUMBERSTRING = SEQÜÊNCIA.NÚMERO +NUMBERVALUE = VALORNUMÉRICO +PHONETIC = FONÉTICA +PROPER = PRI.MAIÚSCULA +REPLACE = MUDAR +REPT = REPT +RIGHT = DIREITA +SEARCH = LOCALIZAR +SUBSTITUTE = SUBSTITUIR +T = T +TEXT = TEXTO +TEXTJOIN = UNIRTEXTO +TRIM = ARRUMAR +UNICHAR = CARACTUNICODE +UNICODE = UNICODE +UPPER = MAIÚSCULA +VALUE = VALOR + +## +## Funções da Web (Web Functions) +## +ENCODEURL = CODIFURL +FILTERXML = FILTROXML +WEBSERVICE = SERVIÇOWEB + +## +## Funções de compatibilidade (Compatibility Functions) +## +BETADIST = DISTBETA +BETAINV = BETA.ACUM.INV +BINOMDIST = DISTRBINOM +CEILING = TETO +CHIDIST = DIST.QUI +CHIINV = INV.QUI +CHITEST = TESTE.QUI +CONCATENATE = CONCATENAR +CONFIDENCE = INT.CONFIANÇA +COVAR = COVAR +CRITBINOM = CRIT.BINOM +EXPONDIST = DISTEXPON +FDIST = DISTF +FINV = INVF +FLOOR = ARREDMULTB +FORECAST = PREVISÃO +FTEST = TESTEF +GAMMADIST = DISTGAMA +GAMMAINV = INVGAMA +HYPGEOMDIST = DIST.HIPERGEOM +LOGINV = INVLOG +LOGNORMDIST = DIST.LOGNORMAL +MODE = MODO +NEGBINOMDIST = DIST.BIN.NEG +NORMDIST = DISTNORM +NORMINV = INV.NORM +NORMSDIST = DISTNORMP +NORMSINV = INV.NORMP +PERCENTILE = PERCENTIL +PERCENTRANK = ORDEM.PORCENTUAL +POISSON = POISSON +QUARTILE = QUARTIL +RANK = ORDEM +STDEV = DESVPAD +STDEVP = DESVPADP +TDIST = DISTT +TINV = INVT +TTEST = TESTET +VAR = VAR +VARP = VARP +WEIBULL = WEIBULL +ZTEST = TESTEZ diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/pt/config b/PhpOffice/PhpSpreadsheet/Calculation/locale/pt/config old mode 100755 new mode 100644 index cd85c17..e661830 --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/pt/config +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/pt/config @@ -1,24 +1,20 @@ +############################################################ ## -## PhpSpreadsheet +## PhpSpreadsheet - locale settings ## +## Português (Portuguese) +## +############################################################ -ArgumentSeparator = ; - +ArgumentSeparator = ; ## -## (For future use) +## Error Codes ## -currencySymbol = € - - -## -## Excel Error Codes (For future use) - -## -NULL = #NULO! -DIV0 = #DIV/0! -VALUE = #VALOR! -REF = #REF! -NAME = #NOME? -NUM = #NÚM! -NA = #N/D +NULL = #NULO! +DIV0 +VALUE = #VALOR! +REF +NAME = #NOME? +NUM = #NÚM! +NA = #N/D diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/pt/functions b/PhpOffice/PhpSpreadsheet/Calculation/locale/pt/functions old mode 100755 new mode 100644 index ba4eb47..70a3bb0 --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/pt/functions +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/pt/functions @@ -1,408 +1,538 @@ +############################################################ ## -## Add-in and Automation functions Funções de Suplemento e Automatização +## PhpSpreadsheet - function name translations ## -GETPIVOTDATA = OBTERDADOSDIN ## Devolve dados armazenados num relatório de Tabela Dinâmica +## Português (Portuguese) +## +############################################################ ## -## Cube functions Funções de cubo +## Funções de cubo (Cube Functions) ## -CUBEKPIMEMBER = MEMBROKPICUBO ## Devolve o nome, propriedade e medição de um KPI (key performance indicator) e apresenta o nome e a propriedade na célula. Um KPI é uma medida quantificável, como, por exemplo, o lucro mensal bruto ou a rotatividade trimestral de pessoal, utilizada para monitorizar o desempenho de uma organização. -CUBEMEMBER = MEMBROCUBO ## Devolve um membro ou cadeia de identificação numa hierarquia de cubo. Utilizada para validar a existência do membro ou cadeia de identificação no cubo. -CUBEMEMBERPROPERTY = PROPRIEDADEMEMBROCUBO ## Devolve o valor de uma propriedade de membro no cubo. Utilizada para validar a existência de um nome de membro no cubo e para devolver a propriedade especificada para esse membro. -CUBERANKEDMEMBER = MEMBROCLASSIFICADOCUBO ## Devolve o enésimo ou a classificação mais alta num conjunto. Utilizada para devolver um ou mais elementos num conjunto, tal como o melhor vendedor ou os 10 melhores alunos. -CUBESET = CONJUNTOCUBO ## Define um conjunto calculado de membros ou cadeias de identificação enviando uma expressão de conjunto para o cubo no servidor, que cria o conjunto e, em seguida, devolve o conjunto ao Microsoft Office Excel. -CUBESETCOUNT = CONTARCONJUNTOCUBO ## Devolve o número de itens num conjunto. -CUBEVALUE = VALORCUBO ## Devolve um valor agregado do cubo. - +CUBEKPIMEMBER = MEMBROKPICUBO +CUBEMEMBER = MEMBROCUBO +CUBEMEMBERPROPERTY = PROPRIEDADEMEMBROCUBO +CUBERANKEDMEMBER = MEMBROCLASSIFICADOCUBO +CUBESET = CONJUNTOCUBO +CUBESETCOUNT = CONTARCONJUNTOCUBO +CUBEVALUE = VALORCUBO ## -## Database functions Funções de base de dados +## Funções de base de dados (Database Functions) ## -DAVERAGE = BDMÉDIA ## Devolve a média das entradas da base de dados seleccionadas -DCOUNT = BDCONTAR ## Conta as células que contêm números numa base de dados -DCOUNTA = BDCONTAR.VAL ## Conta as células que não estejam em branco numa base de dados -DGET = BDOBTER ## Extrai de uma base de dados um único registo que corresponde aos critérios especificados -DMAX = BDMÁX ## Devolve o valor máximo das entradas da base de dados seleccionadas -DMIN = BDMÍN ## Devolve o valor mínimo das entradas da base de dados seleccionadas -DPRODUCT = BDMULTIPL ## Multiplica os valores de um determinado campo de registos que correspondem aos critérios numa base de dados -DSTDEV = BDDESVPAD ## Calcula o desvio-padrão com base numa amostra de entradas da base de dados seleccionadas -DSTDEVP = BDDESVPADP ## Calcula o desvio-padrão com base na população total das entradas da base de dados seleccionadas -DSUM = BDSOMA ## Adiciona os números na coluna de campo dos registos de base de dados que correspondem aos critérios -DVAR = BDVAR ## Calcula a variância com base numa amostra das entradas de base de dados seleccionadas -DVARP = BDVARP ## Calcula a variância com base na população total das entradas de base de dados seleccionadas - +DAVERAGE = BDMÉDIA +DCOUNT = BDCONTAR +DCOUNTA = BDCONTAR.VAL +DGET = BDOBTER +DMAX = BDMÁX +DMIN = BDMÍN +DPRODUCT = BDMULTIPL +DSTDEV = BDDESVPAD +DSTDEVP = BDDESVPADP +DSUM = BDSOMA +DVAR = BDVAR +DVARP = BDVARP ## -## Date and time functions Funções de data e hora +## Funções de data e hora (Date & Time Functions) ## -DATE = DATA ## Devolve o número de série de uma determinada data -DATEVALUE = DATA.VALOR ## Converte uma data em forma de texto num número de série -DAY = DIA ## Converte um número de série num dia do mês -DAYS360 = DIAS360 ## Calcula o número de dias entre duas datas com base num ano com 360 dias -EDATE = DATAM ## Devolve um número de série de data que corresponde ao número de meses indicado antes ou depois da data de início -EOMONTH = FIMMÊS ## Devolve o número de série do último dia do mês antes ou depois de um número de meses especificado -HOUR = HORA ## Converte um número de série numa hora -MINUTE = MINUTO ## Converte um número de série num minuto -MONTH = MÊS ## Converte um número de série num mês -NETWORKDAYS = DIATRABALHOTOTAL ## Devolve o número total de dias úteis entre duas datas -NOW = AGORA ## Devolve o número de série da data e hora actuais -SECOND = SEGUNDO ## Converte um número de série num segundo -TIME = TEMPO ## Devolve o número de série de um determinado tempo -TIMEVALUE = VALOR.TEMPO ## Converte um tempo em forma de texto num número de série -TODAY = HOJE ## Devolve o número de série da data actual -WEEKDAY = DIA.SEMANA ## Converte um número de série num dia da semana -WEEKNUM = NÚMSEMANA ## Converte um número de série num número que representa o número da semana num determinado ano -WORKDAY = DIA.TRABALHO ## Devolve o número de série da data antes ou depois de um número de dias úteis especificado -YEAR = ANO ## Converte um número de série num ano -YEARFRAC = FRACÇÃOANO ## Devolve a fracção de ano que representa o número de dias inteiros entre a data_de_início e a data_de_fim - +DATE = DATA +DATEDIF = DATADIF +DATESTRING = DATA.CADEIA +DATEVALUE = DATA.VALOR +DAY = DIA +DAYS = DIAS +DAYS360 = DIAS360 +EDATE = DATAM +EOMONTH = FIMMÊS +HOUR = HORA +ISOWEEKNUM = NUMSEMANAISO +MINUTE = MINUTO +MONTH = MÊS +NETWORKDAYS = DIATRABALHOTOTAL +NETWORKDAYS.INTL = DIATRABALHOTOTAL.INTL +NOW = AGORA +SECOND = SEGUNDO +THAIDAYOFWEEK = DIA.DA.SEMANA.TAILANDÊS +THAIMONTHOFYEAR = MÊS.DO.ANO.TAILANDÊS +THAIYEAR = ANO.TAILANDÊS +TIME = TEMPO +TIMEVALUE = VALOR.TEMPO +TODAY = HOJE +WEEKDAY = DIA.SEMANA +WEEKNUM = NÚMSEMANA +WORKDAY = DIATRABALHO +WORKDAY.INTL = DIATRABALHO.INTL +YEAR = ANO +YEARFRAC = FRAÇÃOANO ## -## Engineering functions Funções de engenharia +## Funções de engenharia (Engineering Functions) ## -BESSELI = BESSELI ## Devolve a função de Bessel modificada In(x) -BESSELJ = BESSELJ ## Devolve a função de Bessel Jn(x) -BESSELK = BESSELK ## Devolve a função de Bessel modificada Kn(x) -BESSELY = BESSELY ## Devolve a função de Bessel Yn(x) -BIN2DEC = BINADEC ## Converte um número binário em decimal -BIN2HEX = BINAHEX ## Converte um número binário em hexadecimal -BIN2OCT = BINAOCT ## Converte um número binário em octal -COMPLEX = COMPLEXO ## Converte coeficientes reais e imaginários num número complexo -CONVERT = CONVERTER ## Converte um número de um sistema de medida noutro -DEC2BIN = DECABIN ## Converte um número decimal em binário -DEC2HEX = DECAHEX ## Converte um número decimal em hexadecimal -DEC2OCT = DECAOCT ## Converte um número decimal em octal -DELTA = DELTA ## Testa se dois valores são iguais -ERF = FUNCERRO ## Devolve a função de erro -ERFC = FUNCERROCOMPL ## Devolve a função de erro complementar -GESTEP = DEGRAU ## Testa se um número é maior do que um valor limite -HEX2BIN = HEXABIN ## Converte um número hexadecimal em binário -HEX2DEC = HEXADEC ## Converte um número hexadecimal em decimal -HEX2OCT = HEXAOCT ## Converte um número hexadecimal em octal -IMABS = IMABS ## Devolve o valor absoluto (módulo) de um número complexo -IMAGINARY = IMAGINÁRIO ## Devolve o coeficiente imaginário de um número complexo -IMARGUMENT = IMARG ## Devolve o argumento Teta, um ângulo expresso em radianos -IMCONJUGATE = IMCONJ ## Devolve o conjugado complexo de um número complexo -IMCOS = IMCOS ## Devolve o co-seno de um número complexo -IMDIV = IMDIV ## Devolve o quociente de dois números complexos -IMEXP = IMEXP ## Devolve o exponencial de um número complexo -IMLN = IMLN ## Devolve o logaritmo natural de um número complexo -IMLOG10 = IMLOG10 ## Devolve o logaritmo de base 10 de um número complexo -IMLOG2 = IMLOG2 ## Devolve o logaritmo de base 2 de um número complexo -IMPOWER = IMPOT ## Devolve um número complexo elevado a uma potência inteira -IMPRODUCT = IMPROD ## Devolve o produto de números complexos -IMREAL = IMREAL ## Devolve o coeficiente real de um número complexo -IMSIN = IMSENO ## Devolve o seno de um número complexo -IMSQRT = IMRAIZ ## Devolve a raiz quadrada de um número complexo -IMSUB = IMSUBTR ## Devolve a diferença entre dois números complexos -IMSUM = IMSOMA ## Devolve a soma de números complexos -OCT2BIN = OCTABIN ## Converte um número octal em binário -OCT2DEC = OCTADEC ## Converte um número octal em decimal -OCT2HEX = OCTAHEX ## Converte um número octal em hexadecimal - +BESSELI = BESSELI +BESSELJ = BESSELJ +BESSELK = BESSELK +BESSELY = BESSELY +BIN2DEC = BINADEC +BIN2HEX = BINAHEX +BIN2OCT = BINAOCT +BITAND = BIT.E +BITLSHIFT = BITDESL.ESQ +BITOR = BIT.OU +BITRSHIFT = BITDESL.DIR +BITXOR = BIT.XOU +COMPLEX = COMPLEXO +CONVERT = CONVERTER +DEC2BIN = DECABIN +DEC2HEX = DECAHEX +DEC2OCT = DECAOCT +DELTA = DELTA +ERF = FUNCERRO +ERF.PRECISE = FUNCERRO.PRECISO +ERFC = FUNCERROCOMPL +ERFC.PRECISE = FUNCERROCOMPL.PRECISO +GESTEP = DEGRAU +HEX2BIN = HEXABIN +HEX2DEC = HEXADEC +HEX2OCT = HEXAOCT +IMABS = IMABS +IMAGINARY = IMAGINÁRIO +IMARGUMENT = IMARG +IMCONJUGATE = IMCONJ +IMCOS = IMCOS +IMCOSH = IMCOSH +IMCOT = IMCOT +IMCSC = IMCSC +IMCSCH = IMCSCH +IMDIV = IMDIV +IMEXP = IMEXP +IMLN = IMLN +IMLOG10 = IMLOG10 +IMLOG2 = IMLOG2 +IMPOWER = IMPOT +IMPRODUCT = IMPROD +IMREAL = IMREAL +IMSEC = IMSEC +IMSECH = IMSECH +IMSIN = IMSENO +IMSINH = IMSENOH +IMSQRT = IMRAIZ +IMSUB = IMSUBTR +IMSUM = IMSOMA +IMTAN = IMTAN +OCT2BIN = OCTABIN +OCT2DEC = OCTADEC +OCT2HEX = OCTAHEX ## -## Financial functions Funções financeiras +## Funções financeiras (Financial Functions) ## -ACCRINT = JUROSACUM ## Devolve os juros acumulados de um título que paga juros periódicos -ACCRINTM = JUROSACUMV ## Devolve os juros acumulados de um título que paga juros no vencimento -AMORDEGRC = AMORDEGRC ## Devolve a depreciação correspondente a cada período contabilístico utilizando um coeficiente de depreciação -AMORLINC = AMORLINC ## Devolve a depreciação correspondente a cada período contabilístico -COUPDAYBS = CUPDIASINLIQ ## Devolve o número de dias entre o início do período do cupão e a data de regularização -COUPDAYS = CUPDIAS ## Devolve o número de dias no período do cupão que contém a data de regularização -COUPDAYSNC = CUPDIASPRÓX ## Devolve o número de dias entre a data de regularização e a data do cupão seguinte -COUPNCD = CUPDATAPRÓX ## Devolve a data do cupão seguinte após a data de regularização -COUPNUM = CUPNÚM ## Devolve o número de cupões a serem pagos entre a data de regularização e a data de vencimento -COUPPCD = CUPDATAANT ## Devolve a data do cupão anterior antes da data de regularização -CUMIPMT = PGTOJURACUM ## Devolve os juros cumulativos pagos entre dois períodos -CUMPRINC = PGTOCAPACUM ## Devolve o capital cumulativo pago a título de empréstimo entre dois períodos -DB = BD ## Devolve a depreciação de um activo relativo a um período especificado utilizando o método das quotas degressivas fixas -DDB = BDD ## Devolve a depreciação de um activo relativo a um período especificado utilizando o método das quotas degressivas duplas ou qualquer outro método especificado -DISC = DESC ## Devolve a taxa de desconto de um título -DOLLARDE = MOEDADEC ## Converte um preço em unidade monetária, expresso como uma fracção, num preço em unidade monetária, expresso como um número decimal -DOLLARFR = MOEDAFRA ## Converte um preço em unidade monetária, expresso como um número decimal, num preço em unidade monetária, expresso como uma fracção -DURATION = DURAÇÃO ## Devolve a duração anual de um título com pagamentos de juros periódicos -EFFECT = EFECTIVA ## Devolve a taxa de juros anual efectiva -FV = VF ## Devolve o valor futuro de um investimento -FVSCHEDULE = VFPLANO ## Devolve o valor futuro de um capital inicial após a aplicação de uma série de taxas de juro compostas -INTRATE = TAXAJUROS ## Devolve a taxa de juros de um título investido na totalidade -IPMT = IPGTO ## Devolve o pagamento dos juros de um investimento durante um determinado período -IRR = TIR ## Devolve a taxa de rentabilidade interna para uma série de fluxos monetários -ISPMT = É.PGTO ## Calcula os juros pagos durante um período específico de um investimento -MDURATION = MDURAÇÃO ## Devolve a duração modificada de Macauley de um título com um valor de paridade equivalente a € 100 -MIRR = MTIR ## Devolve a taxa interna de rentabilidade em que os fluxos monetários positivos e negativos são financiados com taxas diferentes -NOMINAL = NOMINAL ## Devolve a taxa de juros nominal anual -NPER = NPER ## Devolve o número de períodos de um investimento -NPV = VAL ## Devolve o valor actual líquido de um investimento com base numa série de fluxos monetários periódicos e numa taxa de desconto -ODDFPRICE = PREÇOPRIMINC ## Devolve o preço por € 100 do valor nominal de um título com um período inicial incompleto -ODDFYIELD = LUCROPRIMINC ## Devolve o lucro de um título com um período inicial incompleto -ODDLPRICE = PREÇOÚLTINC ## Devolve o preço por € 100 do valor nominal de um título com um período final incompleto -ODDLYIELD = LUCROÚLTINC ## Devolve o lucro de um título com um período final incompleto -PMT = PGTO ## Devolve o pagamento periódico de uma anuidade -PPMT = PPGTO ## Devolve o pagamento sobre o capital de um investimento num determinado período -PRICE = PREÇO ## Devolve o preço por € 100 do valor nominal de um título que paga juros periódicos -PRICEDISC = PREÇODESC ## Devolve o preço por € 100 do valor nominal de um título descontado -PRICEMAT = PREÇOVENC ## Devolve o preço por € 100 do valor nominal de um título que paga juros no vencimento -PV = VA ## Devolve o valor actual de um investimento -RATE = TAXA ## Devolve a taxa de juros por período de uma anuidade -RECEIVED = RECEBER ## Devolve o montante recebido no vencimento de um título investido na totalidade -SLN = AMORT ## Devolve uma depreciação linear de um activo durante um período -SYD = AMORTD ## Devolve a depreciação por algarismos da soma dos anos de um activo durante um período especificado -TBILLEQ = OTN ## Devolve o lucro de um título equivalente a uma Obrigação do Tesouro -TBILLPRICE = OTNVALOR ## Devolve o preço por € 100 de valor nominal de uma Obrigação do Tesouro -TBILLYIELD = OTNLUCRO ## Devolve o lucro de uma Obrigação do Tesouro -VDB = BDV ## Devolve a depreciação de um activo relativo a um período específico ou parcial utilizando um método de quotas degressivas -XIRR = XTIR ## Devolve a taxa interna de rentabilidade de um plano de fluxos monetários que não seja necessariamente periódica -XNPV = XVAL ## Devolve o valor actual líquido de um plano de fluxos monetários que não seja necessariamente periódico -YIELD = LUCRO ## Devolve o lucro de um título que paga juros periódicos -YIELDDISC = LUCRODESC ## Devolve o lucro anual de um título emitido abaixo do valor nominal, por exemplo, uma Obrigação do Tesouro -YIELDMAT = LUCROVENC ## Devolve o lucro anual de um título que paga juros na data de vencimento - +ACCRINT = JUROSACUM +ACCRINTM = JUROSACUMV +AMORDEGRC = AMORDEGRC +AMORLINC = AMORLINC +COUPDAYBS = CUPDIASINLIQ +COUPDAYS = CUPDIAS +COUPDAYSNC = CUPDIASPRÓX +COUPNCD = CUPDATAPRÓX +COUPNUM = CUPNÚM +COUPPCD = CUPDATAANT +CUMIPMT = PGTOJURACUM +CUMPRINC = PGTOCAPACUM +DB = BD +DDB = BDD +DISC = DESC +DOLLARDE = MOEDADEC +DOLLARFR = MOEDAFRA +DURATION = DURAÇÃO +EFFECT = EFETIVA +FV = VF +FVSCHEDULE = VFPLANO +INTRATE = TAXAJUROS +IPMT = IPGTO +IRR = TIR +ISPMT = É.PGTO +MDURATION = MDURAÇÃO +MIRR = MTIR +NOMINAL = NOMINAL +NPER = NPER +NPV = VAL +ODDFPRICE = PREÇOPRIMINC +ODDFYIELD = LUCROPRIMINC +ODDLPRICE = PREÇOÚLTINC +ODDLYIELD = LUCROÚLTINC +PDURATION = PDURAÇÃO +PMT = PGTO +PPMT = PPGTO +PRICE = PREÇO +PRICEDISC = PREÇODESC +PRICEMAT = PREÇOVENC +PV = VA +RATE = TAXA +RECEIVED = RECEBER +RRI = DEVOLVERTAXAJUROS +SLN = AMORT +SYD = AMORTD +TBILLEQ = OTN +TBILLPRICE = OTNVALOR +TBILLYIELD = OTNLUCRO +VDB = BDV +XIRR = XTIR +XNPV = XVAL +YIELD = LUCRO +YIELDDISC = LUCRODESC +YIELDMAT = LUCROVENC ## -## Information functions Funções de informação +## Funções de informação (Information Functions) ## -CELL = CÉL ## Devolve informações sobre a formatação, localização ou conteúdo de uma célula -ERROR.TYPE = TIPO.ERRO ## Devolve um número correspondente a um tipo de erro -INFO = INFORMAÇÃO ## Devolve informações sobre o ambiente de funcionamento actual -ISBLANK = É.CÉL.VAZIA ## Devolve VERDADEIRO se o valor estiver em branco -ISERR = É.ERROS ## Devolve VERDADEIRO se o valor for um valor de erro diferente de #N/D -ISERROR = É.ERRO ## Devolve VERDADEIRO se o valor for um valor de erro -ISEVEN = ÉPAR ## Devolve VERDADEIRO se o número for par -ISLOGICAL = É.LÓGICO ## Devolve VERDADEIRO se o valor for lógico -ISNA = É.NÃO.DISP ## Devolve VERDADEIRO se o valor for o valor de erro #N/D -ISNONTEXT = É.NÃO.TEXTO ## Devolve VERDADEIRO se o valor não for texto -ISNUMBER = É.NÚM ## Devolve VERDADEIRO se o valor for um número -ISODD = ÉÍMPAR ## Devolve VERDADEIRO se o número for ímpar -ISREF = É.REF ## Devolve VERDADEIRO se o valor for uma referência -ISTEXT = É.TEXTO ## Devolve VERDADEIRO se o valor for texto -N = N ## Devolve um valor convertido num número -NA = NÃO.DISP ## Devolve o valor de erro #N/D -TYPE = TIPO ## Devolve um número que indica o tipo de dados de um valor - +CELL = CÉL +ERROR.TYPE = TIPO.ERRO +INFO = INFORMAÇÃO +ISBLANK = É.CÉL.VAZIA +ISERR = É.ERROS +ISERROR = É.ERRO +ISEVEN = ÉPAR +ISFORMULA = É.FORMULA +ISLOGICAL = É.LÓGICO +ISNA = É.NÃO.DISP +ISNONTEXT = É.NÃO.TEXTO +ISNUMBER = É.NÚM +ISODD = ÉÍMPAR +ISREF = É.REF +ISTEXT = É.TEXTO +N = N +NA = NÃO.DISP +SHEET = FOLHA +SHEETS = FOLHAS +TYPE = TIPO ## -## Logical functions Funções lógicas +## Funções lógicas (Logical Functions) ## -AND = E ## Devolve VERDADEIRO se todos os respectivos argumentos corresponderem a VERDADEIRO -FALSE = FALSO ## Devolve o valor lógico FALSO -IF = SE ## Especifica um teste lógico a ser executado -IFERROR = SE.ERRO ## Devolve um valor definido pelo utilizador se ocorrer um erro na fórmula, e devolve o resultado da fórmula se não ocorrer nenhum erro -NOT = NÃO ## Inverte a lógica do respectivo argumento -OR = OU ## Devolve VERDADEIRO se qualquer argumento for VERDADEIRO -TRUE = VERDADEIRO ## Devolve o valor lógico VERDADEIRO - +AND = E +FALSE = FALSO +IF = SE +IFERROR = SE.ERRO +IFNA = SEND +IFS = SE.S +NOT = NÃO +OR = OU +SWITCH = PARÂMETRO +TRUE = VERDADEIRO +XOR = XOU ## -## Lookup and reference functions Funções de pesquisa e referência +## Funções de pesquisa e referência (Lookup & Reference Functions) ## -ADDRESS = ENDEREÇO ## Devolve uma referência a uma única célula numa folha de cálculo como texto -AREAS = ÁREAS ## Devolve o número de áreas numa referência -CHOOSE = SELECCIONAR ## Selecciona um valor a partir de uma lista de valores -COLUMN = COL ## Devolve o número da coluna de uma referência -COLUMNS = COLS ## Devolve o número de colunas numa referência -HLOOKUP = PROCH ## Procura na linha superior de uma matriz e devolve o valor da célula indicada -HYPERLINK = HIPERLIGAÇÃO ## Cria um atalho ou hiperligação que abre um documento armazenado num servidor de rede, numa intranet ou na Internet -INDEX = ÍNDICE ## Utiliza um índice para escolher um valor de uma referência ou de uma matriz -INDIRECT = INDIRECTO ## Devolve uma referência indicada por um valor de texto -LOOKUP = PROC ## Procura valores num vector ou numa matriz -MATCH = CORRESP ## Procura valores numa referência ou numa matriz -OFFSET = DESLOCAMENTO ## Devolve o deslocamento de referência de uma determinada referência -ROW = LIN ## Devolve o número da linha de uma referência -ROWS = LINS ## Devolve o número de linhas numa referência -RTD = RTD ## Obtém dados em tempo real a partir de um programa que suporte automatização COM (automatização: modo de trabalhar com objectos de uma aplicação a partir de outra aplicação ou ferramenta de desenvolvimento. Anteriormente conhecida como automatização OLE, a automatização é uma norma da indústria de software e uma funcionalidade COM (Component Object Model).) -TRANSPOSE = TRANSPOR ## Devolve a transposição de uma matriz -VLOOKUP = PROCV ## Procura na primeira coluna de uma matriz e percorre a linha para devolver o valor de uma célula - +ADDRESS = ENDEREÇO +AREAS = ÁREAS +CHOOSE = SELECIONAR +COLUMN = COL +COLUMNS = COLS +FORMULATEXT = FÓRMULA.TEXTO +GETPIVOTDATA = OBTERDADOSDIN +HLOOKUP = PROCH +HYPERLINK = HIPERLIGAÇÃO +INDEX = ÍNDICE +INDIRECT = INDIRETO +LOOKUP = PROC +MATCH = CORRESP +OFFSET = DESLOCAMENTO +ROW = LIN +ROWS = LINS +RTD = RTD +TRANSPOSE = TRANSPOR +VLOOKUP = PROCV +*RC = LC ## -## Math and trigonometry functions Funções matemáticas e trigonométricas +## Funções matemáticas e trigonométricas (Math & Trig Functions) ## -ABS = ABS ## Devolve o valor absoluto de um número -ACOS = ACOS ## Devolve o arco de co-seno de um número -ACOSH = ACOSH ## Devolve o co-seno hiperbólico inverso de um número -ASIN = ASEN ## Devolve o arco de seno de um número -ASINH = ASENH ## Devolve o seno hiperbólico inverso de um número -ATAN = ATAN ## Devolve o arco de tangente de um número -ATAN2 = ATAN2 ## Devolve o arco de tangente das coordenadas x e y -ATANH = ATANH ## Devolve a tangente hiperbólica inversa de um número -CEILING = ARRED.EXCESSO ## Arredonda um número para o número inteiro mais próximo ou para o múltiplo de significância mais próximo -COMBIN = COMBIN ## Devolve o número de combinações de um determinado número de objectos -COS = COS ## Devolve o co-seno de um número -COSH = COSH ## Devolve o co-seno hiperbólico de um número -DEGREES = GRAUS ## Converte radianos em graus -EVEN = PAR ## Arredonda um número por excesso para o número inteiro mais próximo -EXP = EXP ## Devolve e elevado à potência de um determinado número -FACT = FACTORIAL ## Devolve o factorial de um número -FACTDOUBLE = FACTDUPLO ## Devolve o factorial duplo de um número -FLOOR = ARRED.DEFEITO ## Arredonda um número por defeito até zero -GCD = MDC ## Devolve o maior divisor comum -INT = INT ## Arredonda um número por defeito para o número inteiro mais próximo -LCM = MMC ## Devolve o mínimo múltiplo comum -LN = LN ## Devolve o logaritmo natural de um número -LOG = LOG ## Devolve o logaritmo de um número com uma base especificada -LOG10 = LOG10 ## Devolve o logaritmo de base 10 de um número -MDETERM = MATRIZ.DETERM ## Devolve o determinante matricial de uma matriz -MINVERSE = MATRIZ.INVERSA ## Devolve o inverso matricial de uma matriz -MMULT = MATRIZ.MULT ## Devolve o produto matricial de duas matrizes -MOD = RESTO ## Devolve o resto da divisão -MROUND = MARRED ## Devolve um número arredondado para o múltiplo pretendido -MULTINOMIAL = POLINOMIAL ## Devolve o polinomial de um conjunto de números -ODD = ÍMPAR ## Arredonda por excesso um número para o número inteiro ímpar mais próximo -PI = PI ## Devolve o valor de pi -POWER = POTÊNCIA ## Devolve o resultado de um número elevado a uma potência -PRODUCT = PRODUTO ## Multiplica os respectivos argumentos -QUOTIENT = QUOCIENTE ## Devolve a parte inteira de uma divisão -RADIANS = RADIANOS ## Converte graus em radianos -RAND = ALEATÓRIO ## Devolve um número aleatório entre 0 e 1 -RANDBETWEEN = ALEATÓRIOENTRE ## Devolve um número aleatório entre os números especificados -ROMAN = ROMANO ## Converte um número árabe em romano, como texto -ROUND = ARRED ## Arredonda um número para um número de dígitos especificado -ROUNDDOWN = ARRED.PARA.BAIXO ## Arredonda um número por defeito até zero -ROUNDUP = ARRED.PARA.CIMA ## Arredonda um número por excesso, afastando-o de zero -SERIESSUM = SOMASÉRIE ## Devolve a soma de uma série de potências baseada na fórmula -SIGN = SINAL ## Devolve o sinal de um número -SIN = SEN ## Devolve o seno de um determinado ângulo -SINH = SENH ## Devolve o seno hiperbólico de um número -SQRT = RAIZQ ## Devolve uma raiz quadrada positiva -SQRTPI = RAIZPI ## Devolve a raiz quadrada de (núm * pi) -SUBTOTAL = SUBTOTAL ## Devolve um subtotal numa lista ou base de dados -SUM = SOMA ## Adiciona os respectivos argumentos -SUMIF = SOMA.SE ## Adiciona as células especificadas por um determinado critério -SUMIFS = SOMA.SE.S ## Adiciona as células num intervalo que cumpre vários critérios -SUMPRODUCT = SOMARPRODUTO ## Devolve a soma dos produtos de componentes de matrizes correspondentes -SUMSQ = SOMARQUAD ## Devolve a soma dos quadrados dos argumentos -SUMX2MY2 = SOMAX2DY2 ## Devolve a soma da diferença dos quadrados dos valores correspondentes em duas matrizes -SUMX2PY2 = SOMAX2SY2 ## Devolve a soma da soma dos quadrados dos valores correspondentes em duas matrizes -SUMXMY2 = SOMAXMY2 ## Devolve a soma dos quadrados da diferença dos valores correspondentes em duas matrizes -TAN = TAN ## Devolve a tangente de um número -TANH = TANH ## Devolve a tangente hiperbólica de um número -TRUNC = TRUNCAR ## Trunca um número para um número inteiro - +ABS = ABS +ACOS = ACOS +ACOSH = ACOSH +ACOT = ACOT +ACOTH = ACOTH +AGGREGATE = AGREGAR +ARABIC = ÁRABE +ASIN = ASEN +ASINH = ASENH +ATAN = ATAN +ATAN2 = ATAN2 +ATANH = ATANH +BASE = BASE +CEILING.MATH = ARRED.EXCESSO.MAT +CEILING.PRECISE = ARRED.EXCESSO.PRECISO +COMBIN = COMBIN +COMBINA = COMBIN.R +COS = COS +COSH = COSH +COT = COT +COTH = COTH +CSC = CSC +CSCH = CSCH +DECIMAL = DECIMAL +DEGREES = GRAUS +ECMA.CEILING = ARRED.EXCESSO.ECMA +EVEN = PAR +EXP = EXP +FACT = FATORIAL +FACTDOUBLE = FATDUPLO +FLOOR.MATH = ARRED.DEFEITO.MAT +FLOOR.PRECISE = ARRED.DEFEITO.PRECISO +GCD = MDC +INT = INT +ISO.CEILING = ARRED.EXCESSO.ISO +LCM = MMC +LN = LN +LOG = LOG +LOG10 = LOG10 +MDETERM = MATRIZ.DETERM +MINVERSE = MATRIZ.INVERSA +MMULT = MATRIZ.MULT +MOD = RESTO +MROUND = MARRED +MULTINOMIAL = POLINOMIAL +MUNIT = UNIDM +ODD = ÍMPAR +PI = PI +POWER = POTÊNCIA +PRODUCT = PRODUTO +QUOTIENT = QUOCIENTE +RADIANS = RADIANOS +RAND = ALEATÓRIO +RANDBETWEEN = ALEATÓRIOENTRE +ROMAN = ROMANO +ROUND = ARRED +ROUNDBAHTDOWN = ARREDOND.BAHT.BAIXO +ROUNDBAHTUP = ARREDOND.BAHT.CIMA +ROUNDDOWN = ARRED.PARA.BAIXO +ROUNDUP = ARRED.PARA.CIMA +SEC = SEC +SECH = SECH +SERIESSUM = SOMASÉRIE +SIGN = SINAL +SIN = SEN +SINH = SENH +SQRT = RAIZQ +SQRTPI = RAIZPI +SUBTOTAL = SUBTOTAL +SUM = SOMA +SUMIF = SOMA.SE +SUMIFS = SOMA.SE.S +SUMPRODUCT = SOMARPRODUTO +SUMSQ = SOMARQUAD +SUMX2MY2 = SOMAX2DY2 +SUMX2PY2 = SOMAX2SY2 +SUMXMY2 = SOMAXMY2 +TAN = TAN +TANH = TANH +TRUNC = TRUNCAR ## -## Statistical functions Funções estatísticas +## Funções estatísticas (Statistical Functions) ## -AVEDEV = DESV.MÉDIO ## Devolve a média aritmética dos desvios absolutos à média dos pontos de dados -AVERAGE = MÉDIA ## Devolve a média dos respectivos argumentos -AVERAGEA = MÉDIAA ## Devolve uma média dos respectivos argumentos, incluindo números, texto e valores lógicos -AVERAGEIF = MÉDIA.SE ## Devolve a média aritmética de todas as células num intervalo que cumprem determinado critério -AVERAGEIFS = MÉDIA.SE.S ## Devolve a média aritmética de todas as células que cumprem múltiplos critérios -BETADIST = DISTBETA ## Devolve a função de distribuição cumulativa beta -BETAINV = BETA.ACUM.INV ## Devolve o inverso da função de distribuição cumulativa relativamente a uma distribuição beta específica -BINOMDIST = DISTRBINOM ## Devolve a probabilidade de distribuição binomial de termo individual -CHIDIST = DIST.CHI ## Devolve a probabilidade unicaudal da distribuição qui-quadrada -CHIINV = INV.CHI ## Devolve o inverso da probabilidade unicaudal da distribuição qui-quadrada -CHITEST = TESTE.CHI ## Devolve o teste para independência -CONFIDENCE = INT.CONFIANÇA ## Devolve o intervalo de confiança correspondente a uma média de população -CORREL = CORREL ## Devolve o coeficiente de correlação entre dois conjuntos de dados -COUNT = CONTAR ## Conta os números que existem na lista de argumentos -COUNTA = CONTAR.VAL ## Conta os valores que existem na lista de argumentos -COUNTBLANK = CONTAR.VAZIO ## Conta o número de células em branco num intervalo -COUNTIF = CONTAR.SE ## Calcula o número de células num intervalo que corresponde aos critérios determinados -COUNTIFS = CONTAR.SE.S ## Conta o número de células num intervalo que cumprem múltiplos critérios -COVAR = COVAR ## Devolve a covariância, que é a média dos produtos de desvios de pares -CRITBINOM = CRIT.BINOM ## Devolve o menor valor em que a distribuição binomial cumulativa é inferior ou igual a um valor de critério -DEVSQ = DESVQ ## Devolve a soma dos quadrados dos desvios -EXPONDIST = DISTEXPON ## Devolve a distribuição exponencial -FDIST = DISTF ## Devolve a distribuição da probabilidade F -FINV = INVF ## Devolve o inverso da distribuição da probabilidade F -FISHER = FISHER ## Devolve a transformação Fisher -FISHERINV = FISHERINV ## Devolve o inverso da transformação Fisher -FORECAST = PREVISÃO ## Devolve um valor ao longo de uma tendência linear -FREQUENCY = FREQUÊNCIA ## Devolve uma distribuição de frequência como uma matriz vertical -FTEST = TESTEF ## Devolve o resultado de um teste F -GAMMADIST = DISTGAMA ## Devolve a distribuição gama -GAMMAINV = INVGAMA ## Devolve o inverso da distribuição gama cumulativa -GAMMALN = LNGAMA ## Devolve o logaritmo natural da função gama, Γ(x) -GEOMEAN = MÉDIA.GEOMÉTRICA ## Devolve a média geométrica -GROWTH = CRESCIMENTO ## Devolve valores ao longo de uma tendência exponencial -HARMEAN = MÉDIA.HARMÓNICA ## Devolve a média harmónica -HYPGEOMDIST = DIST.HIPERGEOM ## Devolve a distribuição hipergeométrica -INTERCEPT = INTERCEPTAR ## Devolve a intercepção da linha de regressão linear -KURT = CURT ## Devolve a curtose de um conjunto de dados -LARGE = MAIOR ## Devolve o maior valor k-ésimo de um conjunto de dados -LINEST = PROJ.LIN ## Devolve os parâmetros de uma tendência linear -LOGEST = PROJ.LOG ## Devolve os parâmetros de uma tendência exponencial -LOGINV = INVLOG ## Devolve o inverso da distribuição normal logarítmica -LOGNORMDIST = DIST.NORMALLOG ## Devolve a distribuição normal logarítmica cumulativa -MAX = MÁXIMO ## Devolve o valor máximo numa lista de argumentos -MAXA = MÁXIMOA ## Devolve o valor máximo numa lista de argumentos, incluindo números, texto e valores lógicos -MEDIAN = MED ## Devolve a mediana dos números indicados -MIN = MÍNIMO ## Devolve o valor mínimo numa lista de argumentos -MINA = MÍNIMOA ## Devolve o valor mínimo numa lista de argumentos, incluindo números, texto e valores lógicos -MODE = MODA ## Devolve o valor mais comum num conjunto de dados -NEGBINOMDIST = DIST.BIN.NEG ## Devolve a distribuição binominal negativa -NORMDIST = DIST.NORM ## Devolve a distribuição cumulativa normal -NORMINV = INV.NORM ## Devolve o inverso da distribuição cumulativa normal -NORMSDIST = DIST.NORMP ## Devolve a distribuição cumulativa normal padrão -NORMSINV = INV.NORMP ## Devolve o inverso da distribuição cumulativa normal padrão -PEARSON = PEARSON ## Devolve o coeficiente de correlação momento/produto de Pearson -PERCENTILE = PERCENTIL ## Devolve o k-ésimo percentil de valores num intervalo -PERCENTRANK = ORDEM.PERCENTUAL ## Devolve a ordem percentual de um valor num conjunto de dados -PERMUT = PERMUTAR ## Devolve o número de permutações de um determinado número de objectos -POISSON = POISSON ## Devolve a distribuição de Poisson -PROB = PROB ## Devolve a probabilidade dos valores num intervalo se encontrarem entre dois limites -QUARTILE = QUARTIL ## Devolve o quartil de um conjunto de dados -RANK = ORDEM ## Devolve a ordem de um número numa lista numérica -RSQ = RQUAD ## Devolve o quadrado do coeficiente de correlação momento/produto de Pearson -SKEW = DISTORÇÃO ## Devolve a distorção de uma distribuição -SLOPE = DECLIVE ## Devolve o declive da linha de regressão linear -SMALL = MENOR ## Devolve o menor valor de k-ésimo de um conjunto de dados -STANDARDIZE = NORMALIZAR ## Devolve um valor normalizado -STDEV = DESVPAD ## Calcula o desvio-padrão com base numa amostra -STDEVA = DESVPADA ## Calcula o desvio-padrão com base numa amostra, incluindo números, texto e valores lógicos -STDEVP = DESVPADP ## Calcula o desvio-padrão com base na população total -STDEVPA = DESVPADPA ## Calcula o desvio-padrão com base na população total, incluindo números, texto e valores lógicos -STEYX = EPADYX ## Devolve o erro-padrão do valor de y previsto para cada x na regressão -TDIST = DISTT ## Devolve a distribuição t de Student -TINV = INVT ## Devolve o inverso da distribuição t de Student -TREND = TENDÊNCIA ## Devolve valores ao longo de uma tendência linear -TRIMMEAN = MÉDIA.INTERNA ## Devolve a média do interior de um conjunto de dados -TTEST = TESTET ## Devolve a probabilidade associada ao teste t de Student -VAR = VAR ## Calcula a variância com base numa amostra -VARA = VARA ## Calcula a variância com base numa amostra, incluindo números, texto e valores lógicos -VARP = VARP ## Calcula a variância com base na população total -VARPA = VARPA ## Calcula a variância com base na população total, incluindo números, texto e valores lógicos -WEIBULL = WEIBULL ## Devolve a distribuição Weibull -ZTEST = TESTEZ ## Devolve o valor de probabilidade unicaudal de um teste-z - +AVEDEV = DESV.MÉDIO +AVERAGE = MÉDIA +AVERAGEA = MÉDIAA +AVERAGEIF = MÉDIA.SE +AVERAGEIFS = MÉDIA.SE.S +BETA.DIST = DIST.BETA +BETA.INV = INV.BETA +BINOM.DIST = DISTR.BINOM +BINOM.DIST.RANGE = DIST.BINOM.INTERVALO +BINOM.INV = INV.BINOM +CHISQ.DIST = DIST.CHIQ +CHISQ.DIST.RT = DIST.CHIQ.DIR +CHISQ.INV = INV.CHIQ +CHISQ.INV.RT = INV.CHIQ.DIR +CHISQ.TEST = TESTE.CHIQ +CONFIDENCE.NORM = INT.CONFIANÇA.NORM +CONFIDENCE.T = INT.CONFIANÇA.T +CORREL = CORREL +COUNT = CONTAR +COUNTA = CONTAR.VAL +COUNTBLANK = CONTAR.VAZIO +COUNTIF = CONTAR.SE +COUNTIFS = CONTAR.SE.S +COVARIANCE.P = COVARIÂNCIA.P +COVARIANCE.S = COVARIÂNCIA.S +DEVSQ = DESVQ +EXPON.DIST = DIST.EXPON +F.DIST = DIST.F +F.DIST.RT = DIST.F.DIR +F.INV = INV.F +F.INV.RT = INV.F.DIR +F.TEST = TESTE.F +FISHER = FISHER +FISHERINV = FISHERINV +FORECAST.ETS = PREVISÃO.ETS +FORECAST.ETS.CONFINT = PREVISÃO.ETS.CONFINT +FORECAST.ETS.SEASONALITY = PREVISÃO.ETS.SAZONALIDADE +FORECAST.ETS.STAT = PREVISÃO.ETS.ESTATÍSTICA +FORECAST.LINEAR = PREVISÃO.LINEAR +FREQUENCY = FREQUÊNCIA +GAMMA = GAMA +GAMMA.DIST = DIST.GAMA +GAMMA.INV = INV.GAMA +GAMMALN = LNGAMA +GAMMALN.PRECISE = LNGAMA.PRECISO +GAUSS = GAUSS +GEOMEAN = MÉDIA.GEOMÉTRICA +GROWTH = CRESCIMENTO +HARMEAN = MÉDIA.HARMÓNICA +HYPGEOM.DIST = DIST.HIPGEOM +INTERCEPT = INTERCETAR +KURT = CURT +LARGE = MAIOR +LINEST = PROJ.LIN +LOGEST = PROJ.LOG +LOGNORM.DIST = DIST.NORMLOG +LOGNORM.INV = INV.NORMALLOG +MAX = MÁXIMO +MAXA = MÁXIMOA +MAXIFS = MÁXIMO.SE.S +MEDIAN = MED +MIN = MÍNIMO +MINA = MÍNIMOA +MINIFS = MÍNIMO.SE.S +MODE.MULT = MODO.MÚLT +MODE.SNGL = MODO.SIMPLES +NEGBINOM.DIST = DIST.BINOM.NEG +NORM.DIST = DIST.NORMAL +NORM.INV = INV.NORMAL +NORM.S.DIST = DIST.S.NORM +NORM.S.INV = INV.S.NORM +PEARSON = PEARSON +PERCENTILE.EXC = PERCENTIL.EXC +PERCENTILE.INC = PERCENTIL.INC +PERCENTRANK.EXC = ORDEM.PERCENTUAL.EXC +PERCENTRANK.INC = ORDEM.PERCENTUAL.INC +PERMUT = PERMUTAR +PERMUTATIONA = PERMUTAR.R +PHI = PHI +POISSON.DIST = DIST.POISSON +PROB = PROB +QUARTILE.EXC = QUARTIL.EXC +QUARTILE.INC = QUARTIL.INC +RANK.AVG = ORDEM.MÉD +RANK.EQ = ORDEM.EQ +RSQ = RQUAD +SKEW = DISTORÇÃO +SKEW.P = DISTORÇÃO.P +SLOPE = DECLIVE +SMALL = MENOR +STANDARDIZE = NORMALIZAR +STDEV.P = DESVPAD.P +STDEV.S = DESVPAD.S +STDEVA = DESVPADA +STDEVPA = DESVPADPA +STEYX = EPADYX +T.DIST = DIST.T +T.DIST.2T = DIST.T.2C +T.DIST.RT = DIST.T.DIR +T.INV = INV.T +T.INV.2T = INV.T.2C +T.TEST = TESTE.T +TREND = TENDÊNCIA +TRIMMEAN = MÉDIA.INTERNA +VAR.P = VAR.P +VAR.S = VAR.S +VARA = VARA +VARPA = VARPA +WEIBULL.DIST = DIST.WEIBULL +Z.TEST = TESTE.Z ## -## Text functions Funções de texto +## Funções de texto (Text Functions) ## -ASC = ASC ## Altera letras ou katakana de largura total (byte duplo) numa cadeia de caracteres para caracteres de largura média (byte único) -BAHTTEXT = TEXTO.BAHT ## Converte um número em texto, utilizando o formato monetário ß (baht) -CHAR = CARÁCT ## Devolve o carácter especificado pelo número de código -CLEAN = LIMPAR ## Remove do texto todos os caracteres não imprimíveis -CODE = CÓDIGO ## Devolve um código numérico correspondente ao primeiro carácter numa cadeia de texto -CONCATENATE = CONCATENAR ## Agrupa vários itens de texto num único item de texto -DOLLAR = MOEDA ## Converte um número em texto, utilizando o formato monetário € (Euro) -EXACT = EXACTO ## Verifica se dois valores de texto são idênticos -FIND = LOCALIZAR ## Localiza um valor de texto dentro de outro (sensível às maiúsculas e minúsculas) -FINDB = LOCALIZARB ## Localiza um valor de texto dentro de outro (sensível às maiúsculas e minúsculas) -FIXED = FIXA ## Formata um número como texto com um número fixo de decimais -JIS = JIS ## Altera letras ou katakana de largura média (byte único) numa cadeia de caracteres para caracteres de largura total (byte duplo) -LEFT = ESQUERDA ## Devolve os caracteres mais à esquerda de um valor de texto -LEFTB = ESQUERDAB ## Devolve os caracteres mais à esquerda de um valor de texto -LEN = NÚM.CARACT ## Devolve o número de caracteres de uma cadeia de texto -LENB = NÚM.CARACTB ## Devolve o número de caracteres de uma cadeia de texto -LOWER = MINÚSCULAS ## Converte o texto em minúsculas -MID = SEG.TEXTO ## Devolve um número específico de caracteres de uma cadeia de texto, a partir da posição especificada -MIDB = SEG.TEXTOB ## Devolve um número específico de caracteres de uma cadeia de texto, a partir da posição especificada -PHONETIC = FONÉTICA ## Retira os caracteres fonéticos (furigana) de uma cadeia de texto -PROPER = INICIAL.MAIÚSCULA ## Coloca em maiúsculas a primeira letra de cada palavra de um valor de texto -REPLACE = SUBSTITUIR ## Substitui caracteres no texto -REPLACEB = SUBSTITUIRB ## Substitui caracteres no texto -REPT = REPETIR ## Repete texto um determinado número de vezes -RIGHT = DIREITA ## Devolve os caracteres mais à direita de um valor de texto -RIGHTB = DIREITAB ## Devolve os caracteres mais à direita de um valor de texto -SEARCH = PROCURAR ## Localiza um valor de texto dentro de outro (não sensível a maiúsculas e minúsculas) -SEARCHB = PROCURARB ## Localiza um valor de texto dentro de outro (não sensível a maiúsculas e minúsculas) -SUBSTITUTE = SUBST ## Substitui texto novo por texto antigo numa cadeia de texto -T = T ## Converte os respectivos argumentos em texto -TEXT = TEXTO ## Formata um número e converte-o em texto -TRIM = COMPACTAR ## Remove espaços do texto -UPPER = MAIÚSCULAS ## Converte texto em maiúsculas -VALUE = VALOR ## Converte um argumento de texto num número +BAHTTEXT = TEXTO.BAHT +CHAR = CARÁT +CLEAN = LIMPARB +CODE = CÓDIGO +CONCAT = CONCAT +DOLLAR = MOEDA +EXACT = EXATO +FIND = LOCALIZAR +FIXED = FIXA +ISTHAIDIGIT = É.DÍGITO.TAILANDÊS +LEFT = ESQUERDA +LEN = NÚM.CARAT +LOWER = MINÚSCULAS +MID = SEG.TEXTO +NUMBERSTRING = NÚMERO.CADEIA +NUMBERVALUE = VALOR.NÚMERO +PHONETIC = FONÉTICA +PROPER = INICIAL.MAIÚSCULA +REPLACE = SUBSTITUIR +REPT = REPETIR +RIGHT = DIREITA +SEARCH = PROCURAR +SUBSTITUTE = SUBST +T = T +TEXT = TEXTO +TEXTJOIN = UNIRTEXTO +THAIDIGIT = DÍGITO.TAILANDÊS +THAINUMSOUND = SOM.NÚM.TAILANDÊS +THAINUMSTRING = CADEIA.NÚM.TAILANDÊS +THAISTRINGLENGTH = COMP.CADEIA.TAILANDÊS +TRIM = COMPACTAR +UNICHAR = UNICARÁT +UNICODE = UNICODE +UPPER = MAIÚSCULAS +VALUE = VALOR + +## +## Funções da Web (Web Functions) +## +ENCODEURL = CODIFICAÇÃOURL +FILTERXML = FILTRARXML +WEBSERVICE = SERVIÇOWEB + +## +## Funções de compatibilidade (Compatibility Functions) +## +BETADIST = DISTBETA +BETAINV = BETA.ACUM.INV +BINOMDIST = DISTRBINOM +CEILING = ARRED.EXCESSO +CHIDIST = DIST.CHI +CHIINV = INV.CHI +CHITEST = TESTE.CHI +CONCATENATE = CONCATENAR +CONFIDENCE = INT.CONFIANÇA +COVAR = COVAR +CRITBINOM = CRIT.BINOM +EXPONDIST = DISTEXPON +FDIST = DISTF +FINV = INVF +FLOOR = ARRED.DEFEITO +FORECAST = PREVISÃO +FTEST = TESTEF +GAMMADIST = DISTGAMA +GAMMAINV = INVGAMA +HYPGEOMDIST = DIST.HIPERGEOM +LOGINV = INVLOG +LOGNORMDIST = DIST.NORMALLOG +MODE = MODA +NEGBINOMDIST = DIST.BIN.NEG +NORMDIST = DIST.NORM +NORMINV = INV.NORM +NORMSDIST = DIST.NORMP +NORMSINV = INV.NORMP +PERCENTILE = PERCENTIL +PERCENTRANK = ORDEM.PERCENTUAL +POISSON = POISSON +QUARTILE = QUARTIL +RANK = ORDEM +STDEV = DESVPAD +STDEVP = DESVPADP +TDIST = DISTT +TINV = INVT +TTEST = TESTET +VAR = VAR +VARP = VARP +WEIBULL = WEIBULL +ZTEST = TESTEZ diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/ru/config b/PhpOffice/PhpSpreadsheet/Calculation/locale/ru/config old mode 100755 new mode 100644 index 9ee9e6c..2a5a0db --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/ru/config +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/ru/config @@ -1,24 +1,20 @@ +############################################################ ## -## PhpSpreadsheet +## PhpSpreadsheet - locale settings ## +## русский язык (Russian) +## +############################################################ -ArgumentSeparator = ; - +ArgumentSeparator = ; ## -## (For future use) +## Error Codes ## -currencySymbol = р - - -## -## Excel Error Codes (For future use) - -## -NULL = #ПУСТО! -DIV0 = #ДЕЛ/0! -VALUE = #ЗНАЧ! -REF = #ССЫЛ! -NAME = #ИМЯ? -NUM = #ЧИСЛО! -NA = #Н/Д +NULL = #ПУСТО! +DIV0 = #ДЕЛ/0! +VALUE = #ЗНАЧ! +REF = #ССЫЛКА! +NAME = #ИМЯ? +NUM = #ЧИСЛО! +NA = #Н/Д diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/ru/functions b/PhpOffice/PhpSpreadsheet/Calculation/locale/ru/functions old mode 100755 new mode 100644 index 3597dbf..9f05d5a --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/ru/functions +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/ru/functions @@ -1,416 +1,555 @@ +############################################################ ## -## PhpSpreadsheet -## -## Data in this file derived from information provided by web-junior (http://www.web-junior.net/) +## PhpSpreadsheet - function name translations ## +## русский язык (Russian) ## +############################################################ ## -## Add-in and Automation functions Функции надстроек и автоматизации +## Функции кубов (Cube Functions) ## -GETPIVOTDATA = ПОЛУЧИТЬ.ДАННЫЕ.СВОДНОЙ.ТАБЛИЦЫ ## Возвращает данные, хранящиеся в отчете сводной таблицы. - +CUBEKPIMEMBER = КУБЭЛЕМЕНТКИП +CUBEMEMBER = КУБЭЛЕМЕНТ +CUBEMEMBERPROPERTY = КУБСВОЙСТВОЭЛЕМЕНТА +CUBERANKEDMEMBER = КУБПОРЭЛЕМЕНТ +CUBESET = КУБМНОЖ +CUBESETCOUNT = КУБЧИСЛОЭЛМНОЖ +CUBEVALUE = КУБЗНАЧЕНИЕ ## -## Cube functions Функции Куб +## Функции для работы с базами данных (Database Functions) ## -CUBEKPIMEMBER = КУБЭЛЕМЕНТКИП ## Возвращает свойство ключевого индикатора производительности «(КИП)» и отображает имя «КИП» в ячейке. «КИП» представляет собой количественную величину, такую как ежемесячная валовая прибыль или ежеквартальная текучесть кадров, используемой для контроля эффективности работы организации. -CUBEMEMBER = КУБЭЛЕМЕНТ ## Возвращает элемент или кортеж из куба. Используется для проверки существования элемента или кортежа в кубе. -CUBEMEMBERPROPERTY = КУБСВОЙСТВОЭЛЕМЕНТА ## Возвращает значение свойства элемента из куба. Используется для проверки существования имени элемента в кубе и возвращает указанное свойство для этого элемента. -CUBERANKEDMEMBER = КУБПОРЭЛЕМЕНТ ## Возвращает n-ый или ранжированный элемент в множество. Используется для возвращения одного или нескольких элементов в множество, например, лучшего продавца или 10 лучших студентов. -CUBESET = КУБМНОЖ ## Определяет вычислительное множество элементов или кортежей, отправляя на сервер выражение, которое создает множество, а затем возвращает его в Microsoft Office Excel. -CUBESETCOUNT = КУБЧИСЛОЭЛМНОЖ ## Возвращает число элементов множества. -CUBEVALUE = КУБЗНАЧЕНИЕ ## Возвращает обобщенное значение из куба. - +DAVERAGE = ДСРЗНАЧ +DCOUNT = БСЧЁТ +DCOUNTA = БСЧЁТА +DGET = БИЗВЛЕЧЬ +DMAX = ДМАКС +DMIN = ДМИН +DPRODUCT = БДПРОИЗВЕД +DSTDEV = ДСТАНДОТКЛ +DSTDEVP = ДСТАНДОТКЛП +DSUM = БДСУММ +DVAR = БДДИСП +DVARP = БДДИСПП ## -## Database functions Функции для работы с базами данных +## Функции даты и времени (Date & Time Functions) ## -DAVERAGE = ДСРЗНАЧ ## Возвращает среднее значение выбранных записей базы данных. -DCOUNT = БСЧЁТ ## Подсчитывает количество числовых ячеек в базе данных. -DCOUNTA = БСЧЁТА ## Подсчитывает количество непустых ячеек в базе данных. -DGET = БИЗВЛЕЧЬ ## Извлекает из базы данных одну запись, удовлетворяющую заданному условию. -DMAX = ДМАКС ## Возвращает максимальное значение среди выделенных записей базы данных. -DMIN = ДМИН ## Возвращает минимальное значение среди выделенных записей базы данных. -DPRODUCT = БДПРОИЗВЕД ## Перемножает значения определенного поля в записях базы данных, удовлетворяющих условию. -DSTDEV = ДСТАНДОТКЛ ## Оценивает стандартное отклонение по выборке для выделенных записей базы данных. -DSTDEVP = ДСТАНДОТКЛП ## Вычисляет стандартное отклонение по генеральной совокупности для выделенных записей базы данных -DSUM = БДСУММ ## Суммирует числа в поле для записей базы данных, удовлетворяющих условию. -DVAR = БДДИСП ## Оценивает дисперсию по выборке из выделенных записей базы данных -DVARP = БДДИСПП ## Вычисляет дисперсию по генеральной совокупности для выделенных записей базы данных - +DATE = ДАТА +DATEDIF = РАЗНДАТ +DATESTRING = СТРОКАДАННЫХ +DATEVALUE = ДАТАЗНАЧ +DAY = ДЕНЬ +DAYS = ДНИ +DAYS360 = ДНЕЙ360 +EDATE = ДАТАМЕС +EOMONTH = КОНМЕСЯЦА +HOUR = ЧАС +ISOWEEKNUM = НОМНЕДЕЛИ.ISO +MINUTE = МИНУТЫ +MONTH = МЕСЯЦ +NETWORKDAYS = ЧИСТРАБДНИ +NETWORKDAYS.INTL = ЧИСТРАБДНИ.МЕЖД +NOW = ТДАТА +SECOND = СЕКУНДЫ +THAIDAYOFWEEK = ТАЙДЕНЬНЕД +THAIMONTHOFYEAR = ТАЙМЕСЯЦ +THAIYEAR = ТАЙГОД +TIME = ВРЕМЯ +TIMEVALUE = ВРЕМЗНАЧ +TODAY = СЕГОДНЯ +WEEKDAY = ДЕНЬНЕД +WEEKNUM = НОМНЕДЕЛИ +WORKDAY = РАБДЕНЬ +WORKDAY.INTL = РАБДЕНЬ.МЕЖД +YEAR = ГОД +YEARFRAC = ДОЛЯГОДА ## -## Date and time functions Функции даты и времени +## Инженерные функции (Engineering Functions) ## -DATE = ДАТА ## Возвращает заданную дату в числовом формате. -DATEVALUE = ДАТАЗНАЧ ## Преобразует дату из текстового формата в числовой формат. -DAY = ДЕНЬ ## Преобразует дату в числовом формате в день месяца. -DAYS360 = ДНЕЙ360 ## Вычисляет количество дней между двумя датами на основе 360-дневного года. -EDATE = ДАТАМЕС ## Возвращает дату в числовом формате, отстоящую на заданное число месяцев вперед или назад от начальной даты. -EOMONTH = КОНМЕСЯЦА ## Возвращает дату в числовом формате для последнего дня месяца, отстоящего вперед или назад на заданное число месяцев. -HOUR = ЧАС ## Преобразует дату в числовом формате в часы. -MINUTE = МИНУТЫ ## Преобразует дату в числовом формате в минуты. -MONTH = МЕСЯЦ ## Преобразует дату в числовом формате в месяцы. -NETWORKDAYS = ЧИСТРАБДНИ ## Возвращает количество рабочих дней между двумя датами. -NOW = ТДАТА ## Возвращает текущую дату и время в числовом формате. -SECOND = СЕКУНДЫ ## Преобразует дату в числовом формате в секунды. -TIME = ВРЕМЯ ## Возвращает заданное время в числовом формате. -TIMEVALUE = ВРЕМЗНАЧ ## Преобразует время из текстового формата в числовой формат. -TODAY = СЕГОДНЯ ## Возвращает текущую дату в числовом формате. -WEEKDAY = ДЕНЬНЕД ## Преобразует дату в числовом формате в день недели. -WEEKNUM = НОМНЕДЕЛИ ## Преобразует числовое представление в число, которое указывает, на какую неделю года приходится указанная дата. -WORKDAY = РАБДЕНЬ ## Возвращает дату в числовом формате, отстоящую вперед или назад на заданное количество рабочих дней. -YEAR = ГОД ## Преобразует дату в числовом формате в год. -YEARFRAC = ДОЛЯГОДА ## Возвращает долю года, которую составляет количество дней между начальной и конечной датами. - +BESSELI = БЕССЕЛЬ.I +BESSELJ = БЕССЕЛЬ.J +BESSELK = БЕССЕЛЬ.K +BESSELY = БЕССЕЛЬ.Y +BIN2DEC = ДВ.В.ДЕС +BIN2HEX = ДВ.В.ШЕСТН +BIN2OCT = ДВ.В.ВОСЬМ +BITAND = БИТ.И +BITLSHIFT = БИТ.СДВИГЛ +BITOR = БИТ.ИЛИ +BITRSHIFT = БИТ.СДВИГП +BITXOR = БИТ.ИСКЛИЛИ +COMPLEX = КОМПЛЕКСН +CONVERT = ПРЕОБР +DEC2BIN = ДЕС.В.ДВ +DEC2HEX = ДЕС.В.ШЕСТН +DEC2OCT = ДЕС.В.ВОСЬМ +DELTA = ДЕЛЬТА +ERF = ФОШ +ERF.PRECISE = ФОШ.ТОЧН +ERFC = ДФОШ +ERFC.PRECISE = ДФОШ.ТОЧН +GESTEP = ПОРОГ +HEX2BIN = ШЕСТН.В.ДВ +HEX2DEC = ШЕСТН.В.ДЕС +HEX2OCT = ШЕСТН.В.ВОСЬМ +IMABS = МНИМ.ABS +IMAGINARY = МНИМ.ЧАСТЬ +IMARGUMENT = МНИМ.АРГУМЕНТ +IMCONJUGATE = МНИМ.СОПРЯЖ +IMCOS = МНИМ.COS +IMCOSH = МНИМ.COSH +IMCOT = МНИМ.COT +IMCSC = МНИМ.CSC +IMCSCH = МНИМ.CSCH +IMDIV = МНИМ.ДЕЛ +IMEXP = МНИМ.EXP +IMLN = МНИМ.LN +IMLOG10 = МНИМ.LOG10 +IMLOG2 = МНИМ.LOG2 +IMPOWER = МНИМ.СТЕПЕНЬ +IMPRODUCT = МНИМ.ПРОИЗВЕД +IMREAL = МНИМ.ВЕЩ +IMSEC = МНИМ.SEC +IMSECH = МНИМ.SECH +IMSIN = МНИМ.SIN +IMSINH = МНИМ.SINH +IMSQRT = МНИМ.КОРЕНЬ +IMSUB = МНИМ.РАЗН +IMSUM = МНИМ.СУММ +IMTAN = МНИМ.TAN +OCT2BIN = ВОСЬМ.В.ДВ +OCT2DEC = ВОСЬМ.В.ДЕС +OCT2HEX = ВОСЬМ.В.ШЕСТН ## -## Engineering functions Инженерные функции +## Финансовые функции (Financial Functions) ## -BESSELI = БЕССЕЛЬ.I ## Возвращает модифицированную функцию Бесселя In(x). -BESSELJ = БЕССЕЛЬ.J ## Возвращает функцию Бесселя Jn(x). -BESSELK = БЕССЕЛЬ.K ## Возвращает модифицированную функцию Бесселя Kn(x). -BESSELY = БЕССЕЛЬ.Y ## Возвращает функцию Бесселя Yn(x). -BIN2DEC = ДВ.В.ДЕС ## Преобразует двоичное число в десятичное. -BIN2HEX = ДВ.В.ШЕСТН ## Преобразует двоичное число в шестнадцатеричное. -BIN2OCT = ДВ.В.ВОСЬМ ## Преобразует двоичное число в восьмеричное. -COMPLEX = КОМПЛЕКСН ## Преобразует коэффициенты при вещественной и мнимой частях комплексного числа в комплексное число. -CONVERT = ПРЕОБР ## Преобразует число из одной системы единиц измерения в другую. -DEC2BIN = ДЕС.В.ДВ ## Преобразует десятичное число в двоичное. -DEC2HEX = ДЕС.В.ШЕСТН ## Преобразует десятичное число в шестнадцатеричное. -DEC2OCT = ДЕС.В.ВОСЬМ ## Преобразует десятичное число в восьмеричное. -DELTA = ДЕЛЬТА ## Проверяет равенство двух значений. -ERF = ФОШ ## Возвращает функцию ошибки. -ERFC = ДФОШ ## Возвращает дополнительную функцию ошибки. -GESTEP = ПОРОГ ## Проверяет, не превышает ли данное число порогового значения. -HEX2BIN = ШЕСТН.В.ДВ ## Преобразует шестнадцатеричное число в двоичное. -HEX2DEC = ШЕСТН.В.ДЕС ## Преобразует шестнадцатеричное число в десятичное. -HEX2OCT = ШЕСТН.В.ВОСЬМ ## Преобразует шестнадцатеричное число в восьмеричное. -IMABS = МНИМ.ABS ## Возвращает абсолютную величину (модуль) комплексного числа. -IMAGINARY = МНИМ.ЧАСТЬ ## Возвращает коэффициент при мнимой части комплексного числа. -IMARGUMENT = МНИМ.АРГУМЕНТ ## Возвращает значение аргумента комплексного числа (тета) — угол, выраженный в радианах. -IMCONJUGATE = МНИМ.СОПРЯЖ ## Возвращает комплексно-сопряженное комплексное число. -IMCOS = МНИМ.COS ## Возвращает косинус комплексного числа. -IMDIV = МНИМ.ДЕЛ ## Возвращает частное от деления двух комплексных чисел. -IMEXP = МНИМ.EXP ## Возвращает экспоненту комплексного числа. -IMLN = МНИМ.LN ## Возвращает натуральный логарифм комплексного числа. -IMLOG10 = МНИМ.LOG10 ## Возвращает обычный (десятичный) логарифм комплексного числа. -IMLOG2 = МНИМ.LOG2 ## Возвращает двоичный логарифм комплексного числа. -IMPOWER = МНИМ.СТЕПЕНЬ ## Возвращает комплексное число, возведенное в целую степень. -IMPRODUCT = МНИМ.ПРОИЗВЕД ## Возвращает произведение от 2 до 29 комплексных чисел. -IMREAL = МНИМ.ВЕЩ ## Возвращает коэффициент при вещественной части комплексного числа. -IMSIN = МНИМ.SIN ## Возвращает синус комплексного числа. -IMSQRT = МНИМ.КОРЕНЬ ## Возвращает значение квадратного корня из комплексного числа. -IMSUB = МНИМ.РАЗН ## Возвращает разность двух комплексных чисел. -IMSUM = МНИМ.СУММ ## Возвращает сумму комплексных чисел. -OCT2BIN = ВОСЬМ.В.ДВ ## Преобразует восьмеричное число в двоичное. -OCT2DEC = ВОСЬМ.В.ДЕС ## Преобразует восьмеричное число в десятичное. -OCT2HEX = ВОСЬМ.В.ШЕСТН ## Преобразует восьмеричное число в шестнадцатеричное. - +ACCRINT = НАКОПДОХОД +ACCRINTM = НАКОПДОХОДПОГАШ +AMORDEGRC = АМОРУМ +AMORLINC = АМОРУВ +COUPDAYBS = ДНЕЙКУПОНДО +COUPDAYS = ДНЕЙКУПОН +COUPDAYSNC = ДНЕЙКУПОНПОСЛЕ +COUPNCD = ДАТАКУПОНПОСЛЕ +COUPNUM = ЧИСЛКУПОН +COUPPCD = ДАТАКУПОНДО +CUMIPMT = ОБЩПЛАТ +CUMPRINC = ОБЩДОХОД +DB = ФУО +DDB = ДДОБ +DISC = СКИДКА +DOLLARDE = РУБЛЬ.ДЕС +DOLLARFR = РУБЛЬ.ДРОБЬ +DURATION = ДЛИТ +EFFECT = ЭФФЕКТ +FV = БС +FVSCHEDULE = БЗРАСПИС +INTRATE = ИНОРМА +IPMT = ПРПЛТ +IRR = ВСД +ISPMT = ПРОЦПЛАТ +MDURATION = МДЛИТ +MIRR = МВСД +NOMINAL = НОМИНАЛ +NPER = КПЕР +NPV = ЧПС +ODDFPRICE = ЦЕНАПЕРВНЕРЕГ +ODDFYIELD = ДОХОДПЕРВНЕРЕГ +ODDLPRICE = ЦЕНАПОСЛНЕРЕГ +ODDLYIELD = ДОХОДПОСЛНЕРЕГ +PDURATION = ПДЛИТ +PMT = ПЛТ +PPMT = ОСПЛТ +PRICE = ЦЕНА +PRICEDISC = ЦЕНАСКИДКА +PRICEMAT = ЦЕНАПОГАШ +PV = ПС +RATE = СТАВКА +RECEIVED = ПОЛУЧЕНО +RRI = ЭКВ.СТАВКА +SLN = АПЛ +SYD = АСЧ +TBILLEQ = РАВНОКЧЕК +TBILLPRICE = ЦЕНАКЧЕК +TBILLYIELD = ДОХОДКЧЕК +USDOLLAR = ДОЛЛСША +VDB = ПУО +XIRR = ЧИСТВНДОХ +XNPV = ЧИСТНЗ +YIELD = ДОХОД +YIELDDISC = ДОХОДСКИДКА +YIELDMAT = ДОХОДПОГАШ ## -## Financial functions Финансовые функции +## Информационные функции (Information Functions) ## -ACCRINT = НАКОПДОХОД ## Возвращает накопленный процент по ценным бумагам с периодической выплатой процентов. -ACCRINTM = НАКОПДОХОДПОГАШ ## Возвращает накопленный процент по ценным бумагам, проценты по которым выплачиваются в срок погашения. -AMORDEGRC = АМОРУМ ## Возвращает величину амортизации для каждого периода, используя коэффициент амортизации. -AMORLINC = АМОРУВ ## Возвращает величину амортизации для каждого периода. -COUPDAYBS = ДНЕЙКУПОНДО ## Возвращает количество дней от начала действия купона до даты соглашения. -COUPDAYS = ДНЕЙКУПОН ## Возвращает число дней в периоде купона, содержащем дату соглашения. -COUPDAYSNC = ДНЕЙКУПОНПОСЛЕ ## Возвращает число дней от даты соглашения до срока следующего купона. -COUPNCD = ДАТАКУПОНПОСЛЕ ## Возвращает следующую дату купона после даты соглашения. -COUPNUM = ЧИСЛКУПОН ## Возвращает количество купонов, которые могут быть оплачены между датой соглашения и сроком вступления в силу. -COUPPCD = ДАТАКУПОНДО ## Возвращает предыдущую дату купона перед датой соглашения. -CUMIPMT = ОБЩПЛАТ ## Возвращает общую выплату, произведенную между двумя периодическими выплатами. -CUMPRINC = ОБЩДОХОД ## Возвращает общую выплату по займу между двумя периодами. -DB = ФУО ## Возвращает величину амортизации актива для заданного периода, рассчитанную методом фиксированного уменьшения остатка. -DDB = ДДОБ ## Возвращает величину амортизации актива за данный период, используя метод двойного уменьшения остатка или иной явно указанный метод. -DISC = СКИДКА ## Возвращает норму скидки для ценных бумаг. -DOLLARDE = РУБЛЬ.ДЕС ## Преобразует цену в рублях, выраженную в виде дроби, в цену в рублях, выраженную десятичным числом. -DOLLARFR = РУБЛЬ.ДРОБЬ ## Преобразует цену в рублях, выраженную десятичным числом, в цену в рублях, выраженную в виде дроби. -DURATION = ДЛИТ ## Возвращает ежегодную продолжительность действия ценных бумаг с периодическими выплатами по процентам. -EFFECT = ЭФФЕКТ ## Возвращает действующие ежегодные процентные ставки. -FV = БС ## Возвращает будущую стоимость инвестиции. -FVSCHEDULE = БЗРАСПИС ## Возвращает будущую стоимость первоначальной основной суммы после начисления ряда сложных процентов. -INTRATE = ИНОРМА ## Возвращает процентную ставку для полностью инвестированных ценных бумаг. -IPMT = ПРПЛТ ## Возвращает величину выплаты прибыли на вложения за данный период. -IRR = ВСД ## Возвращает внутреннюю ставку доходности для ряда потоков денежных средств. -ISPMT = ПРОЦПЛАТ ## Вычисляет выплаты за указанный период инвестиции. -MDURATION = МДЛИТ ## Возвращает модифицированную длительность Маколея для ценных бумаг с предполагаемой номинальной стоимостью 100 рублей. -MIRR = МВСД ## Возвращает внутреннюю ставку доходности, при которой положительные и отрицательные денежные потоки имеют разные значения ставки. -NOMINAL = НОМИНАЛ ## Возвращает номинальную годовую процентную ставку. -NPER = КПЕР ## Возвращает общее количество периодов выплаты для данного вклада. -NPV = ЧПС ## Возвращает чистую приведенную стоимость инвестиции, основанной на серии периодических денежных потоков и ставке дисконтирования. -ODDFPRICE = ЦЕНАПЕРВНЕРЕГ ## Возвращает цену за 100 рублей нарицательной стоимости ценных бумаг с нерегулярным первым периодом. -ODDFYIELD = ДОХОДПЕРВНЕРЕГ ## Возвращает доход по ценным бумагам с нерегулярным первым периодом. -ODDLPRICE = ЦЕНАПОСЛНЕРЕГ ## Возвращает цену за 100 рублей нарицательной стоимости ценных бумаг с нерегулярным последним периодом. -ODDLYIELD = ДОХОДПОСЛНЕРЕГ ## Возвращает доход по ценным бумагам с нерегулярным последним периодом. -PMT = ПЛТ ## Возвращает величину выплаты за один период аннуитета. -PPMT = ОСПЛТ ## Возвращает величину выплат в погашение основной суммы по инвестиции за заданный период. -PRICE = ЦЕНА ## Возвращает цену за 100 рублей нарицательной стоимости ценных бумаг, по которым производится периодическая выплата процентов. -PRICEDISC = ЦЕНАСКИДКА ## Возвращает цену за 100 рублей номинальной стоимости ценных бумаг, на которые сделана скидка. -PRICEMAT = ЦЕНАПОГАШ ## Возвращает цену за 100 рублей номинальной стоимости ценных бумаг, проценты по которым выплачиваются в срок погашения. -PV = ПС ## Возвращает приведенную (к текущему моменту) стоимость инвестиции. -RATE = СТАВКА ## Возвращает процентную ставку по аннуитету за один период. -RECEIVED = ПОЛУЧЕНО ## Возвращает сумму, полученную к сроку погашения полностью обеспеченных ценных бумаг. -SLN = АПЛ ## Возвращает величину линейной амортизации актива за один период. -SYD = АСЧ ## Возвращает величину амортизации актива за данный период, рассчитанную методом суммы годовых чисел. -TBILLEQ = РАВНОКЧЕК ## Возвращает эквивалентный облигации доход по казначейскому чеку. -TBILLPRICE = ЦЕНАКЧЕК ## Возвращает цену за 100 рублей нарицательной стоимости для казначейского чека. -TBILLYIELD = ДОХОДКЧЕК ## Возвращает доход по казначейскому чеку. -VDB = ПУО ## Возвращает величину амортизации актива для указанного или частичного периода при использовании метода сокращающегося баланса. -XIRR = ЧИСТВНДОХ ## Возвращает внутреннюю ставку доходности для графика денежных потоков, которые не обязательно носят периодический характер. -XNPV = ЧИСТНЗ ## Возвращает чистую приведенную стоимость для денежных потоков, которые не обязательно являются периодическими. -YIELD = ДОХОД ## Возвращает доход от ценных бумаг, по которым производятся периодические выплаты процентов. -YIELDDISC = ДОХОДСКИДКА ## Возвращает годовой доход по ценным бумагам, на которые сделана скидка (пример — казначейские чеки). -YIELDMAT = ДОХОДПОГАШ ## Возвращает годовой доход от ценных бумаг, проценты по которым выплачиваются в срок погашения. - +CELL = ЯЧЕЙКА +ERROR.TYPE = ТИП.ОШИБКИ +INFO = ИНФОРМ +ISBLANK = ЕПУСТО +ISERR = ЕОШ +ISERROR = ЕОШИБКА +ISEVEN = ЕЧЁТН +ISFORMULA = ЕФОРМУЛА +ISLOGICAL = ЕЛОГИЧ +ISNA = ЕНД +ISNONTEXT = ЕНЕТЕКСТ +ISNUMBER = ЕЧИСЛО +ISODD = ЕНЕЧЁТ +ISREF = ЕССЫЛКА +ISTEXT = ЕТЕКСТ +N = Ч +NA = НД +SHEET = ЛИСТ +SHEETS = ЛИСТЫ +TYPE = ТИП ## -## Information functions Информационные функции +## Логические функции (Logical Functions) ## -CELL = ЯЧЕЙКА ## Возвращает информацию о формате, расположении или содержимом ячейки. -ERROR.TYPE = ТИП.ОШИБКИ ## Возвращает числовой код, соответствующий типу ошибки. -INFO = ИНФОРМ ## Возвращает информацию о текущей операционной среде. -ISBLANK = ЕПУСТО ## Возвращает значение ИСТИНА, если аргумент является ссылкой на пустую ячейку. -ISERR = ЕОШ ## Возвращает значение ИСТИНА, если аргумент ссылается на любое значение ошибки, кроме #Н/Д. -ISERROR = ЕОШИБКА ## Возвращает значение ИСТИНА, если аргумент ссылается на любое значение ошибки. -ISEVEN = ЕЧЁТН ## Возвращает значение ИСТИНА, если значение аргумента является четным числом. -ISLOGICAL = ЕЛОГИЧ ## Возвращает значение ИСТИНА, если аргумент ссылается на логическое значение. -ISNA = ЕНД ## Возвращает значение ИСТИНА, если аргумент ссылается на значение ошибки #Н/Д. -ISNONTEXT = ЕНЕТЕКСТ ## Возвращает значение ИСТИНА, если значение аргумента не является текстом. -ISNUMBER = ЕЧИСЛО ## Возвращает значение ИСТИНА, если аргумент ссылается на число. -ISODD = ЕНЕЧЁТ ## Возвращает значение ИСТИНА, если значение аргумента является нечетным числом. -ISREF = ЕССЫЛКА ## Возвращает значение ИСТИНА, если значение аргумента является ссылкой. -ISTEXT = ЕТЕКСТ ## Возвращает значение ИСТИНА, если значение аргумента является текстом. -N = Ч ## Возвращает значение, преобразованное в число. -NA = НД ## Возвращает значение ошибки #Н/Д. -TYPE = ТИП ## Возвращает число, обозначающее тип данных значения. - +AND = И +FALSE = ЛОЖЬ +IF = ЕСЛИ +IFERROR = ЕСЛИОШИБКА +IFNA = ЕСНД +IFS = УСЛОВИЯ +NOT = НЕ +OR = ИЛИ +SWITCH = ПЕРЕКЛЮЧ +TRUE = ИСТИНА +XOR = ИСКЛИЛИ ## -## Logical functions Логические функции +## Функции ссылки и поиска (Lookup & Reference Functions) ## -AND = И ## Renvoie VRAI si tous ses arguments sont VRAI. -FALSE = ЛОЖЬ ## Возвращает логическое значение ЛОЖЬ. -IF = ЕСЛИ ## Выполняет проверку условия. -IFERROR = ЕСЛИОШИБКА ## Возвращает введённое значение, если вычисление по формуле вызывает ошибку; в противном случае функция возвращает результат вычисления. -NOT = НЕ ## Меняет логическое значение своего аргумента на противоположное. -OR = ИЛИ ## Возвращает значение ИСТИНА, если хотя бы один аргумент имеет значение ИСТИНА. -TRUE = ИСТИНА ## Возвращает логическое значение ИСТИНА. - +ADDRESS = АДРЕС +AREAS = ОБЛАСТИ +CHOOSE = ВЫБОР +COLUMN = СТОЛБЕЦ +COLUMNS = ЧИСЛСТОЛБ +FILTER = ФИЛЬТР +FORMULATEXT = Ф.ТЕКСТ +GETPIVOTDATA = ПОЛУЧИТЬ.ДАННЫЕ.СВОДНОЙ.ТАБЛИЦЫ +HLOOKUP = ГПР +HYPERLINK = ГИПЕРССЫЛКА +INDEX = ИНДЕКС +INDIRECT = ДВССЫЛ +LOOKUP = ПРОСМОТР +MATCH = ПОИСКПОЗ +OFFSET = СМЕЩ +ROW = СТРОКА +ROWS = ЧСТРОК +RTD = ДРВ +SORT = СОРТ +SORTBY = СОРТПО +TRANSPOSE = ТРАНСП +UNIQUE = УНИК +VLOOKUP = ВПР +XLOOKUP = ПРОСМОТРX +XMATCH = ПОИСКПОЗX ## -## Lookup and reference functions Функции ссылки и поиска +## Математические и тригонометрические функции (Math & Trig Functions) ## -ADDRESS = АДРЕС ## Возвращает ссылку на отдельную ячейку листа в виде текста. -AREAS = ОБЛАСТИ ## Возвращает количество областей в ссылке. -CHOOSE = ВЫБОР ## Выбирает значение из списка значений по индексу. -COLUMN = СТОЛБЕЦ ## Возвращает номер столбца, на который указывает ссылка. -COLUMNS = ЧИСЛСТОЛБ ## Возвращает количество столбцов в ссылке. -HLOOKUP = ГПР ## Ищет в первой строке массива и возвращает значение отмеченной ячейки -HYPERLINK = ГИПЕРССЫЛКА ## Создает ссылку, открывающую документ, который находится на сервере сети, в интрасети или в Интернете. -INDEX = ИНДЕКС ## Использует индекс для выбора значения из ссылки или массива. -INDIRECT = ДВССЫЛ ## Возвращает ссылку, заданную текстовым значением. -LOOKUP = ПРОСМОТР ## Ищет значения в векторе или массиве. -MATCH = ПОИСКПОЗ ## Ищет значения в ссылке или массиве. -OFFSET = СМЕЩ ## Возвращает смещение ссылки относительно заданной ссылки. -ROW = СТРОКА ## Возвращает номер строки, определяемой ссылкой. -ROWS = ЧСТРОК ## Возвращает количество строк в ссылке. -RTD = ДРВ ## Извлекает данные реального времени из программ, поддерживающих автоматизацию COM (Программирование объектов. Стандартное средство для работы с объектами некоторого приложения из другого приложения или средства разработки. Программирование объектов (ранее называемое программированием OLE) является функцией модели COM (Component Object Model, модель компонентных объектов).). -TRANSPOSE = ТРАНСП ## Возвращает транспонированный массив. -VLOOKUP = ВПР ## Ищет значение в первом столбце массива и возвращает значение из ячейки в найденной строке и указанном столбце. - +ABS = ABS +ACOS = ACOS +ACOSH = ACOSH +ACOT = ACOT +ACOTH = ACOTH +AGGREGATE = АГРЕГАТ +ARABIC = АРАБСКОЕ +ASIN = ASIN +ASINH = ASINH +ATAN = ATAN +ATAN2 = ATAN2 +ATANH = ATANH +BASE = ОСНОВАНИЕ +CEILING.MATH = ОКРВВЕРХ.МАТ +CEILING.PRECISE = ОКРВВЕРХ.ТОЧН +COMBIN = ЧИСЛКОМБ +COMBINA = ЧИСЛКОМБА +COS = COS +COSH = COSH +COT = COT +COTH = COTH +CSC = CSC +CSCH = CSCH +DECIMAL = ДЕС +DEGREES = ГРАДУСЫ +ECMA.CEILING = ECMA.ОКРВВЕРХ +EVEN = ЧЁТН +EXP = EXP +FACT = ФАКТР +FACTDOUBLE = ДВФАКТР +FLOOR.MATH = ОКРВНИЗ.МАТ +FLOOR.PRECISE = ОКРВНИЗ.ТОЧН +GCD = НОД +INT = ЦЕЛОЕ +ISO.CEILING = ISO.ОКРВВЕРХ +LCM = НОК +LN = LN +LOG = LOG +LOG10 = LOG10 +MDETERM = МОПРЕД +MINVERSE = МОБР +MMULT = МУМНОЖ +MOD = ОСТАТ +MROUND = ОКРУГЛТ +MULTINOMIAL = МУЛЬТИНОМ +MUNIT = МЕДИН +ODD = НЕЧЁТ +PI = ПИ +POWER = СТЕПЕНЬ +PRODUCT = ПРОИЗВЕД +QUOTIENT = ЧАСТНОЕ +RADIANS = РАДИАНЫ +RAND = СЛЧИС +RANDARRAY = СЛУЧМАССИВ +RANDBETWEEN = СЛУЧМЕЖДУ +ROMAN = РИМСКОЕ +ROUND = ОКРУГЛ +ROUNDBAHTDOWN = ОКРУГЛБАТВНИЗ +ROUNDBAHTUP = ОКРУГЛБАТВВЕРХ +ROUNDDOWN = ОКРУГЛВНИЗ +ROUNDUP = ОКРУГЛВВЕРХ +SEC = SEC +SECH = SECH +SERIESSUM = РЯД.СУММ +SEQUENCE = ПОСЛЕДОВ +SIGN = ЗНАК +SIN = SIN +SINH = SINH +SQRT = КОРЕНЬ +SQRTPI = КОРЕНЬПИ +SUBTOTAL = ПРОМЕЖУТОЧНЫЕ.ИТОГИ +SUM = СУММ +SUMIF = СУММЕСЛИ +SUMIFS = СУММЕСЛИМН +SUMPRODUCT = СУММПРОИЗВ +SUMSQ = СУММКВ +SUMX2MY2 = СУММРАЗНКВ +SUMX2PY2 = СУММСУММКВ +SUMXMY2 = СУММКВРАЗН +TAN = TAN +TANH = TANH +TRUNC = ОТБР ## -## Math and trigonometry functions Математические и тригонометрические функции +## Статистические функции (Statistical Functions) ## -ABS = ABS ## Возвращает модуль (абсолютную величину) числа. -ACOS = ACOS ## Возвращает арккосинус числа. -ACOSH = ACOSH ## Возвращает гиперболический арккосинус числа. -ASIN = ASIN ## Возвращает арксинус числа. -ASINH = ASINH ## Возвращает гиперболический арксинус числа. -ATAN = ATAN ## Возвращает арктангенс числа. -ATAN2 = ATAN2 ## Возвращает арктангенс для заданных координат x и y. -ATANH = ATANH ## Возвращает гиперболический арктангенс числа. -CEILING = ОКРВВЕРХ ## Округляет число до ближайшего целого или до ближайшего кратного указанному значению. -COMBIN = ЧИСЛКОМБ ## Возвращает количество комбинаций для заданного числа объектов. -COS = COS ## Возвращает косинус числа. -COSH = COSH ## Возвращает гиперболический косинус числа. -DEGREES = ГРАДУСЫ ## Преобразует радианы в градусы. -EVEN = ЧЁТН ## Округляет число до ближайшего четного целого. -EXP = EXP ## Возвращает число e, возведенное в указанную степень. -FACT = ФАКТР ## Возвращает факториал числа. -FACTDOUBLE = ДВФАКТР ## Возвращает двойной факториал числа. -FLOOR = ОКРВНИЗ ## Округляет число до ближайшего меньшего по модулю значения. -GCD = НОД ## Возвращает наибольший общий делитель. -INT = ЦЕЛОЕ ## Округляет число до ближайшего меньшего целого. -LCM = НОК ## Возвращает наименьшее общее кратное. -LN = LN ## Возвращает натуральный логарифм числа. -LOG = LOG ## Возвращает логарифм числа по заданному основанию. -LOG10 = LOG10 ## Возвращает десятичный логарифм числа. -MDETERM = МОПРЕД ## Возвращает определитель матрицы массива. -MINVERSE = МОБР ## Возвращает обратную матрицу массива. -MMULT = МУМНОЖ ## Возвращает произведение матриц двух массивов. -MOD = ОСТАТ ## Возвращает остаток от деления. -MROUND = ОКРУГЛТ ## Возвращает число, округленное с требуемой точностью. -MULTINOMIAL = МУЛЬТИНОМ ## Возвращает мультиномиальный коэффициент множества чисел. -ODD = НЕЧЁТ ## Округляет число до ближайшего нечетного целого. -PI = ПИ ## Возвращает число пи. -POWER = СТЕПЕНЬ ## Возвращает результат возведения числа в степень. -PRODUCT = ПРОИЗВЕД ## Возвращает произведение аргументов. -QUOTIENT = ЧАСТНОЕ ## Возвращает целую часть частного при делении. -RADIANS = РАДИАНЫ ## Преобразует градусы в радианы. -RAND = СЛЧИС ## Возвращает случайное число в интервале от 0 до 1. -RANDBETWEEN = СЛУЧМЕЖДУ ## Возвращает случайное число в интервале между двумя заданными числами. -ROMAN = РИМСКОЕ ## Преобразует арабские цифры в римские в виде текста. -ROUND = ОКРУГЛ ## Округляет число до указанного количества десятичных разрядов. -ROUNDDOWN = ОКРУГЛВНИЗ ## Округляет число до ближайшего меньшего по модулю значения. -ROUNDUP = ОКРУГЛВВЕРХ ## Округляет число до ближайшего большего по модулю значения. -SERIESSUM = РЯД.СУММ ## Возвращает сумму степенного ряда, вычисленную по формуле. -SIGN = ЗНАК ## Возвращает знак числа. -SIN = SIN ## Возвращает синус заданного угла. -SINH = SINH ## Возвращает гиперболический синус числа. -SQRT = КОРЕНЬ ## Возвращает положительное значение квадратного корня. -SQRTPI = КОРЕНЬПИ ## Возвращает квадратный корень из значения выражения (число * ПИ). -SUBTOTAL = ПРОМЕЖУТОЧНЫЕ.ИТОГИ ## Возвращает промежуточный итог в списке или базе данных. -SUM = СУММ ## Суммирует аргументы. -SUMIF = СУММЕСЛИ ## Суммирует ячейки, удовлетворяющие заданному условию. -SUMIFS = СУММЕСЛИМН ## Суммирует диапазон ячеек, удовлетворяющих нескольким условиям. -SUMPRODUCT = СУММПРОИЗВ ## Возвращает сумму произведений соответствующих элементов массивов. -SUMSQ = СУММКВ ## Возвращает сумму квадратов аргументов. -SUMX2MY2 = СУММРАЗНКВ ## Возвращает сумму разностей квадратов соответствующих значений в двух массивах. -SUMX2PY2 = СУММСУММКВ ## Возвращает сумму сумм квадратов соответствующих элементов двух массивов. -SUMXMY2 = СУММКВРАЗН ## Возвращает сумму квадратов разностей соответствующих значений в двух массивах. -TAN = TAN ## Возвращает тангенс числа. -TANH = TANH ## Возвращает гиперболический тангенс числа. -TRUNC = ОТБР ## Отбрасывает дробную часть числа. - +AVEDEV = СРОТКЛ +AVERAGE = СРЗНАЧ +AVERAGEA = СРЗНАЧА +AVERAGEIF = СРЗНАЧЕСЛИ +AVERAGEIFS = СРЗНАЧЕСЛИМН +BETA.DIST = БЕТА.РАСП +BETA.INV = БЕТА.ОБР +BINOM.DIST = БИНОМ.РАСП +BINOM.DIST.RANGE = БИНОМ.РАСП.ДИАП +BINOM.INV = БИНОМ.ОБР +CHISQ.DIST = ХИ2.РАСП +CHISQ.DIST.RT = ХИ2.РАСП.ПХ +CHISQ.INV = ХИ2.ОБР +CHISQ.INV.RT = ХИ2.ОБР.ПХ +CHISQ.TEST = ХИ2.ТЕСТ +CONFIDENCE.NORM = ДОВЕРИТ.НОРМ +CONFIDENCE.T = ДОВЕРИТ.СТЬЮДЕНТ +CORREL = КОРРЕЛ +COUNT = СЧЁТ +COUNTA = СЧЁТЗ +COUNTBLANK = СЧИТАТЬПУСТОТЫ +COUNTIF = СЧЁТЕСЛИ +COUNTIFS = СЧЁТЕСЛИМН +COVARIANCE.P = КОВАРИАЦИЯ.Г +COVARIANCE.S = КОВАРИАЦИЯ.В +DEVSQ = КВАДРОТКЛ +EXPON.DIST = ЭКСП.РАСП +F.DIST = F.РАСП +F.DIST.RT = F.РАСП.ПХ +F.INV = F.ОБР +F.INV.RT = F.ОБР.ПХ +F.TEST = F.ТЕСТ +FISHER = ФИШЕР +FISHERINV = ФИШЕРОБР +FORECAST.ETS = ПРЕДСКАЗ.ETS +FORECAST.ETS.CONFINT = ПРЕДСКАЗ.ЕTS.ДОВИНТЕРВАЛ +FORECAST.ETS.SEASONALITY = ПРЕДСКАЗ.ETS.СЕЗОННОСТЬ +FORECAST.ETS.STAT = ПРЕДСКАЗ.ETS.СТАТ +FORECAST.LINEAR = ПРЕДСКАЗ.ЛИНЕЙН +FREQUENCY = ЧАСТОТА +GAMMA = ГАММА +GAMMA.DIST = ГАММА.РАСП +GAMMA.INV = ГАММА.ОБР +GAMMALN = ГАММАНЛОГ +GAMMALN.PRECISE = ГАММАНЛОГ.ТОЧН +GAUSS = ГАУСС +GEOMEAN = СРГЕОМ +GROWTH = РОСТ +HARMEAN = СРГАРМ +HYPGEOM.DIST = ГИПЕРГЕОМ.РАСП +INTERCEPT = ОТРЕЗОК +KURT = ЭКСЦЕСС +LARGE = НАИБОЛЬШИЙ +LINEST = ЛИНЕЙН +LOGEST = ЛГРФПРИБЛ +LOGNORM.DIST = ЛОГНОРМ.РАСП +LOGNORM.INV = ЛОГНОРМ.ОБР +MAX = МАКС +MAXA = МАКСА +MAXIFS = МАКСЕСЛИ +MEDIAN = МЕДИАНА +MIN = МИН +MINA = МИНА +MINIFS = МИНЕСЛИ +MODE.MULT = МОДА.НСК +MODE.SNGL = МОДА.ОДН +NEGBINOM.DIST = ОТРБИНОМ.РАСП +NORM.DIST = НОРМ.РАСП +NORM.INV = НОРМ.ОБР +NORM.S.DIST = НОРМ.СТ.РАСП +NORM.S.INV = НОРМ.СТ.ОБР +PEARSON = PEARSON +PERCENTILE.EXC = ПРОЦЕНТИЛЬ.ИСКЛ +PERCENTILE.INC = ПРОЦЕНТИЛЬ.ВКЛ +PERCENTRANK.EXC = ПРОЦЕНТРАНГ.ИСКЛ +PERCENTRANK.INC = ПРОЦЕНТРАНГ.ВКЛ +PERMUT = ПЕРЕСТ +PERMUTATIONA = ПЕРЕСТА +PHI = ФИ +POISSON.DIST = ПУАССОН.РАСП +PROB = ВЕРОЯТНОСТЬ +QUARTILE.EXC = КВАРТИЛЬ.ИСКЛ +QUARTILE.INC = КВАРТИЛЬ.ВКЛ +RANK.AVG = РАНГ.СР +RANK.EQ = РАНГ.РВ +RSQ = КВПИРСОН +SKEW = СКОС +SKEW.P = СКОС.Г +SLOPE = НАКЛОН +SMALL = НАИМЕНЬШИЙ +STANDARDIZE = НОРМАЛИЗАЦИЯ +STDEV.P = СТАНДОТКЛОН.Г +STDEV.S = СТАНДОТКЛОН.В +STDEVA = СТАНДОТКЛОНА +STDEVPA = СТАНДОТКЛОНПА +STEYX = СТОШYX +T.DIST = СТЬЮДЕНТ.РАСП +T.DIST.2T = СТЬЮДЕНТ.РАСП.2Х +T.DIST.RT = СТЬЮДЕНТ.РАСП.ПХ +T.INV = СТЬЮДЕНТ.ОБР +T.INV.2T = СТЬЮДЕНТ.ОБР.2Х +T.TEST = СТЬЮДЕНТ.ТЕСТ +TREND = ТЕНДЕНЦИЯ +TRIMMEAN = УРЕЗСРЕДНЕЕ +VAR.P = ДИСП.Г +VAR.S = ДИСП.В +VARA = ДИСПА +VARPA = ДИСПРА +WEIBULL.DIST = ВЕЙБУЛЛ.РАСП +Z.TEST = Z.ТЕСТ ## -## Statistical functions Статистические функции +## Текстовые функции (Text Functions) ## -AVEDEV = СРОТКЛ ## Возвращает среднее арифметическое абсолютных значений отклонений точек данных от среднего. -AVERAGE = СРЗНАЧ ## Возвращает среднее арифметическое аргументов. -AVERAGEA = СРЗНАЧА ## Возвращает среднее арифметическое аргументов, включая числа, текст и логические значения. -AVERAGEIF = СРЗНАЧЕСЛИ ## Возвращает среднее значение (среднее арифметическое) всех ячеек в диапазоне, которые удовлетворяют данному условию. -AVERAGEIFS = СРЗНАЧЕСЛИМН ## Возвращает среднее значение (среднее арифметическое) всех ячеек, которые удовлетворяют нескольким условиям. -BETADIST = БЕТАРАСП ## Возвращает интегральную функцию бета-распределения. -BETAINV = БЕТАОБР ## Возвращает обратную интегральную функцию указанного бета-распределения. -BINOMDIST = БИНОМРАСП ## Возвращает отдельное значение биномиального распределения. -CHIDIST = ХИ2РАСП ## Возвращает одностороннюю вероятность распределения хи-квадрат. -CHIINV = ХИ2ОБР ## Возвращает обратное значение односторонней вероятности распределения хи-квадрат. -CHITEST = ХИ2ТЕСТ ## Возвращает тест на независимость. -CONFIDENCE = ДОВЕРИТ ## Возвращает доверительный интервал для среднего значения по генеральной совокупности. -CORREL = КОРРЕЛ ## Возвращает коэффициент корреляции между двумя множествами данных. -COUNT = СЧЁТ ## Подсчитывает количество чисел в списке аргументов. -COUNTA = СЧЁТЗ ## Подсчитывает количество значений в списке аргументов. -COUNTBLANK = СЧИТАТЬПУСТОТЫ ## Подсчитывает количество пустых ячеек в диапазоне -COUNTIF = СЧЁТЕСЛИ ## Подсчитывает количество ячеек в диапазоне, удовлетворяющих заданному условию -COUNTIFS = СЧЁТЕСЛИМН ## Подсчитывает количество ячеек внутри диапазона, удовлетворяющих нескольким условиям. -COVAR = КОВАР ## Возвращает ковариацию, среднее произведений парных отклонений -CRITBINOM = КРИТБИНОМ ## Возвращает наименьшее значение, для которого интегральное биномиальное распределение меньше или равно заданному критерию. -DEVSQ = КВАДРОТКЛ ## Возвращает сумму квадратов отклонений. -EXPONDIST = ЭКСПРАСП ## Возвращает экспоненциальное распределение. -FDIST = FРАСП ## Возвращает F-распределение вероятности. -FINV = FРАСПОБР ## Возвращает обратное значение для F-распределения вероятности. -FISHER = ФИШЕР ## Возвращает преобразование Фишера. -FISHERINV = ФИШЕРОБР ## Возвращает обратное преобразование Фишера. -FORECAST = ПРЕДСКАЗ ## Возвращает значение линейного тренда. -FREQUENCY = ЧАСТОТА ## Возвращает распределение частот в виде вертикального массива. -FTEST = ФТЕСТ ## Возвращает результат F-теста. -GAMMADIST = ГАММАРАСП ## Возвращает гамма-распределение. -GAMMAINV = ГАММАОБР ## Возвращает обратное гамма-распределение. -GAMMALN = ГАММАНЛОГ ## Возвращает натуральный логарифм гамма функции, Γ(x). -GEOMEAN = СРГЕОМ ## Возвращает среднее геометрическое. -GROWTH = РОСТ ## Возвращает значения в соответствии с экспоненциальным трендом. -HARMEAN = СРГАРМ ## Возвращает среднее гармоническое. -HYPGEOMDIST = ГИПЕРГЕОМЕТ ## Возвращает гипергеометрическое распределение. -INTERCEPT = ОТРЕЗОК ## Возвращает отрезок, отсекаемый на оси линией линейной регрессии. -KURT = ЭКСЦЕСС ## Возвращает эксцесс множества данных. -LARGE = НАИБОЛЬШИЙ ## Возвращает k-ое наибольшее значение в множестве данных. -LINEST = ЛИНЕЙН ## Возвращает параметры линейного тренда. -LOGEST = ЛГРФПРИБЛ ## Возвращает параметры экспоненциального тренда. -LOGINV = ЛОГНОРМОБР ## Возвращает обратное логарифмическое нормальное распределение. -LOGNORMDIST = ЛОГНОРМРАСП ## Возвращает интегральное логарифмическое нормальное распределение. -MAX = МАКС ## Возвращает наибольшее значение в списке аргументов. -MAXA = МАКСА ## Возвращает наибольшее значение в списке аргументов, включая числа, текст и логические значения. -MEDIAN = МЕДИАНА ## Возвращает медиану заданных чисел. -MIN = МИН ## Возвращает наименьшее значение в списке аргументов. -MINA = МИНА ## Возвращает наименьшее значение в списке аргументов, включая числа, текст и логические значения. -MODE = МОДА ## Возвращает значение моды множества данных. -NEGBINOMDIST = ОТРБИНОМРАСП ## Возвращает отрицательное биномиальное распределение. -NORMDIST = НОРМРАСП ## Возвращает нормальную функцию распределения. -NORMINV = НОРМОБР ## Возвращает обратное нормальное распределение. -NORMSDIST = НОРМСТРАСП ## Возвращает стандартное нормальное интегральное распределение. -NORMSINV = НОРМСТОБР ## Возвращает обратное значение стандартного нормального распределения. -PEARSON = ПИРСОН ## Возвращает коэффициент корреляции Пирсона. -PERCENTILE = ПЕРСЕНТИЛЬ ## Возвращает k-ую персентиль для значений диапазона. -PERCENTRANK = ПРОЦЕНТРАНГ ## Возвращает процентную норму значения в множестве данных. -PERMUT = ПЕРЕСТ ## Возвращает количество перестановок для заданного числа объектов. -POISSON = ПУАССОН ## Возвращает распределение Пуассона. -PROB = ВЕРОЯТНОСТЬ ## Возвращает вероятность того, что значение из диапазона находится внутри заданных пределов. -QUARTILE = КВАРТИЛЬ ## Возвращает квартиль множества данных. -RANK = РАНГ ## Возвращает ранг числа в списке чисел. -RSQ = КВПИРСОН ## Возвращает квадрат коэффициента корреляции Пирсона. -SKEW = СКОС ## Возвращает асимметрию распределения. -SLOPE = НАКЛОН ## Возвращает наклон линии линейной регрессии. -SMALL = НАИМЕНЬШИЙ ## Возвращает k-ое наименьшее значение в множестве данных. -STANDARDIZE = НОРМАЛИЗАЦИЯ ## Возвращает нормализованное значение. -STDEV = СТАНДОТКЛОН ## Оценивает стандартное отклонение по выборке. -STDEVA = СТАНДОТКЛОНА ## Оценивает стандартное отклонение по выборке, включая числа, текст и логические значения. -STDEVP = СТАНДОТКЛОНП ## Вычисляет стандартное отклонение по генеральной совокупности. -STDEVPA = СТАНДОТКЛОНПА ## Вычисляет стандартное отклонение по генеральной совокупности, включая числа, текст и логические значения. -STEYX = СТОШYX ## Возвращает стандартную ошибку предсказанных значений y для каждого значения x в регрессии. -TDIST = СТЬЮДРАСП ## Возвращает t-распределение Стьюдента. -TINV = СТЬЮДРАСПОБР ## Возвращает обратное t-распределение Стьюдента. -TREND = ТЕНДЕНЦИЯ ## Возвращает значения в соответствии с линейным трендом. -TRIMMEAN = УРЕЗСРЕДНЕЕ ## Возвращает среднее внутренности множества данных. -TTEST = ТТЕСТ ## Возвращает вероятность, соответствующую критерию Стьюдента. -VAR = ДИСП ## Оценивает дисперсию по выборке. -VARA = ДИСПА ## Оценивает дисперсию по выборке, включая числа, текст и логические значения. -VARP = ДИСПР ## Вычисляет дисперсию для генеральной совокупности. -VARPA = ДИСПРА ## Вычисляет дисперсию для генеральной совокупности, включая числа, текст и логические значения. -WEIBULL = ВЕЙБУЛЛ ## Возвращает распределение Вейбулла. -ZTEST = ZТЕСТ ## Возвращает двустороннее P-значение z-теста. - +ARRAYTOTEXT = МАССИВВТЕКСТ +BAHTTEXT = БАТТЕКСТ +CHAR = СИМВОЛ +CLEAN = ПЕЧСИМВ +CODE = КОДСИМВ +CONCAT = СЦЕП +DBCS = БДЦС +DOLLAR = РУБЛЬ +EXACT = СОВПАД +FIND = НАЙТИ +FINDB = НАЙТИБ +FIXED = ФИКСИРОВАННЫЙ +ISTHAIDIGIT = ЕТАЙЦИФРЫ +LEFT = ЛЕВСИМВ +LEFTB = ЛЕВБ +LEN = ДЛСТР +LENB = ДЛИНБ +LOWER = СТРОЧН +MID = ПСТР +MIDB = ПСТРБ +NUMBERSTRING = СТРОКАЧИСЕЛ +NUMBERVALUE = ЧЗНАЧ +PROPER = ПРОПНАЧ +REPLACE = ЗАМЕНИТЬ +REPLACEB = ЗАМЕНИТЬБ +REPT = ПОВТОР +RIGHT = ПРАВСИМВ +RIGHTB = ПРАВБ +SEARCH = ПОИСК +SEARCHB = ПОИСКБ +SUBSTITUTE = ПОДСТАВИТЬ +T = Т +TEXT = ТЕКСТ +TEXTJOIN = ОБЪЕДИНИТЬ +THAIDIGIT = ТАЙЦИФРА +THAINUMSOUND = ТАЙЧИСЛОВЗВУК +THAINUMSTRING = ТАЙЧИСЛОВСТРОКУ +THAISTRINGLENGTH = ТАЙДЛИНАСТРОКИ +TRIM = СЖПРОБЕЛЫ +UNICHAR = ЮНИСИМВ +UNICODE = UNICODE +UPPER = ПРОПИСН +VALUE = ЗНАЧЕН +VALUETOTEXT = ЗНАЧЕНИЕВТЕКСТ ## -## Text functions Текстовые функции +## Веб-функции (Web Functions) ## -ASC = ASC ## Для языков с двухбайтовыми наборами знаков (например, катакана) преобразует полноширинные (двухбайтовые) знаки в полуширинные (однобайтовые). -BAHTTEXT = БАТТЕКСТ ## Преобразует число в текст, используя денежный формат ß (БАТ). -CHAR = СИМВОЛ ## Возвращает знак с заданным кодом. -CLEAN = ПЕЧСИМВ ## Удаляет все непечатаемые знаки из текста. -CODE = КОДСИМВ ## Возвращает числовой код первого знака в текстовой строке. -CONCATENATE = СЦЕПИТЬ ## Объединяет несколько текстовых элементов в один. -DOLLAR = РУБЛЬ ## Преобразует число в текст, используя денежный формат. -EXACT = СОВПАД ## Проверяет идентичность двух текстовых значений. -FIND = НАЙТИ ## Ищет вхождения одного текстового значения в другом (с учетом регистра). -FINDB = НАЙТИБ ## Ищет вхождения одного текстового значения в другом (с учетом регистра). -FIXED = ФИКСИРОВАННЫЙ ## Форматирует число и преобразует его в текст с заданным числом десятичных знаков. -JIS = JIS ## Для языков с двухбайтовыми наборами знаков (например, катакана) преобразует полуширинные (однобайтовые) знаки в текстовой строке в полноширинные (двухбайтовые). -LEFT = ЛЕВСИМВ ## Возвращает крайние слева знаки текстового значения. -LEFTB = ЛЕВБ ## Возвращает крайние слева знаки текстового значения. -LEN = ДЛСТР ## Возвращает количество знаков в текстовой строке. -LENB = ДЛИНБ ## Возвращает количество знаков в текстовой строке. -LOWER = СТРОЧН ## Преобразует все буквы текста в строчные. -MID = ПСТР ## Возвращает заданное число знаков из строки текста, начиная с указанной позиции. -MIDB = ПСТРБ ## Возвращает заданное число знаков из строки текста, начиная с указанной позиции. -PHONETIC = PHONETIC ## Извлекает фонетические (фуригана) знаки из текстовой строки. -PROPER = ПРОПНАЧ ## Преобразует первую букву в каждом слове текста в прописную. -REPLACE = ЗАМЕНИТЬ ## Заменяет знаки в тексте. -REPLACEB = ЗАМЕНИТЬБ ## Заменяет знаки в тексте. -REPT = ПОВТОР ## Повторяет текст заданное число раз. -RIGHT = ПРАВСИМВ ## Возвращает крайние справа знаки текстовой строки. -RIGHTB = ПРАВБ ## Возвращает крайние справа знаки текстовой строки. -SEARCH = ПОИСК ## Ищет вхождения одного текстового значения в другом (без учета регистра). -SEARCHB = ПОИСКБ ## Ищет вхождения одного текстового значения в другом (без учета регистра). -SUBSTITUTE = ПОДСТАВИТЬ ## Заменяет в текстовой строке старый текст новым. -T = Т ## Преобразует аргументы в текст. -TEXT = ТЕКСТ ## Форматирует число и преобразует его в текст. -TRIM = СЖПРОБЕЛЫ ## Удаляет из текста пробелы. -UPPER = ПРОПИСН ## Преобразует все буквы текста в прописные. -VALUE = ЗНАЧЕН ## Преобразует текстовый аргумент в число. +ENCODEURL = КОДИР.URL +FILTERXML = ФИЛЬТР.XML +WEBSERVICE = ВЕБСЛУЖБА + +## +## Функции совместимости (Compatibility Functions) +## +BETADIST = БЕТАРАСП +BETAINV = БЕТАОБР +BINOMDIST = БИНОМРАСП +CEILING = ОКРВВЕРХ +CHIDIST = ХИ2РАСП +CHIINV = ХИ2ОБР +CHITEST = ХИ2ТЕСТ +CONCATENATE = СЦЕПИТЬ +CONFIDENCE = ДОВЕРИТ +COVAR = КОВАР +CRITBINOM = КРИТБИНОМ +EXPONDIST = ЭКСПРАСП +FDIST = FРАСП +FINV = FРАСПОБР +FLOOR = ОКРВНИЗ +FORECAST = ПРЕДСКАЗ +FTEST = ФТЕСТ +GAMMADIST = ГАММАРАСП +GAMMAINV = ГАММАОБР +HYPGEOMDIST = ГИПЕРГЕОМЕТ +LOGINV = ЛОГНОРМОБР +LOGNORMDIST = ЛОГНОРМРАСП +MODE = МОДА +NEGBINOMDIST = ОТРБИНОМРАСП +NORMDIST = НОРМРАСП +NORMINV = НОРМОБР +NORMSDIST = НОРМСТРАСП +NORMSINV = НОРМСТОБР +PERCENTILE = ПЕРСЕНТИЛЬ +PERCENTRANK = ПРОЦЕНТРАНГ +POISSON = ПУАССОН +QUARTILE = КВАРТИЛЬ +RANK = РАНГ +STDEV = СТАНДОТКЛОН +STDEVP = СТАНДОТКЛОНП +TDIST = СТЬЮДРАСП +TINV = СТЬЮДРАСПОБР +TTEST = ТТЕСТ +VAR = ДИСП +VARP = ДИСПР +WEIBULL = ВЕЙБУЛЛ +ZTEST = ZТЕСТ diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/sv/config b/PhpOffice/PhpSpreadsheet/Calculation/locale/sv/config old mode 100755 new mode 100644 index bf72cc4..c7440f7 --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/sv/config +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/sv/config @@ -1,24 +1,20 @@ +############################################################ ## -## PhpSpreadsheet +## PhpSpreadsheet - locale settings ## +## Svenska (Swedish) +## +############################################################ -ArgumentSeparator = ; - +ArgumentSeparator = ; ## -## (For future use) +## Error Codes ## -currencySymbol = kr - - -## -## Excel Error Codes (For future use) - -## -NULL = #Skärning! -DIV0 = #Division/0! -VALUE = #Värdefel! -REF = #Referens! -NAME = #Namn? -NUM = #Ogiltigt! -NA = #Saknas! +NULL = #SKÄRNING! +DIV0 = #DIVISION/0! +VALUE = #VÄRDEFEL! +REF = #REFERENS! +NAME = #NAMN? +NUM = #OGILTIGT! +NA = #SAKNAS! diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/sv/functions b/PhpOffice/PhpSpreadsheet/Calculation/locale/sv/functions old mode 100755 new mode 100644 index 73b2deb..491ecfb --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/sv/functions +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/sv/functions @@ -1,408 +1,533 @@ +############################################################ ## -## Add-in and Automation functions Tilläggs- och automatiseringsfunktioner +## PhpSpreadsheet - function name translations ## -GETPIVOTDATA = HÄMTA.PIVOTDATA ## Returnerar data som lagrats i en pivottabellrapport +## Svenska (Swedish) +## +############################################################ ## -## Cube functions Kubfunktioner +## Kubfunktioner (Cube Functions) ## -CUBEKPIMEMBER = KUBKPIMEDLEM ## Returnerar namn, egenskap och mått för en KPI och visar namnet och egenskapen i cellen. En KPI, eller prestandaindikator, är ett kvantifierbart mått, t.ex. månatlig bruttovinst eller personalomsättning per kvartal, som används för att analysera ett företags resultat. -CUBEMEMBER = KUBMEDLEM ## Returnerar en medlem eller ett par i en kubhierarki. Används för att verifiera att medlemmen eller paret finns i kuben. -CUBEMEMBERPROPERTY = KUBMEDLEMSEGENSKAP ## Returnerar värdet för en medlemsegenskap i kuben. Används för att verifiera att ett medlemsnamn finns i kuben, samt för att returnera den angivna egenskapen för medlemmen. -CUBERANKEDMEMBER = KUBRANGORDNADMEDLEM ## Returnerar den n:te, eller rangordnade, medlemmen i en uppsättning. Används för att returnera ett eller flera element i en uppsättning, till exempelvis den bästa försäljaren eller de tio bästa eleverna. -CUBESET = KUBINSTÄLLNING ## Definierar en beräknad uppsättning medlemmar eller par genom att skicka ett bestämt uttryck till kuben på servern, som skapar uppsättningen och sedan returnerar den till Microsoft Office Excel. -CUBESETCOUNT = KUBINSTÄLLNINGANTAL ## Returnerar antalet objekt i en uppsättning. -CUBEVALUE = KUBVÄRDE ## Returnerar ett mängdvärde från en kub. - +CUBEKPIMEMBER = KUBKPIMEDLEM +CUBEMEMBER = KUBMEDLEM +CUBEMEMBERPROPERTY = KUBMEDLEMSEGENSKAP +CUBERANKEDMEMBER = KUBRANGORDNADMEDLEM +CUBESET = KUBUPPSÄTTNING +CUBESETCOUNT = KUBUPPSÄTTNINGANTAL +CUBEVALUE = KUBVÄRDE ## -## Database functions Databasfunktioner +## Databasfunktioner (Database Functions) ## -DAVERAGE = DMEDEL ## Returnerar medelvärdet av databasposterna -DCOUNT = DANTAL ## Räknar antalet celler som innehåller tal i en databas -DCOUNTA = DANTALV ## Räknar ifyllda celler i en databas -DGET = DHÄMTA ## Hämtar en enstaka post från en databas som uppfyller de angivna villkoren -DMAX = DMAX ## Returnerar det största värdet från databasposterna -DMIN = DMIN ## Returnerar det minsta värdet från databasposterna -DPRODUCT = DPRODUKT ## Multiplicerar värdena i ett visst fält i poster som uppfyller villkoret -DSTDEV = DSTDAV ## Uppskattar standardavvikelsen baserat på ett urval av databasposterna -DSTDEVP = DSTDAVP ## Beräknar standardavvikelsen utifrån hela populationen av valda databasposter -DSUM = DSUMMA ## Summerar talen i kolumnfält i databasposter som uppfyller villkoret -DVAR = DVARIANS ## Uppskattar variansen baserat på ett urval av databasposterna -DVARP = DVARIANSP ## Beräknar variansen utifrån hela populationen av valda databasposter - +DAVERAGE = DMEDEL +DCOUNT = DANTAL +DCOUNTA = DANTALV +DGET = DHÄMTA +DMAX = DMAX +DMIN = DMIN +DPRODUCT = DPRODUKT +DSTDEV = DSTDAV +DSTDEVP = DSTDAVP +DSUM = DSUMMA +DVAR = DVARIANS +DVARP = DVARIANSP ## -## Date and time functions Tid- och datumfunktioner +## Tid- och datumfunktioner (Date & Time Functions) ## -DATE = DATUM ## Returnerar ett serienummer för ett visst datum -DATEVALUE = DATUMVÄRDE ## Konverterar ett datum i textformat till ett serienummer -DAY = DAG ## Konverterar ett serienummer till dag i månaden -DAYS360 = DAGAR360 ## Beräknar antalet dagar mellan två datum baserat på ett 360-dagarsår -EDATE = EDATUM ## Returnerar serienumret för ett datum som infaller ett visst antal månader före eller efter startdatumet -EOMONTH = SLUTMÅNAD ## Returnerar serienumret för sista dagen i månaden ett visst antal månader tidigare eller senare -HOUR = TIMME ## Konverterar ett serienummer till en timme -MINUTE = MINUT ## Konverterar ett serienummer till en minut -MONTH = MÅNAD ## Konverterar ett serienummer till en månad -NETWORKDAYS = NETTOARBETSDAGAR ## Returnerar antalet hela arbetsdagar mellan två datum -NOW = NU ## Returnerar serienumret för dagens datum och aktuell tid -SECOND = SEKUND ## Konverterar ett serienummer till en sekund -TIME = KLOCKSLAG ## Returnerar serienumret för en viss tid -TIMEVALUE = TIDVÄRDE ## Konverterar en tid i textformat till ett serienummer -TODAY = IDAG ## Returnerar serienumret för dagens datum -WEEKDAY = VECKODAG ## Konverterar ett serienummer till en dag i veckan -WEEKNUM = VECKONR ## Konverterar ett serienummer till ett veckonummer -WORKDAY = ARBETSDAGAR ## Returnerar serienumret för ett datum ett visst antal arbetsdagar tidigare eller senare -YEAR = ÅR ## Konverterar ett serienummer till ett år -YEARFRAC = ÅRDEL ## Returnerar en del av ett år som representerar antalet hela dagar mellan start- och slutdatum - +DATE = DATUM +DATEVALUE = DATUMVÄRDE +DAY = DAG +DAYS = DAGAR +DAYS360 = DAGAR360 +EDATE = EDATUM +EOMONTH = SLUTMÅNAD +HOUR = TIMME +ISOWEEKNUM = ISOVECKONR +MINUTE = MINUT +MONTH = MÅNAD +NETWORKDAYS = NETTOARBETSDAGAR +NETWORKDAYS.INTL = NETTOARBETSDAGAR.INT +NOW = NU +SECOND = SEKUND +THAIDAYOFWEEK = THAIVECKODAG +THAIMONTHOFYEAR = THAIMÅNAD +THAIYEAR = THAIÅR +TIME = KLOCKSLAG +TIMEVALUE = TIDVÄRDE +TODAY = IDAG +WEEKDAY = VECKODAG +WEEKNUM = VECKONR +WORKDAY = ARBETSDAGAR +WORKDAY.INTL = ARBETSDAGAR.INT +YEAR = ÅR +YEARFRAC = ÅRDEL ## -## Engineering functions Tekniska funktioner +## Tekniska funktioner (Engineering Functions) ## -BESSELI = BESSELI ## Returnerar den modifierade Bessel-funktionen In(x) -BESSELJ = BESSELJ ## Returnerar Bessel-funktionen Jn(x) -BESSELK = BESSELK ## Returnerar den modifierade Bessel-funktionen Kn(x) -BESSELY = BESSELY ## Returnerar Bessel-funktionen Yn(x) -BIN2DEC = BIN.TILL.DEC ## Omvandlar ett binärt tal till decimalt -BIN2HEX = BIN.TILL.HEX ## Omvandlar ett binärt tal till hexadecimalt -BIN2OCT = BIN.TILL.OKT ## Omvandlar ett binärt tal till oktalt -COMPLEX = KOMPLEX ## Omvandlar reella och imaginära koefficienter till ett komplext tal -CONVERT = KONVERTERA ## Omvandlar ett tal från ett måttsystem till ett annat -DEC2BIN = DEC.TILL.BIN ## Omvandlar ett decimalt tal till binärt -DEC2HEX = DEC.TILL.HEX ## Omvandlar ett decimalt tal till hexadecimalt -DEC2OCT = DEC.TILL.OKT ## Omvandlar ett decimalt tal till oktalt -DELTA = DELTA ## Testar om två värden är lika -ERF = FELF ## Returnerar felfunktionen -ERFC = FELFK ## Returnerar den komplementära felfunktionen -GESTEP = SLSTEG ## Testar om ett tal är större än ett tröskelvärde -HEX2BIN = HEX.TILL.BIN ## Omvandlar ett hexadecimalt tal till binärt -HEX2DEC = HEX.TILL.DEC ## Omvandlar ett hexadecimalt tal till decimalt -HEX2OCT = HEX.TILL.OKT ## Omvandlar ett hexadecimalt tal till oktalt -IMABS = IMABS ## Returnerar absolutvärdet (modulus) för ett komplext tal -IMAGINARY = IMAGINÄR ## Returnerar den imaginära koefficienten för ett komplext tal -IMARGUMENT = IMARGUMENT ## Returnerar det komplexa talets argument, en vinkel uttryckt i radianer -IMCONJUGATE = IMKONJUGAT ## Returnerar det komplexa talets konjugat -IMCOS = IMCOS ## Returnerar cosinus för ett komplext tal -IMDIV = IMDIV ## Returnerar kvoten för två komplexa tal -IMEXP = IMEUPPHÖJT ## Returnerar exponenten för ett komplext tal -IMLN = IMLN ## Returnerar den naturliga logaritmen för ett komplext tal -IMLOG10 = IMLOG10 ## Returnerar 10-logaritmen för ett komplext tal -IMLOG2 = IMLOG2 ## Returnerar 2-logaritmen för ett komplext tal -IMPOWER = IMUPPHÖJT ## Returnerar ett komplext tal upphöjt till en exponent -IMPRODUCT = IMPRODUKT ## Returnerar produkten av komplexa tal -IMREAL = IMREAL ## Returnerar den reella koefficienten för ett komplext tal -IMSIN = IMSIN ## Returnerar sinus för ett komplext tal -IMSQRT = IMROT ## Returnerar kvadratroten av ett komplext tal -IMSUB = IMDIFF ## Returnerar differensen mellan två komplexa tal -IMSUM = IMSUM ## Returnerar summan av komplexa tal -OCT2BIN = OKT.TILL.BIN ## Omvandlar ett oktalt tal till binärt -OCT2DEC = OKT.TILL.DEC ## Omvandlar ett oktalt tal till decimalt -OCT2HEX = OKT.TILL.HEX ## Omvandlar ett oktalt tal till hexadecimalt - +BESSELI = BESSELI +BESSELJ = BESSELJ +BESSELK = BESSELK +BESSELY = BESSELY +BIN2DEC = BIN.TILL.DEC +BIN2HEX = BIN.TILL.HEX +BIN2OCT = BIN.TILL.OKT +BITAND = BITOCH +BITLSHIFT = BITVSKIFT +BITOR = BITELLER +BITRSHIFT = BITHSKIFT +BITXOR = BITXELLER +COMPLEX = KOMPLEX +CONVERT = KONVERTERA +DEC2BIN = DEC.TILL.BIN +DEC2HEX = DEC.TILL.HEX +DEC2OCT = DEC.TILL.OKT +DELTA = DELTA +ERF = FELF +ERF.PRECISE = FELF.EXAKT +ERFC = FELFK +ERFC.PRECISE = FELFK.EXAKT +GESTEP = SLSTEG +HEX2BIN = HEX.TILL.BIN +HEX2DEC = HEX.TILL.DEC +HEX2OCT = HEX.TILL.OKT +IMABS = IMABS +IMAGINARY = IMAGINÄR +IMARGUMENT = IMARGUMENT +IMCONJUGATE = IMKONJUGAT +IMCOS = IMCOS +IMCOSH = IMCOSH +IMCOT = IMCOT +IMCSC = IMCSC +IMCSCH = IMCSCH +IMDIV = IMDIV +IMEXP = IMEUPPHÖJT +IMLN = IMLN +IMLOG10 = IMLOG10 +IMLOG2 = IMLOG2 +IMPOWER = IMUPPHÖJT +IMPRODUCT = IMPRODUKT +IMREAL = IMREAL +IMSEC = IMSEK +IMSECH = IMSEKH +IMSIN = IMSIN +IMSINH = IMSINH +IMSQRT = IMROT +IMSUB = IMDIFF +IMSUM = IMSUM +IMTAN = IMTAN +OCT2BIN = OKT.TILL.BIN +OCT2DEC = OKT.TILL.DEC +OCT2HEX = OKT.TILL.HEX ## -## Financial functions Finansiella funktioner +## Finansiella funktioner (Financial Functions) ## -ACCRINT = UPPLRÄNTA ## Returnerar den upplupna räntan för värdepapper med periodisk ränta -ACCRINTM = UPPLOBLRÄNTA ## Returnerar den upplupna räntan för ett värdepapper som ger avkastning på förfallodagen -AMORDEGRC = AMORDEGRC ## Returnerar avskrivningen för varje redovisningsperiod med hjälp av en avskrivningskoefficient -AMORLINC = AMORLINC ## Returnerar avskrivningen för varje redovisningsperiod -COUPDAYBS = KUPDAGBB ## Returnerar antal dagar från början av kupongperioden till likviddagen -COUPDAYS = KUPDAGARS ## Returnerar antalet dagar i kupongperioden som innehåller betalningsdatumet -COUPDAYSNC = KUPDAGNK ## Returnerar antalet dagar från betalningsdatumet till nästa kupongdatum -COUPNCD = KUPNKD ## Returnerar nästa kupongdatum efter likviddagen -COUPNUM = KUPANT ## Returnerar kuponger som förfaller till betalning mellan likviddagen och förfallodagen -COUPPCD = KUPFKD ## Returnerar föregående kupongdatum före likviddagen -CUMIPMT = KUMRÄNTA ## Returnerar den ackumulerade räntan som betalats mellan två perioder -CUMPRINC = KUMPRIS ## Returnerar det ackumulerade kapitalbeloppet som betalats på ett lån mellan två perioder -DB = DB ## Returnerar avskrivningen för en tillgång under en angiven tid enligt metoden för fast degressiv avskrivning -DDB = DEGAVSKR ## Returnerar en tillgångs värdeminskning under en viss period med hjälp av dubbel degressiv avskrivning eller någon annan metod som du anger -DISC = DISK ## Returnerar diskonteringsräntan för ett värdepapper -DOLLARDE = DECTAL ## Omvandlar ett pris uttryckt som ett bråk till ett decimaltal -DOLLARFR = BRÅK ## Omvandlar ett pris i kronor uttryckt som ett decimaltal till ett bråk -DURATION = LÖPTID ## Returnerar den årliga löptiden för en säkerhet med periodiska räntebetalningar -EFFECT = EFFRÄNTA ## Returnerar den årliga effektiva räntesatsen -FV = SLUTVÄRDE ## Returnerar det framtida värdet på en investering -FVSCHEDULE = FÖRRÄNTNING ## Returnerar det framtida värdet av ett begynnelsekapital beräknat på olika räntenivåer -INTRATE = ÅRSRÄNTA ## Returnerar räntesatsen för ett betalt värdepapper -IPMT = RBETALNING ## Returnerar räntedelen av en betalning för en given period -IRR = IR ## Returnerar internräntan för en serie betalningar -ISPMT = RALÅN ## Beräknar räntan som har betalats under en specifik betalningsperiod -MDURATION = MLÖPTID ## Returnerar den modifierade Macauley-löptiden för ett värdepapper med det antagna nominella värdet 100 kr -MIRR = MODIR ## Returnerar internräntan där positiva och negativa betalningar finansieras med olika räntor -NOMINAL = NOMRÄNTA ## Returnerar den årliga nominella räntesatsen -NPER = PERIODER ## Returnerar antalet perioder för en investering -NPV = NETNUVÄRDE ## Returnerar nuvärdet av en serie periodiska betalningar vid en given diskonteringsränta -ODDFPRICE = UDDAFPRIS ## Returnerar priset per 100 kr nominellt värde för ett värdepapper med en udda första period -ODDFYIELD = UDDAFAVKASTNING ## Returnerar avkastningen för en säkerhet med en udda första period -ODDLPRICE = UDDASPRIS ## Returnerar priset per 100 kr nominellt värde för ett värdepapper med en udda sista period -ODDLYIELD = UDDASAVKASTNING ## Returnerar avkastningen för en säkerhet med en udda sista period -PMT = BETALNING ## Returnerar den periodiska betalningen för en annuitet -PPMT = AMORT ## Returnerar amorteringsdelen av en annuitetsbetalning för en given period -PRICE = PRIS ## Returnerar priset per 100 kr nominellt värde för ett värdepapper som ger periodisk ränta -PRICEDISC = PRISDISK ## Returnerar priset per 100 kr nominellt värde för ett diskonterat värdepapper -PRICEMAT = PRISFÖRF ## Returnerar priset per 100 kr nominellt värde för ett värdepapper som ger ränta på förfallodagen -PV = PV ## Returnerar nuvärdet av en serie lika stora periodiska betalningar -RATE = RÄNTA ## Returnerar räntesatsen per period i en annuitet -RECEIVED = BELOPP ## Returnerar beloppet som utdelas på förfallodagen för ett betalat värdepapper -SLN = LINAVSKR ## Returnerar den linjära avskrivningen för en tillgång under en period -SYD = ÅRSAVSKR ## Returnerar den årliga avskrivningssumman för en tillgång under en angiven period -TBILLEQ = SSVXEKV ## Returnerar avkastningen motsvarande en obligation för en statsskuldväxel -TBILLPRICE = SSVXPRIS ## Returnerar priset per 100 kr nominellt värde för en statsskuldväxel -TBILLYIELD = SSVXRÄNTA ## Returnerar avkastningen för en statsskuldväxel -VDB = VDEGRAVSKR ## Returnerar avskrivningen för en tillgång under en angiven period (med degressiv avskrivning) -XIRR = XIRR ## Returnerar internräntan för en serie betalningar som inte nödvändigtvis är periodiska -XNPV = XNUVÄRDE ## Returnerar det nuvarande nettovärdet för en serie betalningar som inte nödvändigtvis är periodiska -YIELD = NOMAVK ## Returnerar avkastningen för ett värdepapper som ger periodisk ränta -YIELDDISC = NOMAVKDISK ## Returnerar den årliga avkastningen för diskonterade värdepapper, exempelvis en statsskuldväxel -YIELDMAT = NOMAVKFÖRF ## Returnerar den årliga avkastningen för ett värdepapper som ger ränta på förfallodagen - +ACCRINT = UPPLRÄNTA +ACCRINTM = UPPLOBLRÄNTA +AMORDEGRC = AMORDEGRC +AMORLINC = AMORLINC +COUPDAYBS = KUPDAGBB +COUPDAYS = KUPDAGB +COUPDAYSNC = KUPDAGNK +COUPNCD = KUPNKD +COUPNUM = KUPANT +COUPPCD = KUPFKD +CUMIPMT = KUMRÄNTA +CUMPRINC = KUMPRIS +DB = DB +DDB = DEGAVSKR +DISC = DISK +DOLLARDE = DECTAL +DOLLARFR = BRÅK +DURATION = LÖPTID +EFFECT = EFFRÄNTA +FV = SLUTVÄRDE +FVSCHEDULE = FÖRRÄNTNING +INTRATE = ÅRSRÄNTA +IPMT = RBETALNING +IRR = IR +ISPMT = RALÅN +MDURATION = MLÖPTID +MIRR = MODIR +NOMINAL = NOMRÄNTA +NPER = PERIODER +NPV = NETNUVÄRDE +ODDFPRICE = UDDAFPRIS +ODDFYIELD = UDDAFAVKASTNING +ODDLPRICE = UDDASPRIS +ODDLYIELD = UDDASAVKASTNING +PDURATION = PLÖPTID +PMT = BETALNING +PPMT = AMORT +PRICE = PRIS +PRICEDISC = PRISDISK +PRICEMAT = PRISFÖRF +PV = NUVÄRDE +RATE = RÄNTA +RECEIVED = BELOPP +RRI = AVKPÅINVEST +SLN = LINAVSKR +SYD = ÅRSAVSKR +TBILLEQ = SSVXEKV +TBILLPRICE = SSVXPRIS +TBILLYIELD = SSVXRÄNTA +VDB = VDEGRAVSKR +XIRR = XIRR +XNPV = XNUVÄRDE +YIELD = NOMAVK +YIELDDISC = NOMAVKDISK +YIELDMAT = NOMAVKFÖRF ## -## Information functions Informationsfunktioner +## Informationsfunktioner (Information Functions) ## -CELL = CELL ## Returnerar information om formatering, plats och innehåll i en cell -ERROR.TYPE = FEL.TYP ## Returnerar ett tal som motsvarar ett felvärde -INFO = INFO ## Returnerar information om operativsystemet -ISBLANK = ÄRREF ## Returnerar SANT om värdet är tomt -ISERR = Ä ## Returnerar SANT om värdet är ett felvärde annat än #SAKNAS! -ISERROR = ÄRFEL ## Returnerar SANT om värdet är ett felvärde -ISEVEN = ÄRJÄMN ## Returnerar SANT om talet är jämnt -ISLOGICAL = ÄREJTEXT ## Returnerar SANT om värdet är ett logiskt värde -ISNA = ÄRLOGISK ## Returnerar SANT om värdet är felvärdet #SAKNAS! -ISNONTEXT = ÄRSAKNAD ## Returnerar SANT om värdet inte är text -ISNUMBER = ÄRTAL ## Returnerar SANT om värdet är ett tal -ISODD = ÄRUDDA ## Returnerar SANT om talet är udda -ISREF = ÄRTOM ## Returnerar SANT om värdet är en referens -ISTEXT = ÄRTEXT ## Returnerar SANT om värdet är text -N = N ## Returnerar ett värde omvandlat till ett tal -NA = SAKNAS ## Returnerar felvärdet #SAKNAS! -TYPE = VÄRDETYP ## Returnerar ett tal som anger värdets datatyp - +CELL = CELL +ERROR.TYPE = FEL.TYP +INFO = INFO +ISBLANK = ÄRTOM +ISERR = ÄRF +ISERROR = ÄRFEL +ISEVEN = ÄRJÄMN +ISFORMULA = ÄRFORMEL +ISLOGICAL = ÄRLOGISK +ISNA = ÄRSAKNAD +ISNONTEXT = ÄREJTEXT +ISNUMBER = ÄRTAL +ISODD = ÄRUDDA +ISREF = ÄRREF +ISTEXT = ÄRTEXT +N = N +NA = SAKNAS +SHEET = BLAD +SHEETS = ANTALBLAD +TYPE = VÄRDETYP ## -## Logical functions Logiska funktioner +## Logiska funktioner (Logical Functions) ## -AND = OCH ## Returnerar SANT om alla argument är sanna -FALSE = FALSKT ## Returnerar det logiska värdet FALSKT -IF = OM ## Anger vilket logiskt test som ska utföras -IFERROR = OMFEL ## Returnerar ett värde som du anger om en formel utvärderar till ett fel; annars returneras resultatet av formeln -NOT = ICKE ## Inverterar logiken för argumenten -OR = ELLER ## Returnerar SANT om något argument är SANT -TRUE = SANT ## Returnerar det logiska värdet SANT - +AND = OCH +FALSE = FALSKT +IF = OM +IFERROR = OMFEL +IFNA = OMSAKNAS +IFS = IFS +NOT = ICKE +OR = ELLER +SWITCH = VÄXLA +TRUE = SANT +XOR = XELLER ## -## Lookup and reference functions Sök- och referensfunktioner +## Sök- och referensfunktioner (Lookup & Reference Functions) ## -ADDRESS = ADRESS ## Returnerar en referens som text till en enstaka cell i ett kalkylblad -AREAS = OMRÅDEN ## Returnerar antalet områden i en referens -CHOOSE = VÄLJ ## Väljer ett värde i en lista över värden -COLUMN = KOLUMN ## Returnerar kolumnnumret för en referens -COLUMNS = KOLUMNER ## Returnerar antalet kolumner i en referens -HLOOKUP = LETAKOLUMN ## Söker i den översta raden i en matris och returnerar värdet för angiven cell -HYPERLINK = HYPERLÄNK ## Skapar en genväg eller ett hopp till ett dokument i nätverket, i ett intranät eller på Internet -INDEX = INDEX ## Använder ett index för ett välja ett värde i en referens eller matris -INDIRECT = INDIREKT ## Returnerar en referens som anges av ett textvärde -LOOKUP = LETAUPP ## Letar upp värden i en vektor eller matris -MATCH = PASSA ## Letar upp värden i en referens eller matris -OFFSET = FÖRSKJUTNING ## Returnerar en referens förskjuten i förhållande till en given referens -ROW = RAD ## Returnerar radnumret för en referens -ROWS = RADER ## Returnerar antalet rader i en referens -RTD = RTD ## Hämtar realtidsdata från ett program som stöder COM-automation (Automation: Ett sätt att arbeta med ett programs objekt från ett annat program eller utvecklingsverktyg. Detta kallades tidigare för OLE Automation, och är en branschstandard och ingår i Component Object Model (COM).) -TRANSPOSE = TRANSPONERA ## Transponerar en matris -VLOOKUP = LETARAD ## Letar i den första kolumnen i en matris och flyttar över raden för att returnera värdet för en cell - +ADDRESS = ADRESS +AREAS = OMRÅDEN +CHOOSE = VÄLJ +COLUMN = KOLUMN +COLUMNS = KOLUMNER +FORMULATEXT = FORMELTEXT +GETPIVOTDATA = HÄMTA.PIVOTDATA +HLOOKUP = LETAKOLUMN +HYPERLINK = HYPERLÄNK +INDEX = INDEX +INDIRECT = INDIREKT +LOOKUP = LETAUPP +MATCH = PASSA +OFFSET = FÖRSKJUTNING +ROW = RAD +ROWS = RADER +RTD = RTD +TRANSPOSE = TRANSPONERA +VLOOKUP = LETARAD +*RC = RK ## -## Math and trigonometry functions Matematiska och trigonometriska funktioner +## Matematiska och trigonometriska funktioner (Math & Trig Functions) ## -ABS = ABS ## Returnerar absolutvärdet av ett tal -ACOS = ARCCOS ## Returnerar arcus cosinus för ett tal -ACOSH = ARCCOSH ## Returnerar inverterad hyperbolisk cosinus för ett tal -ASIN = ARCSIN ## Returnerar arcus cosinus för ett tal -ASINH = ARCSINH ## Returnerar hyperbolisk arcus sinus för ett tal -ATAN = ARCTAN ## Returnerar arcus tangens för ett tal -ATAN2 = ARCTAN2 ## Returnerar arcus tangens för en x- och en y- koordinat -ATANH = ARCTANH ## Returnerar hyperbolisk arcus tangens för ett tal -CEILING = RUNDA.UPP ## Avrundar ett tal till närmaste heltal eller närmaste signifikanta multipel -COMBIN = KOMBIN ## Returnerar antalet kombinationer för ett givet antal objekt -COS = COS ## Returnerar cosinus för ett tal -COSH = COSH ## Returnerar hyperboliskt cosinus för ett tal -DEGREES = GRADER ## Omvandlar radianer till grader -EVEN = JÄMN ## Avrundar ett tal uppåt till närmaste heltal -EXP = EXP ## Returnerar e upphöjt till ett givet tal -FACT = FAKULTET ## Returnerar fakulteten för ett tal -FACTDOUBLE = DUBBELFAKULTET ## Returnerar dubbelfakulteten för ett tal -FLOOR = RUNDA.NED ## Avrundar ett tal nedåt mot noll -GCD = SGD ## Returnerar den största gemensamma nämnaren -INT = HELTAL ## Avrundar ett tal nedåt till närmaste heltal -LCM = MGM ## Returnerar den minsta gemensamma multipeln -LN = LN ## Returnerar den naturliga logaritmen för ett tal -LOG = LOG ## Returnerar logaritmen för ett tal för en given bas -LOG10 = LOG10 ## Returnerar 10-logaritmen för ett tal -MDETERM = MDETERM ## Returnerar matrisen som är avgörandet av en matris -MINVERSE = MINVERT ## Returnerar matrisinversen av en matris -MMULT = MMULT ## Returnerar matrisprodukten av två matriser -MOD = REST ## Returnerar resten vid en division -MROUND = MAVRUNDA ## Returnerar ett tal avrundat till en given multipel -MULTINOMIAL = MULTINOMIAL ## Returnerar multinomialen för en uppsättning tal -ODD = UDDA ## Avrundar ett tal uppåt till närmaste udda heltal -PI = PI ## Returnerar värdet pi -POWER = UPPHÖJT.TILL ## Returnerar resultatet av ett tal upphöjt till en exponent -PRODUCT = PRODUKT ## Multiplicerar argumenten -QUOTIENT = KVOT ## Returnerar heltalsdelen av en division -RADIANS = RADIANER ## Omvandlar grader till radianer -RAND = SLUMP ## Returnerar ett slumptal mellan 0 och 1 -RANDBETWEEN = SLUMP.MELLAN ## Returnerar ett slumptal mellan de tal som du anger -ROMAN = ROMERSK ## Omvandlar vanliga (arabiska) siffror till romerska som text -ROUND = AVRUNDA ## Avrundar ett tal till ett angivet antal siffror -ROUNDDOWN = AVRUNDA.NEDÅT ## Avrundar ett tal nedåt mot noll -ROUNDUP = AVRUNDA.UPPÅT ## Avrundar ett tal uppåt, från noll -SERIESSUM = SERIESUMMA ## Returnerar summan av en potensserie baserat på formeln -SIGN = TECKEN ## Returnerar tecknet för ett tal -SIN = SIN ## Returnerar sinus för en given vinkel -SINH = SINH ## Returnerar hyperbolisk sinus för ett tal -SQRT = ROT ## Returnerar den positiva kvadratroten -SQRTPI = ROTPI ## Returnerar kvadratroten för (tal * pi) -SUBTOTAL = DELSUMMA ## Returnerar en delsumma i en lista eller databas -SUM = SUMMA ## Summerar argumenten -SUMIF = SUMMA.OM ## Summerar celler enligt ett angivet villkor -SUMIFS = SUMMA.OMF ## Lägger till cellerna i ett område som uppfyller flera kriterier -SUMPRODUCT = PRODUKTSUMMA ## Returnerar summan av produkterna i motsvarande matriskomponenter -SUMSQ = KVADRATSUMMA ## Returnerar summan av argumentens kvadrater -SUMX2MY2 = SUMMAX2MY2 ## Returnerar summan av differensen mellan kvadraterna för motsvarande värden i två matriser -SUMX2PY2 = SUMMAX2PY2 ## Returnerar summan av summan av kvadraterna av motsvarande värden i två matriser -SUMXMY2 = SUMMAXMY2 ## Returnerar summan av kvadraten av skillnaden mellan motsvarande värden i två matriser -TAN = TAN ## Returnerar tangens för ett tal -TANH = TANH ## Returnerar hyperbolisk tangens för ett tal -TRUNC = AVKORTA ## Avkortar ett tal till ett heltal - +ABS = ABS +ACOS = ARCCOS +ACOSH = ARCCOSH +ACOT = ARCCOT +ACOTH = ARCCOTH +AGGREGATE = MÄNGD +ARABIC = ARABISKA +ASIN = ARCSIN +ASINH = ARCSINH +ATAN = ARCTAN +ATAN2 = ARCTAN2 +ATANH = ARCTANH +BASE = BAS +CEILING.MATH = RUNDA.UPP.MATEMATISKT +CEILING.PRECISE = RUNDA.UPP.EXAKT +COMBIN = KOMBIN +COMBINA = KOMBINA +COS = COS +COSH = COSH +COT = COT +COTH = COTH +CSC = CSC +CSCH = CSCH +DECIMAL = DECIMAL +DEGREES = GRADER +ECMA.CEILING = ECMA.RUNDA.UPP +EVEN = JÄMN +EXP = EXP +FACT = FAKULTET +FACTDOUBLE = DUBBELFAKULTET +FLOOR.MATH = RUNDA.NER.MATEMATISKT +FLOOR.PRECISE = RUNDA.NER.EXAKT +GCD = SGD +INT = HELTAL +ISO.CEILING = ISO.RUNDA.UPP +LCM = MGM +LN = LN +LOG = LOG +LOG10 = LOG10 +MDETERM = MDETERM +MINVERSE = MINVERT +MMULT = MMULT +MOD = REST +MROUND = MAVRUNDA +MULTINOMIAL = MULTINOMIAL +MUNIT = MENHET +ODD = UDDA +PI = PI +POWER = UPPHÖJT.TILL +PRODUCT = PRODUKT +QUOTIENT = KVOT +RADIANS = RADIANER +RAND = SLUMP +RANDBETWEEN = SLUMP.MELLAN +ROMAN = ROMERSK +ROUND = AVRUNDA +ROUNDBAHTDOWN = AVRUNDABAHTNEDÅT +ROUNDBAHTUP = AVRUNDABAHTUPPÅT +ROUNDDOWN = AVRUNDA.NEDÅT +ROUNDUP = AVRUNDA.UPPÅT +SEC = SEK +SECH = SEKH +SERIESSUM = SERIESUMMA +SIGN = TECKEN +SIN = SIN +SINH = SINH +SQRT = ROT +SQRTPI = ROTPI +SUBTOTAL = DELSUMMA +SUM = SUMMA +SUMIF = SUMMA.OM +SUMIFS = SUMMA.OMF +SUMPRODUCT = PRODUKTSUMMA +SUMSQ = KVADRATSUMMA +SUMX2MY2 = SUMMAX2MY2 +SUMX2PY2 = SUMMAX2PY2 +SUMXMY2 = SUMMAXMY2 +TAN = TAN +TANH = TANH +TRUNC = AVKORTA ## -## Statistical functions Statistiska funktioner +## Statistiska funktioner (Statistical Functions) ## -AVEDEV = MEDELAVV ## Returnerar medelvärdet för datapunkters absoluta avvikelse från deras medelvärde -AVERAGE = MEDEL ## Returnerar medelvärdet av argumenten -AVERAGEA = AVERAGEA ## Returnerar medelvärdet av argumenten, inklusive tal, text och logiska värden -AVERAGEIF = MEDELOM ## Returnerar medelvärdet (aritmetiskt medelvärde) för alla celler i ett område som uppfyller ett givet kriterium -AVERAGEIFS = MEDELOMF ## Returnerar medelvärdet (det aritmetiska medelvärdet) för alla celler som uppfyller flera villkor. -BETADIST = BETAFÖRD ## Returnerar den kumulativa betafördelningsfunktionen -BETAINV = BETAINV ## Returnerar inversen till den kumulativa fördelningsfunktionen för en viss betafördelning -BINOMDIST = BINOMFÖRD ## Returnerar den individuella binomialfördelningen -CHIDIST = CHI2FÖRD ## Returnerar den ensidiga sannolikheten av c2-fördelningen -CHIINV = CHI2INV ## Returnerar inversen av chi2-fördelningen -CHITEST = CHI2TEST ## Returnerar oberoendetesten -CONFIDENCE = KONFIDENS ## Returnerar konfidensintervallet för en populations medelvärde -CORREL = KORREL ## Returnerar korrelationskoefficienten mellan två datamängder -COUNT = ANTAL ## Räknar hur många tal som finns bland argumenten -COUNTA = ANTALV ## Räknar hur många värden som finns bland argumenten -COUNTBLANK = ANTAL.TOMMA ## Räknar antalet tomma celler i ett område -COUNTIF = ANTAL.OM ## Räknar antalet celler i ett område som uppfyller angivna villkor. -COUNTIFS = ANTAL.OMF ## Räknar antalet celler i ett område som uppfyller flera villkor. -COVAR = KOVAR ## Returnerar kovariansen, d.v.s. medelvärdet av produkterna för parade avvikelser -CRITBINOM = KRITBINOM ## Returnerar det minsta värdet för vilket den kumulativa binomialfördelningen är mindre än eller lika med ett villkorsvärde -DEVSQ = KVADAVV ## Returnerar summan av kvadrater på avvikelser -EXPONDIST = EXPONFÖRD ## Returnerar exponentialfördelningen -FDIST = FFÖRD ## Returnerar F-sannolikhetsfördelningen -FINV = FINV ## Returnerar inversen till F-sannolikhetsfördelningen -FISHER = FISHER ## Returnerar Fisher-transformationen -FISHERINV = FISHERINV ## Returnerar inversen till Fisher-transformationen -FORECAST = PREDIKTION ## Returnerar ett värde längs en linjär trendlinje -FREQUENCY = FREKVENS ## Returnerar en frekvensfördelning som en lodrät matris -FTEST = FTEST ## Returnerar resultatet av en F-test -GAMMADIST = GAMMAFÖRD ## Returnerar gammafördelningen -GAMMAINV = GAMMAINV ## Returnerar inversen till den kumulativa gammafördelningen -GAMMALN = GAMMALN ## Returnerar den naturliga logaritmen för gammafunktionen, G(x) -GEOMEAN = GEOMEDEL ## Returnerar det geometriska medelvärdet -GROWTH = EXPTREND ## Returnerar värden längs en exponentiell trend -HARMEAN = HARMMEDEL ## Returnerar det harmoniska medelvärdet -HYPGEOMDIST = HYPGEOMFÖRD ## Returnerar den hypergeometriska fördelningen -INTERCEPT = SKÄRNINGSPUNKT ## Returnerar skärningspunkten för en linjär regressionslinje -KURT = TOPPIGHET ## Returnerar toppigheten av en mängd data -LARGE = STÖRSTA ## Returnerar det n:te största värdet i en mängd data -LINEST = REGR ## Returnerar parametrar till en linjär trendlinje -LOGEST = EXPREGR ## Returnerar parametrarna i en exponentiell trend -LOGINV = LOGINV ## Returnerar inversen till den lognormala fördelningen -LOGNORMDIST = LOGNORMFÖRD ## Returnerar den kumulativa lognormala fördelningen -MAX = MAX ## Returnerar det största värdet i en lista av argument -MAXA = MAXA ## Returnerar det största värdet i en lista av argument, inklusive tal, text och logiska värden -MEDIAN = MEDIAN ## Returnerar medianen för angivna tal -MIN = MIN ## Returnerar det minsta värdet i en lista med argument -MINA = MINA ## Returnerar det minsta värdet i en lista över argument, inklusive tal, text och logiska värden -MODE = TYPVÄRDE ## Returnerar det vanligaste värdet i en datamängd -NEGBINOMDIST = NEGBINOMFÖRD ## Returnerar den negativa binomialfördelningen -NORMDIST = NORMFÖRD ## Returnerar den kumulativa normalfördelningen -NORMINV = NORMINV ## Returnerar inversen till den kumulativa normalfördelningen -NORMSDIST = NORMSFÖRD ## Returnerar den kumulativa standardnormalfördelningen -NORMSINV = NORMSINV ## Returnerar inversen till den kumulativa standardnormalfördelningen -PEARSON = PEARSON ## Returnerar korrelationskoefficienten till Pearsons momentprodukt -PERCENTILE = PERCENTIL ## Returnerar den n:te percentilen av värden i ett område -PERCENTRANK = PROCENTRANG ## Returnerar procentrangen för ett värde i en datamängd -PERMUT = PERMUT ## Returnerar antal permutationer för ett givet antal objekt -POISSON = POISSON ## Returnerar Poisson-fördelningen -PROB = SANNOLIKHET ## Returnerar sannolikheten att värden i ett område ligger mellan två gränser -QUARTILE = KVARTIL ## Returnerar kvartilen av en mängd data -RANK = RANG ## Returnerar rangordningen för ett tal i en lista med tal -RSQ = RKV ## Returnerar kvadraten av Pearsons produktmomentkorrelationskoefficient -SKEW = SNEDHET ## Returnerar snedheten för en fördelning -SLOPE = LUTNING ## Returnerar lutningen på en linjär regressionslinje -SMALL = MINSTA ## Returnerar det n:te minsta värdet i en mängd data -STANDARDIZE = STANDARDISERA ## Returnerar ett normaliserat värde -STDEV = STDAV ## Uppskattar standardavvikelsen baserat på ett urval -STDEVA = STDEVA ## Uppskattar standardavvikelsen baserat på ett urval, inklusive tal, text och logiska värden -STDEVP = STDAVP ## Beräknar standardavvikelsen baserat på hela populationen -STDEVPA = STDEVPA ## Beräknar standardavvikelsen baserat på hela populationen, inklusive tal, text och logiska värden -STEYX = STDFELYX ## Returnerar standardfelet för ett förutspått y-värde för varje x-värde i regressionen -TDIST = TFÖRD ## Returnerar Students t-fördelning -TINV = TINV ## Returnerar inversen till Students t-fördelning -TREND = TREND ## Returnerar värden längs en linjär trend -TRIMMEAN = TRIMMEDEL ## Returnerar medelvärdet av mittpunkterna i en datamängd -TTEST = TTEST ## Returnerar sannolikheten beräknad ur Students t-test -VAR = VARIANS ## Uppskattar variansen baserat på ett urval -VARA = VARA ## Uppskattar variansen baserat på ett urval, inklusive tal, text och logiska värden -VARP = VARIANSP ## Beräknar variansen baserat på hela populationen -VARPA = VARPA ## Beräknar variansen baserat på hela populationen, inklusive tal, text och logiska värden -WEIBULL = WEIBULL ## Returnerar Weibull-fördelningen -ZTEST = ZTEST ## Returnerar det ensidiga sannolikhetsvärdet av ett z-test - +AVEDEV = MEDELAVV +AVERAGE = MEDEL +AVERAGEA = AVERAGEA +AVERAGEIF = MEDEL.OM +AVERAGEIFS = MEDEL.OMF +BETA.DIST = BETA.FÖRD +BETA.INV = BETA.INV +BINOM.DIST = BINOM.FÖRD +BINOM.DIST.RANGE = BINOM.FÖRD.INTERVALL +BINOM.INV = BINOM.INV +CHISQ.DIST = CHI2.FÖRD +CHISQ.DIST.RT = CHI2.FÖRD.RT +CHISQ.INV = CHI2.INV +CHISQ.INV.RT = CHI2.INV.RT +CHISQ.TEST = CHI2.TEST +CONFIDENCE.NORM = KONFIDENS.NORM +CONFIDENCE.T = KONFIDENS.T +CORREL = KORREL +COUNT = ANTAL +COUNTA = ANTALV +COUNTBLANK = ANTAL.TOMMA +COUNTIF = ANTAL.OM +COUNTIFS = ANTAL.OMF +COVARIANCE.P = KOVARIANS.P +COVARIANCE.S = KOVARIANS.S +DEVSQ = KVADAVV +EXPON.DIST = EXPON.FÖRD +F.DIST = F.FÖRD +F.DIST.RT = F.FÖRD.RT +F.INV = F.INV +F.INV.RT = F.INV.RT +F.TEST = F.TEST +FISHER = FISHER +FISHERINV = FISHERINV +FORECAST.ETS = PROGNOS.ETS +FORECAST.ETS.CONFINT = PROGNOS.ETS.KONFINT +FORECAST.ETS.SEASONALITY = PROGNOS.ETS.SÄSONGSBEROENDE +FORECAST.ETS.STAT = PROGNOS.ETS.STAT +FORECAST.LINEAR = PROGNOS.LINJÄR +FREQUENCY = FREKVENS +GAMMA = GAMMA +GAMMA.DIST = GAMMA.FÖRD +GAMMA.INV = GAMMA.INV +GAMMALN = GAMMALN +GAMMALN.PRECISE = GAMMALN.EXAKT +GAUSS = GAUSS +GEOMEAN = GEOMEDEL +GROWTH = EXPTREND +HARMEAN = HARMMEDEL +HYPGEOM.DIST = HYPGEOM.FÖRD +INTERCEPT = SKÄRNINGSPUNKT +KURT = TOPPIGHET +LARGE = STÖRSTA +LINEST = REGR +LOGEST = EXPREGR +LOGNORM.DIST = LOGNORM.FÖRD +LOGNORM.INV = LOGNORM.INV +MAX = MAX +MAXA = MAXA +MAXIFS = MAXIFS +MEDIAN = MEDIAN +MIN = MIN +MINA = MINA +MINIFS = MINIFS +MODE.MULT = TYPVÄRDE.FLERA +MODE.SNGL = TYPVÄRDE.ETT +NEGBINOM.DIST = NEGBINOM.FÖRD +NORM.DIST = NORM.FÖRD +NORM.INV = NORM.INV +NORM.S.DIST = NORM.S.FÖRD +NORM.S.INV = NORM.S.INV +PEARSON = PEARSON +PERCENTILE.EXC = PERCENTIL.EXK +PERCENTILE.INC = PERCENTIL.INK +PERCENTRANK.EXC = PROCENTRANG.EXK +PERCENTRANK.INC = PROCENTRANG.INK +PERMUT = PERMUT +PERMUTATIONA = PERMUTATIONA +PHI = PHI +POISSON.DIST = POISSON.FÖRD +PROB = SANNOLIKHET +QUARTILE.EXC = KVARTIL.EXK +QUARTILE.INC = KVARTIL.INK +RANK.AVG = RANG.MED +RANK.EQ = RANG.EKV +RSQ = RKV +SKEW = SNEDHET +SKEW.P = SNEDHET.P +SLOPE = LUTNING +SMALL = MINSTA +STANDARDIZE = STANDARDISERA +STDEV.P = STDAV.P +STDEV.S = STDAV.S +STDEVA = STDEVA +STDEVPA = STDEVPA +STEYX = STDFELYX +T.DIST = T.FÖRD +T.DIST.2T = T.FÖRD.2T +T.DIST.RT = T.FÖRD.RT +T.INV = T.INV +T.INV.2T = T.INV.2T +T.TEST = T.TEST +TREND = TREND +TRIMMEAN = TRIMMEDEL +VAR.P = VARIANS.P +VAR.S = VARIANS.S +VARA = VARA +VARPA = VARPA +WEIBULL.DIST = WEIBULL.FÖRD +Z.TEST = Z.TEST ## -## Text functions Textfunktioner +## Textfunktioner (Text Functions) ## -ASC = ASC ## Ändrar helbredds (dubbel byte) engelska bokstäver eller katakana inom en teckensträng till tecken med halvt breddsteg (enkel byte) -BAHTTEXT = BAHTTEXT ## Omvandlar ett tal till text med valutaformatet ß (baht) -CHAR = TECKENKOD ## Returnerar tecknet som anges av kod -CLEAN = STÄDA ## Tar bort alla icke utskrivbara tecken i en text -CODE = KOD ## Returnerar en numerisk kod för det första tecknet i en textsträng -CONCATENATE = SAMMANFOGA ## Sammanfogar flera textdelar till en textsträng -DOLLAR = VALUTA ## Omvandlar ett tal till text med valutaformat -EXACT = EXAKT ## Kontrollerar om två textvärden är identiska -FIND = HITTA ## Hittar en text i en annan (skiljer på gemener och versaler) -FINDB = HITTAB ## Hittar en text i en annan (skiljer på gemener och versaler) -FIXED = FASTTAL ## Formaterar ett tal som text med ett fast antal decimaler -JIS = JIS ## Ändrar halvbredds (enkel byte) engelska bokstäver eller katakana inom en teckensträng till tecken med helt breddsteg (dubbel byte) -LEFT = VÄNSTER ## Returnerar tecken längst till vänster i en sträng -LEFTB = VÄNSTERB ## Returnerar tecken längst till vänster i en sträng -LEN = LÄNGD ## Returnerar antalet tecken i en textsträng -LENB = LÄNGDB ## Returnerar antalet tecken i en textsträng -LOWER = GEMENER ## Omvandlar text till gemener -MID = EXTEXT ## Returnerar angivet antal tecken från en text med början vid den position som du anger -MIDB = EXTEXTB ## Returnerar angivet antal tecken från en text med början vid den position som du anger -PHONETIC = PHONETIC ## Returnerar de fonetiska (furigana) tecknen i en textsträng -PROPER = INITIAL ## Ändrar första bokstaven i varje ord i ett textvärde till versal -REPLACE = ERSÄTT ## Ersätter tecken i text -REPLACEB = ERSÄTTB ## Ersätter tecken i text -REPT = REP ## Upprepar en text ett bestämt antal gånger -RIGHT = HÖGER ## Returnerar tecken längst till höger i en sträng -RIGHTB = HÖGERB ## Returnerar tecken längst till höger i en sträng -SEARCH = SÖK ## Hittar ett textvärde i ett annat (skiljer inte på gemener och versaler) -SEARCHB = SÖKB ## Hittar ett textvärde i ett annat (skiljer inte på gemener och versaler) -SUBSTITUTE = BYT.UT ## Ersätter gammal text med ny text i en textsträng -T = T ## Omvandlar argumenten till text -TEXT = TEXT ## Formaterar ett tal och omvandlar det till text -TRIM = RENSA ## Tar bort blanksteg från text -UPPER = VERSALER ## Omvandlar text till versaler -VALUE = TEXTNUM ## Omvandlar ett textargument till ett tal +BAHTTEXT = BAHTTEXT +CHAR = TECKENKOD +CLEAN = STÄDA +CODE = KOD +CONCAT = SAMMAN +DOLLAR = VALUTA +EXACT = EXAKT +FIND = HITTA +FIXED = FASTTAL +LEFT = VÄNSTER +LEN = LÄNGD +LOWER = GEMENER +MID = EXTEXT +NUMBERVALUE = TALVÄRDE +PROPER = INITIAL +REPLACE = ERSÄTT +REPT = REP +RIGHT = HÖGER +SEARCH = SÖK +SUBSTITUTE = BYT.UT +T = T +TEXT = TEXT +TEXTJOIN = TEXTJOIN +THAIDIGIT = THAISIFFRA +THAINUMSOUND = THAITALLJUD +THAINUMSTRING = THAITALSTRÄNG +THAISTRINGLENGTH = THAISTRÄNGLÄNGD +TRIM = RENSA +UNICHAR = UNITECKENKOD +UNICODE = UNICODE +UPPER = VERSALER +VALUE = TEXTNUM + +## +## Webbfunktioner (Web Functions) +## +ENCODEURL = KODAWEBBADRESS +FILTERXML = FILTRERAXML +WEBSERVICE = WEBBTJÄNST + +## +## Kompatibilitetsfunktioner (Compatibility Functions) +## +BETADIST = BETAFÖRD +BETAINV = BETAINV +BINOMDIST = BINOMFÖRD +CEILING = RUNDA.UPP +CHIDIST = CHI2FÖRD +CHIINV = CHI2INV +CHITEST = CHI2TEST +CONCATENATE = SAMMANFOGA +CONFIDENCE = KONFIDENS +COVAR = KOVAR +CRITBINOM = KRITBINOM +EXPONDIST = EXPONFÖRD +FDIST = FFÖRD +FINV = FINV +FLOOR = RUNDA.NER +FORECAST = PREDIKTION +FTEST = FTEST +GAMMADIST = GAMMAFÖRD +GAMMAINV = GAMMAINV +HYPGEOMDIST = HYPGEOMFÖRD +LOGINV = LOGINV +LOGNORMDIST = LOGNORMFÖRD +MODE = TYPVÄRDE +NEGBINOMDIST = NEGBINOMFÖRD +NORMDIST = NORMFÖRD +NORMINV = NORMINV +NORMSDIST = NORMSFÖRD +NORMSINV = NORMSINV +PERCENTILE = PERCENTIL +PERCENTRANK = PROCENTRANG +POISSON = POISSON +QUARTILE = KVARTIL +RANK = RANG +STDEV = STDAV +STDEVP = STDAVP +TDIST = TFÖRD +TINV = TINV +TTEST = TTEST +VAR = VARIANS +VARP = VARIANSP +WEIBULL = WEIBULL +ZTEST = ZTEST diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/tr/config b/PhpOffice/PhpSpreadsheet/Calculation/locale/tr/config old mode 100755 new mode 100644 index 266e000..63d22fd --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/tr/config +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/tr/config @@ -1,24 +1,20 @@ +############################################################ ## -## PhpSpreadsheet +## PhpSpreadsheet - locale settings ## +## Türkçe (Turkish) +## +############################################################ -ArgumentSeparator = ; - +ArgumentSeparator = ; ## -## (For future use) +## Error Codes ## -currencySymbol = YTL - - -## -## Excel Error Codes (For future use) - -## -NULL = #BOŞ! -DIV0 = #SAYI/0! -VALUE = #DEĞER! -REF = #BAŞV! -NAME = #AD? -NUM = #SAYI! -NA = #YOK +NULL = #BOŞ! +DIV0 = #SAYI/0! +VALUE = #DEĞER! +REF = #BAŞV! +NAME = #AD? +NUM = #SAYI! +NA = #YOK diff --git a/PhpOffice/PhpSpreadsheet/Calculation/locale/tr/functions b/PhpOffice/PhpSpreadsheet/Calculation/locale/tr/functions old mode 100755 new mode 100644 index f03563a..f872274 --- a/PhpOffice/PhpSpreadsheet/Calculation/locale/tr/functions +++ b/PhpOffice/PhpSpreadsheet/Calculation/locale/tr/functions @@ -1,416 +1,537 @@ +############################################################ ## -## PhpSpreadsheet -## -## Data in this file derived from https://www.excel-function-translation.com/ +## PhpSpreadsheet - function name translations ## +## Türkçe (Turkish) ## +############################################################ ## -## Add-in and Automation functions Eklenti ve Otomasyon fonksiyonları +## Küp işlevleri (Cube Functions) ## -GETPIVOTDATA = ÖZETVERİAL ## Bir Özet Tablo raporunda saklanan verileri verir. - +CUBEKPIMEMBER = KÜPKPIÜYESİ +CUBEMEMBER = KÜPÜYESİ +CUBEMEMBERPROPERTY = KÜPÜYEÖZELLİĞİ +CUBERANKEDMEMBER = DERECELİKÜPÜYESİ +CUBESET = KÜPKÜMESİ +CUBESETCOUNT = KÜPKÜMESAYISI +CUBEVALUE = KÜPDEĞERİ ## -## Cube functions Küp işlevleri +## Veritabanı işlevleri (Database Functions) ## -CUBEKPIMEMBER = KÜPKPIÜYE ## Kilit performans göstergesi (KPI-Key Performance Indicator) adını, özelliğini ve ölçüsünü verir ve hücredeki ad ve özelliği gösterir. KPI, bir kurumun performansını izlemek için kullanılan aylık brüt kâr ya da üç aylık çalışan giriş çıkışları gibi ölçülebilen bir birimdir. -CUBEMEMBER = KÜPÜYE ## Bir küp hiyerarşisinde bir üyeyi veya kaydı verir. Üye veya kaydın küpte varolduğunu doğrulamak için kullanılır. -CUBEMEMBERPROPERTY = KÜPÜYEÖZELLİĞİ ## Bir küpte bir üyenin özelliğinin değerini verir. Küp içinde üye adının varlığını doğrulamak ve bu üyenin belli özelliklerini getirmek için kullanılır. -CUBERANKEDMEMBER = KÜPÜYESIRASI ## Bir küme içindeki üyenin derecesini veya kaçıncı olduğunu verir. En iyi satış elemanı, veya en iyi on öğrenci gibi bir kümedeki bir veya daha fazla öğeyi getirmek için kullanılır. -CUBESET = KÜPKÜME ## Kümeyi oluşturan ve ardından bu kümeyi Microsoft Office Excel'e getiren sunucudaki küpe küme ifadelerini göndererek hesaplanan üye veya kayıt kümesini tanımlar. -CUBESETCOUNT = KÜPKÜMESAY ## Bir kümedeki öğelerin sayısını getirir. -CUBEVALUE = KÜPDEĞER ## Bir küpten toplam değeri getirir. - +DAVERAGE = VSEÇORT +DCOUNT = VSEÇSAY +DCOUNTA = VSEÇSAYDOLU +DGET = VAL +DMAX = VSEÇMAK +DMIN = VSEÇMİN +DPRODUCT = VSEÇÇARP +DSTDEV = VSEÇSTDSAPMA +DSTDEVP = VSEÇSTDSAPMAS +DSUM = VSEÇTOPLA +DVAR = VSEÇVAR +DVARP = VSEÇVARS ## -## Database functions Veritabanı işlevleri +## Tarih ve saat işlevleri (Date & Time Functions) ## -DAVERAGE = VSEÇORT ## Seçili veritabanı girdilerinin ortalamasını verir. -DCOUNT = VSEÇSAY ## Veritabanında sayı içeren hücre sayısını hesaplar. -DCOUNTA = VSEÇSAYDOLU ## Veritabanındaki boş olmayan hücreleri sayar. -DGET = VAL ## Veritabanından, belirtilen ölçütlerle eşleşen tek bir rapor çıkarır. -DMAX = VSEÇMAK ## Seçili veritabanı girişlerinin en yüksek değerini verir. -DMIN = VSEÇMİN ## Seçili veritabanı girişlerinin en düşük değerini verir. -DPRODUCT = VSEÇÇARP ## Kayıtların belli bir alanında bulunan, bir veritabanındaki ölçütlerle eşleşen değerleri çarpar. -DSTDEV = VSEÇSTDSAPMA ## Seçili veritabanı girişlerinden oluşan bir örneğe dayanarak, standart sapmayı tahmin eder. -DSTDEVP = VSEÇSTDSAPMAS ## Standart sapmayı, seçili veritabanı girişlerinin tüm popülasyonunu esas alarak hesaplar. -DSUM = VSEÇTOPLA ## Kayıtların alan sütununda bulunan, ölçütle eşleşen sayıları toplar. -DVAR = VSEÇVAR ## Seçili veritabanı girişlerinden oluşan bir örneği esas alarak farkı tahmin eder. -DVARP = VSEÇVARS ## Seçili veritabanı girişlerinin tüm popülasyonunu esas alarak farkı hesaplar. - +DATE = TARİH +DATEDIF = ETARİHLİ +DATESTRING = TARİHDİZİ +DATEVALUE = TARİHSAYISI +DAY = GÜN +DAYS = GÜNSAY +DAYS360 = GÜN360 +EDATE = SERİTARİH +EOMONTH = SERİAY +HOUR = SAAT +ISOWEEKNUM = ISOHAFTASAY +MINUTE = DAKİKA +MONTH = AY +NETWORKDAYS = TAMİŞGÜNÜ +NETWORKDAYS.INTL = TAMİŞGÜNÜ.ULUSL +NOW = ŞİMDİ +SECOND = SANİYE +THAIDAYOFWEEK = TAYHAFTANINGÜNÜ +THAIMONTHOFYEAR = TAYYILINAYI +THAIYEAR = TAYYILI +TIME = ZAMAN +TIMEVALUE = ZAMANSAYISI +TODAY = BUGÜN +WEEKDAY = HAFTANINGÜNÜ +WEEKNUM = HAFTASAY +WORKDAY = İŞGÜNÜ +WORKDAY.INTL = İŞGÜNÜ.ULUSL +YEAR = YIL +YEARFRAC = YILORAN ## -## Date and time functions Tarih ve saat işlevleri +## Mühendislik işlevleri (Engineering Functions) ## -DATE = TARİH ## Belirli bir tarihin seri numarasını verir. -DATEVALUE = TARİHSAYISI ## Metin biçimindeki bir tarihi seri numarasına dönüştürür. -DAY = GÜN ## Seri numarasını, ayın bir gününe dönüştürür. -DAYS360 = GÜN360 ## İki tarih arasındaki gün sayısını, 360 günlük yılı esas alarak hesaplar. -EDATE = SERİTARİH ## Başlangıç tarihinden itibaren, belirtilen ay sayısından önce veya sonraki tarihin seri numarasını verir. -EOMONTH = SERİAY ## Belirtilen sayıda ay önce veya sonraki ayın son gününün seri numarasını verir. -HOUR = SAAT ## Bir seri numarasını saate dönüştürür. -MINUTE = DAKİKA ## Bir seri numarasını dakikaya dönüştürür. -MONTH = AY ## Bir seri numarasını aya dönüştürür. -NETWORKDAYS = TAMİŞGÜNÜ ## İki tarih arasındaki tam çalışma günlerinin sayısını verir. -NOW = ŞİMDİ ## Geçerli tarihin ve saatin seri numarasını verir. -SECOND = SANİYE ## Bir seri numarasını saniyeye dönüştürür. -TIME = ZAMAN ## Belirli bir zamanın seri numarasını verir. -TIMEVALUE = ZAMANSAYISI ## Metin biçimindeki zamanı seri numarasına dönüştürür. -TODAY = BUGÜN ## Bugünün tarihini seri numarasına dönüştürür. -WEEKDAY = HAFTANINGÜNÜ ## Bir seri numarasını, haftanın gününe dönüştürür. -WEEKNUM = HAFTASAY ## Dizisel değerini, haftanın yıl içinde bulunduğu konumu sayısal olarak gösteren sayıya dönüştürür. -WORKDAY = İŞGÜNÜ ## Belirtilen sayıda çalışma günü öncesinin ya da sonrasının tarihinin seri numarasını verir. -YEAR = YIL ## Bir seri numarasını yıla dönüştürür. -YEARFRAC = YILORAN ## Başlangıç_tarihi ve bitiş_tarihi arasındaki tam günleri gösteren yıl kesrini verir. - +BESSELI = BESSELI +BESSELJ = BESSELJ +BESSELK = BESSELK +BESSELY = BESSELY +BIN2DEC = BIN2DEC +BIN2HEX = BIN2HEX +BIN2OCT = BIN2OCT +BITAND = BİTVE +BITLSHIFT = BİTSOLAKAYDIR +BITOR = BİTVEYA +BITRSHIFT = BİTSAĞAKAYDIR +BITXOR = BİTÖZELVEYA +COMPLEX = KARMAŞIK +CONVERT = ÇEVİR +DEC2BIN = DEC2BIN +DEC2HEX = DEC2HEX +DEC2OCT = DEC2OCT +DELTA = DELTA +ERF = HATAİŞLEV +ERF.PRECISE = HATAİŞLEV.DUYARLI +ERFC = TÜMHATAİŞLEV +ERFC.PRECISE = TÜMHATAİŞLEV.DUYARLI +GESTEP = BESINIR +HEX2BIN = HEX2BIN +HEX2DEC = HEX2DEC +HEX2OCT = HEX2OCT +IMABS = SANMUTLAK +IMAGINARY = SANAL +IMARGUMENT = SANBAĞ_DEĞİŞKEN +IMCONJUGATE = SANEŞLENEK +IMCOS = SANCOS +IMCOSH = SANCOSH +IMCOT = SANCOT +IMCSC = SANCSC +IMCSCH = SANCSCH +IMDIV = SANBÖL +IMEXP = SANÜS +IMLN = SANLN +IMLOG10 = SANLOG10 +IMLOG2 = SANLOG2 +IMPOWER = SANKUVVET +IMPRODUCT = SANÇARP +IMREAL = SANGERÇEK +IMSEC = SANSEC +IMSECH = SANSECH +IMSIN = SANSIN +IMSINH = SANSINH +IMSQRT = SANKAREKÖK +IMSUB = SANTOPLA +IMSUM = SANÇIKAR +IMTAN = SANTAN +OCT2BIN = OCT2BIN +OCT2DEC = OCT2DEC +OCT2HEX = OCT2HEX ## -## Engineering functions Mühendislik işlevleri +## Finansal işlevler (Financial Functions) ## -BESSELI = BESSELI ## Değiştirilmiş Bessel fonksiyonu In(x)'i verir. -BESSELJ = BESSELJ ## Bessel fonksiyonu Jn(x)'i verir. -BESSELK = BESSELK ## Değiştirilmiş Bessel fonksiyonu Kn(x)'i verir. -BESSELY = BESSELY ## Bessel fonksiyonu Yn(x)'i verir. -BIN2DEC = BIN2DEC ## İkili bir sayıyı, ondalık sayıya dönüştürür. -BIN2HEX = BIN2HEX ## İkili bir sayıyı, onaltılıya dönüştürür. -BIN2OCT = BIN2OCT ## İkili bir sayıyı, sekizliye dönüştürür. -COMPLEX = KARMAŞIK ## Gerçek ve sanal katsayıları, karmaşık sayıya dönüştürür. -CONVERT = ÇEVİR ## Bir sayıyı, bir ölçüm sisteminden bir başka ölçüm sistemine dönüştürür. -DEC2BIN = DEC2BIN ## Ondalık bir sayıyı, ikiliye dönüştürür. -DEC2HEX = DEC2HEX ## Ondalık bir sayıyı, onaltılıya dönüştürür. -DEC2OCT = DEC2OCT ## Ondalık bir sayıyı sekizliğe dönüştürür. -DELTA = DELTA ## İki değerin eşit olup olmadığını sınar. -ERF = HATAİŞLEV ## Hata işlevini verir. -ERFC = TÜMHATAİŞLEV ## Tümleyici hata işlevini verir. -GESTEP = BESINIR ## Bir sayının eşik değerinden büyük olup olmadığını sınar. -HEX2BIN = HEX2BIN ## Onaltılı bir sayıyı ikiliye dönüştürür. -HEX2DEC = HEX2DEC ## Onaltılı bir sayıyı ondalığa dönüştürür. -HEX2OCT = HEX2OCT ## Onaltılı bir sayıyı sekizliğe dönüştürür. -IMABS = SANMUTLAK ## Karmaşık bir sayının mutlak değerini (modül) verir. -IMAGINARY = SANAL ## Karmaşık bir sayının sanal katsayısını verir. -IMARGUMENT = SANBAĞ_DEĞİŞKEN ## Radyanlarla belirtilen bir açı olan teta bağımsız değişkenini verir. -IMCONJUGATE = SANEŞLENEK ## Karmaşık bir sayının karmaşık eşleniğini verir. -IMCOS = SANCOS ## Karmaşık bir sayının kosinüsünü verir. -IMDIV = SANBÖL ## İki karmaşık sayının bölümünü verir. -IMEXP = SANÜS ## Karmaşık bir sayının üssünü verir. -IMLN = SANLN ## Karmaşık bir sayının doğal logaritmasını verir. -IMLOG10 = SANLOG10 ## Karmaşık bir sayının, 10 tabanında logaritmasını verir. -IMLOG2 = SANLOG2 ## Karmaşık bir sayının 2 tabanında logaritmasını verir. -IMPOWER = SANÜSSÜ ## Karmaşık bir sayıyı, bir tamsayı üssüne yükseltilmiş olarak verir. -IMPRODUCT = SANÇARP ## Karmaşık sayıların çarpımını verir. -IMREAL = SANGERÇEK ## Karmaşık bir sayının, gerçek katsayısını verir. -IMSIN = SANSIN ## Karmaşık bir sayının sinüsünü verir. -IMSQRT = SANKAREKÖK ## Karmaşık bir sayının karekökünü verir. -IMSUB = SANÇIKAR ## İki karmaşık sayının farkını verir. -IMSUM = SANTOPLA ## Karmaşık sayıların toplamını verir. -OCT2BIN = OCT2BIN ## Sekizli bir sayıyı ikiliye dönüştürür. -OCT2DEC = OCT2DEC ## Sekizli bir sayıyı ondalığa dönüştürür. -OCT2HEX = OCT2HEX ## Sekizli bir sayıyı onaltılıya dönüştürür. - +ACCRINT = GERÇEKFAİZ +ACCRINTM = GERÇEKFAİZV +AMORDEGRC = AMORDEGRC +AMORLINC = AMORLINC +COUPDAYBS = KUPONGÜNBD +COUPDAYS = KUPONGÜN +COUPDAYSNC = KUPONGÜNDSK +COUPNCD = KUPONGÜNSKT +COUPNUM = KUPONSAYI +COUPPCD = KUPONGÜNÖKT +CUMIPMT = TOPÖDENENFAİZ +CUMPRINC = TOPANAPARA +DB = AZALANBAKİYE +DDB = ÇİFTAZALANBAKİYE +DISC = İNDİRİM +DOLLARDE = LİRAON +DOLLARFR = LİRAKES +DURATION = SÜRE +EFFECT = ETKİN +FV = GD +FVSCHEDULE = GDPROGRAM +INTRATE = FAİZORANI +IPMT = FAİZTUTARI +IRR = İÇ_VERİM_ORANI +ISPMT = ISPMT +MDURATION = MSÜRE +MIRR = D_İÇ_VERİM_ORANI +NOMINAL = NOMİNAL +NPER = TAKSİT_SAYISI +NPV = NBD +ODDFPRICE = TEKYDEĞER +ODDFYIELD = TEKYÖDEME +ODDLPRICE = TEKSDEĞER +ODDLYIELD = TEKSÖDEME +PDURATION = PSÜRE +PMT = DEVRESEL_ÖDEME +PPMT = ANA_PARA_ÖDEMESİ +PRICE = DEĞER +PRICEDISC = DEĞERİND +PRICEMAT = DEĞERVADE +PV = BD +RATE = FAİZ_ORANI +RECEIVED = GETİRİ +RRI = GERÇEKLEŞENYATIRIMGETİRİSİ +SLN = DA +SYD = YAT +TBILLEQ = HTAHEŞ +TBILLPRICE = HTAHDEĞER +TBILLYIELD = HTAHÖDEME +VDB = DAB +XIRR = AİÇVERİMORANI +XNPV = ANBD +YIELD = ÖDEME +YIELDDISC = ÖDEMEİND +YIELDMAT = ÖDEMEVADE ## -## Financial functions Finansal fonksiyonlar +## Bilgi işlevleri (Information Functions) ## -ACCRINT = GERÇEKFAİZ ## Dönemsel faiz ödeyen hisse senedine ilişkin tahakkuk eden faizi getirir. -ACCRINTM = GERÇEKFAİZV ## Vadesinde ödeme yapan bir tahvilin tahakkuk etmiş faizini verir. -AMORDEGRC = AMORDEGRC ## Yıpranma katsayısı kullanarak her hesap döneminin değer kaybını verir. -AMORLINC = AMORLINC ## Her hesap dönemi içindeki yıpranmayı verir. -COUPDAYBS = KUPONGÜNBD ## Kupon süresinin başlangıcından alış tarihine kadar olan süredeki gün sayısını verir. -COUPDAYS = KUPONGÜN ## Kupon süresindeki, gün sayısını, alış tarihini de içermek üzere, verir. -COUPDAYSNC = KUPONGÜNDSK ## Alış tarihinden bir sonraki kupon tarihine kadar olan gün sayısını verir. -COUPNCD = KUPONGÜNSKT ## Alış tarihinden bir sonraki kupon tarihini verir. -COUPNUM = KUPONSAYI ## Alış tarihiyle vade tarihi arasında ödenecek kuponların sayısını verir. -COUPPCD = KUPONGÜNÖKT ## Alış tarihinden bir önceki kupon tarihini verir. -CUMIPMT = AİÇVERİMORANI ## İki dönem arasında ödenen kümülatif faizi verir. -CUMPRINC = ANA_PARA_ÖDEMESİ ## İki dönem arasında bir borç üzerine ödenen birikimli temeli verir. -DB = AZALANBAKİYE ## Bir malın belirtilen bir süre içindeki yıpranmasını, sabit azalan bakiye yöntemini kullanarak verir. -DDB = ÇİFTAZALANBAKİYE ## Bir malın belirtilen bir süre içindeki yıpranmasını, çift azalan bakiye yöntemi ya da sizin belirttiğiniz başka bir yöntemi kullanarak verir. -DISC = İNDİRİM ## Bir tahvilin indirim oranını verir. -DOLLARDE = LİRAON ## Kesir olarak tanımlanmış lira fiyatını, ondalık sayı olarak tanımlanmış lira fiyatına dönüştürür. -DOLLARFR = LİRAKES ## Ondalık sayı olarak tanımlanmış lira fiyatını, kesir olarak tanımlanmış lira fiyatına dönüştürür. -DURATION = SÜRE ## Belli aralıklarla faiz ödemesi yapan bir tahvilin yıllık süresini verir. -EFFECT = ETKİN ## Efektif yıllık faiz oranını verir. -FV = ANBD ## Bir yatırımın gelecekteki değerini verir. -FVSCHEDULE = GDPROGRAM ## Bir seri birleşik faiz oranı uyguladıktan sonra, bir başlangıçtaki anaparanın gelecekteki değerini verir. -INTRATE = FAİZORANI ## Tam olarak yatırım yapılmış bir tahvilin faiz oranını verir. -IPMT = FAİZTUTARI ## Bir yatırımın verilen bir süre için faiz ödemesini verir. -IRR = İÇ_VERİM_ORANI ## Bir para akışı serisi için, iç verim oranını verir. -ISPMT = ISPMT ## Yatırımın belirli bir dönemi boyunca ödenen faizi hesaplar. -MDURATION = MSÜRE ## Varsayılan par değeri 10.000.000 lira olan bir tahvil için Macauley değiştirilmiş süreyi verir. -MIRR = D_İÇ_VERİM_ORANI ## Pozitif ve negatif para akışlarının farklı oranlarda finanse edildiği durumlarda, iç verim oranını verir. -NOMINAL = NOMİNAL ## Yıllık nominal faiz oranını verir. -NPER = DÖNEM_SAYISI ## Bir yatırımın dönem sayısını verir. -NPV = NBD ## Bir yatırımın bugünkü net değerini, bir dönemsel para akışları serisine ve bir indirim oranına bağlı olarak verir. -ODDFPRICE = TEKYDEĞER ## Tek bir ilk dönemi olan bir tahvilin değerini, her 100.000.000 lirada bir verir. -ODDFYIELD = TEKYÖDEME ## Tek bir ilk dönemi olan bir tahvilin ödemesini verir. -ODDLPRICE = TEKSDEĞER ## Tek bir son dönemi olan bir tahvilin fiyatını her 10.000.000 lirada bir verir. -ODDLYIELD = TEKSÖDEME ## Tek bir son dönemi olan bir tahvilin ödemesini verir. -PMT = DEVRESEL_ÖDEME ## Bir yıllık dönemsel ödemeyi verir. -PPMT = ANA_PARA_ÖDEMESİ ## Verilen bir süre için, bir yatırımın anaparasına dayanan ödemeyi verir. -PRICE = DEĞER ## Dönemsel faiz ödeyen bir tahvilin fiyatını 10.000.00 liralık değer başına verir. -PRICEDISC = DEĞERİND ## İndirimli bir tahvilin fiyatını 10.000.000 liralık nominal değer başına verir. -PRICEMAT = DEĞERVADE ## Faizini vade sonunda ödeyen bir tahvilin fiyatını 10.000.000 nominal değer başına verir. -PV = BD ## Bir yatırımın bugünkü değerini verir. -RATE = FAİZ_ORANI ## Bir yıllık dönem başına düşen faiz oranını verir. -RECEIVED = GETİRİ ## Tam olarak yatırılmış bir tahvilin vadesinin bitiminde alınan miktarı verir. -SLN = DA ## Bir malın bir dönem içindeki doğrusal yıpranmasını verir. -SYD = YAT ## Bir malın belirli bir dönem için olan amortismanını verir. -TBILLEQ = HTAHEŞ ## Bir Hazine bonosunun bono eşdeğeri ödemesini verir. -TBILLPRICE = HTAHDEĞER ## Bir Hazine bonosunun değerini, 10.000.000 liralık nominal değer başına verir. -TBILLYIELD = HTAHÖDEME ## Bir Hazine bonosunun ödemesini verir. -VDB = DAB ## Bir malın amortismanını, belirlenmiş ya da kısmi bir dönem için, bir azalan bakiye yöntemi kullanarak verir. -XIRR = AİÇVERİMORANI ## Dönemsel olması gerekmeyen bir para akışları programı için, iç verim oranını verir. -XNPV = ANBD ## Dönemsel olması gerekmeyen bir para akışları programı için, bugünkü net değeri verir. -YIELD = ÖDEME ## Belirli aralıklarla faiz ödeyen bir tahvilin ödemesini verir. -YIELDDISC = ÖDEMEİND ## İndirimli bir tahvilin yıllık ödemesini verir; örneğin, bir Hazine bonosunun. -YIELDMAT = ÖDEMEVADE ## Vadesinin bitiminde faiz ödeyen bir tahvilin yıllık ödemesini verir. - +CELL = HÜCRE +ERROR.TYPE = HATA.TİPİ +INFO = BİLGİ +ISBLANK = EBOŞSA +ISERR = EHATA +ISERROR = EHATALIYSA +ISEVEN = ÇİFTMİ +ISFORMULA = EFORMÜLSE +ISLOGICAL = EMANTIKSALSA +ISNA = EYOKSA +ISNONTEXT = EMETİNDEĞİLSE +ISNUMBER = ESAYIYSA +ISODD = TEKMİ +ISREF = EREFSE +ISTEXT = EMETİNSE +N = S +NA = YOKSAY +SHEET = SAYFA +SHEETS = SAYFALAR +TYPE = TÜR ## -## Information functions Bilgi fonksiyonları +## Mantıksal işlevler (Logical Functions) ## -CELL = HÜCRE ## Bir hücrenin biçimlendirmesi, konumu ya da içeriği hakkında bilgi verir. -ERROR.TYPE = HATA.TİPİ ## Bir hata türüne ilişkin sayıları verir. -INFO = BİLGİ ## Geçerli işletim ortamı hakkında bilgi verir. -ISBLANK = EBOŞSA ## Değer boşsa, DOĞRU verir. -ISERR = EHATA ## Değer, #YOK dışındaki bir hata değeriyse, DOĞRU verir. -ISERROR = EHATALIYSA ## Değer, herhangi bir hata değeriyse, DOĞRU verir. -ISEVEN = ÇİFTTİR ## Sayı çiftse, DOĞRU verir. -ISLOGICAL = EMANTIKSALSA ## Değer, mantıksal bir değerse, DOĞRU verir. -ISNA = EYOKSA ## Değer, #YOK hata değeriyse, DOĞRU verir. -ISNONTEXT = EMETİNDEĞİLSE ## Değer, metin değilse, DOĞRU verir. -ISNUMBER = ESAYIYSA ## Değer, bir sayıysa, DOĞRU verir. -ISODD = TEKTİR ## Sayı tekse, DOĞRU verir. -ISREF = EREFSE ## Değer bir başvuruysa, DOĞRU verir. -ISTEXT = EMETİNSE ## Değer bir metinse DOĞRU verir. -N = N ## Sayıya dönüştürülmüş bir değer verir. -NA = YOKSAY ## #YOK hata değerini verir. -TYPE = TİP ## Bir değerin veri türünü belirten bir sayı verir. - +AND = VE +FALSE = YANLIŞ +IF = EĞER +IFERROR = EĞERHATA +IFNA = EĞERYOKSA +IFS = ÇOKEĞER +NOT = DEĞİL +OR = YADA +SWITCH = İLKEŞLEŞEN +TRUE = DOĞRU +XOR = ÖZELVEYA ## -## Logical functions Mantıksal fonksiyonlar +## Arama ve başvuru işlevleri (Lookup & Reference Functions) ## -AND = VE ## Bütün bağımsız değişkenleri DOĞRU ise, DOĞRU verir. -FALSE = YANLIŞ ## YANLIŞ mantıksal değerini verir. -IF = EĞER ## Gerçekleştirilecek bir mantıksal sınama belirtir. -IFERROR = EĞERHATA ## Formül hatalıysa belirttiğiniz değeri verir; bunun dışındaki durumlarda formülün sonucunu verir. -NOT = DEĞİL ## Bağımsız değişkeninin mantığını tersine çevirir. -OR = YADA ## Bağımsız değişkenlerden herhangi birisi DOĞRU ise, DOĞRU verir. -TRUE = DOĞRU ## DOĞRU mantıksal değerini verir. - +ADDRESS = ADRES +AREAS = ALANSAY +CHOOSE = ELEMAN +COLUMN = SÜTUN +COLUMNS = SÜTUNSAY +FORMULATEXT = FORMÜLMETNİ +GETPIVOTDATA = ÖZETVERİAL +HLOOKUP = YATAYARA +HYPERLINK = KÖPRÜ +INDEX = İNDİS +INDIRECT = DOLAYLI +LOOKUP = ARA +MATCH = KAÇINCI +OFFSET = KAYDIR +ROW = SATIR +ROWS = SATIRSAY +RTD = GZV +TRANSPOSE = DEVRİK_DÖNÜŞÜM +VLOOKUP = DÜŞEYARA ## -## Lookup and reference functions Arama ve Başvuru fonksiyonları +## Matematik ve trigonometri işlevleri (Math & Trig Functions) ## -ADDRESS = ADRES ## Bir başvuruyu, çalışma sayfasındaki tek bir hücreye metin olarak verir. -AREAS = ALANSAY ## Renvoie le nombre de zones dans une référence. -CHOOSE = ELEMAN ## Değerler listesinden bir değer seçer. -COLUMN = SÜTUN ## Bir başvurunun sütun sayısını verir. -COLUMNS = SÜTUNSAY ## Bir başvurudaki sütunların sayısını verir. -HLOOKUP = YATAYARA ## Bir dizinin en üst satırına bakar ve belirtilen hücrenin değerini verir. -HYPERLINK = KÖPRÜ ## Bir ağ sunucusunda, bir intranette ya da Internet'te depolanan bir belgeyi açan bir kısayol ya da atlama oluşturur. -INDEX = İNDİS ## Başvurudan veya diziden bir değer seçmek için, bir dizin kullanır. -INDIRECT = DOLAYLI ## Metin değeriyle belirtilen bir başvuru verir. -LOOKUP = ARA ## Bir vektördeki veya dizideki değerleri arar. -MATCH = KAÇINCI ## Bir başvurudaki veya dizideki değerleri arar. -OFFSET = KAYDIR ## Verilen bir başvurudan, bir başvuru kaydırmayı verir. -ROW = SATIR ## Bir başvurunun satır sayısını verir. -ROWS = SATIRSAY ## Bir başvurudaki satırların sayısını verir. -RTD = RTD ## COM otomasyonunu destekleyen programdan gerçek zaman verileri alır. -TRANSPOSE = DEVRİK_DÖNÜŞÜM ## Bir dizinin devrik dönüşümünü verir. -VLOOKUP = DÜŞEYARA ## Bir dizinin ilk sütununa bakar ve bir hücrenin değerini vermek için satır boyunca hareket eder. - +ABS = MUTLAK +ACOS = ACOS +ACOSH = ACOSH +ACOT = ACOT +ACOTH = ACOTH +AGGREGATE = TOPLAMA +ARABIC = ARAP +ASIN = ASİN +ASINH = ASİNH +ATAN = ATAN +ATAN2 = ATAN2 +ATANH = ATANH +BASE = TABAN +CEILING.MATH = TAVANAYUVARLA.MATEMATİK +CEILING.PRECISE = TAVANAYUVARLA.DUYARLI +COMBIN = KOMBİNASYON +COMBINA = KOMBİNASYONA +COS = COS +COSH = COSH +COT = COT +COTH = COTH +CSC = CSC +CSCH = CSCH +DECIMAL = ONDALIK +DEGREES = DERECE +ECMA.CEILING = ECMA.TAVAN +EVEN = ÇİFT +EXP = ÜS +FACT = ÇARPINIM +FACTDOUBLE = ÇİFTFAKTÖR +FLOOR.MATH = TABANAYUVARLA.MATEMATİK +FLOOR.PRECISE = TABANAYUVARLA.DUYARLI +GCD = OBEB +INT = TAMSAYI +ISO.CEILING = ISO.TAVAN +LCM = OKEK +LN = LN +LOG = LOG +LOG10 = LOG10 +MDETERM = DETERMİNANT +MINVERSE = DİZEY_TERS +MMULT = DÇARP +MOD = MOD +MROUND = KYUVARLA +MULTINOMIAL = ÇOKTERİMLİ +MUNIT = BİRİMMATRİS +ODD = TEK +PI = Pİ +POWER = KUVVET +PRODUCT = ÇARPIM +QUOTIENT = BÖLÜM +RADIANS = RADYAN +RAND = S_SAYI_ÜRET +RANDBETWEEN = RASTGELEARADA +ROMAN = ROMEN +ROUND = YUVARLA +ROUNDBAHTDOWN = BAHTAŞAĞIYUVARLA +ROUNDBAHTUP = BAHTYUKARIYUVARLA +ROUNDDOWN = AŞAĞIYUVARLA +ROUNDUP = YUKARIYUVARLA +SEC = SEC +SECH = SECH +SERIESSUM = SERİTOPLA +SIGN = İŞARET +SIN = SİN +SINH = SİNH +SQRT = KAREKÖK +SQRTPI = KAREKÖKPİ +SUBTOTAL = ALTTOPLAM +SUM = TOPLA +SUMIF = ETOPLA +SUMIFS = ÇOKETOPLA +SUMPRODUCT = TOPLA.ÇARPIM +SUMSQ = TOPKARE +SUMX2MY2 = TOPX2EY2 +SUMX2PY2 = TOPX2AY2 +SUMXMY2 = TOPXEY2 +TAN = TAN +TANH = TANH +TRUNC = NSAT ## -## Math and trigonometry functions Matematik ve trigonometri fonksiyonları +## İstatistik işlevleri (Statistical Functions) ## -ABS = MUTLAK ## Bir sayının mutlak değerini verir. -ACOS = ACOS ## Bir sayının ark kosinüsünü verir. -ACOSH = ACOSH ## Bir sayının ters hiperbolik kosinüsünü verir. -ASIN = ASİN ## Bir sayının ark sinüsünü verir. -ASINH = ASİNH ## Bir sayının ters hiperbolik sinüsünü verir. -ATAN = ATAN ## Bir sayının ark tanjantını verir. -ATAN2 = ATAN2 ## Ark tanjantı, x- ve y- koordinatlarından verir. -ATANH = ATANH ## Bir sayının ters hiperbolik tanjantını verir. -CEILING = TAVANAYUVARLA ## Bir sayıyı, en yakın tamsayıya ya da en yakın katına yuvarlar. -COMBIN = KOMBİNASYON ## Verilen sayıda öğenin kombinasyon sayısını verir. -COS = COS ## Bir sayının kosinüsünü verir. -COSH = COSH ## Bir sayının hiperbolik kosinüsünü verir. -DEGREES = DERECE ## Radyanları dereceye dönüştürür. -EVEN = ÇİFT ## Bir sayıyı, en yakın daha büyük çift tamsayıya yuvarlar. -EXP = ÜS ## e'yi, verilen bir sayının üssüne yükseltilmiş olarak verir. -FACT = ÇARPINIM ## Bir sayının faktörünü verir. -FACTDOUBLE = ÇİFTFAKTÖR ## Bir sayının çift çarpınımını verir. -FLOOR = TABANAYUVARLA ## Bir sayıyı, daha küçük sayıya, sıfıra yakınsayarak yuvarlar. -GCD = OBEB ## En büyük ortak böleni verir. -INT = TAMSAYI ## Bir sayıyı aşağıya doğru en yakın tamsayıya yuvarlar. -LCM = OKEK ## En küçük ortak katı verir. -LN = LN ## Bir sayının doğal logaritmasını verir. -LOG = LOG ## Bir sayının, belirtilen bir tabandaki logaritmasını verir. -LOG10 = LOG10 ## Bir sayının 10 tabanında logaritmasını verir. -MDETERM = DETERMİNANT ## Bir dizinin dizey determinantını verir. -MINVERSE = DİZEY_TERS ## Bir dizinin dizey tersini verir. -MMULT = DÇARP ## İki dizinin dizey çarpımını verir. -MOD = MODÜLO ## Bölmeden kalanı verir. -MROUND = KYUVARLA ## İstenen kata yuvarlanmış bir sayı verir. -MULTINOMIAL = ÇOKTERİMLİ ## Bir sayılar kümesinin çok terimlisini verir. -ODD = TEK ## Bir sayıyı en yakın daha büyük tek sayıya yuvarlar. -PI = Pİ ## Pi değerini verir. -POWER = KUVVET ## Bir üsse yükseltilmiş sayının sonucunu verir. -PRODUCT = ÇARPIM ## Bağımsız değişkenlerini çarpar. -QUOTIENT = BÖLÜM ## Bir bölme işleminin tamsayı kısmını verir. -RADIANS = RADYAN ## Dereceleri radyanlara dönüştürür. -RAND = S_SAYI_ÜRET ## 0 ile 1 arasında rastgele bir sayı verir. -RANDBETWEEN = RASTGELEARALIK ## Belirttiğiniz sayılar arasında rastgele bir sayı verir. -ROMAN = ROMEN ## Bir normal rakamı, metin olarak, romen rakamına çevirir. -ROUND = YUVARLA ## Bir sayıyı, belirtilen basamak sayısına yuvarlar. -ROUNDDOWN = AŞAĞIYUVARLA ## Bir sayıyı, daha küçük sayıya, sıfıra yakınsayarak yuvarlar. -ROUNDUP = YUKARIYUVARLA ## Bir sayıyı daha büyük sayıya, sıfırdan ıraksayarak yuvarlar. -SERIESSUM = SERİTOPLA ## Bir üs serisinin toplamını, formüle bağlı olarak verir. -SIGN = İŞARET ## Bir sayının işaretini verir. -SIN = SİN ## Verilen bir açının sinüsünü verir. -SINH = SİNH ## Bir sayının hiperbolik sinüsünü verir. -SQRT = KAREKÖK ## Pozitif bir karekök verir. -SQRTPI = KAREKÖKPİ ## (* Pi sayısının) kare kökünü verir. -SUBTOTAL = ALTTOPLAM ## Bir listedeki ya da veritabanındaki bir alt toplamı verir. -SUM = TOPLA ## Bağımsız değişkenlerini toplar. -SUMIF = ETOPLA ## Verilen ölçütle belirlenen hücreleri toplar. -SUMIFS = SUMIFS ## Bir aralıktaki, birden fazla ölçüte uyan hücreleri ekler. -SUMPRODUCT = TOPLA.ÇARPIM ## İlişkili dizi bileşenlerinin çarpımlarının toplamını verir. -SUMSQ = TOPKARE ## Bağımsız değişkenlerin karelerinin toplamını verir. -SUMX2MY2 = TOPX2EY2 ## İki dizideki ilişkili değerlerin farkının toplamını verir. -SUMX2PY2 = TOPX2AY2 ## İki dizideki ilişkili değerlerin karelerinin toplamının toplamını verir. -SUMXMY2 = TOPXEY2 ## İki dizideki ilişkili değerlerin farklarının karelerinin toplamını verir. -TAN = TAN ## Bir sayının tanjantını verir. -TANH = TANH ## Bir sayının hiperbolik tanjantını verir. -TRUNC = NSAT ## Bir sayının, tamsayı durumuna gelecek şekilde, fazlalıklarını atar. - +AVEDEV = ORTSAP +AVERAGE = ORTALAMA +AVERAGEA = ORTALAMAA +AVERAGEIF = EĞERORTALAMA +AVERAGEIFS = ÇOKEĞERORTALAMA +BETA.DIST = BETA.DAĞ +BETA.INV = BETA.TERS +BINOM.DIST = BİNOM.DAĞ +BINOM.DIST.RANGE = BİNOM.DAĞ.ARALIK +BINOM.INV = BİNOM.TERS +CHISQ.DIST = KİKARE.DAĞ +CHISQ.DIST.RT = KİKARE.DAĞ.SAĞK +CHISQ.INV = KİKARE.TERS +CHISQ.INV.RT = KİKARE.TERS.SAĞK +CHISQ.TEST = KİKARE.TEST +CONFIDENCE.NORM = GÜVENİLİRLİK.NORM +CONFIDENCE.T = GÜVENİLİRLİK.T +CORREL = KORELASYON +COUNT = BAĞ_DEĞ_SAY +COUNTA = BAĞ_DEĞ_DOLU_SAY +COUNTBLANK = BOŞLUKSAY +COUNTIF = EĞERSAY +COUNTIFS = ÇOKEĞERSAY +COVARIANCE.P = KOVARYANS.P +COVARIANCE.S = KOVARYANS.S +DEVSQ = SAPKARE +EXPON.DIST = ÜSTEL.DAĞ +F.DIST = F.DAĞ +F.DIST.RT = F.DAĞ.SAĞK +F.INV = F.TERS +F.INV.RT = F.TERS.SAĞK +F.TEST = F.TEST +FISHER = FISHER +FISHERINV = FISHERTERS +FORECAST.ETS = TAHMİN.ETS +FORECAST.ETS.CONFINT = TAHMİN.ETS.GVNARAL +FORECAST.ETS.SEASONALITY = TAHMİN.ETS.MEVSİMSELLİK +FORECAST.ETS.STAT = TAHMİN.ETS.İSTAT +FORECAST.LINEAR = TAHMİN.DOĞRUSAL +FREQUENCY = SIKLIK +GAMMA = GAMA +GAMMA.DIST = GAMA.DAĞ +GAMMA.INV = GAMA.TERS +GAMMALN = GAMALN +GAMMALN.PRECISE = GAMALN.DUYARLI +GAUSS = GAUSS +GEOMEAN = GEOORT +GROWTH = BÜYÜME +HARMEAN = HARORT +HYPGEOM.DIST = HİPERGEOM.DAĞ +INTERCEPT = KESMENOKTASI +KURT = BASIKLIK +LARGE = BÜYÜK +LINEST = DOT +LOGEST = LOT +LOGNORM.DIST = LOGNORM.DAĞ +LOGNORM.INV = LOGNORM.TERS +MAX = MAK +MAXA = MAKA +MAXIFS = ÇOKEĞERMAK +MEDIAN = ORTANCA +MIN = MİN +MINA = MİNA +MINIFS = ÇOKEĞERMİN +MODE.MULT = ENÇOK_OLAN.ÇOK +MODE.SNGL = ENÇOK_OLAN.TEK +NEGBINOM.DIST = NEGBİNOM.DAĞ +NORM.DIST = NORM.DAĞ +NORM.INV = NORM.TERS +NORM.S.DIST = NORM.S.DAĞ +NORM.S.INV = NORM.S.TERS +PEARSON = PEARSON +PERCENTILE.EXC = YÜZDEBİRLİK.HRC +PERCENTILE.INC = YÜZDEBİRLİK.DHL +PERCENTRANK.EXC = YÜZDERANK.HRC +PERCENTRANK.INC = YÜZDERANK.DHL +PERMUT = PERMÜTASYON +PERMUTATIONA = PERMÜTASYONA +PHI = PHI +POISSON.DIST = POISSON.DAĞ +PROB = OLASILIK +QUARTILE.EXC = DÖRTTEBİRLİK.HRC +QUARTILE.INC = DÖRTTEBİRLİK.DHL +RANK.AVG = RANK.ORT +RANK.EQ = RANK.EŞİT +RSQ = RKARE +SKEW = ÇARPIKLIK +SKEW.P = ÇARPIKLIK.P +SLOPE = EĞİM +SMALL = KÜÇÜK +STANDARDIZE = STANDARTLAŞTIRMA +STDEV.P = STDSAPMA.P +STDEV.S = STDSAPMA.S +STDEVA = STDSAPMAA +STDEVPA = STDSAPMASA +STEYX = STHYX +T.DIST = T.DAĞ +T.DIST.2T = T.DAĞ.2K +T.DIST.RT = T.DAĞ.SAĞK +T.INV = T.TERS +T.INV.2T = T.TERS.2K +T.TEST = T.TEST +TREND = EĞİLİM +TRIMMEAN = KIRPORTALAMA +VAR.P = VAR.P +VAR.S = VAR.S +VARA = VARA +VARPA = VARSA +WEIBULL.DIST = WEIBULL.DAĞ +Z.TEST = Z.TEST ## -## Statistical functions İstatistiksel fonksiyonlar +## Metin işlevleri (Text Functions) ## -AVEDEV = ORTSAP ## Veri noktalarının ortalamalarından mutlak sapmalarının ortalamasını verir. -AVERAGE = ORTALAMA ## Bağımsız değişkenlerinin ortalamasını verir. -AVERAGEA = ORTALAMAA ## Bağımsız değişkenlerinin, sayılar, metin ve mantıksal değerleri içermek üzere ortalamasını verir. -AVERAGEIF = EĞERORTALAMA ## Verili ölçütü karşılayan bir aralıktaki bütün hücrelerin ortalamasını (aritmetik ortalama) hesaplar. -AVERAGEIFS = EĞERLERORTALAMA ## Birden çok ölçüte uyan tüm hücrelerin ortalamasını (aritmetik ortalama) hesaplar. -BETADIST = BETADAĞ ## Beta birikimli dağılım fonksiyonunu verir. -BETAINV = BETATERS ## Belirli bir beta dağılımı için birikimli dağılım fonksiyonunun tersini verir. -BINOMDIST = BİNOMDAĞ ## Tek terimli binom dağılımı olasılığını verir. -CHIDIST = KİKAREDAĞ ## Kikare dağılımın tek kuyruklu olasılığını verir. -CHIINV = KİKARETERS ## Kikare dağılımın kuyruklu olasılığının tersini verir. -CHITEST = KİKARETEST ## Bağımsızlık sınamalarını verir. -CONFIDENCE = GÜVENİRLİK ## Bir popülasyon ortalaması için güvenirlik aralığını verir. -CORREL = KORELASYON ## İki veri kümesi arasındaki bağlantı katsayısını verir. -COUNT = BAĞ_DEĞ_SAY ## Bağımsız değişkenler listesinde kaç tane sayı bulunduğunu sayar. -COUNTA = BAĞ_DEĞ_DOLU_SAY ## Bağımsız değişkenler listesinde kaç tane değer bulunduğunu sayar. -COUNTBLANK = BOŞLUKSAY ## Aralıktaki boş hücre sayısını hesaplar. -COUNTIF = EĞERSAY ## Verilen ölçütlere uyan bir aralık içindeki hücreleri sayar. -COUNTIFS = ÇOKEĞERSAY ## Birden çok ölçüte uyan bir aralık içindeki hücreleri sayar. -COVAR = KOVARYANS ## Eşleştirilmiş sapmaların ortalaması olan kovaryansı verir. -CRITBINOM = KRİTİKBİNOM ## Birikimli binom dağılımının bir ölçüt değerinden küçük veya ölçüt değerine eşit olduğu en küçük değeri verir. -DEVSQ = SAPKARE ## Sapmaların karelerinin toplamını verir. -EXPONDIST = ÜSTELDAĞ ## Üstel dağılımı verir. -FDIST = FDAĞ ## F olasılık dağılımını verir. -FINV = FTERS ## F olasılık dağılımının tersini verir. -FISHER = FISHER ## Fisher dönüşümünü verir. -FISHERINV = FISHERTERS ## Fisher dönüşümünün tersini verir. -FORECAST = TAHMİN ## Bir doğrusal eğilim boyunca bir değer verir. -FREQUENCY = SIKLIK ## Bir sıklık dağılımını, dikey bir dizi olarak verir. -FTEST = FTEST ## Bir F-test'in sonucunu verir. -GAMMADIST = GAMADAĞ ## Gama dağılımını verir. -GAMMAINV = GAMATERS ## Gama kümülatif dağılımının tersini verir. -GAMMALN = GAMALN ## Gama fonksiyonunun (?(x)) doğal logaritmasını verir. -GEOMEAN = GEOORT ## Geometrik ortayı verir. -GROWTH = BÜYÜME ## Üstel bir eğilim boyunca değerler verir. -HARMEAN = HARORT ## Harmonik ortayı verir. -HYPGEOMDIST = HİPERGEOMDAĞ ## Hipergeometrik dağılımı verir. -INTERCEPT = KESMENOKTASI ## Doğrusal çakıştırma çizgisinin kesişme noktasını verir. -KURT = BASIKLIK ## Bir veri kümesinin basıklığını verir. -LARGE = BÜYÜK ## Bir veri kümesinde k. en büyük değeri verir. -LINEST = DOT ## Doğrusal bir eğilimin parametrelerini verir. -LOGEST = LOT ## Üstel bir eğilimin parametrelerini verir. -LOGINV = LOGTERS ## Bir lognormal dağılımının tersini verir. -LOGNORMDIST = LOGNORMDAĞ ## Birikimli lognormal dağılımını verir. -MAX = MAK ## Bir bağımsız değişkenler listesindeki en büyük değeri verir. -MAXA = MAKA ## Bir bağımsız değişkenler listesindeki, sayılar, metin ve mantıksal değerleri içermek üzere, en büyük değeri verir. -MEDIAN = ORTANCA ## Belirtilen sayıların orta değerini verir. -MIN = MİN ## Bir bağımsız değişkenler listesindeki en küçük değeri verir. -MINA = MİNA ## Bir bağımsız değişkenler listesindeki, sayılar, metin ve mantıksal değerleri de içermek üzere, en küçük değeri verir. -MODE = ENÇOK_OLAN ## Bir veri kümesindeki en sık rastlanan değeri verir. -NEGBINOMDIST = NEGBİNOMDAĞ ## Negatif binom dağılımını verir. -NORMDIST = NORMDAĞ ## Normal birikimli dağılımı verir. -NORMINV = NORMTERS ## Normal kümülatif dağılımın tersini verir. -NORMSDIST = NORMSDAĞ ## Standart normal birikimli dağılımı verir. -NORMSINV = NORMSTERS ## Standart normal birikimli dağılımın tersini verir. -PEARSON = PEARSON ## Pearson çarpım moment korelasyon katsayısını verir. -PERCENTILE = YÜZDEBİRLİK ## Bir aralık içerisinde bulunan değerlerin k. frekans toplamını verir. -PERCENTRANK = YÜZDERANK ## Bir veri kümesindeki bir değerin yüzde mertebesini verir. -PERMUT = PERMÜTASYON ## Verilen sayıda nesne için permütasyon sayısını verir. -POISSON = POISSON ## Poisson dağılımını verir. -PROB = OLASILIK ## Bir aralıktaki değerlerin iki sınır arasında olması olasılığını verir. -QUARTILE = DÖRTTEBİRLİK ## Bir veri kümesinin dörtte birliğini verir. -RANK = RANK ## Bir sayılar listesinde bir sayının mertebesini verir. -RSQ = RKARE ## Pearson çarpım moment korelasyon katsayısının karesini verir. -SKEW = ÇARPIKLIK ## Bir dağılımın çarpıklığını verir. -SLOPE = EĞİM ## Doğrusal çakışma çizgisinin eğimini verir. -SMALL = KÜÇÜK ## Bir veri kümesinde k. en küçük değeri verir. -STANDARDIZE = STANDARTLAŞTIRMA ## Normalleştirilmiş bir değer verir. -STDEV = STDSAPMA ## Bir örneğe dayanarak standart sapmayı tahmin eder. -STDEVA = STDSAPMAA ## Standart sapmayı, sayılar, metin ve mantıksal değerleri içermek üzere, bir örneğe bağlı olarak tahmin eder. -STDEVP = STDSAPMAS ## Standart sapmayı, tüm popülasyona bağlı olarak hesaplar. -STDEVPA = STDSAPMASA ## Standart sapmayı, sayılar, metin ve mantıksal değerleri içermek üzere, tüm popülasyona bağlı olarak hesaplar. -STEYX = STHYX ## Regresyondaki her x için tahmini y değerinin standart hatasını verir. -TDIST = TDAĞ ## T-dağılımını verir. -TINV = TTERS ## T-dağılımının tersini verir. -TREND = EĞİLİM ## Doğrusal bir eğilim boyunca değerler verir. -TRIMMEAN = KIRPORTALAMA ## Bir veri kümesinin içinin ortalamasını verir. -TTEST = TTEST ## T-test'le ilişkilendirilmiş olasılığı verir. -VAR = VAR ## Varyansı, bir örneğe bağlı olarak tahmin eder. -VARA = VARA ## Varyansı, sayılar, metin ve mantıksal değerleri içermek üzere, bir örneğe bağlı olarak tahmin eder. -VARP = VARS ## Varyansı, tüm popülasyona dayanarak hesaplar. -VARPA = VARSA ## Varyansı, sayılar, metin ve mantıksal değerleri içermek üzere, tüm popülasyona bağlı olarak hesaplar. -WEIBULL = WEIBULL ## Weibull dağılımını hesaplar. -ZTEST = ZTEST ## Z-testinin tek kuyruklu olasılık değerini hesaplar. - +BAHTTEXT = BAHTMETİN +CHAR = DAMGA +CLEAN = TEMİZ +CODE = KOD +CONCAT = ARALIKBİRLEŞTİR +DOLLAR = LİRA +EXACT = ÖZDEŞ +FIND = BUL +FIXED = SAYIDÜZENLE +ISTHAIDIGIT = TAYRAKAMIYSA +LEFT = SOLDAN +LEN = UZUNLUK +LOWER = KÜÇÜKHARF +MID = PARÇAAL +NUMBERSTRING = SAYIDİZİ +NUMBERVALUE = SAYIDEĞERİ +PHONETIC = SES +PROPER = YAZIM.DÜZENİ +REPLACE = DEĞİŞTİR +REPT = YİNELE +RIGHT = SAĞDAN +SEARCH = MBUL +SUBSTITUTE = YERİNEKOY +T = M +TEXT = METNEÇEVİR +TEXTJOIN = METİNBİRLEŞTİR +THAIDIGIT = TAYRAKAM +THAINUMSOUND = TAYSAYISES +THAINUMSTRING = TAYSAYIDİZE +THAISTRINGLENGTH = TAYDİZEUZUNLUĞU +TRIM = KIRP +UNICHAR = UNICODEKARAKTERİ +UNICODE = UNICODE +UPPER = BÜYÜKHARF +VALUE = SAYIYAÇEVİR ## -## Text functions Metin fonksiyonları +## Metin işlevleri (Web Functions) ## -ASC = ASC ## Bir karakter dizesindeki çift enli (iki bayt) İngilizce harfleri veya katakanayı yarım enli (tek bayt) karakterlerle değiştirir. -BAHTTEXT = BAHTTEXT ## Sayıyı, ß (baht) para birimi biçimini kullanarak metne dönüştürür. -CHAR = DAMGA ## Kod sayısıyla belirtilen karakteri verir. -CLEAN = TEMİZ ## Metindeki bütün yazdırılamaz karakterleri kaldırır. -CODE = KOD ## Bir metin dizesindeki ilk karakter için sayısal bir kod verir. -CONCATENATE = BİRLEŞTİR ## Pek çok metin öğesini bir metin öğesi olarak birleştirir. -DOLLAR = LİRA ## Bir sayıyı YTL (yeni Türk lirası) para birimi biçimini kullanarak metne dönüştürür. -EXACT = ÖZDEŞ ## İki metin değerinin özdeş olup olmadığını anlamak için, değerleri denetler. -FIND = BUL ## Bir metin değerini, bir başkasının içinde bulur (büyük küçük harf duyarlıdır). -FINDB = BULB ## Bir metin değerini, bir başkasının içinde bulur (büyük küçük harf duyarlıdır). -FIXED = SAYIDÜZENLE ## Bir sayıyı, sabit sayıda ondalıkla, metin olarak biçimlendirir. -JIS = JIS ## Bir karakter dizesindeki tek enli (tek bayt) İngilizce harfleri veya katakanayı çift enli (iki bayt) karakterlerle değiştirir. -LEFT = SOL ## Bir metin değerinden en soldaki karakterleri verir. -LEFTB = SOLB ## Bir metin değerinden en soldaki karakterleri verir. -LEN = UZUNLUK ## Bir metin dizesindeki karakter sayısını verir. -LENB = UZUNLUKB ## Bir metin dizesindeki karakter sayısını verir. -LOWER = KÜÇÜKHARF ## Metni küçük harfe çevirir. -MID = ORTA ## Bir metin dizesinden belirli sayıda karakteri, belirttiğiniz konumdan başlamak üzere verir. -MIDB = ORTAB ## Bir metin dizesinden belirli sayıda karakteri, belirttiğiniz konumdan başlamak üzere verir. -PHONETIC = SES ## Metin dizesinden ses (furigana) karakterlerini ayıklar. -PROPER = YAZIM.DÜZENİ ## Bir metin değerinin her bir sözcüğünün ilk harfini büyük harfe çevirir. -REPLACE = DEĞİŞTİR ## Metnin içindeki karakterleri değiştirir. -REPLACEB = DEĞİŞTİRB ## Metnin içindeki karakterleri değiştirir. -REPT = YİNELE ## Metni belirtilen sayıda yineler. -RIGHT = SAĞ ## Bir metin değerinden en sağdaki karakterleri verir. -RIGHTB = SAĞB ## Bir metin değerinden en sağdaki karakterleri verir. -SEARCH = BUL ## Bir metin değerini, bir başkasının içinde bulur (büyük küçük harf duyarlı değildir). -SEARCHB = BULB ## Bir metin değerini, bir başkasının içinde bulur (büyük küçük harf duyarlı değildir). -SUBSTITUTE = YERİNEKOY ## Bir metin dizesinde, eski metnin yerine yeni metin koyar. -T = M ## Bağımsız değerlerini metne dönüştürür. -TEXT = METNEÇEVİR ## Bir sayıyı biçimlendirir ve metne dönüştürür. -TRIM = KIRP ## Metindeki boşlukları kaldırır. -UPPER = BÜYÜKHARF ## Metni büyük harfe çevirir. -VALUE = SAYIYAÇEVİR ## Bir metin bağımsız değişkenini sayıya dönüştürür. +ENCODEURL = URLKODLA +FILTERXML = XMLFİLTRELE +WEBSERVICE = WEBHİZMETİ + +## +## Uyumluluk işlevleri (Compatibility Functions) +## +BETADIST = BETADAĞ +BETAINV = BETATERS +BINOMDIST = BİNOMDAĞ +CEILING = TAVANAYUVARLA +CHIDIST = KİKAREDAĞ +CHIINV = KİKARETERS +CHITEST = KİKARETEST +CONCATENATE = BİRLEŞTİR +CONFIDENCE = GÜVENİRLİK +COVAR = KOVARYANS +CRITBINOM = KRİTİKBİNOM +EXPONDIST = ÜSTELDAĞ +FDIST = FDAĞ +FINV = FTERS +FLOOR = TABANAYUVARLA +FORECAST = TAHMİN +FTEST = FTEST +GAMMADIST = GAMADAĞ +GAMMAINV = GAMATERS +HYPGEOMDIST = HİPERGEOMDAĞ +LOGINV = LOGTERS +LOGNORMDIST = LOGNORMDAĞ +MODE = ENÇOK_OLAN +NEGBINOMDIST = NEGBİNOMDAĞ +NORMDIST = NORMDAĞ +NORMINV = NORMTERS +NORMSDIST = NORMSDAĞ +NORMSINV = NORMSTERS +PERCENTILE = YÜZDEBİRLİK +PERCENTRANK = YÜZDERANK +POISSON = POISSON +QUARTILE = DÖRTTEBİRLİK +RANK = RANK +STDEV = STDSAPMA +STDEVP = STDSAPMAS +TDIST = TDAĞ +TINV = TTERS +TTEST = TTEST +VAR = VAR +VARP = VARS +WEIBULL = WEIBULL +ZTEST = ZTEST diff --git a/PhpOffice/PhpSpreadsheet/Cell/AddressHelper.php b/PhpOffice/PhpSpreadsheet/Cell/AddressHelper.php new file mode 100644 index 0000000..ed765aa --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Cell/AddressHelper.php @@ -0,0 +1,177 @@ +setValueExplicit((float) $value, DataType::TYPE_NUMERIC); - - return true; - } - - // Check for fraction + // Check for fractions if (preg_match('/^([+-]?)\s*(\d+)\s?\/\s*(\d+)$/', $value, $matches)) { - // Convert value to number - $value = $matches[2] / $matches[3]; - if ($matches[1] == '-') { - $value = 0 - $value; - } - $cell->setValueExplicit((float) $value, DataType::TYPE_NUMERIC); - // Set style - $cell->getWorksheet()->getStyle($cell->getCoordinate()) - ->getNumberFormat()->setFormatCode('??/??'); - - return true; + return $this->setProperFraction($matches, $cell); } elseif (preg_match('/^([+-]?)(\d*) +(\d*)\s?\/\s*(\d*)$/', $value, $matches)) { - // Convert value to number - $value = $matches[2] + ($matches[3] / $matches[4]); - if ($matches[1] == '-') { - $value = 0 - $value; - } - $cell->setValueExplicit((float) $value, DataType::TYPE_NUMERIC); - // Set style - $cell->getWorksheet()->getStyle($cell->getCoordinate()) - ->getNumberFormat()->setFormatCode('# ??/??'); - - return true; + return $this->setImproperFraction($matches, $cell); } // Check for percentage if (preg_match('/^\-?\d*\.?\d*\s?\%$/', $value)) { - // Convert value to number - $value = (float) str_replace('%', '', $value) / 100; - $cell->setValueExplicit($value, DataType::TYPE_NUMERIC); - // Set style - $cell->getWorksheet()->getStyle($cell->getCoordinate()) - ->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_PERCENTAGE_00); - - return true; + return $this->setPercentage($value, $cell); } // Check for currency @@ -117,29 +83,12 @@ class AdvancedValueBinder extends DefaultValueBinder implements IValueBinder // Check for time without seconds e.g. '9:45', '09:45' if (preg_match('/^(\d|[0-1]\d|2[0-3]):[0-5]\d$/', $value)) { - // Convert value to number - [$h, $m] = explode(':', $value); - $days = $h / 24 + $m / 1440; - $cell->setValueExplicit($days, DataType::TYPE_NUMERIC); - // Set style - $cell->getWorksheet()->getStyle($cell->getCoordinate()) - ->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_TIME3); - - return true; + return $this->setTimeHoursMinutes($value, $cell); } // Check for time with seconds '9:45:59', '09:45:59' if (preg_match('/^(\d|[0-1]\d|2[0-3]):[0-5]\d:[0-5]\d$/', $value)) { - // Convert value to number - [$h, $m, $s] = explode(':', $value); - $days = $h / 24 + $m / 1440 + $s / 86400; - // Convert value to number - $cell->setValueExplicit($days, DataType::TYPE_NUMERIC); - // Set style - $cell->getWorksheet()->getStyle($cell->getCoordinate()) - ->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_TIME4); - - return true; + return $this->setTimeHoursMinutesSeconds($value, $cell); } // Check for datetime, e.g. '2008-12-31', '2008-12-31 15:59', '2008-12-31 15:59:10' @@ -160,7 +109,6 @@ class AdvancedValueBinder extends DefaultValueBinder implements IValueBinder // Check for newline character "\n" if (strpos($value, "\n") !== false) { - $value = StringHelper::sanitizeUTF8($value); $cell->setValueExplicit($value, DataType::TYPE_STRING); // Set style $cell->getWorksheet()->getStyle($cell->getCoordinate()) @@ -173,4 +121,90 @@ class AdvancedValueBinder extends DefaultValueBinder implements IValueBinder // Not bound yet? Use parent... return parent::bindValue($cell, $value); } + + protected function setImproperFraction(array $matches, Cell $cell): bool + { + // Convert value to number + $value = $matches[2] + ($matches[3] / $matches[4]); + if ($matches[1] === '-') { + $value = 0 - $value; + } + $cell->setValueExplicit((float) $value, DataType::TYPE_NUMERIC); + + // Build the number format mask based on the size of the matched values + $dividend = str_repeat('?', strlen($matches[3])); + $divisor = str_repeat('?', strlen($matches[4])); + $fractionMask = "# {$dividend}/{$divisor}"; + // Set style + $cell->getWorksheet()->getStyle($cell->getCoordinate()) + ->getNumberFormat()->setFormatCode($fractionMask); + + return true; + } + + protected function setProperFraction(array $matches, Cell $cell): bool + { + // Convert value to number + $value = $matches[2] / $matches[3]; + if ($matches[1] === '-') { + $value = 0 - $value; + } + $cell->setValueExplicit((float) $value, DataType::TYPE_NUMERIC); + + // Build the number format mask based on the size of the matched values + $dividend = str_repeat('?', strlen($matches[2])); + $divisor = str_repeat('?', strlen($matches[3])); + $fractionMask = "{$dividend}/{$divisor}"; + // Set style + $cell->getWorksheet()->getStyle($cell->getCoordinate()) + ->getNumberFormat()->setFormatCode($fractionMask); + + return true; + } + + protected function setPercentage(string $value, Cell $cell): bool + { + // Convert value to number + $value = ((float) str_replace('%', '', $value)) / 100; + $cell->setValueExplicit($value, DataType::TYPE_NUMERIC); + + // Set style + $cell->getWorksheet()->getStyle($cell->getCoordinate()) + ->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_PERCENTAGE_00); + + return true; + } + + protected function setTimeHoursMinutes(string $value, Cell $cell): bool + { + // Convert value to number + [$hours, $minutes] = explode(':', $value); + $hours = (int) $hours; + $minutes = (int) $minutes; + $days = ($hours / 24) + ($minutes / 1440); + $cell->setValueExplicit($days, DataType::TYPE_NUMERIC); + + // Set style + $cell->getWorksheet()->getStyle($cell->getCoordinate()) + ->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_TIME3); + + return true; + } + + protected function setTimeHoursMinutesSeconds(string $value, Cell $cell): bool + { + // Convert value to number + [$hours, $minutes, $seconds] = explode(':', $value); + $hours = (int) $hours; + $minutes = (int) $minutes; + $seconds = (int) $seconds; + $days = ($hours / 24) + ($minutes / 1440) + ($seconds / 86400); + $cell->setValueExplicit($days, DataType::TYPE_NUMERIC); + + // Set style + $cell->getWorksheet()->getStyle($cell->getCoordinate()) + ->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_DATE_TIME4); + + return true; + } } diff --git a/PhpOffice/PhpSpreadsheet/Cell/Cell.php b/PhpOffice/PhpSpreadsheet/Cell/Cell.php old mode 100755 new mode 100644 index 45d1b5b..58a39af --- a/PhpOffice/PhpSpreadsheet/Cell/Cell.php +++ b/PhpOffice/PhpSpreadsheet/Cell/Cell.php @@ -3,11 +3,16 @@ namespace PhpOffice\PhpSpreadsheet\Cell; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; +use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError; use PhpOffice\PhpSpreadsheet\Collection\Cells; use PhpOffice\PhpSpreadsheet\Exception; use PhpOffice\PhpSpreadsheet\RichText\RichText; +use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDate; +use PhpOffice\PhpSpreadsheet\Shared\StringHelper; +use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\CellStyleAssessor; use PhpOffice\PhpSpreadsheet\Style\NumberFormat; use PhpOffice\PhpSpreadsheet\Style\Style; +use PhpOffice\PhpSpreadsheet\Worksheet\Table; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; class Cell @@ -46,14 +51,14 @@ class Cell private $dataType; /** - * Collection of cells. + * The collection of cells that this cell belongs to (i.e. The Cell Collection for the parent Worksheet). * - * @var Cells + * @var ?Cells */ private $parent; /** - * Index to cellXf. + * Index to the cellXf reference for the styling of this cell. * * @var int */ @@ -61,27 +66,33 @@ class Cell /** * Attributes of the formula. + * + * @var mixed */ private $formulaAttributes; /** * Update the cell into the cell collection. * - * @return self + * @return $this */ - public function updateInCollection() + public function updateInCollection(): self { - $this->parent->update($this); + $parent = $this->parent; + if ($parent === null) { + throw new Exception('Cannot update when cell is not bound to a worksheet'); + } + $parent->update($this); return $this; } - public function detach() + public function detach(): void { $this->parent = null; } - public function attach(Cells $parent) + public function attach(Cells $parent): void { $this->parent = $parent; } @@ -89,27 +100,23 @@ class Cell /** * Create a new Cell. * - * @param mixed $pValue - * @param string $pDataType - * @param Worksheet $pSheet - * - * @throws Exception + * @param mixed $value */ - public function __construct($pValue, $pDataType, Worksheet $pSheet) + public function __construct($value, ?string $dataType, Worksheet $worksheet) { // Initialise cell value - $this->value = $pValue; + $this->value = $value; // Set worksheet cache - $this->parent = $pSheet->getCellCollection(); + $this->parent = $worksheet->getCellCollection(); // Set datatype? - if ($pDataType !== null) { - if ($pDataType == DataType::TYPE_STRING2) { - $pDataType = DataType::TYPE_STRING; + if ($dataType !== null) { + if ($dataType == DataType::TYPE_STRING2) { + $dataType = DataType::TYPE_STRING; } - $this->dataType = $pDataType; - } elseif (!self::getValueBinder()->bindValue($this, $pValue)) { + $this->dataType = $dataType; + } elseif (self::getValueBinder()->bindValue($this, $value) === false) { throw new Exception('Value could not be bound to cell.'); } } @@ -121,7 +128,12 @@ class Cell */ public function getColumn() { - return $this->parent->getCurrentColumn(); + $parent = $this->parent; + if ($parent === null) { + throw new Exception('Cannot get column when cell is not bound to a worksheet'); + } + + return $parent->getCurrentColumn(); } /** @@ -131,7 +143,12 @@ class Cell */ public function getRow() { - return $this->parent->getCurrentRow(); + $parent = $this->parent; + if ($parent === null) { + throw new Exception('Cannot get row when cell is not bound to a worksheet'); + } + + return $parent->getCurrentRow(); } /** @@ -141,7 +158,17 @@ class Cell */ public function getCoordinate() { - return $this->parent->getCurrentCoordinate(); + $parent = $this->parent; + if ($parent !== null) { + $coordinate = $parent->getCurrentCoordinate(); + } else { + $coordinate = null; + } + if ($coordinate === null) { + throw new Exception('Coordinate no longer exists'); + } + + return $coordinate; } /** @@ -156,32 +183,50 @@ class Cell /** * Get cell value with formatting. - * - * @return string */ - public function getFormattedValue() + public function getFormattedValue(): string { return (string) NumberFormat::toFormattedString( $this->getCalculatedValue(), - $this->getStyle() - ->getNumberFormat()->getFormatCode() + (string) $this->getStyle()->getNumberFormat()->getFormatCode() ); } + /** + * @param mixed $oldValue + * @param mixed $newValue + */ + protected static function updateIfCellIsTableHeader(Worksheet $workSheet, self $cell, $oldValue, $newValue): void + { + if (StringHelper::strToLower($oldValue ?? '') === StringHelper::strToLower($newValue ?? '')) { + return; + } + + foreach ($workSheet->getTableCollection() as $table) { + /** @var Table $table */ + if ($cell->isInRange($table->getRange())) { + $rangeRowsColumns = Coordinate::getRangeBoundaries($table->getRange()); + if ($cell->getRow() === (int) $rangeRowsColumns[0][1]) { + Table\Column::updateStructuredReferences($workSheet, $oldValue, $newValue); + } + + return; + } + } + } + /** * Set cell value. * * Sets the value for a cell, automatically determining the datatype using the value binder * - * @param mixed $pValue Value + * @param mixed $value Value * - * @throws Exception - * - * @return Cell + * @return $this */ - public function setValue($pValue) + public function setValue($value): self { - if (!self::getValueBinder()->bindValue($this, $pValue)) { + if (!self::getValueBinder()->bindValue($this, $value)) { throw new Exception('Value could not be bound to cell.'); } @@ -191,57 +236,122 @@ class Cell /** * Set the value for a cell, with the explicit data type passed to the method (bypassing any use of the value binder). * - * @param mixed $pValue Value - * @param string $pDataType Explicit data type, see DataType::TYPE_* - * - * @throws Exception + * @param mixed $value Value + * @param string $dataType Explicit data type, see DataType::TYPE_* + * Note that PhpSpreadsheet does not validate that the value and datatype are consistent, in using this + * method, then it is your responsibility as an end-user developer to validate that the value and + * the datatype match. + * If you do mismatch value and datatype, then the value you enter may be changed to match the datatype + * that you specify. * * @return Cell */ - public function setValueExplicit($pValue, $pDataType) + public function setValueExplicit($value, string $dataType = DataType::TYPE_STRING) { + $oldValue = $this->value; + // set the value according to data type - switch ($pDataType) { + switch ($dataType) { case DataType::TYPE_NULL: - $this->value = $pValue; + $this->value = null; break; case DataType::TYPE_STRING2: - $pDataType = DataType::TYPE_STRING; + $dataType = DataType::TYPE_STRING; // no break case DataType::TYPE_STRING: // Synonym for string case DataType::TYPE_INLINE: // Rich text - $this->value = DataType::checkString($pValue); + $this->value = DataType::checkString($value); break; case DataType::TYPE_NUMERIC: - $this->value = (float) $pValue; + if (is_string($value) && !is_numeric($value)) { + throw new Exception('Invalid numeric value for datatype Numeric'); + } + $this->value = 0 + $value; break; case DataType::TYPE_FORMULA: - $this->value = (string) $pValue; + $this->value = (string) $value; break; case DataType::TYPE_BOOL: - $this->value = (bool) $pValue; + $this->value = (bool) $value; + + break; + case DataType::TYPE_ISO_DATE: + $this->value = SharedDate::convertIsoDate($value); + $dataType = DataType::TYPE_NUMERIC; break; case DataType::TYPE_ERROR: - $this->value = DataType::checkErrorCode($pValue); + $this->value = DataType::checkErrorCode($value); break; default: - throw new Exception('Invalid datatype: ' . $pDataType); - - break; + throw new Exception('Invalid datatype: ' . $dataType); } // set the datatype - $this->dataType = $pDataType; + $this->dataType = $dataType; - return $this->updateInCollection(); + $this->updateInCollection(); + $cellCoordinate = $this->getCoordinate(); + self::updateIfCellIsTableHeader($this->getParent()->getParent(), $this, $oldValue, $value); // @phpstan-ignore-line + + return $this->getParent()->get($cellCoordinate); // @phpstan-ignore-line + } + + public const CALCULATE_DATE_TIME_ASIS = 0; + public const CALCULATE_DATE_TIME_FLOAT = 1; + public const CALCULATE_TIME_FLOAT = 2; + + /** @var int */ + private static $calculateDateTimeType = self::CALCULATE_DATE_TIME_ASIS; + + public static function getCalculateDateTimeType(): int + { + return self::$calculateDateTimeType; + } + + public static function setCalculateDateTimeType(int $calculateDateTimeType): void + { + switch ($calculateDateTimeType) { + case self::CALCULATE_DATE_TIME_ASIS: + case self::CALCULATE_DATE_TIME_FLOAT: + case self::CALCULATE_TIME_FLOAT: + self::$calculateDateTimeType = $calculateDateTimeType; + + break; + default: + throw new \PhpOffice\PhpSpreadsheet\Calculation\Exception("Invalid value $calculateDateTimeType for calculated date time type"); + } + } + + /** + * Convert date, time, or datetime from int to float if desired. + * + * @param mixed $result + * + * @return mixed + */ + private function convertDateTimeInt($result) + { + if (is_int($result)) { + if (self::$calculateDateTimeType === self::CALCULATE_TIME_FLOAT) { + if (SharedDate::isDateTime($this, $result, false)) { + $result = (float) $result; + } + } elseif (self::$calculateDateTimeType === self::CALCULATE_DATE_TIME_FLOAT) { + if (SharedDate::isDateTime($this, $result, true)) { + $result = (float) $result; + } + } + } + + return $result; } /** @@ -249,26 +359,31 @@ class Cell * * @param bool $resetLog Whether the calculation engine logger should be reset or not * - * @throws Exception - * * @return mixed */ - public function getCalculatedValue($resetLog = true) + public function getCalculatedValue(bool $resetLog = true) { - if ($this->dataType == DataType::TYPE_FORMULA) { + if ($this->dataType === DataType::TYPE_FORMULA) { try { + $index = $this->getWorksheet()->getParent()->getActiveSheetIndex(); + $selected = $this->getWorksheet()->getSelectedCells(); $result = Calculation::getInstance( $this->getWorksheet()->getParent() )->calculateCellValue($this, $resetLog); + $result = $this->convertDateTimeInt($result); + $this->getWorksheet()->setSelectedCells($selected); + $this->getWorksheet()->getParent()->setActiveSheetIndex($index); // We don't yet handle array returns if (is_array($result)) { while (is_array($result)) { - $result = array_pop($result); + $result = array_shift($result); } } } catch (Exception $ex) { if (($ex->getMessage() === 'Unable to access External Workbook') && ($this->calculatedValue !== null)) { return $this->calculatedValue; // Fallback for calculations referencing external files. + } elseif (preg_match('/[Uu]ndefined (name|offset: 2|array key 2)/', $ex->getMessage()) === 1) { + return ExcelError::NAME(); } throw new \PhpOffice\PhpSpreadsheet\Calculation\Exception( @@ -285,20 +400,18 @@ class Cell return $this->value->getPlainText(); } - return $this->value; + return $this->convertDateTimeInt($this->value); } /** * Set old calculated value (cached). * - * @param mixed $pValue Value - * - * @return Cell + * @param mixed $originalValue Value */ - public function setCalculatedValue($pValue) + public function setCalculatedValue($originalValue): self { - if ($pValue !== null) { - $this->calculatedValue = (is_numeric($pValue)) ? (float) $pValue : $pValue; + if ($originalValue !== null) { + $this->calculatedValue = (is_numeric($originalValue)) ? (float) $originalValue : $originalValue; } return $this->updateInCollection(); @@ -321,10 +434,8 @@ class Cell /** * Get cell data type. - * - * @return string */ - public function getDataType() + public function getDataType(): string { return $this->dataType; } @@ -332,38 +443,30 @@ class Cell /** * Set cell data type. * - * @param string $pDataType see DataType::TYPE_* - * - * @return Cell + * @param string $dataType see DataType::TYPE_* */ - public function setDataType($pDataType) + public function setDataType($dataType): self { - if ($pDataType == DataType::TYPE_STRING2) { - $pDataType = DataType::TYPE_STRING; + if ($dataType == DataType::TYPE_STRING2) { + $dataType = DataType::TYPE_STRING; } - $this->dataType = $pDataType; + $this->dataType = $dataType; return $this->updateInCollection(); } /** * Identify if the cell contains a formula. - * - * @return bool */ - public function isFormula() + public function isFormula(): bool { - return $this->dataType == DataType::TYPE_FORMULA; + return $this->dataType === DataType::TYPE_FORMULA && $this->getStyle()->getQuotePrefix() === false; } /** * Does this cell contain Data validation rules? - * - * @throws Exception - * - * @return bool */ - public function hasDataValidation() + public function hasDataValidation(): bool { if (!isset($this->parent)) { throw new Exception('Cannot check for data validation when cell is not bound to a worksheet'); @@ -374,12 +477,8 @@ class Cell /** * Get Data validation rules. - * - * @throws Exception - * - * @return DataValidation */ - public function getDataValidation() + public function getDataValidation(): DataValidation { if (!isset($this->parent)) { throw new Exception('Cannot get data validation for cell that is not bound to a worksheet'); @@ -390,30 +489,22 @@ class Cell /** * Set Data validation rules. - * - * @param DataValidation $pDataValidation - * - * @throws Exception - * - * @return Cell */ - public function setDataValidation(DataValidation $pDataValidation = null) + public function setDataValidation(?DataValidation $dataValidation = null): self { if (!isset($this->parent)) { throw new Exception('Cannot set data validation for cell that is not bound to a worksheet'); } - $this->getWorksheet()->setDataValidation($this->getCoordinate(), $pDataValidation); + $this->getWorksheet()->setDataValidation($this->getCoordinate(), $dataValidation); return $this->updateInCollection(); } /** * Does this cell contain valid value? - * - * @return bool */ - public function hasValidValue() + public function hasValidValue(): bool { $validator = new DataValidator(); @@ -422,12 +513,8 @@ class Cell /** * Does this cell contain a Hyperlink? - * - * @throws Exception - * - * @return bool */ - public function hasHyperlink() + public function hasHyperlink(): bool { if (!isset($this->parent)) { throw new Exception('Cannot check for hyperlink when cell is not bound to a worksheet'); @@ -438,12 +525,8 @@ class Cell /** * Get Hyperlink. - * - * @throws Exception - * - * @return Hyperlink */ - public function getHyperlink() + public function getHyperlink(): Hyperlink { if (!isset($this->parent)) { throw new Exception('Cannot get hyperlink for cell that is not bound to a worksheet'); @@ -454,20 +537,14 @@ class Cell /** * Set Hyperlink. - * - * @param Hyperlink $pHyperlink - * - * @throws Exception - * - * @return Cell */ - public function setHyperlink(Hyperlink $pHyperlink = null) + public function setHyperlink(?Hyperlink $hyperlink = null): self { if (!isset($this->parent)) { throw new Exception('Cannot set hyperlink for cell that is not bound to a worksheet'); } - $this->getWorksheet()->setHyperlink($this->getCoordinate(), $pHyperlink); + $this->getWorksheet()->setHyperlink($this->getCoordinate(), $hyperlink); return $this->updateInCollection(); } @@ -475,7 +552,7 @@ class Cell /** * Get cell collection. * - * @return Cells + * @return ?Cells */ public function getParent() { @@ -484,37 +561,53 @@ class Cell /** * Get parent worksheet. - * - * @return Worksheet */ - public function getWorksheet() + public function getWorksheet(): Worksheet { - return $this->parent->getParent(); + $parent = $this->parent; + if ($parent !== null) { + $worksheet = $parent->getParent(); + } else { + $worksheet = null; + } + + if ($worksheet === null) { + throw new Exception('Worksheet no longer exists'); + } + + return $worksheet; + } + + public function getWorksheetOrNull(): ?Worksheet + { + $parent = $this->parent; + if ($parent !== null) { + $worksheet = $parent->getParent(); + } else { + $worksheet = null; + } + + return $worksheet; } /** * Is this cell in a merge range. - * - * @return bool */ - public function isInMergeRange() + public function isInMergeRange(): bool { return (bool) $this->getMergeRange(); } /** * Is this cell the master (top left cell) in a merge range (that holds the actual data value). - * - * @return bool */ - public function isMergeRangeValueCell() + public function isMergeRangeValueCell(): bool { if ($mergeRange = $this->getMergeRange()) { $mergeRange = Coordinate::splitRange($mergeRange); [$startCell] = $mergeRange[0]; - if ($this->getCoordinate() === $startCell) { - return true; - } + + return $this->getCoordinate() === $startCell; } return false; @@ -538,22 +631,34 @@ class Cell /** * Get cell style. - * - * @return Style */ - public function getStyle() + public function getStyle(): Style { return $this->getWorksheet()->getStyle($this->getCoordinate()); } /** - * Re-bind parent. - * - * @param Worksheet $parent - * - * @return Cell + * Get cell style. */ - public function rebindParent(Worksheet $parent) + public function getAppliedStyle(): Style + { + if ($this->getWorksheet()->conditionalStylesExists($this->getCoordinate()) === false) { + return $this->getStyle(); + } + $range = $this->getWorksheet()->getConditionalRange($this->getCoordinate()); + if ($range === null) { + return $this->getStyle(); + } + + $matcher = new CellStyleAssessor($this, $range); + + return $matcher->matchConditions($this->getWorksheet()->getConditionalStyles($this->getCoordinate())); + } + + /** + * Re-bind parent. + */ + public function rebindParent(Worksheet $parent): self { $this->parent = $parent->getCellCollection(); @@ -563,13 +668,11 @@ class Cell /** * Is cell in a specific range? * - * @param string $pRange Cell range (e.g. A1:A1) - * - * @return bool + * @param string $range Cell range (e.g. A1:A1) */ - public function isInRange($pRange) + public function isInRange(string $range): bool { - [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($pRange); + [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($range); // Translate properties $myColumn = Coordinate::columnIndexFromString($this->getColumn()); @@ -588,7 +691,7 @@ class Cell * * @return int Result of comparison (always -1 or 1, never zero!) */ - public static function compareCells(self $a, self $b) + public static function compareCells(self $a, self $b): int { if ($a->getRow() < $b->getRow()) { return -1; @@ -603,10 +706,8 @@ class Cell /** * Get value binder to use. - * - * @return IValueBinder */ - public static function getValueBinder() + public static function getValueBinder(): IValueBinder { if (self::$valueBinder === null) { self::$valueBinder = new DefaultValueBinder(); @@ -617,10 +718,8 @@ class Cell /** * Set value binder to use. - * - * @param IValueBinder $binder */ - public static function setValueBinder(IValueBinder $binder) + public static function setValueBinder(IValueBinder $binder): void { self::$valueBinder = $binder; } @@ -631,35 +730,29 @@ class Cell public function __clone() { $vars = get_object_vars($this); - foreach ($vars as $key => $value) { - if ((is_object($value)) && ($key != 'parent')) { - $this->$key = clone $value; + foreach ($vars as $propertyName => $propertyValue) { + if ((is_object($propertyValue)) && ($propertyName !== 'parent')) { + $this->$propertyName = clone $propertyValue; } else { - $this->$key = $value; + $this->$propertyName = $propertyValue; } } } /** * Get index to cellXf. - * - * @return int */ - public function getXfIndex() + public function getXfIndex(): int { return $this->xfIndex; } /** * Set index to cellXf. - * - * @param int $pValue - * - * @return Cell */ - public function setXfIndex($pValue) + public function setXfIndex(int $indexValue): self { - $this->xfIndex = $pValue; + $this->xfIndex = $indexValue; return $this->updateInCollection(); } @@ -667,19 +760,21 @@ class Cell /** * Set the formula attributes. * - * @param mixed $pAttributes + * @param mixed $attributes * - * @return Cell + * @return $this */ - public function setFormulaAttributes($pAttributes) + public function setFormulaAttributes($attributes): self { - $this->formulaAttributes = $pAttributes; + $this->formulaAttributes = $attributes; return $this; } /** * Get the formula attributes. + * + * @return mixed */ public function getFormulaAttributes() { diff --git a/PhpOffice/PhpSpreadsheet/Cell/CellAddress.php b/PhpOffice/PhpSpreadsheet/Cell/CellAddress.php new file mode 100644 index 0000000..a0c544f --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Cell/CellAddress.php @@ -0,0 +1,166 @@ +cellAddress = str_replace('$', '', $cellAddress); + [$this->columnId, $this->rowId, $this->columnName] = Coordinate::indexesFromString($this->cellAddress); + $this->worksheet = $worksheet; + } + + /** + * @param mixed $columnId + * @param mixed $rowId + */ + private static function validateColumnAndRow($columnId, $rowId): void + { + if (!is_numeric($columnId) || $columnId <= 0 || !is_numeric($rowId) || $rowId <= 0) { + throw new Exception('Row and Column Ids must be positive integer values'); + } + } + + /** + * @param mixed $columnId + * @param mixed $rowId + */ + public static function fromColumnAndRow($columnId, $rowId, ?Worksheet $worksheet = null): self + { + self::validateColumnAndRow($columnId, $rowId); + + /** @phpstan-ignore-next-line */ + return new static(Coordinate::stringFromColumnIndex($columnId) . ((string) $rowId), $worksheet); + } + + public static function fromColumnRowArray(array $array, ?Worksheet $worksheet = null): self + { + [$columnId, $rowId] = $array; + + return static::fromColumnAndRow($columnId, $rowId, $worksheet); + } + + /** + * @param mixed $cellAddress + */ + public static function fromCellAddress($cellAddress, ?Worksheet $worksheet = null): self + { + /** @phpstan-ignore-next-line */ + return new static($cellAddress, $worksheet); + } + + /** + * The returned address string will contain the worksheet name as well, if available, + * (ie. if a Worksheet was provided to the constructor). + * e.g. "'Mark''s Worksheet'!C5". + */ + public function fullCellAddress(): string + { + if ($this->worksheet !== null) { + $title = str_replace("'", "''", $this->worksheet->getTitle()); + + return "'{$title}'!{$this->cellAddress}"; + } + + return $this->cellAddress; + } + + public function worksheet(): ?Worksheet + { + return $this->worksheet; + } + + /** + * The returned address string will contain just the column/row address, + * (even if a Worksheet was provided to the constructor). + * e.g. "C5". + */ + public function cellAddress(): string + { + return $this->cellAddress; + } + + public function rowId(): int + { + return $this->rowId; + } + + public function columnId(): int + { + return $this->columnId; + } + + public function columnName(): string + { + return $this->columnName; + } + + public function nextRow(int $offset = 1): self + { + $newRowId = $this->rowId + $offset; + if ($newRowId < 1) { + $newRowId = 1; + } + + return static::fromColumnAndRow($this->columnId, $newRowId); + } + + public function previousRow(int $offset = 1): self + { + return $this->nextRow(0 - $offset); + } + + public function nextColumn(int $offset = 1): self + { + $newColumnId = $this->columnId + $offset; + if ($newColumnId < 1) { + $newColumnId = 1; + } + + return static::fromColumnAndRow($newColumnId, $this->rowId); + } + + public function previousColumn(int $offset = 1): self + { + return $this->nextColumn(0 - $offset); + } + + /** + * The returned address string will contain the worksheet name as well, if available, + * (ie. if a Worksheet was provided to the constructor). + * e.g. "'Mark''s Worksheet'!C5". + */ + public function __toString() + { + return $this->fullCellAddress(); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Cell/CellRange.php b/PhpOffice/PhpSpreadsheet/Cell/CellRange.php new file mode 100644 index 0000000..908a0d0 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Cell/CellRange.php @@ -0,0 +1,136 @@ +validateFromTo($from, $to); + } + + private function validateFromTo(CellAddress $from, CellAddress $to): void + { + // Identify actual top-left and bottom-right values (in case we've been given top-right and bottom-left) + $firstColumn = min($from->columnId(), $to->columnId()); + $firstRow = min($from->rowId(), $to->rowId()); + $lastColumn = max($from->columnId(), $to->columnId()); + $lastRow = max($from->rowId(), $to->rowId()); + + $fromWorksheet = $from->worksheet(); + $toWorksheet = $to->worksheet(); + $this->validateWorksheets($fromWorksheet, $toWorksheet); + + $this->from = $this->cellAddressWrapper($firstColumn, $firstRow, $fromWorksheet); + $this->to = $this->cellAddressWrapper($lastColumn, $lastRow, $toWorksheet); + } + + private function validateWorksheets(?Worksheet $fromWorksheet, ?Worksheet $toWorksheet): void + { + if ($fromWorksheet !== null && $toWorksheet !== null) { + // We could simply compare worksheets rather than worksheet titles; but at some point we may introduce + // support for 3d ranges; and at that point we drop this check and let the validation fall through + // to the check for same workbook; but unless we check on titles, this test will also detect if the + // worksheets are in different spreadsheets, and the next check will never execute or throw its + // own exception. + if ($fromWorksheet->getTitle() !== $toWorksheet->getTitle()) { + throw new Exception('3d Cell Ranges are not supported'); + } elseif ($fromWorksheet->getParent() !== $toWorksheet->getParent()) { + throw new Exception('Worksheets must be in the same spreadsheet'); + } + } + } + + private function cellAddressWrapper(int $column, int $row, ?Worksheet $worksheet = null): CellAddress + { + $cellAddress = Coordinate::stringFromColumnIndex($column) . (string) $row; + + return new class ($cellAddress, $worksheet) extends CellAddress { + public function nextRow(int $offset = 1): CellAddress + { + /** @var CellAddress $result */ + $result = parent::nextRow($offset); + $this->rowId = $result->rowId; + $this->cellAddress = $result->cellAddress; + + return $this; + } + + public function previousRow(int $offset = 1): CellAddress + { + /** @var CellAddress $result */ + $result = parent::previousRow($offset); + $this->rowId = $result->rowId; + $this->cellAddress = $result->cellAddress; + + return $this; + } + + public function nextColumn(int $offset = 1): CellAddress + { + /** @var CellAddress $result */ + $result = parent::nextColumn($offset); + $this->columnId = $result->columnId; + $this->columnName = $result->columnName; + $this->cellAddress = $result->cellAddress; + + return $this; + } + + public function previousColumn(int $offset = 1): CellAddress + { + /** @var CellAddress $result */ + $result = parent::previousColumn($offset); + $this->columnId = $result->columnId; + $this->columnName = $result->columnName; + $this->cellAddress = $result->cellAddress; + + return $this; + } + }; + } + + public function from(): CellAddress + { + // Re-order from/to in case the cell addresses have been modified + $this->validateFromTo($this->from, $this->to); + + return $this->from; + } + + public function to(): CellAddress + { + // Re-order from/to in case the cell addresses have been modified + $this->validateFromTo($this->from, $this->to); + + return $this->to; + } + + public function __toString(): string + { + // Re-order from/to in case the cell addresses have been modified + $this->validateFromTo($this->from, $this->to); + + if ($this->from->cellAddress() === $this->to->cellAddress()) { + return "{$this->from->fullCellAddress()}"; + } + + $fromAddress = $this->from->fullCellAddress(); + $toAddress = $this->to->cellAddress(); + + return "{$fromAddress}:{$toAddress}"; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Cell/ColumnRange.php b/PhpOffice/PhpSpreadsheet/Cell/ColumnRange.php new file mode 100644 index 0000000..1e521a1 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Cell/ColumnRange.php @@ -0,0 +1,125 @@ +validateFromTo( + Coordinate::columnIndexFromString($from), + Coordinate::columnIndexFromString($to ?? $from) + ); + $this->worksheet = $worksheet; + } + + public static function fromColumnIndexes(int $from, int $to, ?Worksheet $worksheet = null): self + { + return new self(Coordinate::stringFromColumnIndex($from), Coordinate::stringFromColumnIndex($to), $worksheet); + } + + /** + * @param array $array + */ + public static function fromArray(array $array, ?Worksheet $worksheet = null): self + { + array_walk( + $array, + function (&$column): void { + $column = is_numeric($column) ? Coordinate::stringFromColumnIndex((int) $column) : $column; + } + ); + /** @var string $from */ + /** @var string $to */ + [$from, $to] = $array; + + return new self($from, $to, $worksheet); + } + + private function validateFromTo(int $from, int $to): void + { + // Identify actual top and bottom values (in case we've been given bottom and top) + $this->from = min($from, $to); + $this->to = max($from, $to); + } + + public function columnCount(): int + { + return $this->to - $this->from + 1; + } + + public function shiftDown(int $offset = 1): self + { + $newFrom = $this->from + $offset; + $newFrom = ($newFrom < 1) ? 1 : $newFrom; + + $newTo = $this->to + $offset; + $newTo = ($newTo < 1) ? 1 : $newTo; + + return self::fromColumnIndexes($newFrom, $newTo, $this->worksheet); + } + + public function shiftUp(int $offset = 1): self + { + return $this->shiftDown(0 - $offset); + } + + public function from(): string + { + return Coordinate::stringFromColumnIndex($this->from); + } + + public function to(): string + { + return Coordinate::stringFromColumnIndex($this->to); + } + + public function fromIndex(): int + { + return $this->from; + } + + public function toIndex(): int + { + return $this->to; + } + + public function toCellRange(): CellRange + { + return new CellRange( + CellAddress::fromColumnAndRow($this->from, 1, $this->worksheet), + CellAddress::fromColumnAndRow($this->to, AddressRange::MAX_ROW) + ); + } + + public function __toString(): string + { + $from = $this->from(); + $to = $this->to(); + + if ($this->worksheet !== null) { + $title = str_replace("'", "''", $this->worksheet->getTitle()); + + return "'{$title}'!{$from}:{$to}"; + } + + return "{$from}:{$to}"; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Cell/Coordinate.php b/PhpOffice/PhpSpreadsheet/Cell/Coordinate.php old mode 100755 new mode 100644 index cc0543f..89b4768 --- a/PhpOffice/PhpSpreadsheet/Cell/Coordinate.php +++ b/PhpOffice/PhpSpreadsheet/Cell/Coordinate.php @@ -2,6 +2,7 @@ namespace PhpOffice\PhpSpreadsheet\Cell; +use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Exception; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; @@ -13,6 +14,8 @@ use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; */ abstract class Coordinate { + public const A1_COORDINATE_REGEX = '/^(?\$?[A-Z]{1,3})(?\$?\d{1,7})$/i'; + /** * Default range variable constant. * @@ -21,94 +24,108 @@ abstract class Coordinate const DEFAULT_RANGE = 'A1:A1'; /** - * Coordinate from string. + * Convert string coordinate to [0 => int column index, 1 => int row index]. * - * @param string $pCoordinateString eg: 'A1' + * @param string $cellAddress eg: 'A1' * - * @throws Exception - * - * @return string[] Array containing column and row (indexes 0 and 1) + * @return array{0: string, 1: string} Array containing column and row (indexes 0 and 1) */ - public static function coordinateFromString($pCoordinateString) + public static function coordinateFromString($cellAddress): array { - if (preg_match('/^([$]?[A-Z]{1,3})([$]?\\d{1,7})$/', $pCoordinateString, $matches)) { - return [$matches[1], $matches[2]]; - } elseif (self::coordinateIsRange($pCoordinateString)) { + if (preg_match(self::A1_COORDINATE_REGEX, $cellAddress, $matches)) { + return [$matches['col'], $matches['row']]; + } elseif (self::coordinateIsRange($cellAddress)) { throw new Exception('Cell coordinate string can not be a range of cells'); - } elseif ($pCoordinateString == '') { + } elseif ($cellAddress == '') { throw new Exception('Cell coordinate can not be zero-length string'); } - throw new Exception('Invalid cell coordinate ' . $pCoordinateString); + throw new Exception('Invalid cell coordinate ' . $cellAddress); } /** - * Checks if a coordinate represents a range of cells. + * Convert string coordinate to [0 => int column index, 1 => int row index, 2 => string column string]. * - * @param string $coord eg: 'A1' or 'A1:A2' or 'A1:A2,C1:C2' + * @param string $coordinates eg: 'A1', '$B$12' + * + * @return array{0: int, 1: int, 2: string} Array containing column and row index, and column string + */ + public static function indexesFromString(string $coordinates): array + { + [$column, $row] = self::coordinateFromString($coordinates); + $column = ltrim($column, '$'); + + return [ + self::columnIndexFromString($column), + (int) ltrim($row, '$'), + $column, + ]; + } + + /** + * Checks if a Cell Address represents a range of cells. + * + * @param string $cellAddress eg: 'A1' or 'A1:A2' or 'A1:A2,C1:C2' * * @return bool Whether the coordinate represents a range of cells */ - public static function coordinateIsRange($coord) + public static function coordinateIsRange($cellAddress) { - return (strpos($coord, ':') !== false) || (strpos($coord, ',') !== false); + return (strpos($cellAddress, ':') !== false) || (strpos($cellAddress, ',') !== false); } /** * Make string row, column or cell coordinate absolute. * - * @param string $pCoordinateString e.g. 'A' or '1' or 'A1' + * @param string $cellAddress e.g. 'A' or '1' or 'A1' * Note that this value can be a row or column reference as well as a cell reference * - * @throws Exception - * * @return string Absolute coordinate e.g. '$A' or '$1' or '$A$1' */ - public static function absoluteReference($pCoordinateString) + public static function absoluteReference($cellAddress) { - if (self::coordinateIsRange($pCoordinateString)) { + if (self::coordinateIsRange($cellAddress)) { throw new Exception('Cell coordinate string can not be a range of cells'); } // Split out any worksheet name from the reference - [$worksheet, $pCoordinateString] = Worksheet::extractSheetTitle($pCoordinateString, true); + [$worksheet, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); if ($worksheet > '') { $worksheet .= '!'; } // Create absolute coordinate - if (ctype_digit($pCoordinateString)) { - return $worksheet . '$' . $pCoordinateString; - } elseif (ctype_alpha($pCoordinateString)) { - return $worksheet . '$' . strtoupper($pCoordinateString); + $cellAddress = "$cellAddress"; + if (ctype_digit($cellAddress)) { + return $worksheet . '$' . $cellAddress; + } elseif (ctype_alpha($cellAddress)) { + return $worksheet . '$' . strtoupper($cellAddress); } - return $worksheet . self::absoluteCoordinate($pCoordinateString); + return $worksheet . self::absoluteCoordinate($cellAddress); } /** * Make string coordinate absolute. * - * @param string $pCoordinateString e.g. 'A1' - * - * @throws Exception + * @param string $cellAddress e.g. 'A1' * * @return string Absolute coordinate e.g. '$A$1' */ - public static function absoluteCoordinate($pCoordinateString) + public static function absoluteCoordinate($cellAddress) { - if (self::coordinateIsRange($pCoordinateString)) { + if (self::coordinateIsRange($cellAddress)) { throw new Exception('Cell coordinate string can not be a range of cells'); } // Split out any worksheet name from the coordinate - [$worksheet, $pCoordinateString] = Worksheet::extractSheetTitle($pCoordinateString, true); + [$worksheet, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); if ($worksheet > '') { $worksheet .= '!'; } // Create absolute coordinate - [$column, $row] = self::coordinateFromString($pCoordinateString); + [$column, $row] = self::coordinateFromString($cellAddress); $column = ltrim($column, '$'); $row = ltrim($row, '$'); @@ -118,22 +135,23 @@ abstract class Coordinate /** * Split range into coordinate strings. * - * @param string $pRange e.g. 'B4:D9' or 'B4:D9,H2:O11' or 'B4' + * @param string $range e.g. 'B4:D9' or 'B4:D9,H2:O11' or 'B4' * * @return array Array containing one or more arrays containing one or two coordinate strings * e.g. ['B4','D9'] or [['B4','D9'], ['H2','O11']] * or ['B4'] */ - public static function splitRange($pRange) + public static function splitRange($range) { // Ensure $pRange is a valid range - if (empty($pRange)) { - $pRange = self::DEFAULT_RANGE; + if (empty($range)) { + $range = self::DEFAULT_RANGE; } - $exploded = explode(',', $pRange); + $exploded = explode(',', $range); $counter = count($exploded); for ($i = 0; $i < $counter; ++$i) { + // @phpstan-ignore-next-line $exploded[$i] = explode(':', $exploded[$i]); } @@ -143,51 +161,59 @@ abstract class Coordinate /** * Build range from coordinate strings. * - * @param array $pRange Array containg one or more arrays containing one or two coordinate strings - * - * @throws Exception + * @param array $range Array containing one or more arrays containing one or two coordinate strings * * @return string String representation of $pRange */ - public static function buildRange(array $pRange) + public static function buildRange(array $range) { // Verify range - if (empty($pRange) || !is_array($pRange[0])) { + if (empty($range) || !is_array($range[0])) { throw new Exception('Range does not contain any information'); } // Build range - $counter = count($pRange); + $counter = count($range); for ($i = 0; $i < $counter; ++$i) { - $pRange[$i] = implode(':', $pRange[$i]); + $range[$i] = implode(':', $range[$i]); } - return implode(',', $pRange); + return implode(',', $range); } /** * Calculate range boundaries. * - * @param string $pRange Cell range (e.g. A1:A1) + * @param string $range Cell range, Single Cell, Row/Column Range (e.g. A1:A1, B2, B:C, 2:3) * * @return array Range coordinates [Start Cell, End Cell] * where Start Cell and End Cell are arrays (Column Number, Row Number) */ - public static function rangeBoundaries($pRange) + public static function rangeBoundaries(string $range): array { // Ensure $pRange is a valid range - if (empty($pRange)) { - $pRange = self::DEFAULT_RANGE; + if (empty($range)) { + $range = self::DEFAULT_RANGE; } // Uppercase coordinate - $pRange = strtoupper($pRange); + $range = strtoupper($range); // Extract range - if (strpos($pRange, ':') === false) { - $rangeA = $rangeB = $pRange; + if (strpos($range, ':') === false) { + $rangeA = $rangeB = $range; } else { - [$rangeA, $rangeB] = explode(':', $pRange); + [$rangeA, $rangeB] = explode(':', $range); + } + + if (is_numeric($rangeA) && is_numeric($rangeB)) { + $rangeA = 'A' . $rangeA; + $rangeB = AddressRange::MAX_COLUMN . $rangeB; + } + + if (ctype_alpha($rangeA) && ctype_alpha($rangeB)) { + $rangeA = $rangeA . '1'; + $rangeB = $rangeB . AddressRange::MAX_ROW; } // Calculate range outer borders @@ -204,14 +230,14 @@ abstract class Coordinate /** * Calculate range dimension. * - * @param string $pRange Cell range (e.g. A1:A1) + * @param string $range Cell range, Single Cell, Row/Column Range (e.g. A1:A1, B2, B:C, 2:3) * * @return array Range dimension (width, height) */ - public static function rangeDimension($pRange) + public static function rangeDimension($range) { // Calculate range outer borders - [$rangeStart, $rangeEnd] = self::rangeBoundaries($pRange); + [$rangeStart, $rangeEnd] = self::rangeBoundaries($range); return [($rangeEnd[0] - $rangeStart[0] + 1), ($rangeEnd[1] - $rangeStart[1] + 1)]; } @@ -219,77 +245,74 @@ abstract class Coordinate /** * Calculate range boundaries. * - * @param string $pRange Cell range (e.g. A1:A1) + * @param string $range Cell range, Single Cell, Row/Column Range (e.g. A1:A1, B2, B:C, 2:3) * * @return array Range coordinates [Start Cell, End Cell] * where Start Cell and End Cell are arrays [Column ID, Row Number] */ - public static function getRangeBoundaries($pRange) + public static function getRangeBoundaries($range) { - // Ensure $pRange is a valid range - if (empty($pRange)) { - $pRange = self::DEFAULT_RANGE; - } + [$rangeA, $rangeB] = self::rangeBoundaries($range); - // Uppercase coordinate - $pRange = strtoupper($pRange); - - // Extract range - if (strpos($pRange, ':') === false) { - $rangeA = $rangeB = $pRange; - } else { - [$rangeA, $rangeB] = explode(':', $pRange); - } - - return [self::coordinateFromString($rangeA), self::coordinateFromString($rangeB)]; + return [ + [self::stringFromColumnIndex($rangeA[0]), $rangeA[1]], + [self::stringFromColumnIndex($rangeB[0]), $rangeB[1]], + ]; } /** * Column index from string. * - * @param string $pString eg 'A' + * @param string $columnAddress eg 'A' * * @return int Column index (A = 1) */ - public static function columnIndexFromString($pString) + public static function columnIndexFromString($columnAddress) { // Using a lookup cache adds a slight memory overhead, but boosts speed // caching using a static within the method is faster than a class static, // though it's additional memory overhead static $indexCache = []; - if (isset($indexCache[$pString])) { - return $indexCache[$pString]; + if (isset($indexCache[$columnAddress])) { + return $indexCache[$columnAddress]; } - // It's surprising how costly the strtoupper() and ord() calls actually are, so we use a lookup array rather than use ord() - // and make it case insensitive to get rid of the strtoupper() as well. Because it's a static, there's no significant - // memory overhead either + // It's surprising how costly the strtoupper() and ord() calls actually are, so we use a lookup array + // rather than use ord() and make it case insensitive to get rid of the strtoupper() as well. + // Because it's a static, there's no significant memory overhead either. static $columnLookup = [ - 'A' => 1, 'B' => 2, 'C' => 3, 'D' => 4, 'E' => 5, 'F' => 6, 'G' => 7, 'H' => 8, 'I' => 9, 'J' => 10, 'K' => 11, 'L' => 12, 'M' => 13, - 'N' => 14, 'O' => 15, 'P' => 16, 'Q' => 17, 'R' => 18, 'S' => 19, 'T' => 20, 'U' => 21, 'V' => 22, 'W' => 23, 'X' => 24, 'Y' => 25, 'Z' => 26, - 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5, 'f' => 6, 'g' => 7, 'h' => 8, 'i' => 9, 'j' => 10, 'k' => 11, 'l' => 12, 'm' => 13, - 'n' => 14, 'o' => 15, 'p' => 16, 'q' => 17, 'r' => 18, 's' => 19, 't' => 20, 'u' => 21, 'v' => 22, 'w' => 23, 'x' => 24, 'y' => 25, 'z' => 26, + 'A' => 1, 'B' => 2, 'C' => 3, 'D' => 4, 'E' => 5, 'F' => 6, 'G' => 7, 'H' => 8, 'I' => 9, 'J' => 10, + 'K' => 11, 'L' => 12, 'M' => 13, 'N' => 14, 'O' => 15, 'P' => 16, 'Q' => 17, 'R' => 18, 'S' => 19, + 'T' => 20, 'U' => 21, 'V' => 22, 'W' => 23, 'X' => 24, 'Y' => 25, 'Z' => 26, + 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5, 'f' => 6, 'g' => 7, 'h' => 8, 'i' => 9, 'j' => 10, + 'k' => 11, 'l' => 12, 'm' => 13, 'n' => 14, 'o' => 15, 'p' => 16, 'q' => 17, 'r' => 18, 's' => 19, + 't' => 20, 'u' => 21, 'v' => 22, 'w' => 23, 'x' => 24, 'y' => 25, 'z' => 26, ]; - // We also use the language construct isset() rather than the more costly strlen() function to match the length of $pString - // for improved performance - if (isset($pString[0])) { - if (!isset($pString[1])) { - $indexCache[$pString] = $columnLookup[$pString]; + // We also use the language construct isset() rather than the more costly strlen() function to match the + // length of $columnAddress for improved performance + if (isset($columnAddress[0])) { + if (!isset($columnAddress[1])) { + $indexCache[$columnAddress] = $columnLookup[$columnAddress]; - return $indexCache[$pString]; - } elseif (!isset($pString[2])) { - $indexCache[$pString] = $columnLookup[$pString[0]] * 26 + $columnLookup[$pString[1]]; + return $indexCache[$columnAddress]; + } elseif (!isset($columnAddress[2])) { + $indexCache[$columnAddress] = $columnLookup[$columnAddress[0]] * 26 + + $columnLookup[$columnAddress[1]]; - return $indexCache[$pString]; - } elseif (!isset($pString[3])) { - $indexCache[$pString] = $columnLookup[$pString[0]] * 676 + $columnLookup[$pString[1]] * 26 + $columnLookup[$pString[2]]; + return $indexCache[$columnAddress]; + } elseif (!isset($columnAddress[3])) { + $indexCache[$columnAddress] = $columnLookup[$columnAddress[0]] * 676 + + $columnLookup[$columnAddress[1]] * 26 + + $columnLookup[$columnAddress[2]]; - return $indexCache[$pString]; + return $indexCache[$columnAddress]; } } - throw new Exception('Column string index can not be ' . ((isset($pString[0])) ? 'longer than 3 characters' : 'empty')); + throw new Exception( + 'Column string index can not be ' . ((isset($columnAddress[0])) ? 'longer than 3 characters' : 'empty') + ); } /** @@ -302,14 +325,15 @@ abstract class Coordinate public static function stringFromColumnIndex($columnIndex) { static $indexCache = []; + static $lookupCache = ' ABCDEFGHIJKLMNOPQRSTUVWXYZ'; if (!isset($indexCache[$columnIndex])) { $indexValue = $columnIndex; - $base26 = null; + $base26 = ''; do { $characterValue = ($indexValue % 26) ?: 26; $indexValue = ($indexValue - $characterValue) / 26; - $base26 = chr($characterValue + 64) . ($base26 ?: ''); + $base26 = $lookupCache[$characterValue] . $base26; } while ($indexValue > 0); $indexCache[$columnIndex] = $base26; } @@ -320,32 +344,81 @@ abstract class Coordinate /** * Extract all cell references in range, which may be comprised of multiple cell ranges. * - * @param string $pRange Range (e.g. A1 or A1:C10 or A1:E10 A20:E25) + * @param string $cellRange Range: e.g. 'A1' or 'A1:C10' or 'A1:E10,A20:E25' or 'A1:E5 C3:G7' or 'A1:C1,A3:C3 B1:C3' * * @return array Array containing single cell references */ - public static function extractAllCellReferencesInRange($pRange) + public static function extractAllCellReferencesInRange($cellRange): array { - $returnValue = []; - - // Explode spaces - $cellBlocks = self::getCellBlocksFromRangeString($pRange); - foreach ($cellBlocks as $cellBlock) { - $returnValue = array_merge($returnValue, self::getReferencesForCellBlock($cellBlock)); + if (substr_count($cellRange, '!') > 1) { + throw new Exception('3-D Range References are not supported'); } + [$worksheet, $cellRange] = Worksheet::extractSheetTitle($cellRange, true); + $quoted = ''; + if ($worksheet > '') { + $quoted = Worksheet::nameRequiresQuotes($worksheet) ? "'" : ''; + if (substr($worksheet, 0, 1) === "'" && substr($worksheet, -1, 1) === "'") { + $worksheet = substr($worksheet, 1, -1); + } + $worksheet = str_replace("'", "''", $worksheet); + } + [$ranges, $operators] = self::getCellBlocksFromRangeString($cellRange); + + $cells = []; + foreach ($ranges as $range) { + $cells[] = self::getReferencesForCellBlock($range); + } + + $cells = self::processRangeSetOperators($operators, $cells); + + if (empty($cells)) { + return []; + } + + $cellList = array_merge(...$cells); + + return array_map( + function ($cellAddress) use ($worksheet, $quoted) { + return ($worksheet !== '') ? "{$quoted}{$worksheet}{$quoted}!{$cellAddress}" : $cellAddress; + }, + self::sortCellReferenceArray($cellList) + ); + } + + private static function processRangeSetOperators(array $operators, array $cells): array + { + $operatorCount = count($operators); + for ($offset = 0; $offset < $operatorCount; ++$offset) { + $operator = $operators[$offset]; + if ($operator !== ' ') { + continue; + } + + $cells[$offset] = array_intersect($cells[$offset], $cells[$offset + 1]); + unset($operators[$offset], $cells[$offset + 1]); + $operators = array_values($operators); + $cells = array_values($cells); + --$offset; + --$operatorCount; + } + + return $cells; + } + + private static function sortCellReferenceArray(array $cellList): array + { // Sort the result by column and row $sortKeys = []; - foreach (array_unique($returnValue) as $coord) { + foreach ($cellList as $coordinate) { $column = ''; $row = 0; - - sscanf($coord, '%[A-Z]%d', $column, $row); - $sortKeys[sprintf('%3s%09d', $column, $row)] = $coord; + sscanf($coordinate, '%[A-Z]%d', $column, $row); + $key = (--$row * 16384) + self::columnIndexFromString((string) $column); + $sortKeys[$key] = $coordinate; } ksort($sortKeys); - // Return value return array_values($sortKeys); } @@ -416,16 +489,16 @@ abstract class Coordinate * * [ 'A1:A3' => 'x', 'A4' => 'y' ] * - * @param array $pCoordCollection associative array mapping coordinates to values + * @param array $coordinateCollection associative array mapping coordinates to values * * @return array associative array mapping coordinate ranges to valuea */ - public static function mergeRangesInCollection(array $pCoordCollection) + public static function mergeRangesInCollection(array $coordinateCollection) { $hashedValues = []; $mergedCoordCollection = []; - foreach ($pCoordCollection as $coord => $value) { + foreach ($coordinateCollection as $coord => $value) { if (self::coordinateIsRange($coord)) { $mergedCoordCollection[$coord] = $value; @@ -490,15 +563,25 @@ abstract class Coordinate } /** - * Get the individual cell blocks from a range string, splitting by space and removing any $ characters. + * Get the individual cell blocks from a range string, removing any $ characters. + * then splitting by operators and returning an array with ranges and operators. * - * @param string $pRange + * @param string $rangeString * - * @return string[] + * @return array[] */ - private static function getCellBlocksFromRangeString($pRange) + private static function getCellBlocksFromRangeString($rangeString) { - return explode(' ', str_replace('$', '', strtoupper($pRange))); + $rangeString = str_replace('$', '', strtoupper($rangeString)); + + // split range sets on intersection (space) or union (,) operators + $tokens = preg_split('/([ ,])/', $rangeString, -1, PREG_SPLIT_DELIM_CAPTURE); + /** @phpstan-ignore-next-line */ + $split = array_chunk($tokens, 2); + $ranges = array_column($split, 0); + $operators = array_column($split, 1); + + return [$ranges, $operators]; } /** @@ -511,7 +594,7 @@ abstract class Coordinate * @param int $currentRow * @param int $endRow */ - private static function validateRange($cellBlock, $startColumnIndex, $endColumnIndex, $currentRow, $endRow) + private static function validateRange($cellBlock, $startColumnIndex, $endColumnIndex, $currentRow, $endRow): void { if ($startColumnIndex >= $endColumnIndex || $currentRow > $endRow) { throw new Exception('Invalid range: "' . $cellBlock . '"'); diff --git a/PhpOffice/PhpSpreadsheet/Cell/DataType.php b/PhpOffice/PhpSpreadsheet/Cell/DataType.php old mode 100755 new mode 100644 index ba03579..f19984d --- a/PhpOffice/PhpSpreadsheet/Cell/DataType.php +++ b/PhpOffice/PhpSpreadsheet/Cell/DataType.php @@ -16,11 +16,12 @@ class DataType const TYPE_NULL = 'null'; const TYPE_INLINE = 'inlineStr'; const TYPE_ERROR = 'e'; + const TYPE_ISO_DATE = 'd'; /** * List of error codes. * - * @var array + * @var array */ private static $errorCodes = [ '#NULL!' => 0, @@ -30,12 +31,15 @@ class DataType '#NAME?' => 4, '#NUM!' => 5, '#N/A' => 6, + '#CALC!' => 7, ]; + public const MAX_STRING_LENGTH = 32767; + /** * Get list of error codes. * - * @return array + * @return array */ public static function getErrorCodes() { @@ -45,41 +49,41 @@ class DataType /** * Check a string that it satisfies Excel requirements. * - * @param null|RichText|string $pValue Value to sanitize to an Excel string + * @param null|RichText|string $textValue Value to sanitize to an Excel string * - * @return null|RichText|string Sanitized value + * @return RichText|string Sanitized value */ - public static function checkString($pValue) + public static function checkString($textValue) { - if ($pValue instanceof RichText) { + if ($textValue instanceof RichText) { // TODO: Sanitize Rich-Text string (max. character count is 32,767) - return $pValue; + return $textValue; } // string must never be longer than 32,767 characters, truncate if necessary - $pValue = StringHelper::substring($pValue, 0, 32767); + $textValue = StringHelper::substring((string) $textValue, 0, self::MAX_STRING_LENGTH); // we require that newline is represented as "\n" in core, not as "\r\n" or "\r" - $pValue = str_replace(["\r\n", "\r"], "\n", $pValue); + $textValue = str_replace(["\r\n", "\r"], "\n", $textValue); - return $pValue; + return $textValue; } /** * Check a value that it is a valid error code. * - * @param mixed $pValue Value to sanitize to an Excel error code + * @param mixed $value Value to sanitize to an Excel error code * * @return string Sanitized value */ - public static function checkErrorCode($pValue) + public static function checkErrorCode($value) { - $pValue = (string) $pValue; + $value = (string) $value; - if (!isset(self::$errorCodes[$pValue])) { - $pValue = '#NULL!'; + if (!isset(self::$errorCodes[$value])) { + $value = '#NULL!'; } - return $pValue; + return $value; } } diff --git a/PhpOffice/PhpSpreadsheet/Cell/DataValidation.php b/PhpOffice/PhpSpreadsheet/Cell/DataValidation.php old mode 100755 new mode 100644 index a041ea0..7ee53ea --- a/PhpOffice/PhpSpreadsheet/Cell/DataValidation.php +++ b/PhpOffice/PhpSpreadsheet/Cell/DataValidation.php @@ -140,13 +140,13 @@ class DataValidation /** * Set Formula 1. * - * @param string $value + * @param string $formula * - * @return DataValidation + * @return $this */ - public function setFormula1($value) + public function setFormula1($formula) { - $this->formula1 = $value; + $this->formula1 = $formula; return $this; } @@ -164,13 +164,13 @@ class DataValidation /** * Set Formula 2. * - * @param string $value + * @param string $formula * - * @return DataValidation + * @return $this */ - public function setFormula2($value) + public function setFormula2($formula) { - $this->formula2 = $value; + $this->formula2 = $formula; return $this; } @@ -188,13 +188,13 @@ class DataValidation /** * Set Type. * - * @param string $value + * @param string $type * - * @return DataValidation + * @return $this */ - public function setType($value) + public function setType($type) { - $this->type = $value; + $this->type = $type; return $this; } @@ -212,13 +212,13 @@ class DataValidation /** * Set Error style. * - * @param string $value see self::STYLE_* + * @param string $errorStyle see self::STYLE_* * - * @return DataValidation + * @return $this */ - public function setErrorStyle($value) + public function setErrorStyle($errorStyle) { - $this->errorStyle = $value; + $this->errorStyle = $errorStyle; return $this; } @@ -236,13 +236,13 @@ class DataValidation /** * Set Operator. * - * @param string $value + * @param string $operator * - * @return DataValidation + * @return $this */ - public function setOperator($value) + public function setOperator($operator) { - $this->operator = $value; + $this->operator = $operator; return $this; } @@ -260,13 +260,13 @@ class DataValidation /** * Set Allow Blank. * - * @param bool $value + * @param bool $allowBlank * - * @return DataValidation + * @return $this */ - public function setAllowBlank($value) + public function setAllowBlank($allowBlank) { - $this->allowBlank = $value; + $this->allowBlank = $allowBlank; return $this; } @@ -284,13 +284,13 @@ class DataValidation /** * Set Show DropDown. * - * @param bool $value + * @param bool $showDropDown * - * @return DataValidation + * @return $this */ - public function setShowDropDown($value) + public function setShowDropDown($showDropDown) { - $this->showDropDown = $value; + $this->showDropDown = $showDropDown; return $this; } @@ -308,13 +308,13 @@ class DataValidation /** * Set Show InputMessage. * - * @param bool $value + * @param bool $showInputMessage * - * @return DataValidation + * @return $this */ - public function setShowInputMessage($value) + public function setShowInputMessage($showInputMessage) { - $this->showInputMessage = $value; + $this->showInputMessage = $showInputMessage; return $this; } @@ -332,13 +332,13 @@ class DataValidation /** * Set Show ErrorMessage. * - * @param bool $value + * @param bool $showErrorMessage * - * @return DataValidation + * @return $this */ - public function setShowErrorMessage($value) + public function setShowErrorMessage($showErrorMessage) { - $this->showErrorMessage = $value; + $this->showErrorMessage = $showErrorMessage; return $this; } @@ -356,13 +356,13 @@ class DataValidation /** * Set Error title. * - * @param string $value + * @param string $errorTitle * - * @return DataValidation + * @return $this */ - public function setErrorTitle($value) + public function setErrorTitle($errorTitle) { - $this->errorTitle = $value; + $this->errorTitle = $errorTitle; return $this; } @@ -380,13 +380,13 @@ class DataValidation /** * Set Error. * - * @param string $value + * @param string $error * - * @return DataValidation + * @return $this */ - public function setError($value) + public function setError($error) { - $this->error = $value; + $this->error = $error; return $this; } @@ -404,13 +404,13 @@ class DataValidation /** * Set Prompt title. * - * @param string $value + * @param string $promptTitle * - * @return DataValidation + * @return $this */ - public function setPromptTitle($value) + public function setPromptTitle($promptTitle) { - $this->promptTitle = $value; + $this->promptTitle = $promptTitle; return $this; } @@ -428,13 +428,13 @@ class DataValidation /** * Set Prompt. * - * @param string $value + * @param string $prompt * - * @return DataValidation + * @return $this */ - public function setPrompt($value) + public function setPrompt($prompt) { - $this->prompt = $value; + $this->prompt = $prompt; return $this; } @@ -460,6 +460,7 @@ class DataValidation $this->error . $this->promptTitle . $this->prompt . + $this->sqref . __CLASS__ ); } @@ -478,4 +479,19 @@ class DataValidation } } } + + /** @var ?string */ + private $sqref; + + public function getSqref(): ?string + { + return $this->sqref; + } + + public function setSqref(?string $str): self + { + $this->sqref = $str; + + return $this; + } } diff --git a/PhpOffice/PhpSpreadsheet/Cell/DataValidator.php b/PhpOffice/PhpSpreadsheet/Cell/DataValidator.php old mode 100755 new mode 100644 index 430d81b..0e395a7 --- a/PhpOffice/PhpSpreadsheet/Cell/DataValidator.php +++ b/PhpOffice/PhpSpreadsheet/Cell/DataValidator.php @@ -3,7 +3,7 @@ namespace PhpOffice\PhpSpreadsheet\Cell; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; +use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError; use PhpOffice\PhpSpreadsheet\Exception; /** @@ -64,8 +64,11 @@ class DataValidator try { $result = $calculation->calculateFormula($matchFormula, $cell->getCoordinate(), $cell); + while (is_array($result)) { + $result = array_pop($result); + } - return $result !== Functions::NA(); + return $result !== ExcelError::NA(); } catch (Exception $ex) { return false; } diff --git a/PhpOffice/PhpSpreadsheet/Cell/DefaultValueBinder.php b/PhpOffice/PhpSpreadsheet/Cell/DefaultValueBinder.php old mode 100755 new mode 100644 index 961e6de..92e1d25 --- a/PhpOffice/PhpSpreadsheet/Cell/DefaultValueBinder.php +++ b/PhpOffice/PhpSpreadsheet/Cell/DefaultValueBinder.php @@ -14,8 +14,6 @@ class DefaultValueBinder implements IValueBinder * @param Cell $cell Cell to bind value to * @param mixed $value Value to bind in cell * - * @throws \PhpOffice\PhpSpreadsheet\Exception - * * @return bool */ public function bindValue(Cell $cell, $value) @@ -28,7 +26,8 @@ class DefaultValueBinder implements IValueBinder if ($value instanceof DateTimeInterface) { $value = $value->format('Y-m-d H:i:s'); } elseif (!($value instanceof RichText)) { - $value = (string) $value; + // Attempt to cast any unexpected objects to string + $value = (string) $value; // @phpstan-ignore-line } } @@ -42,37 +41,39 @@ class DefaultValueBinder implements IValueBinder /** * DataType for value. * - * @param mixed $pValue + * @param mixed $value * * @return string */ - public static function dataTypeForValue($pValue) + public static function dataTypeForValue($value) { // Match the value against a few data types - if ($pValue === null) { + if ($value === null) { return DataType::TYPE_NULL; - } elseif ($pValue === '') { - return DataType::TYPE_STRING; - } elseif ($pValue instanceof RichText) { - return DataType::TYPE_INLINE; - } elseif (strpos($pValue, "=") === 0 && strlen($pValue) > 1) { - return DataType::TYPE_FORMULA; - } elseif (is_bool($pValue)) { - return DataType::TYPE_BOOL; - } elseif (is_float($pValue) || is_int($pValue)) { + } elseif (is_float($value) || is_int($value)) { return DataType::TYPE_NUMERIC; - } elseif (preg_match('/^[\+\-]?(\d+\\.?\d*|\d*\\.?\d+)([Ee][\-\+]?[0-2]?\d{1,3})?$/', $pValue)) { - $tValue = ltrim($pValue, '+-'); - if (is_string($pValue) && $tValue[0] === '0' && strlen($tValue) > 1 && $tValue[1] !== '.') { + } elseif (is_bool($value)) { + return DataType::TYPE_BOOL; + } elseif ($value === '') { + return DataType::TYPE_STRING; + } elseif ($value instanceof RichText) { + return DataType::TYPE_INLINE; + } elseif (is_string($value) && strlen($value) > 1 && $value[0] === '=') { + return DataType::TYPE_FORMULA; + } elseif (preg_match('/^[\+\-]?(\d+\\.?\d*|\d*\\.?\d+)([Ee][\-\+]?[0-2]?\d{1,3})?$/', $value)) { + $tValue = ltrim($value, '+-'); + if (is_string($value) && strlen($tValue) > 1 && $tValue[0] === '0' && $tValue[1] !== '.') { return DataType::TYPE_STRING; - } elseif ((strpos($pValue, '.') === false) && ($pValue > PHP_INT_MAX)) { + } elseif ((strpos($value, '.') === false) && ($value > PHP_INT_MAX)) { + return DataType::TYPE_STRING; + } elseif (!is_numeric($value)) { return DataType::TYPE_STRING; } return DataType::TYPE_NUMERIC; - } elseif (is_string($pValue)) { + } elseif (is_string($value)) { $errorCodes = DataType::getErrorCodes(); - if (isset($errorCodes[$pValue])) { + if (isset($errorCodes[$value])) { return DataType::TYPE_ERROR; } } diff --git a/PhpOffice/PhpSpreadsheet/Cell/Hyperlink.php b/PhpOffice/PhpSpreadsheet/Cell/Hyperlink.php old mode 100755 new mode 100644 index e17c20d..ffdcbac --- a/PhpOffice/PhpSpreadsheet/Cell/Hyperlink.php +++ b/PhpOffice/PhpSpreadsheet/Cell/Hyperlink.php @@ -21,14 +21,14 @@ class Hyperlink /** * Create a new Hyperlink. * - * @param string $pUrl Url to link the cell to - * @param string $pTooltip Tooltip to display on the hyperlink + * @param string $url Url to link the cell to + * @param string $tooltip Tooltip to display on the hyperlink */ - public function __construct($pUrl = '', $pTooltip = '') + public function __construct($url = '', $tooltip = '') { // Initialise member variables - $this->url = $pUrl; - $this->tooltip = $pTooltip; + $this->url = $url; + $this->tooltip = $tooltip; } /** @@ -44,13 +44,13 @@ class Hyperlink /** * Set URL. * - * @param string $value + * @param string $url * - * @return Hyperlink + * @return $this */ - public function setUrl($value) + public function setUrl($url) { - $this->url = $value; + $this->url = $url; return $this; } @@ -68,13 +68,13 @@ class Hyperlink /** * Set tooltip. * - * @param string $value + * @param string $tooltip * - * @return Hyperlink + * @return $this */ - public function setTooltip($value) + public function setTooltip($tooltip) { - $this->tooltip = $value; + $this->tooltip = $tooltip; return $this; } diff --git a/PhpOffice/PhpSpreadsheet/Cell/IValueBinder.php b/PhpOffice/PhpSpreadsheet/Cell/IValueBinder.php old mode 100755 new mode 100644 diff --git a/PhpOffice/PhpSpreadsheet/Cell/RowRange.php b/PhpOffice/PhpSpreadsheet/Cell/RowRange.php new file mode 100644 index 0000000..38e6c14 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Cell/RowRange.php @@ -0,0 +1,93 @@ +validateFromTo($from, $to ?? $from); + $this->worksheet = $worksheet; + } + + public static function fromArray(array $array, ?Worksheet $worksheet = null): self + { + [$from, $to] = $array; + + return new self($from, $to, $worksheet); + } + + private function validateFromTo(int $from, int $to): void + { + // Identify actual top and bottom values (in case we've been given bottom and top) + $this->from = min($from, $to); + $this->to = max($from, $to); + } + + public function from(): int + { + return $this->from; + } + + public function to(): int + { + return $this->to; + } + + public function rowCount(): int + { + return $this->to - $this->from + 1; + } + + public function shiftRight(int $offset = 1): self + { + $newFrom = $this->from + $offset; + $newFrom = ($newFrom < 1) ? 1 : $newFrom; + + $newTo = $this->to + $offset; + $newTo = ($newTo < 1) ? 1 : $newTo; + + return new self($newFrom, $newTo, $this->worksheet); + } + + public function shiftLeft(int $offset = 1): self + { + return $this->shiftRight(0 - $offset); + } + + public function toCellRange(): CellRange + { + return new CellRange( + CellAddress::fromColumnAndRow(Coordinate::columnIndexFromString('A'), $this->from, $this->worksheet), + CellAddress::fromColumnAndRow(Coordinate::columnIndexFromString(AddressRange::MAX_COLUMN), $this->to) + ); + } + + public function __toString(): string + { + if ($this->worksheet !== null) { + $title = str_replace("'", "''", $this->worksheet->getTitle()); + + return "'{$title}'!{$this->from}:{$this->to}"; + } + + return "{$this->from}:{$this->to}"; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Cell/StringValueBinder.php b/PhpOffice/PhpSpreadsheet/Cell/StringValueBinder.php old mode 100755 new mode 100644 index 0552677..1dcb612 --- a/PhpOffice/PhpSpreadsheet/Cell/StringValueBinder.php +++ b/PhpOffice/PhpSpreadsheet/Cell/StringValueBinder.php @@ -2,30 +2,123 @@ namespace PhpOffice\PhpSpreadsheet\Cell; +use DateTimeInterface; +use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; class StringValueBinder implements IValueBinder { + /** + * @var bool + */ + protected $convertNull = true; + + /** + * @var bool + */ + protected $convertBoolean = true; + + /** + * @var bool + */ + protected $convertNumeric = true; + + /** + * @var bool + */ + protected $convertFormula = true; + + public function setNullConversion(bool $suppressConversion = false): self + { + $this->convertNull = $suppressConversion; + + return $this; + } + + public function setBooleanConversion(bool $suppressConversion = false): self + { + $this->convertBoolean = $suppressConversion; + + return $this; + } + + public function getBooleanConversion(): bool + { + return $this->convertBoolean; + } + + public function setNumericConversion(bool $suppressConversion = false): self + { + $this->convertNumeric = $suppressConversion; + + return $this; + } + + public function setFormulaConversion(bool $suppressConversion = false): self + { + $this->convertFormula = $suppressConversion; + + return $this; + } + + public function setConversionForAllValueTypes(bool $suppressConversion = false): self + { + $this->convertNull = $suppressConversion; + $this->convertBoolean = $suppressConversion; + $this->convertNumeric = $suppressConversion; + $this->convertFormula = $suppressConversion; + + return $this; + } + /** * Bind value to a cell. * * @param Cell $cell Cell to bind value to * @param mixed $value Value to bind in cell - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - * - * @return bool */ public function bindValue(Cell $cell, $value) { + if (is_object($value)) { + return $this->bindObjectValue($cell, $value); + } + // sanitize UTF-8 strings if (is_string($value)) { $value = StringHelper::sanitizeUTF8($value); } - $cell->setValueExplicit((string) $value, DataType::TYPE_STRING); + if ($value === null && $this->convertNull === false) { + $cell->setValueExplicit($value, DataType::TYPE_NULL); + } elseif (is_bool($value) && $this->convertBoolean === false) { + $cell->setValueExplicit($value, DataType::TYPE_BOOL); + } elseif ((is_int($value) || is_float($value)) && $this->convertNumeric === false) { + $cell->setValueExplicit($value, DataType::TYPE_NUMERIC); + } elseif (is_string($value) && strlen($value) > 1 && $value[0] === '=' && $this->convertFormula === false) { + $cell->setValueExplicit($value, DataType::TYPE_FORMULA); + } else { + if (is_string($value) && strlen($value) > 1 && $value[0] === '=') { + $cell->getStyle()->setQuotePrefix(true); + } + $cell->setValueExplicit((string) $value, DataType::TYPE_STRING); + } + + return true; + } + + protected function bindObjectValue(Cell $cell, object $value): bool + { + // Handle any objects that might be injected + if ($value instanceof DateTimeInterface) { + $value = $value->format('Y-m-d H:i:s'); + } elseif ($value instanceof RichText) { + $cell->setValueExplicit($value, DataType::TYPE_INLINE); + + return true; + } + + $cell->setValueExplicit((string) $value, DataType::TYPE_STRING); // @phpstan-ignore-line - // Done! return true; } } diff --git a/PhpOffice/PhpSpreadsheet/CellReferenceHelper.php b/PhpOffice/PhpSpreadsheet/CellReferenceHelper.php new file mode 100644 index 0000000..24694d5 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/CellReferenceHelper.php @@ -0,0 +1,131 @@ +beforeCellAddress = str_replace('$', '', $beforeCellAddress); + $this->numberOfColumns = $numberOfColumns; + $this->numberOfRows = $numberOfRows; + + // Get coordinate of $beforeCellAddress + [$beforeColumn, $beforeRow] = Coordinate::coordinateFromString($beforeCellAddress); + $this->beforeColumn = (int) Coordinate::columnIndexFromString($beforeColumn); + $this->beforeRow = (int) $beforeRow; + } + + public function beforeCellAddress(): string + { + return $this->beforeCellAddress; + } + + public function refreshRequired(string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): bool + { + return $this->beforeCellAddress !== $beforeCellAddress || + $this->numberOfColumns !== $numberOfColumns || + $this->numberOfRows !== $numberOfRows; + } + + public function updateCellReference(string $cellReference = 'A1', bool $includeAbsoluteReferences = false): string + { + if (Coordinate::coordinateIsRange($cellReference)) { + throw new Exception('Only single cell references may be passed to this method.'); + } + + // Get coordinate of $cellReference + [$newColumn, $newRow] = Coordinate::coordinateFromString($cellReference); + $newColumnIndex = (int) Coordinate::columnIndexFromString(str_replace('$', '', $newColumn)); + $newRowIndex = (int) str_replace('$', '', $newRow); + + $absoluteColumn = $newColumn[0] === '$' ? '$' : ''; + $absoluteRow = $newRow[0] === '$' ? '$' : ''; + // Verify which parts should be updated + if ($includeAbsoluteReferences === false) { + $updateColumn = (($absoluteColumn !== '$') && $newColumnIndex >= $this->beforeColumn); + $updateRow = (($absoluteRow !== '$') && $newRowIndex >= $this->beforeRow); + } else { + $updateColumn = ($newColumnIndex >= $this->beforeColumn); + $updateRow = ($newRowIndex >= $this->beforeRow); + } + + // Create new column reference + if ($updateColumn) { + $newColumn = $this->updateColumnReference($newColumnIndex, $absoluteColumn); + } + + // Create new row reference + if ($updateRow) { + $newRow = $this->updateRowReference($newRowIndex, $absoluteRow); + } + + // Return new reference + return "{$newColumn}{$newRow}"; + } + + public function cellAddressInDeleteRange(string $cellAddress): bool + { + [$cellColumn, $cellRow] = Coordinate::coordinateFromString($cellAddress); + $cellColumnIndex = Coordinate::columnIndexFromString($cellColumn); + // Is cell within the range of rows/columns if we're deleting + if ( + $this->numberOfRows < 0 && + ($cellRow >= ($this->beforeRow + $this->numberOfRows)) && + ($cellRow < $this->beforeRow) + ) { + return true; + } elseif ( + $this->numberOfColumns < 0 && + ($cellColumnIndex >= ($this->beforeColumn + $this->numberOfColumns)) && + ($cellColumnIndex < $this->beforeColumn) + ) { + return true; + } + + return false; + } + + protected function updateColumnReference(int $newColumnIndex, string $absoluteColumn): string + { + $newColumn = Coordinate::stringFromColumnIndex(min($newColumnIndex + $this->numberOfColumns, AddressRange::MAX_COLUMN_INT)); + + return $absoluteColumn . $newColumn; + } + + protected function updateRowReference(int $newRowIndex, string $absoluteRow): string + { + $newRow = $newRowIndex + $this->numberOfRows; + $newRow = ($newRow > AddressRange::MAX_ROW) ? AddressRange::MAX_ROW : $newRow; + + return $absoluteRow . (string) $newRow; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Chart/Axis.php b/PhpOffice/PhpSpreadsheet/Chart/Axis.php old mode 100755 new mode 100644 index 3d1dd22..ade7b99 --- a/PhpOffice/PhpSpreadsheet/Chart/Axis.php +++ b/PhpOffice/PhpSpreadsheet/Chart/Axis.php @@ -10,20 +10,52 @@ namespace PhpOffice\PhpSpreadsheet\Chart; */ class Axis extends Properties { + const AXIS_TYPE_CATEGORY = 'catAx'; + const AXIS_TYPE_DATE = 'dateAx'; + const AXIS_TYPE_VALUE = 'valAx'; + + const TIME_UNIT_DAYS = 'days'; + const TIME_UNIT_MONTHS = 'months'; + const TIME_UNIT_YEARS = 'years'; + + public function __construct() + { + parent::__construct(); + $this->fillColor = new ChartColor(); + } + + /** + * Chart Major Gridlines as. + * + * @var ?GridLines + */ + private $majorGridlines; + + /** + * Chart Minor Gridlines as. + * + * @var ?GridLines + */ + private $minorGridlines; + /** * Axis Number. * - * @var array of mixed + * @var mixed[] */ private $axisNumber = [ 'format' => self::FORMAT_CODE_GENERAL, 'source_linked' => 1, + 'numeric' => null, ]; + /** @var string */ + private $axisType = ''; + /** * Axis Options. * - * @var array of mixed + * @var mixed[] */ private $axisOptions = [ 'minimum' => null, @@ -36,112 +68,41 @@ class Axis extends Properties 'axis_labels' => self::AXIS_LABELS_NEXT_TO, 'horizontal_crosses' => self::HORIZONTAL_CROSSES_AUTOZERO, 'horizontal_crosses_value' => null, + 'textRotation' => null, + 'hidden' => null, + 'majorTimeUnit' => self::TIME_UNIT_YEARS, + 'minorTimeUnit' => self::TIME_UNIT_MONTHS, + 'baseTimeUnit' => self::TIME_UNIT_DAYS, ]; /** * Fill Properties. * - * @var array of mixed + * @var ChartColor */ - private $fillProperties = [ - 'type' => self::EXCEL_COLOR_TYPE_ARGB, - 'value' => null, - 'alpha' => 0, - ]; + private $fillColor; - /** - * Line Properties. - * - * @var array of mixed - */ - private $lineProperties = [ - 'type' => self::EXCEL_COLOR_TYPE_ARGB, - 'value' => null, - 'alpha' => 0, - ]; - - /** - * Line Style Properties. - * - * @var array of mixed - */ - private $lineStyleProperties = [ - 'width' => '9525', - 'compound' => self::LINE_STYLE_COMPOUND_SIMPLE, - 'dash' => self::LINE_STYLE_DASH_SOLID, - 'cap' => self::LINE_STYLE_CAP_FLAT, - 'join' => self::LINE_STYLE_JOIN_BEVEL, - 'arrow' => [ - 'head' => [ - 'type' => self::LINE_STYLE_ARROW_TYPE_NOARROW, - 'size' => self::LINE_STYLE_ARROW_SIZE_5, - ], - 'end' => [ - 'type' => self::LINE_STYLE_ARROW_TYPE_NOARROW, - 'size' => self::LINE_STYLE_ARROW_SIZE_8, - ], - ], - ]; - - /** - * Shadow Properties. - * - * @var array of mixed - */ - private $shadowProperties = [ - 'presets' => self::SHADOW_PRESETS_NOSHADOW, - 'effect' => null, - 'color' => [ - 'type' => self::EXCEL_COLOR_TYPE_STANDARD, - 'value' => 'black', - 'alpha' => 40, - ], - 'size' => [ - 'sx' => null, - 'sy' => null, - 'kx' => null, - ], - 'blur' => null, - 'direction' => null, - 'distance' => null, - 'algn' => null, - 'rotWithShape' => null, - ]; - - /** - * Glow Properties. - * - * @var array of mixed - */ - private $glowProperties = [ - 'size' => null, - 'color' => [ - 'type' => self::EXCEL_COLOR_TYPE_STANDARD, - 'value' => 'black', - 'alpha' => 40, - ], - ]; - - /** - * Soft Edge Properties. - * - * @var array of mixed - */ - private $softEdges = [ - 'size' => null, + private const NUMERIC_FORMAT = [ + Properties::FORMAT_CODE_NUMBER, + Properties::FORMAT_CODE_DATE, + Properties::FORMAT_CODE_DATE_ISO8601, ]; /** * Get Series Data Type. * * @param mixed $format_code - * - * @return string */ - public function setAxisNumberProperties($format_code) + public function setAxisNumberProperties($format_code, ?bool $numeric = null, int $sourceLinked = 0): void { - $this->axisNumber['format'] = (string) $format_code; - $this->axisNumber['source_linked'] = 0; + $format = (string) $format_code; + $this->axisNumber['format'] = $format; + $this->axisNumber['source_linked'] = $sourceLinked; + if (is_bool($numeric)) { + $this->axisNumber['numeric'] = $numeric; + } elseif (in_array($format, self::NUMERIC_FORMAT, true)) { + $this->axisNumber['numeric'] = true; + } } /** @@ -164,33 +125,53 @@ class Axis extends Properties return (string) $this->axisNumber['source_linked']; } + public function getAxisIsNumericFormat(): bool + { + return $this->axisType === self::AXIS_TYPE_DATE || (bool) $this->axisNumber['numeric']; + } + + public function setAxisOption(string $key, ?string $value): void + { + if ($value !== null && $value !== '') { + $this->axisOptions[$key] = $value; + } + } + /** * Set Axis Options Properties. - * - * @param string $axis_labels - * @param string $horizontal_crosses_value - * @param string $horizontal_crosses - * @param string $axis_orientation - * @param string $major_tmt - * @param string $minor_tmt - * @param string $minimum - * @param string $maximum - * @param string $major_unit - * @param string $minor_unit */ - public function setAxisOptionsProperties($axis_labels, $horizontal_crosses_value = null, $horizontal_crosses = null, $axis_orientation = null, $major_tmt = null, $minor_tmt = null, $minimum = null, $maximum = null, $major_unit = null, $minor_unit = null) - { - $this->axisOptions['axis_labels'] = (string) $axis_labels; - ($horizontal_crosses_value !== null) ? $this->axisOptions['horizontal_crosses_value'] = (string) $horizontal_crosses_value : null; - ($horizontal_crosses !== null) ? $this->axisOptions['horizontal_crosses'] = (string) $horizontal_crosses : null; - ($axis_orientation !== null) ? $this->axisOptions['orientation'] = (string) $axis_orientation : null; - ($major_tmt !== null) ? $this->axisOptions['major_tick_mark'] = (string) $major_tmt : null; - ($minor_tmt !== null) ? $this->axisOptions['minor_tick_mark'] = (string) $minor_tmt : null; - ($minor_tmt !== null) ? $this->axisOptions['minor_tick_mark'] = (string) $minor_tmt : null; - ($minimum !== null) ? $this->axisOptions['minimum'] = (string) $minimum : null; - ($maximum !== null) ? $this->axisOptions['maximum'] = (string) $maximum : null; - ($major_unit !== null) ? $this->axisOptions['major_unit'] = (string) $major_unit : null; - ($minor_unit !== null) ? $this->axisOptions['minor_unit'] = (string) $minor_unit : null; + public function setAxisOptionsProperties( + string $axisLabels, + ?string $horizontalCrossesValue = null, + ?string $horizontalCrosses = null, + ?string $axisOrientation = null, + ?string $majorTmt = null, + ?string $minorTmt = null, + ?string $minimum = null, + ?string $maximum = null, + ?string $majorUnit = null, + ?string $minorUnit = null, + ?string $textRotation = null, + ?string $hidden = null, + ?string $baseTimeUnit = null, + ?string $majorTimeUnit = null, + ?string $minorTimeUnit = null + ): void { + $this->axisOptions['axis_labels'] = $axisLabels; + $this->setAxisOption('horizontal_crosses_value', $horizontalCrossesValue); + $this->setAxisOption('horizontal_crosses', $horizontalCrosses); + $this->setAxisOption('orientation', $axisOrientation); + $this->setAxisOption('major_tick_mark', $majorTmt); + $this->setAxisOption('minor_tick_mark', $minorTmt); + $this->setAxisOption('minimum', $minimum); + $this->setAxisOption('maximum', $maximum); + $this->setAxisOption('major_unit', $majorUnit); + $this->setAxisOption('minor_unit', $minorUnit); + $this->setAxisOption('textRotation', $textRotation); + $this->setAxisOption('hidden', $hidden); + $this->setAxisOption('baseTimeUnit', $baseTimeUnit); + $this->setAxisOption('majorTimeUnit', $majorTimeUnit); + $this->setAxisOption('minorTimeUnit', $minorTimeUnit); } /** @@ -198,7 +179,7 @@ class Axis extends Properties * * @param string $property * - * @return string + * @return ?string */ public function getAxisOptionsProperty($property) { @@ -210,33 +191,37 @@ class Axis extends Properties * * @param string $orientation */ - public function setAxisOrientation($orientation) + public function setAxisOrientation($orientation): void { $this->axisOptions['orientation'] = (string) $orientation; } + public function getAxisType(): string + { + return $this->axisType; + } + + public function setAxisType(string $type): self + { + if ($type === self::AXIS_TYPE_CATEGORY || $type === self::AXIS_TYPE_VALUE || $type === self::AXIS_TYPE_DATE) { + $this->axisType = $type; + } else { + $this->axisType = ''; + } + + return $this; + } + /** * Set Fill Property. * - * @param string $color - * @param int $alpha - * @param string $type + * @param ?string $color + * @param ?int $alpha + * @param ?string $AlphaType */ - public function setFillParameters($color, $alpha = 0, $type = self::EXCEL_COLOR_TYPE_ARGB) + public function setFillParameters($color, $alpha = null, $AlphaType = ChartColor::EXCEL_COLOR_TYPE_RGB): void { - $this->fillProperties = $this->setColorProperties($color, $alpha, $type); - } - - /** - * Set Line Property. - * - * @param string $color - * @param int $alpha - * @param string $type - */ - public function setLineParameters($color, $alpha = 0, $type = self::EXCEL_COLOR_TYPE_ARGB) - { - $this->lineProperties = $this->setColorProperties($color, $alpha, $type); + $this->fillColor->setColorProperties($color, $alpha, $AlphaType); } /** @@ -248,310 +233,66 @@ class Axis extends Properties */ public function getFillProperty($property) { - return $this->fillProperties[$property]; + return (string) $this->fillColor->getColorProperty($property); + } + + public function getFillColorObject(): ChartColor + { + return $this->fillColor; } /** - * Get Line Property. + * Get Line Color Property. * - * @param string $property + * @deprecated 1.24.0 + * Use the getLineColor property in the Properties class instead + * @see Properties::getLineColorProperty() * - * @return string + * @param string $propertyName + * + * @return null|int|string */ - public function getLineProperty($property) + public function getLineProperty($propertyName) { - return $this->lineProperties[$property]; + return $this->getLineColorProperty($propertyName); } - /** - * Set Line Style Properties. - * - * @param float $line_width - * @param string $compound_type - * @param string $dash_type - * @param string $cap_type - * @param string $join_type - * @param string $head_arrow_type - * @param string $head_arrow_size - * @param string $end_arrow_type - * @param string $end_arrow_size - */ - public function setLineStyleProperties($line_width = null, $compound_type = null, $dash_type = null, $cap_type = null, $join_type = null, $head_arrow_type = null, $head_arrow_size = null, $end_arrow_type = null, $end_arrow_size = null) - { - ($line_width !== null) ? $this->lineStyleProperties['width'] = $this->getExcelPointsWidth((float) $line_width) : null; - ($compound_type !== null) ? $this->lineStyleProperties['compound'] = (string) $compound_type : null; - ($dash_type !== null) ? $this->lineStyleProperties['dash'] = (string) $dash_type : null; - ($cap_type !== null) ? $this->lineStyleProperties['cap'] = (string) $cap_type : null; - ($join_type !== null) ? $this->lineStyleProperties['join'] = (string) $join_type : null; - ($head_arrow_type !== null) ? $this->lineStyleProperties['arrow']['head']['type'] = (string) $head_arrow_type : null; - ($head_arrow_size !== null) ? $this->lineStyleProperties['arrow']['head']['size'] = (string) $head_arrow_size : null; - ($end_arrow_type !== null) ? $this->lineStyleProperties['arrow']['end']['type'] = (string) $end_arrow_type : null; - ($end_arrow_size !== null) ? $this->lineStyleProperties['arrow']['end']['size'] = (string) $end_arrow_size : null; - } + /** @var string */ + private $crossBetween = ''; // 'between' or 'midCat' might be better - /** - * Get Line Style Property. - * - * @param array|string $elements - * - * @return string - */ - public function getLineStyleProperty($elements) + public function setCrossBetween(string $crossBetween): self { - return $this->getArrayElementsValue($this->lineStyleProperties, $elements); - } - - /** - * Get Line Style Arrow Excel Width. - * - * @param string $arrow - * - * @return string - */ - public function getLineStyleArrowWidth($arrow) - { - return $this->getLineStyleArrowSize($this->lineStyleProperties['arrow'][$arrow]['size'], 'w'); - } - - /** - * Get Line Style Arrow Excel Length. - * - * @param string $arrow - * - * @return string - */ - public function getLineStyleArrowLength($arrow) - { - return $this->getLineStyleArrowSize($this->lineStyleProperties['arrow'][$arrow]['size'], 'len'); - } - - /** - * Set Shadow Properties. - * - * @param int $sh_presets - * @param string $sh_color_value - * @param string $sh_color_type - * @param string $sh_color_alpha - * @param float $sh_blur - * @param int $sh_angle - * @param float $sh_distance - */ - public function setShadowProperties($sh_presets, $sh_color_value = null, $sh_color_type = null, $sh_color_alpha = null, $sh_blur = null, $sh_angle = null, $sh_distance = null) - { - $this->setShadowPresetsProperties((int) $sh_presets) - ->setShadowColor( - $sh_color_value === null ? $this->shadowProperties['color']['value'] : $sh_color_value, - $sh_color_alpha === null ? (int) $this->shadowProperties['color']['alpha'] : $sh_color_alpha, - $sh_color_type === null ? $this->shadowProperties['color']['type'] : $sh_color_type - ) - ->setShadowBlur($sh_blur) - ->setShadowAngle($sh_angle) - ->setShadowDistance($sh_distance); - } - - /** - * Set Shadow Color. - * - * @param int $shadow_presets - * - * @return Axis - */ - private function setShadowPresetsProperties($shadow_presets) - { - $this->shadowProperties['presets'] = $shadow_presets; - $this->setShadowProperiesMapValues($this->getShadowPresetsMap($shadow_presets)); + $this->crossBetween = $crossBetween; return $this; } - /** - * Set Shadow Properties from Mapped Values. - * - * @param array $properties_map - * @param mixed &$reference - * - * @return Axis - */ - private function setShadowProperiesMapValues(array $properties_map, &$reference = null) + public function getCrossBetween(): string { - $base_reference = $reference; - foreach ($properties_map as $property_key => $property_val) { - if (is_array($property_val)) { - if ($reference === null) { - $reference = &$this->shadowProperties[$property_key]; - } else { - $reference = &$reference[$property_key]; - } - $this->setShadowProperiesMapValues($property_val, $reference); - } else { - if ($base_reference === null) { - $this->shadowProperties[$property_key] = $property_val; - } else { - $reference[$property_key] = $property_val; - } - } - } + return $this->crossBetween; + } + + public function getMajorGridlines(): ?GridLines + { + return $this->majorGridlines; + } + + public function getMinorGridlines(): ?GridLines + { + return $this->minorGridlines; + } + + public function setMajorGridlines(?GridLines $gridlines): self + { + $this->majorGridlines = $gridlines; return $this; } - /** - * Set Shadow Color. - * - * @param string $color - * @param int $alpha - * @param string $type - * - * @return Axis - */ - private function setShadowColor($color, $alpha, $type) + public function setMinorGridlines(?GridLines $gridlines): self { - $this->shadowProperties['color'] = $this->setColorProperties($color, $alpha, $type); + $this->minorGridlines = $gridlines; return $this; } - - /** - * Set Shadow Blur. - * - * @param float $blur - * - * @return Axis - */ - private function setShadowBlur($blur) - { - if ($blur !== null) { - $this->shadowProperties['blur'] = (string) $this->getExcelPointsWidth($blur); - } - - return $this; - } - - /** - * Set Shadow Angle. - * - * @param int $angle - * - * @return Axis - */ - private function setShadowAngle($angle) - { - if ($angle !== null) { - $this->shadowProperties['direction'] = (string) $this->getExcelPointsAngle($angle); - } - - return $this; - } - - /** - * Set Shadow Distance. - * - * @param float $distance - * - * @return Axis - */ - private function setShadowDistance($distance) - { - if ($distance !== null) { - $this->shadowProperties['distance'] = (string) $this->getExcelPointsWidth($distance); - } - - return $this; - } - - /** - * Get Shadow Property. - * - * @param string|string[] $elements - * - * @return null|array|int|string - */ - public function getShadowProperty($elements) - { - return $this->getArrayElementsValue($this->shadowProperties, $elements); - } - - /** - * Set Glow Properties. - * - * @param float $size - * @param string $color_value - * @param int $color_alpha - * @param string $color_type - */ - public function setGlowProperties($size, $color_value = null, $color_alpha = null, $color_type = null) - { - $this->setGlowSize($size) - ->setGlowColor( - $color_value === null ? $this->glowProperties['color']['value'] : $color_value, - $color_alpha === null ? (int) $this->glowProperties['color']['alpha'] : $color_alpha, - $color_type === null ? $this->glowProperties['color']['type'] : $color_type - ); - } - - /** - * Get Glow Property. - * - * @param array|string $property - * - * @return string - */ - public function getGlowProperty($property) - { - return $this->getArrayElementsValue($this->glowProperties, $property); - } - - /** - * Set Glow Color. - * - * @param float $size - * - * @return Axis - */ - private function setGlowSize($size) - { - if ($size !== null) { - $this->glowProperties['size'] = $this->getExcelPointsWidth($size); - } - - return $this; - } - - /** - * Set Glow Color. - * - * @param string $color - * @param int $alpha - * @param string $type - * - * @return Axis - */ - private function setGlowColor($color, $alpha, $type) - { - $this->glowProperties['color'] = $this->setColorProperties($color, $alpha, $type); - - return $this; - } - - /** - * Set Soft Edges Size. - * - * @param float $size - */ - public function setSoftEdges($size) - { - if ($size !== null) { - $softEdges['size'] = (string) $this->getExcelPointsWidth($size); - } - } - - /** - * Get Soft Edges Size. - * - * @return string - */ - public function getSoftEdgesSize() - { - return $this->softEdges['size']; - } } diff --git a/PhpOffice/PhpSpreadsheet/Chart/Chart.php b/PhpOffice/PhpSpreadsheet/Chart/Chart.php old mode 100755 new mode 100644 index 3d09a04..2ff22a3 --- a/PhpOffice/PhpSpreadsheet/Chart/Chart.php +++ b/PhpOffice/PhpSpreadsheet/Chart/Chart.php @@ -17,42 +17,42 @@ class Chart /** * Worksheet. * - * @var Worksheet + * @var ?Worksheet */ private $worksheet; /** * Chart Title. * - * @var Title + * @var ?Title */ private $title; /** * Chart Legend. * - * @var Legend + * @var ?Legend */ private $legend; /** * X-Axis Label. * - * @var Title + * @var ?Title */ private $xAxisLabel; /** * Y-Axis Label. * - * @var Title + * @var ?Title */ private $yAxisLabel; /** * Chart Plot Area. * - * @var PlotArea + * @var ?PlotArea */ private $plotArea; @@ -68,7 +68,7 @@ class Chart * * @var string */ - private $displayBlanksAs = '0'; + private $displayBlanksAs = DataSeries::EMPTY_AS_GAP; /** * Chart Asix Y as. @@ -84,20 +84,6 @@ class Chart */ private $xAxis; - /** - * Chart Major Gridlines as. - * - * @var GridLines - */ - private $majorGridlines; - - /** - * Chart Minor Gridlines as. - * - * @var GridLines - */ - private $minorGridlines; - /** * Top-Left Cell Position. * @@ -124,7 +110,7 @@ class Chart * * @var string */ - private $bottomRightCellRef = 'A1'; + private $bottomRightCellRef = ''; /** * Bottom-Right X-Offset. @@ -140,23 +126,39 @@ class Chart */ private $bottomRightYOffset = 10; + /** @var ?int */ + private $rotX; + + /** @var ?int */ + private $rotY; + + /** @var ?int */ + private $rAngAx; + + /** @var ?int */ + private $perspective; + + /** @var bool */ + private $oneCellAnchor = false; + + /** @var bool */ + private $autoTitleDeleted = false; + + /** @var bool */ + private $noFill = false; + + /** @var bool */ + private $roundedCorners = false; + /** * Create a new Chart. + * majorGridlines and minorGridlines are deprecated, moved to Axis. * * @param mixed $name - * @param null|Title $title - * @param null|Legend $legend - * @param null|PlotArea $plotArea * @param mixed $plotVisibleOnly - * @param mixed $displayBlanksAs - * @param null|Title $xAxisLabel - * @param null|Title $yAxisLabel - * @param null|Axis $xAxis - * @param null|Axis $yAxis - * @param null|GridLines $majorGridlines - * @param null|GridLines $minorGridlines + * @param string $displayBlanksAs */ - public function __construct($name, Title $title = null, Legend $legend = null, PlotArea $plotArea = null, $plotVisibleOnly = true, $displayBlanksAs = '0', Title $xAxisLabel = null, Title $yAxisLabel = null, Axis $xAxis = null, Axis $yAxis = null, GridLines $majorGridlines = null, GridLines $minorGridlines = null) + public function __construct($name, ?Title $title = null, ?Legend $legend = null, ?PlotArea $plotArea = null, $plotVisibleOnly = true, $displayBlanksAs = DataSeries::EMPTY_AS_GAP, ?Title $xAxisLabel = null, ?Title $yAxisLabel = null, ?Axis $xAxis = null, ?Axis $yAxis = null, ?GridLines $majorGridlines = null, ?GridLines $minorGridlines = null) { $this->name = $name; $this->title = $title; @@ -166,10 +168,14 @@ class Chart $this->plotArea = $plotArea; $this->plotVisibleOnly = $plotVisibleOnly; $this->displayBlanksAs = $displayBlanksAs; - $this->xAxis = $xAxis; - $this->yAxis = $yAxis; - $this->majorGridlines = $majorGridlines; - $this->minorGridlines = $minorGridlines; + $this->xAxis = $xAxis ?? new Axis(); + $this->yAxis = $yAxis ?? new Axis(); + if ($majorGridlines !== null) { + $this->yAxis->setMajorGridlines($majorGridlines); + } + if ($minorGridlines !== null) { + $this->yAxis->setMinorGridlines($minorGridlines); + } } /** @@ -182,12 +188,17 @@ class Chart return $this->name; } + public function setName(string $name): self + { + $this->name = $name; + + return $this; + } + /** * Get Worksheet. - * - * @return Worksheet */ - public function getWorksheet() + public function getWorksheet(): ?Worksheet { return $this->worksheet; } @@ -195,23 +206,16 @@ class Chart /** * Set Worksheet. * - * @param Worksheet $pValue - * - * @return Chart + * @return $this */ - public function setWorksheet(Worksheet $pValue = null) + public function setWorksheet(?Worksheet $worksheet = null) { - $this->worksheet = $pValue; + $this->worksheet = $worksheet; return $this; } - /** - * Get Title. - * - * @return Title - */ - public function getTitle() + public function getTitle(): ?Title { return $this->title; } @@ -219,9 +223,7 @@ class Chart /** * Set Title. * - * @param Title $title - * - * @return Chart + * @return $this */ public function setTitle(Title $title) { @@ -230,12 +232,7 @@ class Chart return $this; } - /** - * Get Legend. - * - * @return Legend - */ - public function getLegend() + public function getLegend(): ?Legend { return $this->legend; } @@ -243,9 +240,7 @@ class Chart /** * Set Legend. * - * @param Legend $legend - * - * @return Chart + * @return $this */ public function setLegend(Legend $legend) { @@ -254,12 +249,7 @@ class Chart return $this; } - /** - * Get X-Axis Label. - * - * @return Title - */ - public function getXAxisLabel() + public function getXAxisLabel(): ?Title { return $this->xAxisLabel; } @@ -267,9 +257,7 @@ class Chart /** * Set X-Axis Label. * - * @param Title $label - * - * @return Chart + * @return $this */ public function setXAxisLabel(Title $label) { @@ -278,12 +266,7 @@ class Chart return $this; } - /** - * Get Y-Axis Label. - * - * @return Title - */ - public function getYAxisLabel() + public function getYAxisLabel(): ?Title { return $this->yAxisLabel; } @@ -291,9 +274,7 @@ class Chart /** * Set Y-Axis Label. * - * @param Title $label - * - * @return Chart + * @return $this */ public function setYAxisLabel(Title $label) { @@ -302,16 +283,21 @@ class Chart return $this; } - /** - * Get Plot Area. - * - * @return PlotArea - */ - public function getPlotArea() + public function getPlotArea(): ?PlotArea { return $this->plotArea; } + /** + * Set Plot Area. + */ + public function setPlotArea(PlotArea $plotArea): self + { + $this->plotArea = $plotArea; + + return $this; + } + /** * Get Plot Visible Only. * @@ -327,7 +313,7 @@ class Chart * * @param bool $plotVisibleOnly * - * @return Chart + * @return $this */ public function setPlotVisibleOnly($plotVisibleOnly) { @@ -351,7 +337,7 @@ class Chart * * @param string $displayBlanksAs * - * @return Chart + * @return $this */ public function setDisplayBlanksAs($displayBlanksAs) { @@ -360,74 +346,74 @@ class Chart return $this; } - /** - * Get yAxis. - * - * @return Axis - */ - public function getChartAxisY() + public function getChartAxisY(): Axis { - if ($this->yAxis !== null) { - return $this->yAxis; - } - - return new Axis(); + return $this->yAxis; } /** - * Get xAxis. - * - * @return Axis + * Set yAxis. */ - public function getChartAxisX() + public function setChartAxisY(?Axis $axis): self { - if ($this->xAxis !== null) { - return $this->xAxis; - } + $this->yAxis = $axis ?? new Axis(); - return new Axis(); + return $this; + } + + public function getChartAxisX(): Axis + { + return $this->xAxis; + } + + /** + * Set xAxis. + */ + public function setChartAxisX(?Axis $axis): self + { + $this->xAxis = $axis ?? new Axis(); + + return $this; } /** * Get Major Gridlines. * - * @return GridLines + * @deprecated 1.24.0 Use Axis->getMajorGridlines() + * @see Axis::getMajorGridlines() + * + * @codeCoverageIgnore */ - public function getMajorGridlines() + public function getMajorGridlines(): ?GridLines { - if ($this->majorGridlines !== null) { - return $this->majorGridlines; - } - - return new GridLines(); + return $this->yAxis->getMajorGridLines(); } /** * Get Minor Gridlines. * - * @return GridLines + * @deprecated 1.24.0 Use Axis->getMinorGridlines() + * @see Axis::getMinorGridlines() + * + * @codeCoverageIgnore */ - public function getMinorGridlines() + public function getMinorGridlines(): ?GridLines { - if ($this->minorGridlines !== null) { - return $this->minorGridlines; - } - - return new GridLines(); + return $this->yAxis->getMinorGridLines(); } /** * Set the Top Left position for the chart. * - * @param string $cell + * @param string $cellAddress * @param int $xOffset * @param int $yOffset * - * @return Chart + * @return $this */ - public function setTopLeftPosition($cell, $xOffset = null, $yOffset = null) + public function setTopLeftPosition($cellAddress, $xOffset = null, $yOffset = null) { - $this->topLeftCellRef = $cell; + $this->topLeftCellRef = $cellAddress; if ($xOffset !== null) { $this->setTopLeftXOffset($xOffset); } @@ -441,9 +427,11 @@ class Chart /** * Get the top left position of the chart. * - * @return array an associative array containing the cell address, X-Offset and Y-Offset from the top left of that cell + * Returns ['cell' => string cell address, 'xOffset' => int, 'yOffset' => int]. + * + * @return array{cell: string, xOffset: int, yOffset: int} an associative array containing the cell address, X-Offset and Y-Offset from the top left of that cell */ - public function getTopLeftPosition() + public function getTopLeftPosition(): array { return [ 'cell' => $this->topLeftCellRef, @@ -465,13 +453,13 @@ class Chart /** * Set the Top Left cell position for the chart. * - * @param string $cell + * @param string $cellAddress * - * @return Chart + * @return $this */ - public function setTopLeftCell($cell) + public function setTopLeftCell($cellAddress) { - $this->topLeftCellRef = $cell; + $this->topLeftCellRef = $cellAddress; return $this; } @@ -479,10 +467,10 @@ class Chart /** * Set the offset position within the Top Left cell for the chart. * - * @param int $xOffset - * @param int $yOffset + * @param ?int $xOffset + * @param ?int $yOffset * - * @return Chart + * @return $this */ public function setTopLeftOffset($xOffset, $yOffset) { @@ -510,6 +498,11 @@ class Chart ]; } + /** + * @param int $xOffset + * + * @return $this + */ public function setTopLeftXOffset($xOffset) { $this->topLeftXOffset = $xOffset; @@ -517,11 +510,16 @@ class Chart return $this; } - public function getTopLeftXOffset() + public function getTopLeftXOffset(): int { return $this->topLeftXOffset; } + /** + * @param int $yOffset + * + * @return $this + */ public function setTopLeftYOffset($yOffset) { $this->topLeftYOffset = $yOffset; @@ -529,7 +527,7 @@ class Chart return $this; } - public function getTopLeftYOffset() + public function getTopLeftYOffset(): int { return $this->topLeftYOffset; } @@ -537,15 +535,15 @@ class Chart /** * Set the Bottom Right position of the chart. * - * @param string $cell + * @param string $cellAddress * @param int $xOffset * @param int $yOffset * - * @return Chart + * @return $this */ - public function setBottomRightPosition($cell, $xOffset = null, $yOffset = null) + public function setBottomRightPosition($cellAddress = '', $xOffset = null, $yOffset = null) { - $this->bottomRightCellRef = $cell; + $this->bottomRightCellRef = $cellAddress; if ($xOffset !== null) { $this->setBottomRightXOffset($xOffset); } @@ -570,19 +568,22 @@ class Chart ]; } - public function setBottomRightCell($cell) + /** + * Set the Bottom Right cell for the chart. + * + * @return $this + */ + public function setBottomRightCell(string $cellAddress = '') { - $this->bottomRightCellRef = $cell; + $this->bottomRightCellRef = $cellAddress; return $this; } /** * Get the cell address where the bottom right of the chart is fixed. - * - * @return string */ - public function getBottomRightCell() + public function getBottomRightCell(): string { return $this->bottomRightCellRef; } @@ -590,10 +591,10 @@ class Chart /** * Set the offset position within the Bottom Right cell for the chart. * - * @param int $xOffset - * @param int $yOffset + * @param ?int $xOffset + * @param ?int $yOffset * - * @return Chart + * @return $this */ public function setBottomRightOffset($xOffset, $yOffset) { @@ -621,6 +622,11 @@ class Chart ]; } + /** + * @param int $xOffset + * + * @return $this + */ public function setBottomRightXOffset($xOffset) { $this->bottomRightXOffset = $xOffset; @@ -628,11 +634,16 @@ class Chart return $this; } - public function getBottomRightXOffset() + public function getBottomRightXOffset(): int { return $this->bottomRightXOffset; } + /** + * @param int $yOffset + * + * @return $this + */ public function setBottomRightYOffset($yOffset) { $this->bottomRightYOffset = $yOffset; @@ -640,14 +651,14 @@ class Chart return $this; } - public function getBottomRightYOffset() + public function getBottomRightYOffset(): int { return $this->bottomRightYOffset; } - public function refresh() + public function refresh(): void { - if ($this->worksheet !== null) { + if ($this->worksheet !== null && $this->plotArea !== null) { $this->plotArea->refresh($this->worksheet); } } @@ -675,6 +686,104 @@ class Chart $renderer = new $libraryName($this); - return $renderer->render($outputDestination); + return $renderer->render($outputDestination); // @phpstan-ignore-line + } + + public function getRotX(): ?int + { + return $this->rotX; + } + + public function setRotX(?int $rotX): self + { + $this->rotX = $rotX; + + return $this; + } + + public function getRotY(): ?int + { + return $this->rotY; + } + + public function setRotY(?int $rotY): self + { + $this->rotY = $rotY; + + return $this; + } + + public function getRAngAx(): ?int + { + return $this->rAngAx; + } + + public function setRAngAx(?int $rAngAx): self + { + $this->rAngAx = $rAngAx; + + return $this; + } + + public function getPerspective(): ?int + { + return $this->perspective; + } + + public function setPerspective(?int $perspective): self + { + $this->perspective = $perspective; + + return $this; + } + + public function getOneCellAnchor(): bool + { + return $this->oneCellAnchor; + } + + public function setOneCellAnchor(bool $oneCellAnchor): self + { + $this->oneCellAnchor = $oneCellAnchor; + + return $this; + } + + public function getAutoTitleDeleted(): bool + { + return $this->autoTitleDeleted; + } + + public function setAutoTitleDeleted(bool $autoTitleDeleted): self + { + $this->autoTitleDeleted = $autoTitleDeleted; + + return $this; + } + + public function getNoFill(): bool + { + return $this->noFill; + } + + public function setNoFill(bool $noFill): self + { + $this->noFill = $noFill; + + return $this; + } + + public function getRoundedCorners(): bool + { + return $this->roundedCorners; + } + + public function setRoundedCorners(?bool $roundedCorners): self + { + if ($roundedCorners !== null) { + $this->roundedCorners = $roundedCorners; + } + + return $this; } } diff --git a/PhpOffice/PhpSpreadsheet/Chart/ChartColor.php b/PhpOffice/PhpSpreadsheet/Chart/ChartColor.php new file mode 100644 index 0000000..87f3102 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Chart/ChartColor.php @@ -0,0 +1,177 @@ +setColorPropertiesArray($value); + } else { + $this->setColorProperties($value, $alpha, $type, $brightness); + } + } + + public function getValue(): string + { + return $this->value; + } + + public function setValue(string $value): self + { + $this->value = $value; + + return $this; + } + + public function getType(): string + { + return $this->type; + } + + public function setType(string $type): self + { + $this->type = $type; + + return $this; + } + + public function getAlpha(): ?int + { + return $this->alpha; + } + + public function setAlpha(?int $alpha): self + { + $this->alpha = $alpha; + + return $this; + } + + public function getBrightness(): ?int + { + return $this->brightness; + } + + public function setBrightness(?int $brightness): self + { + $this->brightness = $brightness; + + return $this; + } + + /** + * @param null|float|int|string $alpha + * @param null|float|int|string $brightness + */ + public function setColorProperties(?string $color, $alpha = null, ?string $type = null, $brightness = null): self + { + if (empty($type) && !empty($color)) { + if (substr($color, 0, 1) === '*') { + $type = 'schemeClr'; + $color = substr($color, 1); + } elseif (substr($color, 0, 1) === '/') { + $type = 'prstClr'; + $color = substr($color, 1); + } elseif (preg_match('/^[0-9A-Fa-f]{6}$/', $color) === 1) { + $type = 'srgbClr'; + } + } + if ($color !== null) { + $this->setValue("$color"); + } + if ($type !== null) { + $this->setType($type); + } + if ($alpha === null) { + $this->setAlpha(null); + } elseif (is_numeric($alpha)) { + $this->setAlpha((int) $alpha); + } + if ($brightness === null) { + $this->setBrightness(null); + } elseif (is_numeric($brightness)) { + $this->setBrightness((int) $brightness); + } + + return $this; + } + + public function setColorPropertiesArray(array $color): self + { + return $this->setColorProperties( + $color['value'] ?? '', + $color['alpha'] ?? null, + $color['type'] ?? null, + $color['brightness'] ?? null + ); + } + + public function isUsable(): bool + { + return $this->type !== '' && $this->value !== ''; + } + + /** + * Get Color Property. + * + * @param string $propertyName + * + * @return null|int|string + */ + public function getColorProperty($propertyName) + { + $retVal = null; + if ($propertyName === 'value') { + $retVal = $this->value; + } elseif ($propertyName === 'type') { + $retVal = $this->type; + } elseif ($propertyName === 'alpha') { + $retVal = $this->alpha; + } elseif ($propertyName === 'brightness') { + $retVal = $this->brightness; + } + + return $retVal; + } + + public static function alphaToXml(int $alpha): string + { + return (string) (100 - $alpha) . '000'; + } + + /** + * @param float|int|string $alpha + */ + public static function alphaFromXml($alpha): int + { + return 100 - ((int) $alpha / 1000); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Chart/DataSeries.php b/PhpOffice/PhpSpreadsheet/Chart/DataSeries.php old mode 100755 new mode 100644 index 8056bbe..5d33e96 --- a/PhpOffice/PhpSpreadsheet/Chart/DataSeries.php +++ b/PhpOffice/PhpSpreadsheet/Chart/DataSeries.php @@ -40,6 +40,10 @@ class DataSeries const STYLE_MARKER = 'marker'; const STYLE_FILLED = 'filled'; + const EMPTY_AS_GAP = 'gap'; + const EMPTY_AS_ZERO = 'zero'; + const EMPTY_AS_SPAN = 'span'; + /** * Series Plot Type. * @@ -71,26 +75,26 @@ class DataSeries /** * Order of plots in Series. * - * @var array of integer + * @var int[] */ private $plotOrder = []; /** * Plot Label. * - * @var array of DataSeriesValues + * @var DataSeriesValues[] */ private $plotLabel = []; /** * Plot Category. * - * @var array of DataSeriesValues + * @var DataSeriesValues[] */ private $plotCategory = []; /** - * Smooth Line. + * Smooth Line. Must be specified for both DataSeries and DataSeriesValues. * * @var bool */ @@ -99,10 +103,17 @@ class DataSeries /** * Plot Values. * - * @var array of DataSeriesValues + * @var DataSeriesValues[] */ private $plotValues = []; + /** + * Plot Bubble Sizes. + * + * @var DataSeriesValues[] + */ + private $plotBubbleSizes = []; + /** * Create a new DataSeries. * @@ -123,12 +134,12 @@ class DataSeries $this->plotOrder = $plotOrder; $keys = array_keys($plotValues); $this->plotValues = $plotValues; - if ((count($plotLabel) == 0) || ($plotLabel[$keys[0]] === null)) { + if (!isset($plotLabel[$keys[0]])) { $plotLabel[$keys[0]] = new DataSeriesValues(); } $this->plotLabel = $plotLabel; - if ((count($plotCategory) == 0) || ($plotCategory[$keys[0]] === null)) { + if (!isset($plotCategory[$keys[0]])) { $plotCategory[$keys[0]] = new DataSeriesValues(); } $this->plotCategory = $plotCategory; @@ -157,7 +168,7 @@ class DataSeries * * @param string $plotType * - * @return DataSeries + * @return $this */ public function setPlotType($plotType) { @@ -181,7 +192,7 @@ class DataSeries * * @param string $groupingType * - * @return DataSeries + * @return $this */ public function setPlotGrouping($groupingType) { @@ -205,7 +216,7 @@ class DataSeries * * @param string $plotDirection * - * @return DataSeries + * @return $this */ public function setPlotDirection($plotDirection) { @@ -227,7 +238,7 @@ class DataSeries /** * Get Plot Labels. * - * @return array of DataSeriesValues + * @return DataSeriesValues[] */ public function getPlotLabels() { @@ -239,15 +250,13 @@ class DataSeries * * @param mixed $index * - * @return DataSeriesValues + * @return DataSeriesValues|false */ public function getPlotLabelByIndex($index) { $keys = array_keys($this->plotLabel); if (in_array($index, $keys)) { return $this->plotLabel[$index]; - } elseif (isset($keys[$index])) { - return $this->plotLabel[$keys[$index]]; } return false; @@ -256,7 +265,7 @@ class DataSeries /** * Get Plot Categories. * - * @return array of DataSeriesValues + * @return DataSeriesValues[] */ public function getPlotCategories() { @@ -268,7 +277,7 @@ class DataSeries * * @param mixed $index * - * @return DataSeriesValues + * @return DataSeriesValues|false */ public function getPlotCategoryByIndex($index) { @@ -297,7 +306,7 @@ class DataSeries * * @param null|string $plotStyle * - * @return DataSeries + * @return $this */ public function setPlotStyle($plotStyle) { @@ -309,7 +318,7 @@ class DataSeries /** * Get Plot Values. * - * @return array of DataSeriesValues + * @return DataSeriesValues[] */ public function getPlotValues() { @@ -321,20 +330,40 @@ class DataSeries * * @param mixed $index * - * @return DataSeriesValues + * @return DataSeriesValues|false */ public function getPlotValuesByIndex($index) { $keys = array_keys($this->plotValues); if (in_array($index, $keys)) { return $this->plotValues[$index]; - } elseif (isset($keys[$index])) { - return $this->plotValues[$keys[$index]]; } return false; } + /** + * Get Plot Bubble Sizes. + * + * @return DataSeriesValues[] + */ + public function getPlotBubbleSizes(): array + { + return $this->plotBubbleSizes; + } + + /** + * Set Plot Bubble Sizes. + * + * @param DataSeriesValues[] $plotBubbleSizes + */ + public function setPlotBubbleSizes(array $plotBubbleSizes): self + { + $this->plotBubbleSizes = $plotBubbleSizes; + + return $this; + } + /** * Get Number of Plot Series. * @@ -360,7 +389,7 @@ class DataSeries * * @param bool $smoothLine * - * @return DataSeries + * @return $this */ public function setSmoothLine($smoothLine) { @@ -369,7 +398,7 @@ class DataSeries return $this; } - public function refresh(Worksheet $worksheet) + public function refresh(Worksheet $worksheet): void { foreach ($this->plotValues as $plotValues) { if ($plotValues !== null) { diff --git a/PhpOffice/PhpSpreadsheet/Chart/DataSeriesValues.php b/PhpOffice/PhpSpreadsheet/Chart/DataSeriesValues.php old mode 100755 new mode 100644 index 73905d9..c86f556 --- a/PhpOffice/PhpSpreadsheet/Chart/DataSeriesValues.php +++ b/PhpOffice/PhpSpreadsheet/Chart/DataSeriesValues.php @@ -7,12 +7,12 @@ use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; -class DataSeriesValues +class DataSeriesValues extends Properties { const DATASERIES_TYPE_STRING = 'String'; const DATASERIES_TYPE_NUMBER = 'Number'; - private static $dataTypeValues = [ + private const DATA_TYPE_VALUES = [ self::DATASERIES_TYPE_STRING, self::DATASERIES_TYPE_NUMBER, ]; @@ -27,7 +27,7 @@ class DataSeriesValues /** * Series Data Source. * - * @var string + * @var ?string */ private $dataSource; @@ -45,6 +45,19 @@ class DataSeriesValues */ private $pointMarker; + /** @var ChartColor */ + private $markerFillColor; + + /** @var ChartColor */ + private $markerBorderColor; + + /** + * Series Point Size. + * + * @var int + */ + private $pointSize = 3; + /** * Point Count (The number of datapoints in the dataseries). * @@ -55,23 +68,28 @@ class DataSeriesValues /** * Data Values. * - * @var array of mixed + * @var mixed[] */ private $dataValues = []; /** * Fill color (can be array with colors if dataseries have custom colors). * - * @var string|string[] + * @var null|ChartColor|ChartColor[] */ private $fillColor; - /** - * Line Width. - * - * @var int - */ - private $lineWidth = 12700; + /** @var bool */ + private $scatterLines = true; + + /** @var bool */ + private $bubble3D = false; + + /** @var ?Layout */ + private $labelLayout; + + /** @var TrendLine[] */ + private $trendLines = []; /** * Create a new DataSeriesValues object. @@ -82,17 +100,26 @@ class DataSeriesValues * @param int $pointCount * @param mixed $dataValues * @param null|mixed $marker - * @param null|string|string[] $fillColor + * @param null|ChartColor|ChartColor[]|string|string[] $fillColor + * @param string $pointSize */ - public function __construct($dataType = self::DATASERIES_TYPE_NUMBER, $dataSource = null, $formatCode = null, $pointCount = 0, $dataValues = [], $marker = null, $fillColor = null) + public function __construct($dataType = self::DATASERIES_TYPE_NUMBER, $dataSource = null, $formatCode = null, $pointCount = 0, $dataValues = [], $marker = null, $fillColor = null, $pointSize = '3') { + parent::__construct(); + $this->markerFillColor = new ChartColor(); + $this->markerBorderColor = new ChartColor(); $this->setDataType($dataType); $this->dataSource = $dataSource; $this->formatCode = $formatCode; $this->pointCount = $pointCount; $this->dataValues = $dataValues; $this->pointMarker = $marker; - $this->fillColor = $fillColor; + if ($fillColor !== null) { + $this->setFillColor($fillColor); + } + if (is_numeric($pointSize)) { + $this->pointSize = (int) $pointSize; + } } /** @@ -115,13 +142,11 @@ class DataSeriesValues * DataSeriesValues::DATASERIES_TYPE_NUMBER * Normally used for chart data values * - * @throws Exception - * - * @return DataSeriesValues + * @return $this */ public function setDataType($dataType) { - if (!in_array($dataType, self::$dataTypeValues)) { + if (!in_array($dataType, self::DATA_TYPE_VALUES)) { throw new Exception('Invalid datatype for chart data series values'); } $this->dataType = $dataType; @@ -132,7 +157,7 @@ class DataSeriesValues /** * Get Series Data Source (formula). * - * @return string + * @return ?string */ public function getDataSource() { @@ -142,9 +167,9 @@ class DataSeriesValues /** * Set Series Data Source (formula). * - * @param string $dataSource + * @param ?string $dataSource * - * @return DataSeriesValues + * @return $this */ public function setDataSource($dataSource) { @@ -168,7 +193,7 @@ class DataSeriesValues * * @param string $marker * - * @return DataSeriesValues + * @return $this */ public function setPointMarker($marker) { @@ -177,6 +202,36 @@ class DataSeriesValues return $this; } + public function getMarkerFillColor(): ChartColor + { + return $this->markerFillColor; + } + + public function getMarkerBorderColor(): ChartColor + { + return $this->markerBorderColor; + } + + /** + * Get Point Size. + */ + public function getPointSize(): int + { + return $this->pointSize; + } + + /** + * Set Point Size. + * + * @return $this + */ + public function setPointSize(int $size = 3) + { + $this->pointSize = $size; + + return $this; + } + /** * Get Series Format Code. * @@ -192,7 +247,7 @@ class DataSeriesValues * * @param string $formatCode * - * @return DataSeriesValues + * @return $this */ public function setFormatCode($formatCode) { @@ -211,6 +266,51 @@ class DataSeriesValues return $this->pointCount; } + /** + * Get fill color object. + * + * @return null|ChartColor|ChartColor[] + */ + public function getFillColorObject() + { + return $this->fillColor; + } + + private function stringToChartColor(string $fillString): ChartColor + { + $value = $type = ''; + if (substr($fillString, 0, 1) === '*') { + $type = 'schemeClr'; + $value = substr($fillString, 1); + } elseif (substr($fillString, 0, 1) === '/') { + $type = 'prstClr'; + $value = substr($fillString, 1); + } elseif ($fillString !== '') { + $type = 'srgbClr'; + $value = $fillString; + $this->validateColor($value); + } + + return new ChartColor($value, null, $type); + } + + private function chartColorToString(ChartColor $chartColor): string + { + $type = (string) $chartColor->getColorProperty('type'); + $value = (string) $chartColor->getColorProperty('value'); + if ($type === '' || $value === '') { + return ''; + } + if ($type === 'schemeClr') { + return "*$value"; + } + if ($type === 'prstClr') { + return "/$value"; + } + + return $value; + } + /** * Get fill color. * @@ -218,26 +318,44 @@ class DataSeriesValues */ public function getFillColor() { - return $this->fillColor; + if ($this->fillColor === null) { + return ''; + } + if (is_array($this->fillColor)) { + $array = []; + foreach ($this->fillColor as $chartColor) { + $array[] = $this->chartColorToString($chartColor); + } + + return $array; + } + + return $this->chartColorToString($this->fillColor); } /** * Set fill color for series. * - * @param string|string[] $color HEX color or array with HEX colors + * @param ChartColor|ChartColor[]|string|string[] $color HEX color or array with HEX colors * * @return DataSeriesValues */ public function setFillColor($color) { if (is_array($color)) { - foreach ($color as $colorValue) { - $this->validateColor($colorValue); + $this->fillColor = []; + foreach ($color as $fillString) { + if ($fillString instanceof ChartColor) { + $this->fillColor[] = $fillString; + } else { + $this->fillColor[] = $this->stringToChartColor($fillString); + } } + } elseif ($color instanceof ChartColor) { + $this->fillColor = $color; } else { - $this->validateColor($color); + $this->fillColor = $this->stringToChartColor($color); } - $this->fillColor = $color; return $this; } @@ -247,8 +365,6 @@ class DataSeriesValues * * @param string $color value for color * - * @throws \Exception thrown if color is invalid - * * @return bool true if validation was successful */ private function validateColor($color) @@ -263,24 +379,23 @@ class DataSeriesValues /** * Get line width for series. * - * @return int + * @return null|float|int */ public function getLineWidth() { - return $this->lineWidth; + return $this->lineStyleProperties['width']; } /** * Set line width for the series. * - * @param int $width + * @param null|float|int $width * - * @return DataSeriesValues + * @return $this */ public function setLineWidth($width) { - $minWidth = 12700; - $this->lineWidth = max($minWidth, $width); + $this->lineStyleProperties['width'] = $width; return $this; } @@ -292,7 +407,7 @@ class DataSeriesValues */ public function isMultiLevelSeries() { - if (count($this->dataValues) > 0) { + if (!empty($this->dataValues)) { return is_array(array_values($this->dataValues)[0]); } @@ -317,7 +432,7 @@ class DataSeriesValues /** * Get Series Data Values. * - * @return array of mixed + * @return mixed[] */ public function getDataValues() { @@ -346,7 +461,7 @@ class DataSeriesValues * * @param array $dataValues * - * @return DataSeriesValues + * @return $this */ public function setDataValues($dataValues) { @@ -356,7 +471,7 @@ class DataSeriesValues return $this; } - public function refresh(Worksheet $worksheet, $flatten = true) + public function refresh(Worksheet $worksheet, bool $flatten = true): void { if ($this->dataSource !== null) { $calcEngine = Calculation::getInstance($worksheet->getParent()); @@ -370,7 +485,7 @@ class DataSeriesValues if ($flatten) { $this->dataValues = Functions::flattenArray($newDataValues); foreach ($this->dataValues as &$dataValue) { - if ((!empty($dataValue)) && ($dataValue[0] == '#')) { + if (is_string($dataValue) && !empty($dataValue) && $dataValue[0] == '#') { $dataValue = 0.0; } } @@ -381,7 +496,7 @@ class DataSeriesValues if (($dimensions[0] == 1) || ($dimensions[1] == 1)) { $this->dataValues = Functions::flattenArray($newDataValues); } else { - $newArray = array_values(array_shift($newDataValues)); + $newArray = array_values(array_shift(/** @scrutinizer ignore-type */ $newDataValues)); foreach ($newArray as $i => $newDataSet) { $newArray[$i] = [$newDataSet]; } @@ -398,4 +513,83 @@ class DataSeriesValues $this->pointCount = count($this->dataValues); } } + + public function getScatterLines(): bool + { + return $this->scatterLines; + } + + public function setScatterLines(bool $scatterLines): self + { + $this->scatterLines = $scatterLines; + + return $this; + } + + public function getBubble3D(): bool + { + return $this->bubble3D; + } + + public function setBubble3D(bool $bubble3D): self + { + $this->bubble3D = $bubble3D; + + return $this; + } + + /** + * Smooth Line. Must be specified for both DataSeries and DataSeriesValues. + * + * @var bool + */ + private $smoothLine; + + /** + * Get Smooth Line. + * + * @return bool + */ + public function getSmoothLine() + { + return $this->smoothLine; + } + + /** + * Set Smooth Line. + * + * @param bool $smoothLine + * + * @return $this + */ + public function setSmoothLine($smoothLine) + { + $this->smoothLine = $smoothLine; + + return $this; + } + + public function getLabelLayout(): ?Layout + { + return $this->labelLayout; + } + + public function setLabelLayout(?Layout $labelLayout): self + { + $this->labelLayout = $labelLayout; + + return $this; + } + + public function setTrendLines(array $trendLines): self + { + $this->trendLines = $trendLines; + + return $this; + } + + public function getTrendLines(): array + { + return $this->trendLines; + } } diff --git a/PhpOffice/PhpSpreadsheet/Chart/Exception.php b/PhpOffice/PhpSpreadsheet/Chart/Exception.php old mode 100755 new mode 100644 diff --git a/PhpOffice/PhpSpreadsheet/Chart/GridLines.php b/PhpOffice/PhpSpreadsheet/Chart/GridLines.php old mode 100755 new mode 100644 index 8cc83e5..8b86ccb --- a/PhpOffice/PhpSpreadsheet/Chart/GridLines.php +++ b/PhpOffice/PhpSpreadsheet/Chart/GridLines.php @@ -10,446 +10,4 @@ namespace PhpOffice\PhpSpreadsheet\Chart; */ class GridLines extends Properties { - /** - * Properties of Class: - * Object State (State for Minor Tick Mark) @var bool - * Line Properties @var array of mixed - * Shadow Properties @var array of mixed - * Glow Properties @var array of mixed - * Soft Properties @var array of mixed. - */ - private $objectState = false; - - private $lineProperties = [ - 'color' => [ - 'type' => self::EXCEL_COLOR_TYPE_STANDARD, - 'value' => null, - 'alpha' => 0, - ], - 'style' => [ - 'width' => '9525', - 'compound' => self::LINE_STYLE_COMPOUND_SIMPLE, - 'dash' => self::LINE_STYLE_DASH_SOLID, - 'cap' => self::LINE_STYLE_CAP_FLAT, - 'join' => self::LINE_STYLE_JOIN_BEVEL, - 'arrow' => [ - 'head' => [ - 'type' => self::LINE_STYLE_ARROW_TYPE_NOARROW, - 'size' => self::LINE_STYLE_ARROW_SIZE_5, - ], - 'end' => [ - 'type' => self::LINE_STYLE_ARROW_TYPE_NOARROW, - 'size' => self::LINE_STYLE_ARROW_SIZE_8, - ], - ], - ], - ]; - - private $shadowProperties = [ - 'presets' => self::SHADOW_PRESETS_NOSHADOW, - 'effect' => null, - 'color' => [ - 'type' => self::EXCEL_COLOR_TYPE_STANDARD, - 'value' => 'black', - 'alpha' => 85, - ], - 'size' => [ - 'sx' => null, - 'sy' => null, - 'kx' => null, - ], - 'blur' => null, - 'direction' => null, - 'distance' => null, - 'algn' => null, - 'rotWithShape' => null, - ]; - - private $glowProperties = [ - 'size' => null, - 'color' => [ - 'type' => self::EXCEL_COLOR_TYPE_STANDARD, - 'value' => 'black', - 'alpha' => 40, - ], - ]; - - private $softEdges = [ - 'size' => null, - ]; - - /** - * Get Object State. - * - * @return bool - */ - public function getObjectState() - { - return $this->objectState; - } - - /** - * Change Object State to True. - * - * @return GridLines - */ - private function activateObject() - { - $this->objectState = true; - - return $this; - } - - /** - * Set Line Color Properties. - * - * @param string $value - * @param int $alpha - * @param string $type - */ - public function setLineColorProperties($value, $alpha = 0, $type = self::EXCEL_COLOR_TYPE_STANDARD) - { - $this->activateObject() - ->lineProperties['color'] = $this->setColorProperties( - $value, - $alpha, - $type - ); - } - - /** - * Set Line Color Properties. - * - * @param float $line_width - * @param string $compound_type - * @param string $dash_type - * @param string $cap_type - * @param string $join_type - * @param string $head_arrow_type - * @param string $head_arrow_size - * @param string $end_arrow_type - * @param string $end_arrow_size - */ - public function setLineStyleProperties($line_width = null, $compound_type = null, $dash_type = null, $cap_type = null, $join_type = null, $head_arrow_type = null, $head_arrow_size = null, $end_arrow_type = null, $end_arrow_size = null) - { - $this->activateObject(); - ($line_width !== null) - ? $this->lineProperties['style']['width'] = $this->getExcelPointsWidth((float) $line_width) - : null; - ($compound_type !== null) - ? $this->lineProperties['style']['compound'] = (string) $compound_type - : null; - ($dash_type !== null) - ? $this->lineProperties['style']['dash'] = (string) $dash_type - : null; - ($cap_type !== null) - ? $this->lineProperties['style']['cap'] = (string) $cap_type - : null; - ($join_type !== null) - ? $this->lineProperties['style']['join'] = (string) $join_type - : null; - ($head_arrow_type !== null) - ? $this->lineProperties['style']['arrow']['head']['type'] = (string) $head_arrow_type - : null; - ($head_arrow_size !== null) - ? $this->lineProperties['style']['arrow']['head']['size'] = (string) $head_arrow_size - : null; - ($end_arrow_type !== null) - ? $this->lineProperties['style']['arrow']['end']['type'] = (string) $end_arrow_type - : null; - ($end_arrow_size !== null) - ? $this->lineProperties['style']['arrow']['end']['size'] = (string) $end_arrow_size - : null; - } - - /** - * Get Line Color Property. - * - * @param string $parameter - * - * @return string - */ - public function getLineColorProperty($parameter) - { - return $this->lineProperties['color'][$parameter]; - } - - /** - * Get Line Style Property. - * - * @param array|string $elements - * - * @return string - */ - public function getLineStyleProperty($elements) - { - return $this->getArrayElementsValue($this->lineProperties['style'], $elements); - } - - /** - * Set Glow Properties. - * - * @param float $size - * @param string $color_value - * @param int $color_alpha - * @param string $color_type - */ - public function setGlowProperties($size, $color_value = null, $color_alpha = null, $color_type = null) - { - $this - ->activateObject() - ->setGlowSize($size) - ->setGlowColor($color_value, $color_alpha, $color_type); - } - - /** - * Get Glow Color Property. - * - * @param string $property - * - * @return string - */ - public function getGlowColor($property) - { - return $this->glowProperties['color'][$property]; - } - - /** - * Get Glow Size. - * - * @return string - */ - public function getGlowSize() - { - return $this->glowProperties['size']; - } - - /** - * Set Glow Size. - * - * @param float $size - * - * @return GridLines - */ - private function setGlowSize($size) - { - $this->glowProperties['size'] = $this->getExcelPointsWidth((float) $size); - - return $this; - } - - /** - * Set Glow Color. - * - * @param string $color - * @param int $alpha - * @param string $type - * - * @return GridLines - */ - private function setGlowColor($color, $alpha, $type) - { - if ($color !== null) { - $this->glowProperties['color']['value'] = (string) $color; - } - if ($alpha !== null) { - $this->glowProperties['color']['alpha'] = $this->getTrueAlpha((int) $alpha); - } - if ($type !== null) { - $this->glowProperties['color']['type'] = (string) $type; - } - - return $this; - } - - /** - * Get Line Style Arrow Parameters. - * - * @param string $arrow_selector - * @param string $property_selector - * - * @return string - */ - public function getLineStyleArrowParameters($arrow_selector, $property_selector) - { - return $this->getLineStyleArrowSize($this->lineProperties['style']['arrow'][$arrow_selector]['size'], $property_selector); - } - - /** - * Set Shadow Properties. - * - * @param int $sh_presets - * @param string $sh_color_value - * @param string $sh_color_type - * @param int $sh_color_alpha - * @param string $sh_blur - * @param int $sh_angle - * @param float $sh_distance - */ - public function setShadowProperties($sh_presets, $sh_color_value = null, $sh_color_type = null, $sh_color_alpha = null, $sh_blur = null, $sh_angle = null, $sh_distance = null) - { - $this->activateObject() - ->setShadowPresetsProperties((int) $sh_presets) - ->setShadowColor( - $sh_color_value === null ? $this->shadowProperties['color']['value'] : $sh_color_value, - $sh_color_alpha === null ? (int) $this->shadowProperties['color']['alpha'] : $this->getTrueAlpha($sh_color_alpha), - $sh_color_type === null ? $this->shadowProperties['color']['type'] : $sh_color_type - ) - ->setShadowBlur($sh_blur) - ->setShadowAngle($sh_angle) - ->setShadowDistance($sh_distance); - } - - /** - * Set Shadow Presets Properties. - * - * @param int $shadow_presets - * - * @return GridLines - */ - private function setShadowPresetsProperties($shadow_presets) - { - $this->shadowProperties['presets'] = $shadow_presets; - $this->setShadowProperiesMapValues($this->getShadowPresetsMap($shadow_presets)); - - return $this; - } - - /** - * Set Shadow Properties Values. - * - * @param array $properties_map - * @param mixed &$reference - * - * @return GridLines - */ - private function setShadowProperiesMapValues(array $properties_map, &$reference = null) - { - $base_reference = $reference; - foreach ($properties_map as $property_key => $property_val) { - if (is_array($property_val)) { - if ($reference === null) { - $reference = &$this->shadowProperties[$property_key]; - } else { - $reference = &$reference[$property_key]; - } - $this->setShadowProperiesMapValues($property_val, $reference); - } else { - if ($base_reference === null) { - $this->shadowProperties[$property_key] = $property_val; - } else { - $reference[$property_key] = $property_val; - } - } - } - - return $this; - } - - /** - * Set Shadow Color. - * - * @param string $color - * @param int $alpha - * @param string $type - * - * @return GridLines - */ - private function setShadowColor($color, $alpha, $type) - { - if ($color !== null) { - $this->shadowProperties['color']['value'] = (string) $color; - } - if ($alpha !== null) { - $this->shadowProperties['color']['alpha'] = $this->getTrueAlpha((int) $alpha); - } - if ($type !== null) { - $this->shadowProperties['color']['type'] = (string) $type; - } - - return $this; - } - - /** - * Set Shadow Blur. - * - * @param float $blur - * - * @return GridLines - */ - private function setShadowBlur($blur) - { - if ($blur !== null) { - $this->shadowProperties['blur'] = (string) $this->getExcelPointsWidth($blur); - } - - return $this; - } - - /** - * Set Shadow Angle. - * - * @param int $angle - * - * @return GridLines - */ - private function setShadowAngle($angle) - { - if ($angle !== null) { - $this->shadowProperties['direction'] = (string) $this->getExcelPointsAngle($angle); - } - - return $this; - } - - /** - * Set Shadow Distance. - * - * @param float $distance - * - * @return GridLines - */ - private function setShadowDistance($distance) - { - if ($distance !== null) { - $this->shadowProperties['distance'] = (string) $this->getExcelPointsWidth($distance); - } - - return $this; - } - - /** - * Get Shadow Property. - * - * @param string|string[] $elements - * - * @return string - */ - public function getShadowProperty($elements) - { - return $this->getArrayElementsValue($this->shadowProperties, $elements); - } - - /** - * Set Soft Edges Size. - * - * @param float $size - */ - public function setSoftEdgesSize($size) - { - if ($size !== null) { - $this->activateObject(); - $this->softEdges['size'] = (string) $this->getExcelPointsWidth($size); - } - } - - /** - * Get Soft Edges Size. - * - * @return string - */ - public function getSoftEdgesSize() - { - return $this->softEdges['size']; - } } diff --git a/PhpOffice/PhpSpreadsheet/Chart/Layout.php b/PhpOffice/PhpSpreadsheet/Chart/Layout.php old mode 100755 new mode 100644 index a8a96d2..0018d79 --- a/PhpOffice/PhpSpreadsheet/Chart/Layout.php +++ b/PhpOffice/PhpSpreadsheet/Chart/Layout.php @@ -7,57 +7,70 @@ class Layout /** * layoutTarget. * - * @var string + * @var ?string */ private $layoutTarget; /** * X Mode. * - * @var string + * @var ?string */ private $xMode; /** * Y Mode. * - * @var string + * @var ?string */ private $yMode; /** * X-Position. * - * @var float + * @var ?float */ private $xPos; /** * Y-Position. * - * @var float + * @var ?float */ private $yPos; /** * width. * - * @var float + * @var ?float */ private $width; /** * height. * - * @var float + * @var ?float */ private $height; + /** + * Position - t=top. + * + * @var string + */ + private $dLblPos = ''; + + /** @var string */ + private $numFmtCode = ''; + + /** @var bool */ + private $numFmtLinked = false; + /** * show legend key * Specifies that legend keys should be shown in data labels. * - * @var bool + * @var ?bool */ private $showLegendKey; @@ -65,7 +78,7 @@ class Layout * show value * Specifies that the value should be shown in a data label. * - * @var bool + * @var ?bool */ private $showVal; @@ -73,7 +86,7 @@ class Layout * show category name * Specifies that the category name should be shown in the data label. * - * @var bool + * @var ?bool */ private $showCatName; @@ -81,7 +94,7 @@ class Layout * show data series name * Specifies that the series name should be shown in the data label. * - * @var bool + * @var ?bool */ private $showSerName; @@ -89,14 +102,14 @@ class Layout * show percentage * Specifies that the percentage should be shown in the data label. * - * @var bool + * @var ?bool */ private $showPercent; /** * show bubble size. * - * @var bool + * @var ?bool */ private $showBubbleSize; @@ -104,14 +117,21 @@ class Layout * show leader lines * Specifies that leader lines should be shown for the data label. * - * @var bool + * @var ?bool */ private $showLeaderLines; + /** @var ?ChartColor */ + private $labelFillColor; + + /** @var ?ChartColor */ + private $labelBorderColor; + + /** @var ?ChartColor */ + private $labelFontColor; + /** * Create a new Layout. - * - * @param array $layout */ public function __construct(array $layout = []) { @@ -136,12 +156,43 @@ class Layout if (isset($layout['h'])) { $this->height = (float) $layout['h']; } + if (isset($layout['dLblPos'])) { + $this->dLblPos = (string) $layout['dLblPos']; + } + if (isset($layout['numFmtCode'])) { + $this->numFmtCode = (string) $layout['numFmtCode']; + } + $this->initBoolean($layout, 'showLegendKey'); + $this->initBoolean($layout, 'showVal'); + $this->initBoolean($layout, 'showCatName'); + $this->initBoolean($layout, 'showSerName'); + $this->initBoolean($layout, 'showPercent'); + $this->initBoolean($layout, 'showBubbleSize'); + $this->initBoolean($layout, 'showLeaderLines'); + $this->initBoolean($layout, 'numFmtLinked'); + $this->initColor($layout, 'labelFillColor'); + $this->initColor($layout, 'labelBorderColor'); + $this->initColor($layout, 'labelFontColor'); + } + + private function initBoolean(array $layout, string $name): void + { + if (isset($layout[$name])) { + $this->$name = (bool) $layout[$name]; + } + } + + private function initColor(array $layout, string $name): void + { + if (isset($layout[$name]) && $layout[$name] instanceof ChartColor) { + $this->$name = $layout[$name]; + } } /** * Get Layout Target. * - * @return string + * @return ?string */ public function getLayoutTarget() { @@ -151,13 +202,13 @@ class Layout /** * Set Layout Target. * - * @param string $value + * @param ?string $target * - * @return Layout + * @return $this */ - public function setLayoutTarget($value) + public function setLayoutTarget($target) { - $this->layoutTarget = $value; + $this->layoutTarget = $target; return $this; } @@ -165,7 +216,7 @@ class Layout /** * Get X-Mode. * - * @return string + * @return ?string */ public function getXMode() { @@ -175,13 +226,13 @@ class Layout /** * Set X-Mode. * - * @param string $value + * @param ?string $mode * - * @return Layout + * @return $this */ - public function setXMode($value) + public function setXMode($mode) { - $this->xMode = (string) $value; + $this->xMode = (string) $mode; return $this; } @@ -189,7 +240,7 @@ class Layout /** * Get Y-Mode. * - * @return string + * @return ?string */ public function getYMode() { @@ -199,13 +250,13 @@ class Layout /** * Set Y-Mode. * - * @param string $value + * @param ?string $mode * - * @return Layout + * @return $this */ - public function setYMode($value) + public function setYMode($mode) { - $this->yMode = (string) $value; + $this->yMode = (string) $mode; return $this; } @@ -213,7 +264,7 @@ class Layout /** * Get X-Position. * - * @return number + * @return null|float|int */ public function getXPosition() { @@ -223,13 +274,13 @@ class Layout /** * Set X-Position. * - * @param float $value + * @param ?float $position * - * @return Layout + * @return $this */ - public function setXPosition($value) + public function setXPosition($position) { - $this->xPos = (float) $value; + $this->xPos = (float) $position; return $this; } @@ -237,7 +288,7 @@ class Layout /** * Get Y-Position. * - * @return number + * @return null|float */ public function getYPosition() { @@ -247,13 +298,13 @@ class Layout /** * Set Y-Position. * - * @param float $value + * @param ?float $position * - * @return Layout + * @return $this */ - public function setYPosition($value) + public function setYPosition($position) { - $this->yPos = (float) $value; + $this->yPos = (float) $position; return $this; } @@ -261,7 +312,7 @@ class Layout /** * Get Width. * - * @return number + * @return ?float */ public function getWidth() { @@ -271,13 +322,13 @@ class Layout /** * Set Width. * - * @param float $value + * @param ?float $width * - * @return Layout + * @return $this */ - public function setWidth($value) + public function setWidth($width) { - $this->width = $value; + $this->width = $width; return $this; } @@ -285,7 +336,7 @@ class Layout /** * Get Height. * - * @return number + * @return null|float */ public function getHeight() { @@ -295,23 +346,18 @@ class Layout /** * Set Height. * - * @param float $value + * @param ?float $height * - * @return Layout + * @return $this */ - public function setHeight($value) + public function setHeight($height) { - $this->height = $value; + $this->height = $height; return $this; } - /** - * Get show legend key. - * - * @return bool - */ - public function getShowLegendKey() + public function getShowLegendKey(): ?bool { return $this->showLegendKey; } @@ -319,24 +365,15 @@ class Layout /** * Set show legend key * Specifies that legend keys should be shown in data labels. - * - * @param bool $value Show legend key - * - * @return Layout */ - public function setShowLegendKey($value) + public function setShowLegendKey(?bool $showLegendKey): self { - $this->showLegendKey = $value; + $this->showLegendKey = $showLegendKey; return $this; } - /** - * Get show value. - * - * @return bool - */ - public function getShowVal() + public function getShowVal(): ?bool { return $this->showVal; } @@ -344,24 +381,15 @@ class Layout /** * Set show val * Specifies that the value should be shown in data labels. - * - * @param bool $value Show val - * - * @return Layout */ - public function setShowVal($value) + public function setShowVal(?bool $showDataLabelValues): self { - $this->showVal = $value; + $this->showVal = $showDataLabelValues; return $this; } - /** - * Get show category name. - * - * @return bool - */ - public function getShowCatName() + public function getShowCatName(): ?bool { return $this->showCatName; } @@ -369,114 +397,146 @@ class Layout /** * Set show cat name * Specifies that the category name should be shown in data labels. - * - * @param bool $value Show cat name - * - * @return Layout */ - public function setShowCatName($value) + public function setShowCatName(?bool $showCategoryName): self { - $this->showCatName = $value; + $this->showCatName = $showCategoryName; return $this; } - /** - * Get show data series name. - * - * @return bool - */ - public function getShowSerName() + public function getShowSerName(): ?bool { return $this->showSerName; } /** - * Set show ser name + * Set show data series name. * Specifies that the series name should be shown in data labels. - * - * @param bool $value Show series name - * - * @return Layout */ - public function setShowSerName($value) + public function setShowSerName(?bool $showSeriesName): self { - $this->showSerName = $value; + $this->showSerName = $showSeriesName; return $this; } - /** - * Get show percentage. - * - * @return bool - */ - public function getShowPercent() + public function getShowPercent(): ?bool { return $this->showPercent; } /** - * Set show percentage + * Set show percentage. * Specifies that the percentage should be shown in data labels. - * - * @param bool $value Show percentage - * - * @return Layout */ - public function setShowPercent($value) + public function setShowPercent(?bool $showPercentage): self { - $this->showPercent = $value; + $this->showPercent = $showPercentage; return $this; } - /** - * Get show bubble size. - * - * @return bool - */ - public function getShowBubbleSize() + public function getShowBubbleSize(): ?bool { return $this->showBubbleSize; } /** - * Set show bubble size + * Set show bubble size. * Specifies that the bubble size should be shown in data labels. - * - * @param bool $value Show bubble size - * - * @return Layout */ - public function setShowBubbleSize($value) + public function setShowBubbleSize(?bool $showBubbleSize): self { - $this->showBubbleSize = $value; + $this->showBubbleSize = $showBubbleSize; return $this; } - /** - * Get show leader lines. - * - * @return bool - */ - public function getShowLeaderLines() + public function getShowLeaderLines(): ?bool { return $this->showLeaderLines; } /** - * Set show leader lines + * Set show leader lines. * Specifies that leader lines should be shown in data labels. - * - * @param bool $value Show leader lines - * - * @return Layout */ - public function setShowLeaderLines($value) + public function setShowLeaderLines(?bool $showLeaderLines): self { - $this->showLeaderLines = $value; + $this->showLeaderLines = $showLeaderLines; + + return $this; + } + + public function getLabelFillColor(): ?ChartColor + { + return $this->labelFillColor; + } + + public function setLabelFillColor(?ChartColor $chartColor): self + { + $this->labelFillColor = $chartColor; + + return $this; + } + + public function getLabelBorderColor(): ?ChartColor + { + return $this->labelBorderColor; + } + + public function setLabelBorderColor(?ChartColor $chartColor): self + { + $this->labelBorderColor = $chartColor; + + return $this; + } + + public function getLabelFontColor(): ?ChartColor + { + return $this->labelFontColor; + } + + public function setLabelFontColor(?ChartColor $chartColor): self + { + $this->labelFontColor = $chartColor; + + return $this; + } + + public function getDLblPos(): string + { + return $this->dLblPos; + } + + public function setDLblPos(string $dLblPos): self + { + $this->dLblPos = $dLblPos; + + return $this; + } + + public function getNumFmtCode(): string + { + return $this->numFmtCode; + } + + public function setNumFmtCode(string $numFmtCode): self + { + $this->numFmtCode = $numFmtCode; + + return $this; + } + + public function getNumFmtLinked(): bool + { + return $this->numFmtLinked; + } + + public function setNumFmtLinked(bool $numFmtLinked): self + { + $this->numFmtLinked = $numFmtLinked; return $this; } diff --git a/PhpOffice/PhpSpreadsheet/Chart/Legend.php b/PhpOffice/PhpSpreadsheet/Chart/Legend.php old mode 100755 new mode 100644 index d077626..e499e77 --- a/PhpOffice/PhpSpreadsheet/Chart/Legend.php +++ b/PhpOffice/PhpSpreadsheet/Chart/Legend.php @@ -18,7 +18,7 @@ class Legend const POSITION_TOP = 't'; const POSITION_TOPRIGHT = 'tr'; - private static $positionXLref = [ + const POSITION_XLREF = [ self::XL_LEGEND_POSITION_BOTTOM => self::POSITION_BOTTOM, self::XL_LEGEND_POSITION_CORNER => self::POSITION_TOPRIGHT, self::XL_LEGEND_POSITION_CUSTOM => '??', @@ -44,7 +44,7 @@ class Legend /** * Legend Layout. * - * @var Layout + * @var ?Layout */ private $layout; @@ -52,10 +52,9 @@ class Legend * Create a new Legend. * * @param string $position - * @param null|Layout $layout * @param bool $overlay */ - public function __construct($position = self::POSITION_RIGHT, Layout $layout = null, $overlay = false) + public function __construct($position = self::POSITION_RIGHT, ?Layout $layout = null, $overlay = false) { $this->setPosition($position); $this->layout = $layout; @@ -81,7 +80,7 @@ class Legend */ public function setPosition($position) { - if (!in_array($position, self::$positionXLref)) { + if (!in_array($position, self::POSITION_XLREF)) { return false; } @@ -93,11 +92,12 @@ class Legend /** * Get legend position as an Excel internal numeric value. * - * @return int + * @return false|int */ public function getPositionXL() { - return array_search($this->position, self::$positionXLref); + // Scrutinizer thinks the following could return string. It is wrong. + return array_search($this->position, self::POSITION_XLREF); } /** @@ -109,11 +109,11 @@ class Legend */ public function setPositionXL($positionXL) { - if (!isset(self::$positionXLref[$positionXL])) { + if (!isset(self::POSITION_XLREF[$positionXL])) { return false; } - $this->position = self::$positionXLref[$positionXL]; + $this->position = self::POSITION_XLREF[$positionXL]; return true; } @@ -132,24 +132,16 @@ class Legend * Set allow overlay of other elements? * * @param bool $overlay - * - * @return bool */ - public function setOverlay($overlay) + public function setOverlay($overlay): void { - if (!is_bool($overlay)) { - return false; - } - $this->overlay = $overlay; - - return true; } /** * Get Layout. * - * @return Layout + * @return ?Layout */ public function getLayout() { diff --git a/PhpOffice/PhpSpreadsheet/Chart/PlotArea.php b/PhpOffice/PhpSpreadsheet/Chart/PlotArea.php old mode 100755 new mode 100644 index b98c638..ccde4bb --- a/PhpOffice/PhpSpreadsheet/Chart/PlotArea.php +++ b/PhpOffice/PhpSpreadsheet/Chart/PlotArea.php @@ -6,10 +6,34 @@ use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; class PlotArea { + /** + * No fill in plot area (show Excel gridlines through chart). + * + * @var bool + */ + private $noFill = false; + + /** + * PlotArea Gradient Stop list. + * Each entry is a 2-element array. + * First is position in %. + * Second is ChartColor. + * + * @var array[] + */ + private $gradientFillStops = []; + + /** + * PlotArea Gradient Angle. + * + * @var ?float + */ + private $gradientFillAngle; + /** * PlotArea Layout. * - * @var Layout + * @var ?Layout */ private $layout; @@ -23,31 +47,23 @@ class PlotArea /** * Create a new PlotArea. * - * @param null|Layout $layout * @param DataSeries[] $plotSeries */ - public function __construct(Layout $layout = null, array $plotSeries = []) + public function __construct(?Layout $layout = null, array $plotSeries = []) { $this->layout = $layout; $this->plotSeries = $plotSeries; } - /** - * Get Layout. - * - * @return Layout - */ - public function getLayout() + public function getLayout(): ?Layout { return $this->layout; } /** * Get Number of Plot Groups. - * - * @return array of DataSeries */ - public function getPlotGroupCount() + public function getPlotGroupCount(): int { return count($this->plotSeries); } @@ -70,7 +86,7 @@ class PlotArea /** * Get Plot Series. * - * @return array of DataSeries + * @return DataSeries[] */ public function getPlotGroup() { @@ -94,7 +110,7 @@ class PlotArea * * @param DataSeries[] $plotSeries * - * @return PlotArea + * @return $this */ public function setPlotSeries(array $plotSeries) { @@ -103,10 +119,48 @@ class PlotArea return $this; } - public function refresh(Worksheet $worksheet) + public function refresh(Worksheet $worksheet): void { foreach ($this->plotSeries as $plotSeries) { $plotSeries->refresh($worksheet); } } + + public function setNoFill(bool $noFill): self + { + $this->noFill = $noFill; + + return $this; + } + + public function getNoFill(): bool + { + return $this->noFill; + } + + public function setGradientFillProperties(array $gradientFillStops, ?float $gradientFillAngle): self + { + $this->gradientFillStops = $gradientFillStops; + $this->gradientFillAngle = $gradientFillAngle; + + return $this; + } + + /** + * Get gradientFillAngle. + */ + public function getGradientFillAngle(): ?float + { + return $this->gradientFillAngle; + } + + /** + * Get gradientFillStops. + * + * @return array + */ + public function getGradientFillStops() + { + return $this->gradientFillStops; + } } diff --git a/PhpOffice/PhpSpreadsheet/Chart/Properties.php b/PhpOffice/PhpSpreadsheet/Chart/Properties.php old mode 100755 new mode 100644 index 98095f0..edb7754 --- a/PhpOffice/PhpSpreadsheet/Chart/Properties.php +++ b/PhpOffice/PhpSpreadsheet/Chart/Properties.php @@ -10,10 +10,12 @@ namespace PhpOffice\PhpSpreadsheet\Chart; */ abstract class Properties { - const - EXCEL_COLOR_TYPE_STANDARD = 'prstClr'; - const EXCEL_COLOR_TYPE_SCHEME = 'schemeClr'; - const EXCEL_COLOR_TYPE_ARGB = 'srgbClr'; + /** @deprecated 1.24 use constant from ChartColor instead */ + const EXCEL_COLOR_TYPE_STANDARD = ChartColor::EXCEL_COLOR_TYPE_STANDARD; + /** @deprecated 1.24 use constant from ChartColor instead */ + const EXCEL_COLOR_TYPE_SCHEME = ChartColor::EXCEL_COLOR_TYPE_SCHEME; + /** @deprecated 1.24 use constant from ChartColor instead */ + const EXCEL_COLOR_TYPE_ARGB = ChartColor::EXCEL_COLOR_TYPE_ARGB; const AXIS_LABELS_LOW = 'low'; @@ -37,6 +39,7 @@ abstract class Properties const FORMAT_CODE_CURRENCY = '$#,##0.00'; const FORMAT_CODE_ACCOUNTING = '_($* #,##0.00_);_($* (#,##0.00);_($* "-"??_);_(@_)'; const FORMAT_CODE_DATE = 'm/d/yyyy'; + const FORMAT_CODE_DATE_ISO8601 = 'yyyy-mm-dd'; const FORMAT_CODE_TIME = '[$-F400]h:mm:ss AM/PM'; const FORMAT_CODE_PERCENTAGE = '0.00%'; const FORMAT_CODE_FRACTION = '# ?/?'; @@ -56,6 +59,8 @@ abstract class Properties const LINE_STYLE_COMPOUND_TRIPLE = 'tri'; const LINE_STYLE_DASH_SOLID = 'solid'; const LINE_STYLE_DASH_ROUND_DOT = 'sysDot'; + const LINE_STYLE_DASH_SQUARE_DOT = 'sysDash'; + /** @deprecated 1.24 use LINE_STYLE_DASH_SQUARE_DOT instead */ const LINE_STYLE_DASH_SQUERE_DOT = 'sysDash'; const LINE_STYPE_DASH_DASH = 'dash'; const LINE_STYLE_DASH_DASH_DOT = 'dashDot'; @@ -65,7 +70,7 @@ abstract class Properties const LINE_STYLE_CAP_SQUARE = 'sq'; const LINE_STYLE_CAP_ROUND = 'rnd'; const LINE_STYLE_CAP_FLAT = 'flat'; - const LINE_STYLE_JOIN_ROUND = 'bevel'; + const LINE_STYLE_JOIN_ROUND = 'round'; const LINE_STYLE_JOIN_MITER = 'miter'; const LINE_STYLE_JOIN_BEVEL = 'bevel'; const LINE_STYLE_ARROW_TYPE_NOARROW = null; @@ -110,249 +115,325 @@ abstract class Properties const SHADOW_PRESETS_PERSPECTIVE_LOWER_RIGHT = 22; const SHADOW_PRESETS_PERSPECTIVE_LOWER_LEFT = 23; - /** - * @param float $width - * - * @return float - */ - protected function getExcelPointsWidth($width) + const POINTS_WIDTH_MULTIPLIER = 12700; + const ANGLE_MULTIPLIER = 60000; // direction and size-kx size-ky + const PERCENTAGE_MULTIPLIER = 100000; // size sx and sy + + /** @var bool */ + protected $objectState = false; // used only for minor gridlines + + /** @var ?float */ + protected $glowSize; + + /** @var ChartColor */ + protected $glowColor; + + /** @var array */ + protected $softEdges = [ + 'size' => null, + ]; + + /** @var array */ + protected $shadowProperties = self::PRESETS_OPTIONS[0]; + + /** @var ChartColor */ + protected $shadowColor; + + public function __construct() { - return $width * 12700; + $this->lineColor = new ChartColor(); + $this->glowColor = new ChartColor(); + $this->shadowColor = new ChartColor(); + $this->shadowColor->setType(ChartColor::EXCEL_COLOR_TYPE_STANDARD); + $this->shadowColor->setValue('black'); + $this->shadowColor->setAlpha(40); } /** - * @param float $angle + * Get Object State. * - * @return float + * @return bool */ - protected function getExcelPointsAngle($angle) + public function getObjectState() { - return $angle * 60000; + return $this->objectState; } - protected function getTrueAlpha($alpha) + /** + * Change Object State to True. + * + * @return $this + */ + public function activateObject() { - return (string) 100 - $alpha . '000'; + $this->objectState = true; + + return $this; } - protected function setColorProperties($color, $alpha, $type) + public static function pointsToXml(float $width): string + { + return (string) (int) ($width * self::POINTS_WIDTH_MULTIPLIER); + } + + public static function xmlToPoints(string $width): float + { + return ((float) $width) / self::POINTS_WIDTH_MULTIPLIER; + } + + public static function angleToXml(float $angle): string + { + return (string) (int) ($angle * self::ANGLE_MULTIPLIER); + } + + public static function xmlToAngle(string $angle): float + { + return ((float) $angle) / self::ANGLE_MULTIPLIER; + } + + public static function tenthOfPercentToXml(float $value): string + { + return (string) (int) ($value * self::PERCENTAGE_MULTIPLIER); + } + + public static function xmlToTenthOfPercent(string $value): float + { + return ((float) $value) / self::PERCENTAGE_MULTIPLIER; + } + + /** + * @param null|float|int|string $alpha + */ + protected function setColorProperties(?string $color, $alpha, ?string $colorType): array { return [ - 'type' => (string) $type, - 'value' => (string) $color, - 'alpha' => (string) $this->getTrueAlpha($alpha), + 'type' => $colorType, + 'value' => $color, + 'alpha' => ($alpha === null) ? null : (int) $alpha, ]; } - protected function getLineStyleArrowSize($array_selector, $array_kay_selector) + protected const PRESETS_OPTIONS = [ + //NONE + 0 => [ + 'presets' => self::SHADOW_PRESETS_NOSHADOW, + 'effect' => null, + //'color' => [ + // 'type' => ChartColor::EXCEL_COLOR_TYPE_STANDARD, + // 'value' => 'black', + // 'alpha' => 40, + //], + 'size' => [ + 'sx' => null, + 'sy' => null, + 'kx' => null, + 'ky' => null, + ], + 'blur' => null, + 'direction' => null, + 'distance' => null, + 'algn' => null, + 'rotWithShape' => null, + ], + //OUTER + 1 => [ + 'effect' => 'outerShdw', + 'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 2700000 / self::ANGLE_MULTIPLIER, + 'algn' => 'tl', + 'rotWithShape' => '0', + ], + 2 => [ + 'effect' => 'outerShdw', + 'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 5400000 / self::ANGLE_MULTIPLIER, + 'algn' => 't', + 'rotWithShape' => '0', + ], + 3 => [ + 'effect' => 'outerShdw', + 'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 8100000 / self::ANGLE_MULTIPLIER, + 'algn' => 'tr', + 'rotWithShape' => '0', + ], + 4 => [ + 'effect' => 'outerShdw', + 'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER, + 'algn' => 'l', + 'rotWithShape' => '0', + ], + 5 => [ + 'effect' => 'outerShdw', + 'size' => [ + 'sx' => 102000 / self::PERCENTAGE_MULTIPLIER, + 'sy' => 102000 / self::PERCENTAGE_MULTIPLIER, + ], + 'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER, + 'algn' => 'ctr', + 'rotWithShape' => '0', + ], + 6 => [ + 'effect' => 'outerShdw', + 'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 10800000 / self::ANGLE_MULTIPLIER, + 'algn' => 'r', + 'rotWithShape' => '0', + ], + 7 => [ + 'effect' => 'outerShdw', + 'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 18900000 / self::ANGLE_MULTIPLIER, + 'algn' => 'bl', + 'rotWithShape' => '0', + ], + 8 => [ + 'effect' => 'outerShdw', + 'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 16200000 / self::ANGLE_MULTIPLIER, + 'rotWithShape' => '0', + ], + 9 => [ + 'effect' => 'outerShdw', + 'blur' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 38100 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 13500000 / self::ANGLE_MULTIPLIER, + 'algn' => 'br', + 'rotWithShape' => '0', + ], + //INNER + 10 => [ + 'effect' => 'innerShdw', + 'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 2700000 / self::ANGLE_MULTIPLIER, + ], + 11 => [ + 'effect' => 'innerShdw', + 'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 5400000 / self::ANGLE_MULTIPLIER, + ], + 12 => [ + 'effect' => 'innerShdw', + 'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 8100000 / self::ANGLE_MULTIPLIER, + ], + 13 => [ + 'effect' => 'innerShdw', + 'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + ], + 14 => [ + 'effect' => 'innerShdw', + 'blur' => 114300 / self::POINTS_WIDTH_MULTIPLIER, + ], + 15 => [ + 'effect' => 'innerShdw', + 'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 10800000 / self::ANGLE_MULTIPLIER, + ], + 16 => [ + 'effect' => 'innerShdw', + 'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 18900000 / self::ANGLE_MULTIPLIER, + ], + 17 => [ + 'effect' => 'innerShdw', + 'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 16200000 / self::ANGLE_MULTIPLIER, + ], + 18 => [ + 'effect' => 'innerShdw', + 'blur' => 63500 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 50800 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 13500000 / self::ANGLE_MULTIPLIER, + ], + //perspective + 19 => [ + 'effect' => 'outerShdw', + 'blur' => 152400 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 317500 / self::POINTS_WIDTH_MULTIPLIER, + 'size' => [ + 'sx' => 90000 / self::PERCENTAGE_MULTIPLIER, + 'sy' => -19000 / self::PERCENTAGE_MULTIPLIER, + ], + 'direction' => 5400000 / self::ANGLE_MULTIPLIER, + 'rotWithShape' => '0', + ], + 20 => [ + 'effect' => 'outerShdw', + 'blur' => 76200 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 18900000 / self::ANGLE_MULTIPLIER, + 'size' => [ + 'sy' => 23000 / self::PERCENTAGE_MULTIPLIER, + 'kx' => -1200000 / self::ANGLE_MULTIPLIER, + ], + 'algn' => 'bl', + 'rotWithShape' => '0', + ], + 21 => [ + 'effect' => 'outerShdw', + 'blur' => 76200 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 13500000 / self::ANGLE_MULTIPLIER, + 'size' => [ + 'sy' => 23000 / self::PERCENTAGE_MULTIPLIER, + 'kx' => 1200000 / self::ANGLE_MULTIPLIER, + ], + 'algn' => 'br', + 'rotWithShape' => '0', + ], + 22 => [ + 'effect' => 'outerShdw', + 'blur' => 76200 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 12700 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 2700000 / self::ANGLE_MULTIPLIER, + 'size' => [ + 'sy' => -23000 / self::PERCENTAGE_MULTIPLIER, + 'kx' => -800400 / self::ANGLE_MULTIPLIER, + ], + 'algn' => 'bl', + 'rotWithShape' => '0', + ], + 23 => [ + 'effect' => 'outerShdw', + 'blur' => 76200 / self::POINTS_WIDTH_MULTIPLIER, + 'distance' => 12700 / self::POINTS_WIDTH_MULTIPLIER, + 'direction' => 8100000 / self::ANGLE_MULTIPLIER, + 'size' => [ + 'sy' => -23000 / self::PERCENTAGE_MULTIPLIER, + 'kx' => 800400 / self::ANGLE_MULTIPLIER, + ], + 'algn' => 'br', + 'rotWithShape' => '0', + ], + ]; + + protected function getShadowPresetsMap(int $presetsOption): array { - $sizes = [ - 1 => ['w' => 'sm', 'len' => 'sm'], - 2 => ['w' => 'sm', 'len' => 'med'], - 3 => ['w' => 'sm', 'len' => 'lg'], - 4 => ['w' => 'med', 'len' => 'sm'], - 5 => ['w' => 'med', 'len' => 'med'], - 6 => ['w' => 'med', 'len' => 'lg'], - 7 => ['w' => 'lg', 'len' => 'sm'], - 8 => ['w' => 'lg', 'len' => 'med'], - 9 => ['w' => 'lg', 'len' => 'lg'], - ]; - - return $sizes[$array_selector][$array_kay_selector]; - } - - protected function getShadowPresetsMap($shadow_presets_option) - { - $presets_options = [ - //OUTER - 1 => [ - 'effect' => 'outerShdw', - 'blur' => '50800', - 'distance' => '38100', - 'direction' => '2700000', - 'algn' => 'tl', - 'rotWithShape' => '0', - ], - 2 => [ - 'effect' => 'outerShdw', - 'blur' => '50800', - 'distance' => '38100', - 'direction' => '5400000', - 'algn' => 't', - 'rotWithShape' => '0', - ], - 3 => [ - 'effect' => 'outerShdw', - 'blur' => '50800', - 'distance' => '38100', - 'direction' => '8100000', - 'algn' => 'tr', - 'rotWithShape' => '0', - ], - 4 => [ - 'effect' => 'outerShdw', - 'blur' => '50800', - 'distance' => '38100', - 'algn' => 'l', - 'rotWithShape' => '0', - ], - 5 => [ - 'effect' => 'outerShdw', - 'size' => [ - 'sx' => '102000', - 'sy' => '102000', - ], - 'blur' => '63500', - 'distance' => '38100', - 'algn' => 'ctr', - 'rotWithShape' => '0', - ], - 6 => [ - 'effect' => 'outerShdw', - 'blur' => '50800', - 'distance' => '38100', - 'direction' => '10800000', - 'algn' => 'r', - 'rotWithShape' => '0', - ], - 7 => [ - 'effect' => 'outerShdw', - 'blur' => '50800', - 'distance' => '38100', - 'direction' => '18900000', - 'algn' => 'bl', - 'rotWithShape' => '0', - ], - 8 => [ - 'effect' => 'outerShdw', - 'blur' => '50800', - 'distance' => '38100', - 'direction' => '16200000', - 'rotWithShape' => '0', - ], - 9 => [ - 'effect' => 'outerShdw', - 'blur' => '50800', - 'distance' => '38100', - 'direction' => '13500000', - 'algn' => 'br', - 'rotWithShape' => '0', - ], - //INNER - 10 => [ - 'effect' => 'innerShdw', - 'blur' => '63500', - 'distance' => '50800', - 'direction' => '2700000', - ], - 11 => [ - 'effect' => 'innerShdw', - 'blur' => '63500', - 'distance' => '50800', - 'direction' => '5400000', - ], - 12 => [ - 'effect' => 'innerShdw', - 'blur' => '63500', - 'distance' => '50800', - 'direction' => '8100000', - ], - 13 => [ - 'effect' => 'innerShdw', - 'blur' => '63500', - 'distance' => '50800', - ], - 14 => [ - 'effect' => 'innerShdw', - 'blur' => '114300', - ], - 15 => [ - 'effect' => 'innerShdw', - 'blur' => '63500', - 'distance' => '50800', - 'direction' => '10800000', - ], - 16 => [ - 'effect' => 'innerShdw', - 'blur' => '63500', - 'distance' => '50800', - 'direction' => '18900000', - ], - 17 => [ - 'effect' => 'innerShdw', - 'blur' => '63500', - 'distance' => '50800', - 'direction' => '16200000', - ], - 18 => [ - 'effect' => 'innerShdw', - 'blur' => '63500', - 'distance' => '50800', - 'direction' => '13500000', - ], - //perspective - 19 => [ - 'effect' => 'outerShdw', - 'blur' => '152400', - 'distance' => '317500', - 'size' => [ - 'sx' => '90000', - 'sy' => '-19000', - ], - 'direction' => '5400000', - 'rotWithShape' => '0', - ], - 20 => [ - 'effect' => 'outerShdw', - 'blur' => '76200', - 'direction' => '18900000', - 'size' => [ - 'sy' => '23000', - 'kx' => '-1200000', - ], - 'algn' => 'bl', - 'rotWithShape' => '0', - ], - 21 => [ - 'effect' => 'outerShdw', - 'blur' => '76200', - 'direction' => '13500000', - 'size' => [ - 'sy' => '23000', - 'kx' => '1200000', - ], - 'algn' => 'br', - 'rotWithShape' => '0', - ], - 22 => [ - 'effect' => 'outerShdw', - 'blur' => '76200', - 'distance' => '12700', - 'direction' => '2700000', - 'size' => [ - 'sy' => '-23000', - 'kx' => '-800400', - ], - 'algn' => 'bl', - 'rotWithShape' => '0', - ], - 23 => [ - 'effect' => 'outerShdw', - 'blur' => '76200', - 'distance' => '12700', - 'direction' => '8100000', - 'size' => [ - 'sy' => '-23000', - 'kx' => '800400', - ], - 'algn' => 'br', - 'rotWithShape' => '0', - ], - ]; - - return $presets_options[$shadow_presets_option]; + return self::PRESETS_OPTIONS[$presetsOption] ?? self::PRESETS_OPTIONS[0]; } + /** + * Get value of array element. + * + * @param mixed $properties + * @param mixed $elements + * + * @return mixed + */ protected function getArrayElementsValue($properties, $elements) { $reference = &$properties; @@ -366,4 +447,539 @@ abstract class Properties return $reference; } + + /** + * Set Glow Properties. + * + * @param float $size + * @param ?string $colorValue + * @param ?int $colorAlpha + * @param ?string $colorType + */ + public function setGlowProperties($size, $colorValue = null, $colorAlpha = null, $colorType = null): void + { + $this + ->activateObject() + ->setGlowSize($size); + $this->glowColor->setColorPropertiesArray( + [ + 'value' => $colorValue, + 'type' => $colorType, + 'alpha' => $colorAlpha, + ] + ); + } + + /** + * Get Glow Property. + * + * @param array|string $property + * + * @return null|array|float|int|string + */ + public function getGlowProperty($property) + { + $retVal = null; + if ($property === 'size') { + $retVal = $this->glowSize; + } elseif ($property === 'color') { + $retVal = [ + 'value' => $this->glowColor->getColorProperty('value'), + 'type' => $this->glowColor->getColorProperty('type'), + 'alpha' => $this->glowColor->getColorProperty('alpha'), + ]; + } elseif (is_array($property) && count($property) >= 2 && $property[0] === 'color') { + $retVal = $this->glowColor->getColorProperty($property[1]); + } + + return $retVal; + } + + /** + * Get Glow Color Property. + * + * @param string $propertyName + * + * @return null|int|string + */ + public function getGlowColor($propertyName) + { + return $this->glowColor->getColorProperty($propertyName); + } + + public function getGlowColorObject(): ChartColor + { + return $this->glowColor; + } + + /** + * Get Glow Size. + * + * @return ?float + */ + public function getGlowSize() + { + return $this->glowSize; + } + + /** + * Set Glow Size. + * + * @param ?float $size + * + * @return $this + */ + protected function setGlowSize($size) + { + $this->glowSize = $size; + + return $this; + } + + /** + * Set Soft Edges Size. + * + * @param ?float $size + */ + public function setSoftEdges($size): void + { + if ($size !== null) { + $this->activateObject(); + $this->softEdges['size'] = $size; + } + } + + /** + * Get Soft Edges Size. + * + * @return string + */ + public function getSoftEdgesSize() + { + return $this->softEdges['size']; + } + + /** + * @param mixed $value + */ + public function setShadowProperty(string $propertyName, $value): self + { + $this->activateObject(); + if ($propertyName === 'color' && is_array($value)) { + $this->shadowColor->setColorPropertiesArray($value); + } else { + $this->shadowProperties[$propertyName] = $value; + } + + return $this; + } + + /** + * Set Shadow Properties. + * + * @param int $presets + * @param string $colorValue + * @param string $colorType + * @param null|float|int|string $colorAlpha + * @param null|float $blur + * @param null|int $angle + * @param null|float $distance + */ + public function setShadowProperties($presets, $colorValue = null, $colorType = null, $colorAlpha = null, $blur = null, $angle = null, $distance = null): void + { + $this->activateObject()->setShadowPresetsProperties((int) $presets); + if ($presets === 0) { + $this->shadowColor->setType(ChartColor::EXCEL_COLOR_TYPE_STANDARD); + $this->shadowColor->setValue('black'); + $this->shadowColor->setAlpha(40); + } + if ($colorValue !== null) { + $this->shadowColor->setValue($colorValue); + } + if ($colorType !== null) { + $this->shadowColor->setType($colorType); + } + if (is_numeric($colorAlpha)) { + $this->shadowColor->setAlpha((int) $colorAlpha); + } + $this + ->setShadowBlur($blur) + ->setShadowAngle($angle) + ->setShadowDistance($distance); + } + + /** + * Set Shadow Presets Properties. + * + * @param int $presets + * + * @return $this + */ + protected function setShadowPresetsProperties($presets) + { + $this->shadowProperties['presets'] = $presets; + $this->setShadowPropertiesMapValues($this->getShadowPresetsMap($presets)); + + return $this; + } + + protected const SHADOW_ARRAY_KEYS = ['size', 'color']; + + /** + * Set Shadow Properties Values. + * + * @param mixed $reference + * + * @return $this + */ + protected function setShadowPropertiesMapValues(array $propertiesMap, &$reference = null) + { + $base_reference = $reference; + foreach ($propertiesMap as $property_key => $property_val) { + if (is_array($property_val)) { + if (in_array($property_key, self::SHADOW_ARRAY_KEYS, true)) { + $reference = &$this->shadowProperties[$property_key]; + $this->setShadowPropertiesMapValues($property_val, $reference); + } + } else { + if ($base_reference === null) { + $this->shadowProperties[$property_key] = $property_val; + } else { + $reference[$property_key] = $property_val; + } + } + } + + return $this; + } + + /** + * Set Shadow Blur. + * + * @param ?float $blur + * + * @return $this + */ + protected function setShadowBlur($blur) + { + if ($blur !== null) { + $this->shadowProperties['blur'] = $blur; + } + + return $this; + } + + /** + * Set Shadow Angle. + * + * @param null|float|int|string $angle + * + * @return $this + */ + protected function setShadowAngle($angle) + { + if (is_numeric($angle)) { + $this->shadowProperties['direction'] = $angle; + } + + return $this; + } + + /** + * Set Shadow Distance. + * + * @param ?float $distance + * + * @return $this + */ + protected function setShadowDistance($distance) + { + if ($distance !== null) { + $this->shadowProperties['distance'] = $distance; + } + + return $this; + } + + public function getShadowColorObject(): ChartColor + { + return $this->shadowColor; + } + + /** + * Get Shadow Property. + * + * @param string|string[] $elements + * + * @return array|string + */ + public function getShadowProperty($elements) + { + if ($elements === 'color') { + return [ + 'value' => $this->shadowColor->getValue(), + 'type' => $this->shadowColor->getType(), + 'alpha' => $this->shadowColor->getAlpha(), + ]; + } + + return $this->getArrayElementsValue($this->shadowProperties, $elements); + } + + public function getShadowArray(): array + { + $array = $this->shadowProperties; + if ($this->getShadowColorObject()->isUsable()) { + $array['color'] = $this->getShadowProperty('color'); + } + + return $array; + } + + /** @var ChartColor */ + protected $lineColor; + + /** @var array */ + protected $lineStyleProperties = [ + 'width' => null, //'9525', + 'compound' => '', //self::LINE_STYLE_COMPOUND_SIMPLE, + 'dash' => '', //self::LINE_STYLE_DASH_SOLID, + 'cap' => '', //self::LINE_STYLE_CAP_FLAT, + 'join' => '', //self::LINE_STYLE_JOIN_BEVEL, + 'arrow' => [ + 'head' => [ + 'type' => '', //self::LINE_STYLE_ARROW_TYPE_NOARROW, + 'size' => '', //self::LINE_STYLE_ARROW_SIZE_5, + 'w' => '', + 'len' => '', + ], + 'end' => [ + 'type' => '', //self::LINE_STYLE_ARROW_TYPE_NOARROW, + 'size' => '', //self::LINE_STYLE_ARROW_SIZE_8, + 'w' => '', + 'len' => '', + ], + ], + ]; + + public function copyLineStyles(self $otherProperties): void + { + $this->lineStyleProperties = $otherProperties->lineStyleProperties; + $this->lineColor = $otherProperties->lineColor; + $this->glowSize = $otherProperties->glowSize; + $this->glowColor = $otherProperties->glowColor; + $this->softEdges = $otherProperties->softEdges; + $this->shadowProperties = $otherProperties->shadowProperties; + } + + public function getLineColor(): ChartColor + { + return $this->lineColor; + } + + /** + * Set Line Color Properties. + * + * @param string $value + * @param ?int $alpha + * @param ?string $colorType + */ + public function setLineColorProperties($value, $alpha = null, $colorType = null): void + { + $this->activateObject(); + $this->lineColor->setColorPropertiesArray( + $this->setColorProperties( + $value, + $alpha, + $colorType + ) + ); + } + + /** + * Get Line Color Property. + * + * @param string $propertyName + * + * @return null|int|string + */ + public function getLineColorProperty($propertyName) + { + return $this->lineColor->getColorProperty($propertyName); + } + + /** + * Set Line Style Properties. + * + * @param null|float|int|string $lineWidth + * @param string $compoundType + * @param string $dashType + * @param string $capType + * @param string $joinType + * @param string $headArrowType + * @param string $headArrowSize + * @param string $endArrowType + * @param string $endArrowSize + * @param string $headArrowWidth + * @param string $headArrowLength + * @param string $endArrowWidth + * @param string $endArrowLength + */ + public function setLineStyleProperties($lineWidth = null, $compoundType = '', $dashType = '', $capType = '', $joinType = '', $headArrowType = '', $headArrowSize = '', $endArrowType = '', $endArrowSize = '', $headArrowWidth = '', $headArrowLength = '', $endArrowWidth = '', $endArrowLength = ''): void + { + $this->activateObject(); + if (is_numeric($lineWidth)) { + $this->lineStyleProperties['width'] = $lineWidth; + } + if ($compoundType !== '') { + $this->lineStyleProperties['compound'] = $compoundType; + } + if ($dashType !== '') { + $this->lineStyleProperties['dash'] = $dashType; + } + if ($capType !== '') { + $this->lineStyleProperties['cap'] = $capType; + } + if ($joinType !== '') { + $this->lineStyleProperties['join'] = $joinType; + } + if ($headArrowType !== '') { + $this->lineStyleProperties['arrow']['head']['type'] = $headArrowType; + } + if (array_key_exists($headArrowSize, self::ARROW_SIZES)) { + $this->lineStyleProperties['arrow']['head']['size'] = $headArrowSize; + $this->lineStyleProperties['arrow']['head']['w'] = self::ARROW_SIZES[$headArrowSize]['w']; + $this->lineStyleProperties['arrow']['head']['len'] = self::ARROW_SIZES[$headArrowSize]['len']; + } + if ($endArrowType !== '') { + $this->lineStyleProperties['arrow']['end']['type'] = $endArrowType; + } + if (array_key_exists($endArrowSize, self::ARROW_SIZES)) { + $this->lineStyleProperties['arrow']['end']['size'] = $endArrowSize; + $this->lineStyleProperties['arrow']['end']['w'] = self::ARROW_SIZES[$endArrowSize]['w']; + $this->lineStyleProperties['arrow']['end']['len'] = self::ARROW_SIZES[$endArrowSize]['len']; + } + if ($headArrowWidth !== '') { + $this->lineStyleProperties['arrow']['head']['w'] = $headArrowWidth; + } + if ($headArrowLength !== '') { + $this->lineStyleProperties['arrow']['head']['len'] = $headArrowLength; + } + if ($endArrowWidth !== '') { + $this->lineStyleProperties['arrow']['end']['w'] = $endArrowWidth; + } + if ($endArrowLength !== '') { + $this->lineStyleProperties['arrow']['end']['len'] = $endArrowLength; + } + } + + public function getLineStyleArray(): array + { + return $this->lineStyleProperties; + } + + public function setLineStyleArray(array $lineStyleProperties = []): self + { + $this->activateObject(); + $this->lineStyleProperties['width'] = $lineStyleProperties['width'] ?? null; + $this->lineStyleProperties['compound'] = $lineStyleProperties['compound'] ?? ''; + $this->lineStyleProperties['dash'] = $lineStyleProperties['dash'] ?? ''; + $this->lineStyleProperties['cap'] = $lineStyleProperties['cap'] ?? ''; + $this->lineStyleProperties['join'] = $lineStyleProperties['join'] ?? ''; + $this->lineStyleProperties['arrow']['head']['type'] = $lineStyleProperties['arrow']['head']['type'] ?? ''; + $this->lineStyleProperties['arrow']['head']['size'] = $lineStyleProperties['arrow']['head']['size'] ?? ''; + $this->lineStyleProperties['arrow']['head']['w'] = $lineStyleProperties['arrow']['head']['w'] ?? ''; + $this->lineStyleProperties['arrow']['head']['len'] = $lineStyleProperties['arrow']['head']['len'] ?? ''; + $this->lineStyleProperties['arrow']['end']['type'] = $lineStyleProperties['arrow']['end']['type'] ?? ''; + $this->lineStyleProperties['arrow']['end']['size'] = $lineStyleProperties['arrow']['end']['size'] ?? ''; + $this->lineStyleProperties['arrow']['end']['w'] = $lineStyleProperties['arrow']['end']['w'] ?? ''; + $this->lineStyleProperties['arrow']['end']['len'] = $lineStyleProperties['arrow']['end']['len'] ?? ''; + + return $this; + } + + /** + * @param mixed $value + */ + public function setLineStyleProperty(string $propertyName, $value): self + { + $this->activateObject(); + $this->lineStyleProperties[$propertyName] = $value; + + return $this; + } + + /** + * Get Line Style Property. + * + * @param array|string $elements + * + * @return string + */ + public function getLineStyleProperty($elements) + { + return $this->getArrayElementsValue($this->lineStyleProperties, $elements); + } + + protected const ARROW_SIZES = [ + 1 => ['w' => 'sm', 'len' => 'sm'], + 2 => ['w' => 'sm', 'len' => 'med'], + 3 => ['w' => 'sm', 'len' => 'lg'], + 4 => ['w' => 'med', 'len' => 'sm'], + 5 => ['w' => 'med', 'len' => 'med'], + 6 => ['w' => 'med', 'len' => 'lg'], + 7 => ['w' => 'lg', 'len' => 'sm'], + 8 => ['w' => 'lg', 'len' => 'med'], + 9 => ['w' => 'lg', 'len' => 'lg'], + ]; + + /** + * Get Line Style Arrow Size. + * + * @param int $arraySelector + * @param string $arrayKaySelector + * + * @return string + */ + protected function getLineStyleArrowSize($arraySelector, $arrayKaySelector) + { + return self::ARROW_SIZES[$arraySelector][$arrayKaySelector] ?? ''; + } + + /** + * Get Line Style Arrow Parameters. + * + * @param string $arrowSelector + * @param string $propertySelector + * + * @return string + */ + public function getLineStyleArrowParameters($arrowSelector, $propertySelector) + { + return $this->getLineStyleArrowSize($this->lineStyleProperties['arrow'][$arrowSelector]['size'], $propertySelector); + } + + /** + * Get Line Style Arrow Width. + * + * @param string $arrow + * + * @return string + */ + public function getLineStyleArrowWidth($arrow) + { + return $this->getLineStyleProperty(['arrow', $arrow, 'w']); + } + + /** + * Get Line Style Arrow Excel Length. + * + * @param string $arrow + * + * @return string + */ + public function getLineStyleArrowLength($arrow) + { + return $this->getLineStyleProperty(['arrow', $arrow, 'len']); + } } diff --git a/PhpOffice/PhpSpreadsheet/Chart/Renderer/IRenderer.php b/PhpOffice/PhpSpreadsheet/Chart/Renderer/IRenderer.php old mode 100755 new mode 100644 index c6fcfbf..3032f6b --- a/PhpOffice/PhpSpreadsheet/Chart/Renderer/IRenderer.php +++ b/PhpOffice/PhpSpreadsheet/Chart/Renderer/IRenderer.php @@ -8,8 +8,6 @@ interface IRenderer { /** * IRenderer constructor. - * - * @param \PhpOffice\PhpSpreadsheet\Chart\Chart $chart */ public function __construct(Chart $chart); diff --git a/PhpOffice/PhpSpreadsheet/Chart/Renderer/JpGraph.php b/PhpOffice/PhpSpreadsheet/Chart/Renderer/JpGraph.php old mode 100755 new mode 100644 index 9dcab04..faadd9a --- a/PhpOffice/PhpSpreadsheet/Chart/Renderer/JpGraph.php +++ b/PhpOffice/PhpSpreadsheet/Chart/Renderer/JpGraph.php @@ -2,54 +2,28 @@ namespace PhpOffice\PhpSpreadsheet\Chart\Renderer; -use PhpOffice\PhpSpreadsheet\Chart\Chart; -use PhpOffice\PhpSpreadsheet\Style\NumberFormat; - -require_once __DIR__ . '/Polyfill.php'; - -class JpGraph implements IRenderer +/** + * Jpgraph is not oficially maintained in Composer, so the version there + * could be out of date. For that reason, all unit test requiring Jpgraph + * are skipped. So, do not measure code coverage for this class till that + * is fixed. + * + * This implementation uses abandoned package + * https://packagist.org/packages/jpgraph/jpgraph + * + * @codeCoverageIgnore + */ +class JpGraph extends JpGraphRendererBase { - private static $width = 640; - - private static $height = 480; - - private static $colourSet = [ - 'mediumpurple1', 'palegreen3', 'gold1', 'cadetblue1', - 'darkmagenta', 'coral', 'dodgerblue3', 'eggplant', - 'mediumblue', 'magenta', 'sandybrown', 'cyan', - 'firebrick1', 'forestgreen', 'deeppink4', 'darkolivegreen', - 'goldenrod2', - ]; - - private static $markSet; - - private $chart; - - private $graph; - - private static $plotColour = 0; - - private static $plotMark = 0; - - /** - * Create a new jpgraph. - * - * @param Chart $chart - */ - public function __construct(Chart $chart) - { - self::init(); - $this->graph = null; - $this->chart = $chart; - } - - private static function init() + protected static function init(): void { static $loaded = false; if ($loaded) { return; } + // JpGraph is no longer included with distribution, but user may install it. + // So Scrutinizer's complaint that it can't find it is reasonable, but unfixable. \JpGraph\JpGraph::load(); \JpGraph\JpGraph::module('bar'); \JpGraph\JpGraph::module('contour'); @@ -61,796 +35,6 @@ class JpGraph implements IRenderer \JpGraph\JpGraph::module('scatter'); \JpGraph\JpGraph::module('stock'); - self::$markSet = [ - 'diamond' => MARK_DIAMOND, - 'square' => MARK_SQUARE, - 'triangle' => MARK_UTRIANGLE, - 'x' => MARK_X, - 'star' => MARK_STAR, - 'dot' => MARK_FILLEDCIRCLE, - 'dash' => MARK_DTRIANGLE, - 'circle' => MARK_CIRCLE, - 'plus' => MARK_CROSS, - ]; - $loaded = true; } - - private function formatPointMarker($seriesPlot, $markerID) - { - $plotMarkKeys = array_keys(self::$markSet); - if ($markerID === null) { - // Use default plot marker (next marker in the series) - self::$plotMark %= count(self::$markSet); - $seriesPlot->mark->SetType(self::$markSet[$plotMarkKeys[self::$plotMark++]]); - } elseif ($markerID !== 'none') { - // Use specified plot marker (if it exists) - if (isset(self::$markSet[$markerID])) { - $seriesPlot->mark->SetType(self::$markSet[$markerID]); - } else { - // If the specified plot marker doesn't exist, use default plot marker (next marker in the series) - self::$plotMark %= count(self::$markSet); - $seriesPlot->mark->SetType(self::$markSet[$plotMarkKeys[self::$plotMark++]]); - } - } else { - // Hide plot marker - $seriesPlot->mark->Hide(); - } - $seriesPlot->mark->SetColor(self::$colourSet[self::$plotColour]); - $seriesPlot->mark->SetFillColor(self::$colourSet[self::$plotColour]); - $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]); - - return $seriesPlot; - } - - private function formatDataSetLabels($groupID, $datasetLabels, $labelCount, $rotation = '') - { - $datasetLabelFormatCode = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getFormatCode(); - if ($datasetLabelFormatCode !== null) { - // Retrieve any label formatting code - $datasetLabelFormatCode = stripslashes($datasetLabelFormatCode); - } - - $testCurrentIndex = 0; - foreach ($datasetLabels as $i => $datasetLabel) { - if (is_array($datasetLabel)) { - if ($rotation == 'bar') { - $datasetLabels[$i] = implode(' ', $datasetLabel); - } else { - $datasetLabel = array_reverse($datasetLabel); - $datasetLabels[$i] = implode("\n", $datasetLabel); - } - } else { - // Format labels according to any formatting code - if ($datasetLabelFormatCode !== null) { - $datasetLabels[$i] = NumberFormat::toFormattedString($datasetLabel, $datasetLabelFormatCode); - } - } - ++$testCurrentIndex; - } - - return $datasetLabels; - } - - private function percentageSumCalculation($groupID, $seriesCount) - { - $sumValues = []; - // Adjust our values to a percentage value across all series in the group - for ($i = 0; $i < $seriesCount; ++$i) { - if ($i == 0) { - $sumValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); - } else { - $nextValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); - foreach ($nextValues as $k => $value) { - if (isset($sumValues[$k])) { - $sumValues[$k] += $value; - } else { - $sumValues[$k] = $value; - } - } - } - } - - return $sumValues; - } - - private function percentageAdjustValues($dataValues, $sumValues) - { - foreach ($dataValues as $k => $dataValue) { - $dataValues[$k] = $dataValue / $sumValues[$k] * 100; - } - - return $dataValues; - } - - private function getCaption($captionElement) - { - // Read any caption - $caption = ($captionElement !== null) ? $captionElement->getCaption() : null; - // Test if we have a title caption to display - if ($caption !== null) { - // If we do, it could be a plain string or an array - if (is_array($caption)) { - // Implode an array to a plain string - $caption = implode('', $caption); - } - } - - return $caption; - } - - private function renderTitle() - { - $title = $this->getCaption($this->chart->getTitle()); - if ($title !== null) { - $this->graph->title->Set($title); - } - } - - private function renderLegend() - { - $legend = $this->chart->getLegend(); - if ($legend !== null) { - $legendPosition = $legend->getPosition(); - switch ($legendPosition) { - case 'r': - $this->graph->legend->SetPos(0.01, 0.5, 'right', 'center'); // right - $this->graph->legend->SetColumns(1); - - break; - case 'l': - $this->graph->legend->SetPos(0.01, 0.5, 'left', 'center'); // left - $this->graph->legend->SetColumns(1); - - break; - case 't': - $this->graph->legend->SetPos(0.5, 0.01, 'center', 'top'); // top - break; - case 'b': - $this->graph->legend->SetPos(0.5, 0.99, 'center', 'bottom'); // bottom - break; - default: - $this->graph->legend->SetPos(0.01, 0.01, 'right', 'top'); // top-right - $this->graph->legend->SetColumns(1); - - break; - } - } else { - $this->graph->legend->Hide(); - } - } - - private function renderCartesianPlotArea($type = 'textlin') - { - $this->graph = new \Graph(self::$width, self::$height); - $this->graph->SetScale($type); - - $this->renderTitle(); - - // Rotate for bar rather than column chart - $rotation = $this->chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotDirection(); - $reverse = $rotation == 'bar'; - - $xAxisLabel = $this->chart->getXAxisLabel(); - if ($xAxisLabel !== null) { - $title = $this->getCaption($xAxisLabel); - if ($title !== null) { - $this->graph->xaxis->SetTitle($title, 'center'); - $this->graph->xaxis->title->SetMargin(35); - if ($reverse) { - $this->graph->xaxis->title->SetAngle(90); - $this->graph->xaxis->title->SetMargin(90); - } - } - } - - $yAxisLabel = $this->chart->getYAxisLabel(); - if ($yAxisLabel !== null) { - $title = $this->getCaption($yAxisLabel); - if ($title !== null) { - $this->graph->yaxis->SetTitle($title, 'center'); - if ($reverse) { - $this->graph->yaxis->title->SetAngle(0); - $this->graph->yaxis->title->SetMargin(-55); - } - } - } - } - - private function renderPiePlotArea() - { - $this->graph = new \PieGraph(self::$width, self::$height); - - $this->renderTitle(); - } - - private function renderRadarPlotArea() - { - $this->graph = new \RadarGraph(self::$width, self::$height); - $this->graph->SetScale('lin'); - - $this->renderTitle(); - } - - private function renderPlotLine($groupID, $filled = false, $combination = false, $dimensions = '2d') - { - $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping(); - - $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount(); - if ($labelCount > 0) { - $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues(); - $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $labelCount); - $this->graph->xaxis->SetTickLabels($datasetLabels); - } - - $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); - $seriesPlots = []; - if ($grouping == 'percentStacked') { - $sumValues = $this->percentageSumCalculation($groupID, $seriesCount); - } - - // Loop through each data series in turn - for ($i = 0; $i < $seriesCount; ++$i) { - $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); - $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker(); - - if ($grouping == 'percentStacked') { - $dataValues = $this->percentageAdjustValues($dataValues, $sumValues); - } - - // Fill in any missing values in the $dataValues array - $testCurrentIndex = 0; - foreach ($dataValues as $k => $dataValue) { - while ($k != $testCurrentIndex) { - $dataValues[$testCurrentIndex] = null; - ++$testCurrentIndex; - } - ++$testCurrentIndex; - } - - $seriesPlot = new \LinePlot($dataValues); - if ($combination) { - $seriesPlot->SetBarCenter(); - } - - if ($filled) { - $seriesPlot->SetFilled(true); - $seriesPlot->SetColor('black'); - $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour++]); - } else { - // Set the appropriate plot marker - $this->formatPointMarker($seriesPlot, $marker); - } - $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue(); - $seriesPlot->SetLegend($dataLabel); - - $seriesPlots[] = $seriesPlot; - } - - if ($grouping == 'standard') { - $groupPlot = $seriesPlots; - } else { - $groupPlot = new \AccLinePlot($seriesPlots); - } - $this->graph->Add($groupPlot); - } - - private function renderPlotBar($groupID, $dimensions = '2d') - { - $rotation = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotDirection(); - // Rotate for bar rather than column chart - if (($groupID == 0) && ($rotation == 'bar')) { - $this->graph->Set90AndMargin(); - } - $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping(); - - $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount(); - if ($labelCount > 0) { - $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues(); - $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $labelCount, $rotation); - // Rotate for bar rather than column chart - if ($rotation == 'bar') { - $datasetLabels = array_reverse($datasetLabels); - $this->graph->yaxis->SetPos('max'); - $this->graph->yaxis->SetLabelAlign('center', 'top'); - $this->graph->yaxis->SetLabelSide(SIDE_RIGHT); - } - $this->graph->xaxis->SetTickLabels($datasetLabels); - } - - $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); - $seriesPlots = []; - if ($grouping == 'percentStacked') { - $sumValues = $this->percentageSumCalculation($groupID, $seriesCount); - } - - // Loop through each data series in turn - for ($j = 0; $j < $seriesCount; ++$j) { - $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($j)->getDataValues(); - if ($grouping == 'percentStacked') { - $dataValues = $this->percentageAdjustValues($dataValues, $sumValues); - } - - // Fill in any missing values in the $dataValues array - $testCurrentIndex = 0; - foreach ($dataValues as $k => $dataValue) { - while ($k != $testCurrentIndex) { - $dataValues[$testCurrentIndex] = null; - ++$testCurrentIndex; - } - ++$testCurrentIndex; - } - - // Reverse the $dataValues order for bar rather than column chart - if ($rotation == 'bar') { - $dataValues = array_reverse($dataValues); - } - $seriesPlot = new \BarPlot($dataValues); - $seriesPlot->SetColor('black'); - $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour++]); - if ($dimensions == '3d') { - $seriesPlot->SetShadow(); - } - if (!$this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($j)) { - $dataLabel = ''; - } else { - $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($j)->getDataValue(); - } - $seriesPlot->SetLegend($dataLabel); - - $seriesPlots[] = $seriesPlot; - } - // Reverse the plot order for bar rather than column chart - if (($rotation == 'bar') && ($grouping != 'percentStacked')) { - $seriesPlots = array_reverse($seriesPlots); - } - - if ($grouping == 'clustered') { - $groupPlot = new \GroupBarPlot($seriesPlots); - } elseif ($grouping == 'standard') { - $groupPlot = new \GroupBarPlot($seriesPlots); - } else { - $groupPlot = new \AccBarPlot($seriesPlots); - if ($dimensions == '3d') { - $groupPlot->SetShadow(); - } - } - - $this->graph->Add($groupPlot); - } - - private function renderPlotScatter($groupID, $bubble) - { - $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping(); - $scatterStyle = $bubbleSize = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle(); - - $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); - $seriesPlots = []; - - // Loop through each data series in turn - for ($i = 0; $i < $seriesCount; ++$i) { - $dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues(); - $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); - - foreach ($dataValuesY as $k => $dataValueY) { - $dataValuesY[$k] = $k; - } - - $seriesPlot = new \ScatterPlot($dataValuesX, $dataValuesY); - if ($scatterStyle == 'lineMarker') { - $seriesPlot->SetLinkPoints(); - $seriesPlot->link->SetColor(self::$colourSet[self::$plotColour]); - } elseif ($scatterStyle == 'smoothMarker') { - $spline = new \Spline($dataValuesY, $dataValuesX); - [$splineDataY, $splineDataX] = $spline->Get(count($dataValuesX) * self::$width / 20); - $lplot = new \LinePlot($splineDataX, $splineDataY); - $lplot->SetColor(self::$colourSet[self::$plotColour]); - - $this->graph->Add($lplot); - } - - if ($bubble) { - $this->formatPointMarker($seriesPlot, 'dot'); - $seriesPlot->mark->SetColor('black'); - $seriesPlot->mark->SetSize($bubbleSize); - } else { - $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker(); - $this->formatPointMarker($seriesPlot, $marker); - } - $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue(); - $seriesPlot->SetLegend($dataLabel); - - $this->graph->Add($seriesPlot); - } - } - - private function renderPlotRadar($groupID) - { - $radarStyle = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle(); - - $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); - $seriesPlots = []; - - // Loop through each data series in turn - for ($i = 0; $i < $seriesCount; ++$i) { - $dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues(); - $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); - $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker(); - - $dataValues = []; - foreach ($dataValuesY as $k => $dataValueY) { - $dataValues[$k] = implode(' ', array_reverse($dataValueY)); - } - $tmp = array_shift($dataValues); - $dataValues[] = $tmp; - $tmp = array_shift($dataValuesX); - $dataValuesX[] = $tmp; - - $this->graph->SetTitles(array_reverse($dataValues)); - - $seriesPlot = new \RadarPlot(array_reverse($dataValuesX)); - - $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue(); - $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]); - if ($radarStyle == 'filled') { - $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour]); - } - $this->formatPointMarker($seriesPlot, $marker); - $seriesPlot->SetLegend($dataLabel); - - $this->graph->Add($seriesPlot); - } - } - - private function renderPlotContour($groupID) - { - $contourStyle = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle(); - - $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); - $seriesPlots = []; - - $dataValues = []; - // Loop through each data series in turn - for ($i = 0; $i < $seriesCount; ++$i) { - $dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues(); - $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); - - $dataValues[$i] = $dataValuesX; - } - $seriesPlot = new \ContourPlot($dataValues); - - $this->graph->Add($seriesPlot); - } - - private function renderPlotStock($groupID) - { - $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); - $plotOrder = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder(); - - $dataValues = []; - // Loop through each data series in turn and build the plot arrays - foreach ($plotOrder as $i => $v) { - $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($v)->getDataValues(); - foreach ($dataValuesX as $j => $dataValueX) { - $dataValues[$plotOrder[$i]][$j] = $dataValueX; - } - } - if (empty($dataValues)) { - return; - } - - $dataValuesPlot = []; - // Flatten the plot arrays to a single dimensional array to work with jpgraph - $jMax = count($dataValues[0]); - for ($j = 0; $j < $jMax; ++$j) { - for ($i = 0; $i < $seriesCount; ++$i) { - $dataValuesPlot[] = $dataValues[$i][$j]; - } - } - - // Set the x-axis labels - $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount(); - if ($labelCount > 0) { - $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues(); - $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $labelCount); - $this->graph->xaxis->SetTickLabels($datasetLabels); - } - - $seriesPlot = new \StockPlot($dataValuesPlot); - $seriesPlot->SetWidth(20); - - $this->graph->Add($seriesPlot); - } - - private function renderAreaChart($groupCount, $dimensions = '2d') - { - $this->renderCartesianPlotArea(); - - for ($i = 0; $i < $groupCount; ++$i) { - $this->renderPlotLine($i, true, false, $dimensions); - } - } - - private function renderLineChart($groupCount, $dimensions = '2d') - { - $this->renderCartesianPlotArea(); - - for ($i = 0; $i < $groupCount; ++$i) { - $this->renderPlotLine($i, false, false, $dimensions); - } - } - - private function renderBarChart($groupCount, $dimensions = '2d') - { - $this->renderCartesianPlotArea(); - - for ($i = 0; $i < $groupCount; ++$i) { - $this->renderPlotBar($i, $dimensions); - } - } - - private function renderScatterChart($groupCount) - { - $this->renderCartesianPlotArea('linlin'); - - for ($i = 0; $i < $groupCount; ++$i) { - $this->renderPlotScatter($i, false); - } - } - - private function renderBubbleChart($groupCount) - { - $this->renderCartesianPlotArea('linlin'); - - for ($i = 0; $i < $groupCount; ++$i) { - $this->renderPlotScatter($i, true); - } - } - - private function renderPieChart($groupCount, $dimensions = '2d', $doughnut = false, $multiplePlots = false) - { - $this->renderPiePlotArea(); - - $iLimit = ($multiplePlots) ? $groupCount : 1; - for ($groupID = 0; $groupID < $iLimit; ++$groupID) { - $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping(); - $exploded = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle(); - $datasetLabels = []; - if ($groupID == 0) { - $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount(); - if ($labelCount > 0) { - $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues(); - $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $labelCount); - } - } - - $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); - $seriesPlots = []; - // For pie charts, we only display the first series: doughnut charts generally display all series - $jLimit = ($multiplePlots) ? $seriesCount : 1; - // Loop through each data series in turn - for ($j = 0; $j < $jLimit; ++$j) { - $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($j)->getDataValues(); - - // Fill in any missing values in the $dataValues array - $testCurrentIndex = 0; - foreach ($dataValues as $k => $dataValue) { - while ($k != $testCurrentIndex) { - $dataValues[$testCurrentIndex] = null; - ++$testCurrentIndex; - } - ++$testCurrentIndex; - } - - if ($dimensions == '3d') { - $seriesPlot = new \PiePlot3D($dataValues); - } else { - if ($doughnut) { - $seriesPlot = new \PiePlotC($dataValues); - } else { - $seriesPlot = new \PiePlot($dataValues); - } - } - - if ($multiplePlots) { - $seriesPlot->SetSize(($jLimit - $j) / ($jLimit * 4)); - } - - if ($doughnut) { - $seriesPlot->SetMidColor('white'); - } - - $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]); - if (count($datasetLabels) > 0) { - $seriesPlot->SetLabels(array_fill(0, count($datasetLabels), '')); - } - if ($dimensions != '3d') { - $seriesPlot->SetGuideLines(false); - } - if ($j == 0) { - if ($exploded) { - $seriesPlot->ExplodeAll(); - } - $seriesPlot->SetLegends($datasetLabels); - } - - $this->graph->Add($seriesPlot); - } - } - } - - private function renderRadarChart($groupCount) - { - $this->renderRadarPlotArea(); - - for ($groupID = 0; $groupID < $groupCount; ++$groupID) { - $this->renderPlotRadar($groupID); - } - } - - private function renderStockChart($groupCount) - { - $this->renderCartesianPlotArea('intint'); - - for ($groupID = 0; $groupID < $groupCount; ++$groupID) { - $this->renderPlotStock($groupID); - } - } - - private function renderContourChart($groupCount, $dimensions) - { - $this->renderCartesianPlotArea('intint'); - - for ($i = 0; $i < $groupCount; ++$i) { - $this->renderPlotContour($i); - } - } - - private function renderCombinationChart($groupCount, $dimensions, $outputDestination) - { - $this->renderCartesianPlotArea(); - - for ($i = 0; $i < $groupCount; ++$i) { - $dimensions = null; - $chartType = $this->chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType(); - switch ($chartType) { - case 'area3DChart': - $dimensions = '3d'; - // no break - case 'areaChart': - $this->renderPlotLine($i, true, true, $dimensions); - - break; - case 'bar3DChart': - $dimensions = '3d'; - // no break - case 'barChart': - $this->renderPlotBar($i, $dimensions); - - break; - case 'line3DChart': - $dimensions = '3d'; - // no break - case 'lineChart': - $this->renderPlotLine($i, false, true, $dimensions); - - break; - case 'scatterChart': - $this->renderPlotScatter($i, false); - - break; - case 'bubbleChart': - $this->renderPlotScatter($i, true); - - break; - default: - $this->graph = null; - - return false; - } - } - - $this->renderLegend(); - - $this->graph->Stroke($outputDestination); - - return true; - } - - public function render($outputDestination) - { - self::$plotColour = 0; - - $groupCount = $this->chart->getPlotArea()->getPlotGroupCount(); - - $dimensions = null; - if ($groupCount == 1) { - $chartType = $this->chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotType(); - } else { - $chartTypes = []; - for ($i = 0; $i < $groupCount; ++$i) { - $chartTypes[] = $this->chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType(); - } - $chartTypes = array_unique($chartTypes); - if (count($chartTypes) == 1) { - $chartType = array_pop($chartTypes); - } elseif (count($chartTypes) == 0) { - echo 'Chart is not yet implemented
'; - - return false; - } else { - return $this->renderCombinationChart($groupCount, $dimensions, $outputDestination); - } - } - - switch ($chartType) { - case 'area3DChart': - $dimensions = '3d'; - // no break - case 'areaChart': - $this->renderAreaChart($groupCount, $dimensions); - - break; - case 'bar3DChart': - $dimensions = '3d'; - // no break - case 'barChart': - $this->renderBarChart($groupCount, $dimensions); - - break; - case 'line3DChart': - $dimensions = '3d'; - // no break - case 'lineChart': - $this->renderLineChart($groupCount, $dimensions); - - break; - case 'pie3DChart': - $dimensions = '3d'; - // no break - case 'pieChart': - $this->renderPieChart($groupCount, $dimensions, false, false); - - break; - case 'doughnut3DChart': - $dimensions = '3d'; - // no break - case 'doughnutChart': - $this->renderPieChart($groupCount, $dimensions, true, true); - - break; - case 'scatterChart': - $this->renderScatterChart($groupCount); - - break; - case 'bubbleChart': - $this->renderBubbleChart($groupCount); - - break; - case 'radarChart': - $this->renderRadarChart($groupCount); - - break; - case 'surface3DChart': - $dimensions = '3d'; - // no break - case 'surfaceChart': - $this->renderContourChart($groupCount, $dimensions); - - break; - case 'stockChart': - $this->renderStockChart($groupCount); - - break; - default: - echo $chartType . ' is not yet implemented
'; - - return false; - } - $this->renderLegend(); - - $this->graph->Stroke($outputDestination); - - return true; - } } diff --git a/PhpOffice/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php b/PhpOffice/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php new file mode 100644 index 0000000..cb9b544 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php @@ -0,0 +1,852 @@ +graph = null; + $this->chart = $chart; + + self::$markSet = [ + 'diamond' => MARK_DIAMOND, + 'square' => MARK_SQUARE, + 'triangle' => MARK_UTRIANGLE, + 'x' => MARK_X, + 'star' => MARK_STAR, + 'dot' => MARK_FILLEDCIRCLE, + 'dash' => MARK_DTRIANGLE, + 'circle' => MARK_CIRCLE, + 'plus' => MARK_CROSS, + ]; + } + + /** + * This method should be overriden in descendants to do real JpGraph library initialization. + */ + abstract protected static function init(): void; + + private function formatPointMarker($seriesPlot, $markerID) + { + $plotMarkKeys = array_keys(self::$markSet); + if ($markerID === null) { + // Use default plot marker (next marker in the series) + self::$plotMark %= count(self::$markSet); + $seriesPlot->mark->SetType(self::$markSet[$plotMarkKeys[self::$plotMark++]]); + } elseif ($markerID !== 'none') { + // Use specified plot marker (if it exists) + if (isset(self::$markSet[$markerID])) { + $seriesPlot->mark->SetType(self::$markSet[$markerID]); + } else { + // If the specified plot marker doesn't exist, use default plot marker (next marker in the series) + self::$plotMark %= count(self::$markSet); + $seriesPlot->mark->SetType(self::$markSet[$plotMarkKeys[self::$plotMark++]]); + } + } else { + // Hide plot marker + $seriesPlot->mark->Hide(); + } + $seriesPlot->mark->SetColor(self::$colourSet[self::$plotColour]); + $seriesPlot->mark->SetFillColor(self::$colourSet[self::$plotColour]); + $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]); + + return $seriesPlot; + } + + private function formatDataSetLabels($groupID, $datasetLabels, $rotation = '') + { + $datasetLabelFormatCode = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getFormatCode() ?? ''; + // Retrieve any label formatting code + $datasetLabelFormatCode = stripslashes($datasetLabelFormatCode); + + $testCurrentIndex = 0; + foreach ($datasetLabels as $i => $datasetLabel) { + if (is_array($datasetLabel)) { + if ($rotation == 'bar') { + $datasetLabels[$i] = implode(' ', $datasetLabel); + } else { + $datasetLabel = array_reverse($datasetLabel); + $datasetLabels[$i] = implode("\n", $datasetLabel); + } + } else { + // Format labels according to any formatting code + if ($datasetLabelFormatCode !== null) { + $datasetLabels[$i] = NumberFormat::toFormattedString($datasetLabel, $datasetLabelFormatCode); + } + } + ++$testCurrentIndex; + } + + return $datasetLabels; + } + + private function percentageSumCalculation($groupID, $seriesCount) + { + $sumValues = []; + // Adjust our values to a percentage value across all series in the group + for ($i = 0; $i < $seriesCount; ++$i) { + if ($i == 0) { + $sumValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); + } else { + $nextValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); + foreach ($nextValues as $k => $value) { + if (isset($sumValues[$k])) { + $sumValues[$k] += $value; + } else { + $sumValues[$k] = $value; + } + } + } + } + + return $sumValues; + } + + private function percentageAdjustValues($dataValues, $sumValues) + { + foreach ($dataValues as $k => $dataValue) { + $dataValues[$k] = $dataValue / $sumValues[$k] * 100; + } + + return $dataValues; + } + + private function getCaption($captionElement) + { + // Read any caption + $caption = ($captionElement !== null) ? $captionElement->getCaption() : null; + // Test if we have a title caption to display + if ($caption !== null) { + // If we do, it could be a plain string or an array + if (is_array($caption)) { + // Implode an array to a plain string + $caption = implode('', $caption); + } + } + + return $caption; + } + + private function renderTitle(): void + { + $title = $this->getCaption($this->chart->getTitle()); + if ($title !== null) { + $this->graph->title->Set($title); + } + } + + private function renderLegend(): void + { + $legend = $this->chart->getLegend(); + if ($legend !== null) { + $legendPosition = $legend->getPosition(); + switch ($legendPosition) { + case 'r': + $this->graph->legend->SetPos(0.01, 0.5, 'right', 'center'); // right + $this->graph->legend->SetColumns(1); + + break; + case 'l': + $this->graph->legend->SetPos(0.01, 0.5, 'left', 'center'); // left + $this->graph->legend->SetColumns(1); + + break; + case 't': + $this->graph->legend->SetPos(0.5, 0.01, 'center', 'top'); // top + + break; + case 'b': + $this->graph->legend->SetPos(0.5, 0.99, 'center', 'bottom'); // bottom + + break; + default: + $this->graph->legend->SetPos(0.01, 0.01, 'right', 'top'); // top-right + $this->graph->legend->SetColumns(1); + + break; + } + } else { + $this->graph->legend->Hide(); + } + } + + private function renderCartesianPlotArea($type = 'textlin'): void + { + $this->graph = new Graph(self::$width, self::$height); + $this->graph->SetScale($type); + + $this->renderTitle(); + + // Rotate for bar rather than column chart + $rotation = $this->chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotDirection(); + $reverse = $rotation == 'bar'; + + $xAxisLabel = $this->chart->getXAxisLabel(); + if ($xAxisLabel !== null) { + $title = $this->getCaption($xAxisLabel); + if ($title !== null) { + $this->graph->xaxis->SetTitle($title, 'center'); + $this->graph->xaxis->title->SetMargin(35); + if ($reverse) { + $this->graph->xaxis->title->SetAngle(90); + $this->graph->xaxis->title->SetMargin(90); + } + } + } + + $yAxisLabel = $this->chart->getYAxisLabel(); + if ($yAxisLabel !== null) { + $title = $this->getCaption($yAxisLabel); + if ($title !== null) { + $this->graph->yaxis->SetTitle($title, 'center'); + if ($reverse) { + $this->graph->yaxis->title->SetAngle(0); + $this->graph->yaxis->title->SetMargin(-55); + } + } + } + } + + private function renderPiePlotArea(): void + { + $this->graph = new PieGraph(self::$width, self::$height); + + $this->renderTitle(); + } + + private function renderRadarPlotArea(): void + { + $this->graph = new RadarGraph(self::$width, self::$height); + $this->graph->SetScale('lin'); + + $this->renderTitle(); + } + + private function renderPlotLine($groupID, $filled = false, $combination = false): void + { + $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping(); + + $index = array_keys($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder())[0]; + $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getPointCount(); + if ($labelCount > 0) { + $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues(); + $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels); + $this->graph->xaxis->SetTickLabels($datasetLabels); + } + + $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); + $seriesPlots = []; + if ($grouping == 'percentStacked') { + $sumValues = $this->percentageSumCalculation($groupID, $seriesCount); + } else { + $sumValues = []; + } + + // Loop through each data series in turn + for ($i = 0; $i < $seriesCount; ++$i) { + $index = array_keys($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder())[$i]; + $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getDataValues(); + $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getPointMarker(); + + if ($grouping == 'percentStacked') { + $dataValues = $this->percentageAdjustValues($dataValues, $sumValues); + } + + // Fill in any missing values in the $dataValues array + $testCurrentIndex = 0; + foreach ($dataValues as $k => $dataValue) { + while ($k != $testCurrentIndex) { + $dataValues[$testCurrentIndex] = null; + ++$testCurrentIndex; + } + ++$testCurrentIndex; + } + + $seriesPlot = new LinePlot($dataValues); + if ($combination) { + $seriesPlot->SetBarCenter(); + } + + if ($filled) { + $seriesPlot->SetFilled(true); + $seriesPlot->SetColor('black'); + $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour++]); + } else { + // Set the appropriate plot marker + $this->formatPointMarker($seriesPlot, $marker); + } + $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($index)->getDataValue(); + $seriesPlot->SetLegend($dataLabel); + + $seriesPlots[] = $seriesPlot; + } + + if ($grouping == 'standard') { + $groupPlot = $seriesPlots; + } else { + $groupPlot = new AccLinePlot($seriesPlots); + } + $this->graph->Add($groupPlot); + } + + private function renderPlotBar($groupID, $dimensions = '2d'): void + { + $rotation = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotDirection(); + // Rotate for bar rather than column chart + if (($groupID == 0) && ($rotation == 'bar')) { + $this->graph->Set90AndMargin(); + } + $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping(); + + $index = array_keys($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder())[0]; + $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getPointCount(); + if ($labelCount > 0) { + $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues(); + $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $rotation); + // Rotate for bar rather than column chart + if ($rotation == 'bar') { + $datasetLabels = array_reverse($datasetLabels); + $this->graph->yaxis->SetPos('max'); + $this->graph->yaxis->SetLabelAlign('center', 'top'); + $this->graph->yaxis->SetLabelSide(SIDE_RIGHT); + } + $this->graph->xaxis->SetTickLabels($datasetLabels); + } + + $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); + $seriesPlots = []; + if ($grouping == 'percentStacked') { + $sumValues = $this->percentageSumCalculation($groupID, $seriesCount); + } else { + $sumValues = []; + } + + // Loop through each data series in turn + for ($j = 0; $j < $seriesCount; ++$j) { + $index = array_keys($this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder())[$j]; + $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($index)->getDataValues(); + if ($grouping == 'percentStacked') { + $dataValues = $this->percentageAdjustValues($dataValues, $sumValues); + } + + // Fill in any missing values in the $dataValues array + $testCurrentIndex = 0; + foreach ($dataValues as $k => $dataValue) { + while ($k != $testCurrentIndex) { + $dataValues[$testCurrentIndex] = null; + ++$testCurrentIndex; + } + ++$testCurrentIndex; + } + + // Reverse the $dataValues order for bar rather than column chart + if ($rotation == 'bar') { + $dataValues = array_reverse($dataValues); + } + $seriesPlot = new BarPlot($dataValues); + $seriesPlot->SetColor('black'); + $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour++]); + if ($dimensions == '3d') { + $seriesPlot->SetShadow(); + } + if (!$this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($j)) { + $dataLabel = ''; + } else { + $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($j)->getDataValue(); + } + $seriesPlot->SetLegend($dataLabel); + + $seriesPlots[] = $seriesPlot; + } + // Reverse the plot order for bar rather than column chart + if (($rotation == 'bar') && ($grouping != 'percentStacked')) { + $seriesPlots = array_reverse($seriesPlots); + } + + if ($grouping == 'clustered') { + $groupPlot = new GroupBarPlot($seriesPlots); + } elseif ($grouping == 'standard') { + $groupPlot = new GroupBarPlot($seriesPlots); + } else { + $groupPlot = new AccBarPlot($seriesPlots); + if ($dimensions == '3d') { + $groupPlot->SetShadow(); + } + } + + $this->graph->Add($groupPlot); + } + + private function renderPlotScatter($groupID, $bubble): void + { + $scatterStyle = $bubbleSize = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle(); + + $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); + + // Loop through each data series in turn + for ($i = 0; $i < $seriesCount; ++$i) { + $dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues(); + $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); + + foreach ($dataValuesY as $k => $dataValueY) { + $dataValuesY[$k] = $k; + } + + $seriesPlot = new ScatterPlot($dataValuesX, $dataValuesY); + if ($scatterStyle == 'lineMarker') { + $seriesPlot->SetLinkPoints(); + $seriesPlot->link->SetColor(self::$colourSet[self::$plotColour]); + } elseif ($scatterStyle == 'smoothMarker') { + $spline = new Spline($dataValuesY, $dataValuesX); + [$splineDataY, $splineDataX] = $spline->Get(count($dataValuesX) * self::$width / 20); + $lplot = new LinePlot($splineDataX, $splineDataY); + $lplot->SetColor(self::$colourSet[self::$plotColour]); + + $this->graph->Add($lplot); + } + + if ($bubble) { + $this->formatPointMarker($seriesPlot, 'dot'); + $seriesPlot->mark->SetColor('black'); + $seriesPlot->mark->SetSize($bubbleSize); + } else { + $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker(); + $this->formatPointMarker($seriesPlot, $marker); + } + $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue(); + $seriesPlot->SetLegend($dataLabel); + + $this->graph->Add($seriesPlot); + } + } + + private function renderPlotRadar($groupID): void + { + $radarStyle = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle(); + + $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); + + // Loop through each data series in turn + for ($i = 0; $i < $seriesCount; ++$i) { + $dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues(); + $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); + $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker(); + + $dataValues = []; + foreach ($dataValuesY as $k => $dataValueY) { + $dataValues[$k] = implode(' ', array_reverse($dataValueY)); + } + $tmp = array_shift($dataValues); + $dataValues[] = $tmp; + $tmp = array_shift($dataValuesX); + $dataValuesX[] = $tmp; + + $this->graph->SetTitles(array_reverse($dataValues)); + + $seriesPlot = new RadarPlot(array_reverse($dataValuesX)); + + $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue(); + $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]); + if ($radarStyle == 'filled') { + $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour]); + } + $this->formatPointMarker($seriesPlot, $marker); + $seriesPlot->SetLegend($dataLabel); + + $this->graph->Add($seriesPlot); + } + } + + private function renderPlotContour($groupID): void + { + $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); + + $dataValues = []; + // Loop through each data series in turn + for ($i = 0; $i < $seriesCount; ++$i) { + $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues(); + + $dataValues[$i] = $dataValuesX; + } + $seriesPlot = new ContourPlot($dataValues); + + $this->graph->Add($seriesPlot); + } + + private function renderPlotStock($groupID): void + { + $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); + $plotOrder = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder(); + + $dataValues = []; + // Loop through each data series in turn and build the plot arrays + foreach ($plotOrder as $i => $v) { + $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($v); + if ($dataValuesX === false) { + continue; + } + $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($v)->getDataValues(); + foreach ($dataValuesX as $j => $dataValueX) { + $dataValues[$plotOrder[$i]][$j] = $dataValueX; + } + } + if (empty($dataValues)) { + return; + } + + $dataValuesPlot = []; + // Flatten the plot arrays to a single dimensional array to work with jpgraph + $jMax = count($dataValues[0]); + for ($j = 0; $j < $jMax; ++$j) { + for ($i = 0; $i < $seriesCount; ++$i) { + $dataValuesPlot[] = $dataValues[$i][$j] ?? null; + } + } + + // Set the x-axis labels + $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount(); + if ($labelCount > 0) { + $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues(); + $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels); + $this->graph->xaxis->SetTickLabels($datasetLabels); + } + + $seriesPlot = new StockPlot($dataValuesPlot); + $seriesPlot->SetWidth(20); + + $this->graph->Add($seriesPlot); + } + + private function renderAreaChart($groupCount): void + { + $this->renderCartesianPlotArea(); + + for ($i = 0; $i < $groupCount; ++$i) { + $this->renderPlotLine($i, true, false); + } + } + + private function renderLineChart($groupCount): void + { + $this->renderCartesianPlotArea(); + + for ($i = 0; $i < $groupCount; ++$i) { + $this->renderPlotLine($i, false, false); + } + } + + private function renderBarChart($groupCount, $dimensions = '2d'): void + { + $this->renderCartesianPlotArea(); + + for ($i = 0; $i < $groupCount; ++$i) { + $this->renderPlotBar($i, $dimensions); + } + } + + private function renderScatterChart($groupCount): void + { + $this->renderCartesianPlotArea('linlin'); + + for ($i = 0; $i < $groupCount; ++$i) { + $this->renderPlotScatter($i, false); + } + } + + private function renderBubbleChart($groupCount): void + { + $this->renderCartesianPlotArea('linlin'); + + for ($i = 0; $i < $groupCount; ++$i) { + $this->renderPlotScatter($i, true); + } + } + + private function renderPieChart($groupCount, $dimensions = '2d', $doughnut = false, $multiplePlots = false): void + { + $this->renderPiePlotArea(); + + $iLimit = ($multiplePlots) ? $groupCount : 1; + for ($groupID = 0; $groupID < $iLimit; ++$groupID) { + $exploded = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle(); + $datasetLabels = []; + if ($groupID == 0) { + $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount(); + if ($labelCount > 0) { + $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues(); + $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels); + } + } + + $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount(); + // For pie charts, we only display the first series: doughnut charts generally display all series + $jLimit = ($multiplePlots) ? $seriesCount : 1; + // Loop through each data series in turn + for ($j = 0; $j < $jLimit; ++$j) { + $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($j)->getDataValues(); + + // Fill in any missing values in the $dataValues array + $testCurrentIndex = 0; + foreach ($dataValues as $k => $dataValue) { + while ($k != $testCurrentIndex) { + $dataValues[$testCurrentIndex] = null; + ++$testCurrentIndex; + } + ++$testCurrentIndex; + } + + if ($dimensions == '3d') { + $seriesPlot = new PiePlot3D($dataValues); + } else { + if ($doughnut) { + $seriesPlot = new PiePlotC($dataValues); + } else { + $seriesPlot = new PiePlot($dataValues); + } + } + + if ($multiplePlots) { + $seriesPlot->SetSize(($jLimit - $j) / ($jLimit * 4)); + } + + if ($doughnut && method_exists($seriesPlot, 'SetMidColor')) { + $seriesPlot->SetMidColor('white'); + } + + $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]); + if (count($datasetLabels) > 0) { + $seriesPlot->SetLabels(array_fill(0, count($datasetLabels), '')); + } + if ($dimensions != '3d') { + $seriesPlot->SetGuideLines(false); + } + if ($j == 0) { + if ($exploded) { + $seriesPlot->ExplodeAll(); + } + $seriesPlot->SetLegends($datasetLabels); + } + + $this->graph->Add($seriesPlot); + } + } + } + + private function renderRadarChart($groupCount): void + { + $this->renderRadarPlotArea(); + + for ($groupID = 0; $groupID < $groupCount; ++$groupID) { + $this->renderPlotRadar($groupID); + } + } + + private function renderStockChart($groupCount): void + { + $this->renderCartesianPlotArea('intint'); + + for ($groupID = 0; $groupID < $groupCount; ++$groupID) { + $this->renderPlotStock($groupID); + } + } + + private function renderContourChart($groupCount): void + { + $this->renderCartesianPlotArea('intint'); + + for ($i = 0; $i < $groupCount; ++$i) { + $this->renderPlotContour($i); + } + } + + private function renderCombinationChart($groupCount, $outputDestination) + { + $this->renderCartesianPlotArea(); + + for ($i = 0; $i < $groupCount; ++$i) { + $dimensions = null; + $chartType = $this->chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType(); + switch ($chartType) { + case 'area3DChart': + case 'areaChart': + $this->renderPlotLine($i, true, true); + + break; + case 'bar3DChart': + $dimensions = '3d'; + // no break + case 'barChart': + $this->renderPlotBar($i, $dimensions); + + break; + case 'line3DChart': + case 'lineChart': + $this->renderPlotLine($i, false, true); + + break; + case 'scatterChart': + $this->renderPlotScatter($i, false); + + break; + case 'bubbleChart': + $this->renderPlotScatter($i, true); + + break; + default: + $this->graph = null; + + return false; + } + } + + $this->renderLegend(); + + $this->graph->Stroke($outputDestination); + + return true; + } + + public function render($outputDestination) + { + self::$plotColour = 0; + + $groupCount = $this->chart->getPlotArea()->getPlotGroupCount(); + + $dimensions = null; + if ($groupCount == 1) { + $chartType = $this->chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotType(); + } else { + $chartTypes = []; + for ($i = 0; $i < $groupCount; ++$i) { + $chartTypes[] = $this->chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType(); + } + $chartTypes = array_unique($chartTypes); + if (count($chartTypes) == 1) { + $chartType = array_pop($chartTypes); + } elseif (count($chartTypes) == 0) { + echo 'Chart is not yet implemented
'; + + return false; + } else { + return $this->renderCombinationChart($groupCount, $outputDestination); + } + } + + switch ($chartType) { + case 'area3DChart': + $dimensions = '3d'; + // no break + case 'areaChart': + $this->renderAreaChart($groupCount); + + break; + case 'bar3DChart': + $dimensions = '3d'; + // no break + case 'barChart': + $this->renderBarChart($groupCount, $dimensions); + + break; + case 'line3DChart': + $dimensions = '3d'; + // no break + case 'lineChart': + $this->renderLineChart($groupCount); + + break; + case 'pie3DChart': + $dimensions = '3d'; + // no break + case 'pieChart': + $this->renderPieChart($groupCount, $dimensions, false, false); + + break; + case 'doughnut3DChart': + $dimensions = '3d'; + // no break + case 'doughnutChart': + $this->renderPieChart($groupCount, $dimensions, true, true); + + break; + case 'scatterChart': + $this->renderScatterChart($groupCount); + + break; + case 'bubbleChart': + $this->renderBubbleChart($groupCount); + + break; + case 'radarChart': + $this->renderRadarChart($groupCount); + + break; + case 'surface3DChart': + case 'surfaceChart': + $this->renderContourChart($groupCount); + + break; + case 'stockChart': + $this->renderStockChart($groupCount); + + break; + default: + echo $chartType . ' is not yet implemented
'; + + return false; + } + $this->renderLegend(); + + $this->graph->Stroke($outputDestination); + + return true; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Chart/Renderer/MtJpGraphRenderer.php b/PhpOffice/PhpSpreadsheet/Chart/Renderer/MtJpGraphRenderer.php new file mode 100644 index 0000000..e1f0f90 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Chart/Renderer/MtJpGraphRenderer.php @@ -0,0 +1,36 @@ +caption = $caption; $this->layout = $layout; @@ -33,19 +34,42 @@ class Title /** * Get caption. * - * @return string + * @return array|RichText|string */ public function getCaption() { return $this->caption; } + public function getCaptionText(): string + { + $caption = $this->caption; + if (is_string($caption)) { + return $caption; + } + if ($caption instanceof RichText) { + return $caption->getPlainText(); + } + $retVal = ''; + foreach ($caption as $textx) { + /** @var RichText|string */ + $text = $textx; + if ($text instanceof RichText) { + $retVal .= $text->getPlainText(); + } else { + $retVal .= $text; + } + } + + return $retVal; + } + /** * Set caption. * - * @param string $caption + * @param array|RichText|string $caption * - * @return Title + * @return $this */ public function setCaption($caption) { @@ -54,12 +78,7 @@ class Title return $this; } - /** - * Get Layout. - * - * @return Layout - */ - public function getLayout() + public function getLayout(): ?Layout { return $this->layout; } diff --git a/PhpOffice/PhpSpreadsheet/Chart/TrendLine.php b/PhpOffice/PhpSpreadsheet/Chart/TrendLine.php new file mode 100644 index 0000000..75a5896 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Chart/TrendLine.php @@ -0,0 +1,226 @@ +setTrendLineProperties( + $trendLineType, + $order, + $period, + $dispRSqr, + $dispEq, + $backward, + $forward, + $intercept, + $name + ); + } + + public function getTrendLineType(): string + { + return $this->trendLineType; + } + + public function setTrendLineType(string $trendLineType): self + { + $this->trendLineType = $trendLineType; + + return $this; + } + + public function getOrder(): int + { + return $this->order; + } + + public function setOrder(int $order): self + { + $this->order = $order; + + return $this; + } + + public function getPeriod(): int + { + return $this->period; + } + + public function setPeriod(int $period): self + { + $this->period = $period; + + return $this; + } + + public function getDispRSqr(): bool + { + return $this->dispRSqr; + } + + public function setDispRSqr(bool $dispRSqr): self + { + $this->dispRSqr = $dispRSqr; + + return $this; + } + + public function getDispEq(): bool + { + return $this->dispEq; + } + + public function setDispEq(bool $dispEq): self + { + $this->dispEq = $dispEq; + + return $this; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): self + { + $this->name = $name; + + return $this; + } + + public function getBackward(): float + { + return $this->backward; + } + + public function setBackward(float $backward): self + { + $this->backward = $backward; + + return $this; + } + + public function getForward(): float + { + return $this->forward; + } + + public function setForward(float $forward): self + { + $this->forward = $forward; + + return $this; + } + + public function getIntercept(): float + { + return $this->intercept; + } + + public function setIntercept(float $intercept): self + { + $this->intercept = $intercept; + + return $this; + } + + public function setTrendLineProperties( + ?string $trendLineType = null, + ?int $order = 0, + ?int $period = 0, + ?bool $dispRSqr = false, + ?bool $dispEq = false, + ?float $backward = null, + ?float $forward = null, + ?float $intercept = null, + ?string $name = null + ): self { + if (!empty($trendLineType)) { + $this->setTrendLineType($trendLineType); + } + if ($order !== null) { + $this->setOrder($order); + } + if ($period !== null) { + $this->setPeriod($period); + } + if ($dispRSqr !== null) { + $this->setDispRSqr($dispRSqr); + } + if ($dispEq !== null) { + $this->setDispEq($dispEq); + } + if ($backward !== null) { + $this->setBackward($backward); + } + if ($forward !== null) { + $this->setForward($forward); + } + if ($intercept !== null) { + $this->setIntercept($intercept); + } + if ($name !== null) { + $this->setName($name); + } + + return $this; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Collection/Cells.php b/PhpOffice/PhpSpreadsheet/Collection/Cells.php old mode 100755 new mode 100644 index 76f42c1..9a9df22 --- a/PhpOffice/PhpSpreadsheet/Collection/Cells.php +++ b/PhpOffice/PhpSpreadsheet/Collection/Cells.php @@ -2,35 +2,41 @@ namespace PhpOffice\PhpSpreadsheet\Collection; +use Generator; use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; +use PhpOffice\PhpSpreadsheet\Settings; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use Psr\SimpleCache\CacheInterface; class Cells { + protected const MAX_COLUMN_ID = 16384; + /** + * @var CacheInterface */ private $cache; /** * Parent worksheet. * - * @var Worksheet + * @var null|Worksheet */ private $parent; /** * The currently active Cell. * - * @var Cell + * @var null|Cell */ private $currentCell; /** * Coordinate of the currently active Cell. * - * @var string + * @var null|string */ private $currentCoordinate; @@ -42,9 +48,10 @@ class Cells private $currentCellIsDirty = false; /** - * An index of existing cells. Booleans indexed by their coordinate. + * An index of existing cells. int pointer to the coordinate (0-base-indexed row * 16,384 + 1-base indexed column) + * indexed by their coordinate. * - * @var bool[] + * @var int[] */ private $index = []; @@ -59,9 +66,8 @@ class Cells * Initialise this new cell collection. * * @param Worksheet $parent The worksheet for this cell collection - * @param CacheInterface $cache */ - public function __construct(Worksheet $parent, $cache) + public function __construct(Worksheet $parent, CacheInterface $cache) { // Set our parent worksheet. // This is maintained here to facilitate re-attaching it to Cell objects when @@ -74,7 +80,7 @@ class Cells /** * Return the parent worksheet for this cell collection. * - * @return Worksheet + * @return null|Worksheet */ public function getParent() { @@ -84,30 +90,19 @@ class Cells /** * Whether the collection holds a cell for the given coordinate. * - * @param string $pCoord Coordinate of the cell to check - * - * @return bool + * @param string $cellCoordinate Coordinate of the cell to check */ - public function has($pCoord) + public function has($cellCoordinate): bool { - if ($pCoord === $this->currentCoordinate) { - return true; - } - - // Check if the requested entry exists in the index - return isset($this->index[$pCoord]); + return ($cellCoordinate === $this->currentCoordinate) || isset($this->index[$cellCoordinate]); } /** * Add or update a cell in the collection. * * @param Cell $cell Cell to update - * - * @throws PhpSpreadsheetException - * - * @return Cell */ - public function update(Cell $cell) + public function update(Cell $cell): Cell { return $this->add($cell->getCoordinate(), $cell); } @@ -115,21 +110,21 @@ class Cells /** * Delete a cell in cache identified by coordinate. * - * @param string $pCoord Coordinate of the cell to delete + * @param string $cellCoordinate Coordinate of the cell to delete */ - public function delete($pCoord) + public function delete($cellCoordinate): void { - if ($pCoord === $this->currentCoordinate && $this->currentCell !== null) { + if ($cellCoordinate === $this->currentCoordinate && $this->currentCell !== null) { $this->currentCell->detach(); $this->currentCoordinate = null; $this->currentCell = null; $this->currentCellIsDirty = false; } - unset($this->index[$pCoord]); + unset($this->index[$cellCoordinate]); // Delete the entry from cache - $this->cache->delete($this->cachePrefix . $pCoord); + $this->cache->delete($this->cachePrefix . $cellCoordinate); } /** @@ -149,16 +144,43 @@ class Cells */ public function getSortedCoordinates() { - $sortKeys = []; - foreach ($this->getCoordinates() as $coord) { - $column = ''; - $row = 0; - sscanf($coord, '%[A-Z]%d', $column, $row); - $sortKeys[sprintf('%09d%3s', $row, $column)] = $coord; - } - ksort($sortKeys); + asort($this->index); - return array_values($sortKeys); + return array_keys($this->index); + } + + /** + * Return the cell coordinate of the currently active cell object. + * + * @return null|string + */ + public function getCurrentCoordinate() + { + return $this->currentCoordinate; + } + + /** + * Return the column coordinate of the currently active cell object. + */ + public function getCurrentColumn(): string + { + $column = 0; + $row = ''; + sscanf($this->currentCoordinate ?? '', '%[A-Z]%d', $column, $row); + + return (string) $column; + } + + /** + * Return the row coordinate of the currently active cell object. + */ + public function getCurrentRow(): int + { + $column = 0; + $row = ''; + sscanf($this->currentCoordinate ?? '', '%[A-Z]%d', $column, $row); + + return (int) $row; } /** @@ -169,70 +191,24 @@ class Cells public function getHighestRowAndColumn() { // Lookup highest column and highest row - $col = ['A' => '1A']; - $row = [1]; - foreach ($this->getCoordinates() as $coord) { - $c = ''; - $r = 0; - sscanf($coord, '%[A-Z]%d', $c, $r); - $row[$r] = $r; - $col[$c] = strlen($c) . $c; + $maxRow = $maxColumn = 1; + foreach ($this->index as $coordinate) { + $row = (int) floor($coordinate / self::MAX_COLUMN_ID) + 1; + $maxRow = ($maxRow > $row) ? $maxRow : $row; + $column = $coordinate % self::MAX_COLUMN_ID; + $maxColumn = ($maxColumn > $column) ? $maxColumn : $column; } - // Determine highest column and row - $highestRow = max($row); - $highestColumn = substr(max($col), 1); - return [ - 'row' => $highestRow, - 'column' => $highestColumn, + 'row' => $maxRow, + 'column' => Coordinate::stringFromColumnIndex($maxColumn), ]; } - /** - * Return the cell coordinate of the currently active cell object. - * - * @return string - */ - public function getCurrentCoordinate() - { - return $this->currentCoordinate; - } - - /** - * Return the column coordinate of the currently active cell object. - * - * @return string - */ - public function getCurrentColumn() - { - $column = ''; - $row = 0; - - sscanf($this->currentCoordinate, '%[A-Z]%d', $column, $row); - - return $column; - } - - /** - * Return the row coordinate of the currently active cell object. - * - * @return int - */ - public function getCurrentRow() - { - $column = ''; - $row = 0; - - sscanf($this->currentCoordinate, '%[A-Z]%d', $column, $row); - - return (int) $row; - } - /** * Get highest worksheet column. * - * @param string $row Return the highest column for the specified row, + * @param null|int|string $row Return the highest column for the specified row, * or the highest column of any row if no row number is passed * * @return string Highest column name @@ -240,30 +216,32 @@ class Cells public function getHighestColumn($row = null) { if ($row === null) { - $colRow = $this->getHighestRowAndColumn(); - - return $colRow['column']; + return $this->getHighestRowAndColumn()['column']; } - $columnList = [1]; - foreach ($this->getCoordinates() as $coord) { - $c = ''; - $r = 0; + $row = (int) $row; + if ($row <= 0) { + throw new PhpSpreadsheetException('Row number must be a positive integer'); + } - sscanf($coord, '%[A-Z]%d', $c, $r); - if ($r != $row) { + $maxColumn = 1; + $toRow = $row * self::MAX_COLUMN_ID; + $fromRow = --$row * self::MAX_COLUMN_ID; + foreach ($this->index as $coordinate) { + if ($coordinate < $fromRow || $coordinate >= $toRow) { continue; } - $columnList[] = Coordinate::columnIndexFromString($c); + $column = $coordinate % self::MAX_COLUMN_ID; + $maxColumn = $maxColumn > $column ? $maxColumn : $column; } - return Coordinate::stringFromColumnIndex(max($columnList)); + return Coordinate::stringFromColumnIndex($maxColumn); } /** * Get highest worksheet row. * - * @param string $column Return the highest row for the specified column, + * @param null|string $column Return the highest row for the specified column, * or the highest row of any column if no column letter is passed * * @return int Highest row number @@ -271,24 +249,20 @@ class Cells public function getHighestRow($column = null) { if ($column === null) { - $colRow = $this->getHighestRowAndColumn(); - - return $colRow['row']; + return $this->getHighestRowAndColumn()['row']; } - $rowList = [0]; - foreach ($this->getCoordinates() as $coord) { - $c = ''; - $r = 0; - - sscanf($coord, '%[A-Z]%d', $c, $r); - if ($c != $column) { + $maxRow = 1; + $columnIndex = Coordinate::columnIndexFromString($column); + foreach ($this->index as $coordinate) { + if ($coordinate % self::MAX_COLUMN_ID !== $columnIndex) { continue; } - $rowList[] = $r; + $row = (int) floor($coordinate / self::MAX_COLUMN_ID) + 1; + $maxRow = ($maxRow > $row) ? $maxRow : $row; } - return max($rowList); + return $maxRow; } /** @@ -298,44 +272,35 @@ class Cells */ private function getUniqueID() { - return uniqid('phpspreadsheet.', true) . '.'; + $cacheType = Settings::getCache(); + + return ($cacheType instanceof Memory\SimpleCache1 || $cacheType instanceof Memory\SimpleCache3) + ? random_bytes(7) . ':' + : uniqid('phpspreadsheet.', true) . '.'; } /** * Clone the cell collection. * - * @param Worksheet $parent The new worksheet that we're copying to - * * @return self */ - public function cloneCellCollection(Worksheet $parent) + public function cloneCellCollection(Worksheet $worksheet) { $this->storeCurrentCell(); $newCollection = clone $this; - $newCollection->parent = $parent; - if (($newCollection->currentCell !== null) && (is_object($newCollection->currentCell))) { - $newCollection->currentCell->attach($this); - } - - // Get old values - $oldKeys = $newCollection->getAllCacheKeys(); - $oldValues = $newCollection->cache->getMultiple($oldKeys); - $newValues = []; - $oldCachePrefix = $newCollection->cachePrefix; - - // Change prefix + $newCollection->parent = $worksheet; $newCollection->cachePrefix = $newCollection->getUniqueID(); - foreach ($oldValues as $oldKey => $value) { - $newValues[str_replace($oldCachePrefix, $newCollection->cachePrefix, $oldKey)] = clone $value; - } - // Store new values - $stored = $newCollection->cache->setMultiple($newValues); - if (!$stored) { - $newCollection->__destruct(); - - throw new PhpSpreadsheetException('Failed to copy cells in cache'); + foreach ($this->index as $key => $value) { + $newCollection->index[$key] = $value; + $stored = $newCollection->cache->set( + $newCollection->cachePrefix . $key, + clone $this->cache->get($this->cachePrefix . $key) + ); + if ($stored === false) { + $this->destructIfNeeded($newCollection, 'Failed to copy cells in cache'); + } } return $newCollection; @@ -344,17 +309,23 @@ class Cells /** * Remove a row, deleting all cells in that row. * - * @param string $row Row number to remove + * @param int|string $row Row number to remove */ - public function removeRow($row) + public function removeRow($row): void { - foreach ($this->getCoordinates() as $coord) { - $c = ''; - $r = 0; + $this->storeCurrentCell(); + $row = (int) $row; + if ($row <= 0) { + throw new PhpSpreadsheetException('Row number must be a positive integer'); + } - sscanf($coord, '%[A-Z]%d', $c, $r); - if ($r == $row) { - $this->delete($coord); + $toRow = $row * self::MAX_COLUMN_ID; + $fromRow = --$row * self::MAX_COLUMN_ID; + foreach ($this->index as $coordinate) { + if ($coordinate >= $fromRow && $coordinate < $toRow) { + $row = (int) floor($coordinate / self::MAX_COLUMN_ID) + 1; + $column = Coordinate::stringFromColumnIndex($coordinate % self::MAX_COLUMN_ID); + $this->delete("{$column}{$row}"); } } } @@ -364,15 +335,16 @@ class Cells * * @param string $column Column ID to remove */ - public function removeColumn($column) + public function removeColumn($column): void { - foreach ($this->getCoordinates() as $coord) { - $c = ''; - $r = 0; + $this->storeCurrentCell(); - sscanf($coord, '%[A-Z]%d', $c, $r); - if ($c == $column) { - $this->delete($coord); + $columnIndex = Coordinate::columnIndexFromString($column); + foreach ($this->index as $coordinate) { + if ($coordinate % self::MAX_COLUMN_ID === $columnIndex) { + $row = (int) floor($coordinate / self::MAX_COLUMN_ID) + 1; + $column = Coordinate::stringFromColumnIndex($coordinate % self::MAX_COLUMN_ID); + $this->delete("{$column}{$row}"); } } } @@ -380,19 +352,15 @@ class Cells /** * Store cell data in cache for the current cell object if it's "dirty", * and the 'nullify' the current cell object. - * - * @throws PhpSpreadsheetException */ - private function storeCurrentCell() + private function storeCurrentCell(): void { - if ($this->currentCellIsDirty && !empty($this->currentCoordinate)) { - $this->currentCell->detach(); + if ($this->currentCellIsDirty && isset($this->currentCoordinate, $this->currentCell)) { + $this->currentCell->/** @scrutinizer ignore-call */ detach(); $stored = $this->cache->set($this->cachePrefix . $this->currentCoordinate, $this->currentCell); - if (!$stored) { - $this->__destruct(); - - throw new PhpSpreadsheetException("Failed to store cell {$this->currentCoordinate} in cache"); + if ($stored === false) { + $this->destructIfNeeded($this, "Failed to store cell {$this->currentCoordinate} in cache"); } $this->currentCellIsDirty = false; } @@ -401,24 +369,32 @@ class Cells $this->currentCell = null; } + private function destructIfNeeded(self $cells, string $message): void + { + $cells->__destruct(); + + throw new PhpSpreadsheetException($message); + } + /** * Add or update a cell identified by its coordinate into the collection. * - * @param string $pCoord Coordinate of the cell to update + * @param string $cellCoordinate Coordinate of the cell to update * @param Cell $cell Cell to update * - * @throws PhpSpreadsheetException - * - * @return \PhpOffice\PhpSpreadsheet\Cell\Cell + * @return Cell */ - public function add($pCoord, Cell $cell) + public function add($cellCoordinate, Cell $cell) { - if ($pCoord !== $this->currentCoordinate) { + if ($cellCoordinate !== $this->currentCoordinate) { $this->storeCurrentCell(); } - $this->index[$pCoord] = true; + $column = 0; + $row = ''; + sscanf($cellCoordinate, '%[A-Z]%d', $column, $row); + $this->index[$cellCoordinate] = (--$row * self::MAX_COLUMN_ID) + Coordinate::columnIndexFromString((string) $column); - $this->currentCoordinate = $pCoord; + $this->currentCoordinate = $cellCoordinate; $this->currentCell = $cell; $this->currentCellIsDirty = true; @@ -428,32 +404,30 @@ class Cells /** * Get cell at a specific coordinate. * - * @param string $pCoord Coordinate of the cell + * @param string $cellCoordinate Coordinate of the cell * - * @throws PhpSpreadsheetException - * - * @return \PhpOffice\PhpSpreadsheet\Cell\Cell Cell that was found, or null if not found + * @return null|Cell Cell that was found, or null if not found */ - public function get($pCoord) + public function get($cellCoordinate) { - if ($pCoord === $this->currentCoordinate) { + if ($cellCoordinate === $this->currentCoordinate) { return $this->currentCell; } $this->storeCurrentCell(); // Return null if requested entry doesn't exist in collection - if (!$this->has($pCoord)) { + if ($this->has($cellCoordinate) === false) { return null; } - // Check if the entry that has been requested actually exists - $cell = $this->cache->get($this->cachePrefix . $pCoord); + // Check if the entry that has been requested actually exists in the cache + $cell = $this->cache->get($this->cachePrefix . $cellCoordinate); if ($cell === null) { - throw new PhpSpreadsheetException("Cell entry {$pCoord} no longer exists in cache. This probably means that the cache was cleared by someone else."); + throw new PhpSpreadsheetException("Cell entry {$cellCoordinate} no longer exists in cache. This probably means that the cache was cleared by someone else."); } // Set current entry to the requested entry - $this->currentCoordinate = $pCoord; + $this->currentCoordinate = $cellCoordinate; $this->currentCell = $cell; // Re-attach this as the cell's parent $this->currentCell->attach($this); @@ -465,7 +439,7 @@ class Cells /** * Clear the cell collection and disconnect from our parent. */ - public function unsetWorksheetCells() + public function unsetWorksheetCells(): void { if ($this->currentCell !== null) { $this->currentCell->detach(); @@ -493,11 +467,11 @@ class Cells /** * Returns all known cache keys. * - * @return \Generator|string[] + * @return Generator|string[] */ private function getAllCacheKeys() { - foreach ($this->getCoordinates() as $coordinate) { + foreach ($this->index as $coordinate => $value) { yield $this->cachePrefix . $coordinate; } } diff --git a/PhpOffice/PhpSpreadsheet/Collection/CellsFactory.php b/PhpOffice/PhpSpreadsheet/Collection/CellsFactory.php old mode 100755 new mode 100644 index 46d3cf7..b3833bd --- a/PhpOffice/PhpSpreadsheet/Collection/CellsFactory.php +++ b/PhpOffice/PhpSpreadsheet/Collection/CellsFactory.php @@ -10,14 +10,11 @@ abstract class CellsFactory /** * Initialise the cache storage. * - * @param Worksheet $parent Enable cell caching for this worksheet + * @param Worksheet $worksheet Enable cell caching for this worksheet * - * @return Cells * */ - public static function getInstance(Worksheet $parent) + public static function getInstance(Worksheet $worksheet): Cells { - $instance = new Cells($parent, Settings::getCache()); - - return $instance; + return new Cells($worksheet, Settings::getCache()); } } diff --git a/PhpOffice/PhpSpreadsheet/Collection/Memory.php b/PhpOffice/PhpSpreadsheet/Collection/Memory/SimpleCache1.php old mode 100755 new mode 100644 similarity index 58% rename from PhpOffice/PhpSpreadsheet/Collection/Memory.php rename to PhpOffice/PhpSpreadsheet/Collection/Memory/SimpleCache1.php index fceafeb..a0eb6ec --- a/PhpOffice/PhpSpreadsheet/Collection/Memory.php +++ b/PhpOffice/PhpSpreadsheet/Collection/Memory/SimpleCache1.php @@ -1,6 +1,9 @@ cache = []; @@ -19,6 +28,11 @@ class Memory return true; } + /** + * @param string $key + * + * @return bool + */ public function delete($key) { unset($this->cache[$key]); @@ -26,6 +40,11 @@ class Memory return true; } + /** + * @param iterable $keys + * + * @return bool + */ public function deleteMultiple($keys) { foreach ($keys as $key) { @@ -35,6 +54,12 @@ class Memory return true; } + /** + * @param string $key + * @param mixed $default + * + * @return mixed + */ public function get($key, $default = null) { if ($this->has($key)) { @@ -44,6 +69,12 @@ class Memory return $default; } + /** + * @param iterable $keys + * @param mixed $default + * + * @return iterable + */ public function getMultiple($keys, $default = null) { $results = []; @@ -54,11 +85,23 @@ class Memory return $results; } + /** + * @param string $key + * + * @return bool + */ public function has($key) { return array_key_exists($key, $this->cache); } + /** + * @param string $key + * @param mixed $value + * @param null|DateInterval|int $ttl + * + * @return bool + */ public function set($key, $value, $ttl = null) { $this->cache[$key] = $value; @@ -66,6 +109,12 @@ class Memory return true; } + /** + * @param iterable $values + * @param null|DateInterval|int $ttl + * + * @return bool + */ public function setMultiple($values, $ttl = null) { foreach ($values as $key => $value) { diff --git a/PhpOffice/PhpSpreadsheet/Collection/Memory/SimpleCache3.php b/PhpOffice/PhpSpreadsheet/Collection/Memory/SimpleCache3.php new file mode 100644 index 0000000..34f6447 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Collection/Memory/SimpleCache3.php @@ -0,0 +1,109 @@ +cache = []; + + return true; + } + + /** + * @param string $key + */ + public function delete($key): bool + { + unset($this->cache[$key]); + + return true; + } + + /** + * @param iterable $keys + */ + public function deleteMultiple($keys): bool + { + foreach ($keys as $key) { + $this->delete($key); + } + + return true; + } + + /** + * @param string $key + * @param mixed $default + */ + public function get($key, $default = null): mixed + { + if ($this->has($key)) { + return $this->cache[$key]; + } + + return $default; + } + + /** + * @param iterable $keys + * @param mixed $default + */ + public function getMultiple($keys, $default = null): iterable + { + $results = []; + foreach ($keys as $key) { + $results[$key] = $this->get($key, $default); + } + + return $results; + } + + /** + * @param string $key + */ + public function has($key): bool + { + return array_key_exists($key, $this->cache); + } + + /** + * @param string $key + * @param mixed $value + * @param null|DateInterval|int $ttl + */ + public function set($key, $value, $ttl = null): bool + { + $this->cache[$key] = $value; + + return true; + } + + /** + * @param iterable $values + * @param null|DateInterval|int $ttl + */ + public function setMultiple($values, $ttl = null): bool + { + foreach ($values as $key => $value) { + $this->set($key, $value); + } + + return true; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Comment.php b/PhpOffice/PhpSpreadsheet/Comment.php old mode 100755 new mode 100644 index 1b5ab1f..abadc7d --- a/PhpOffice/PhpSpreadsheet/Comment.php +++ b/PhpOffice/PhpSpreadsheet/Comment.php @@ -2,7 +2,13 @@ namespace PhpOffice\PhpSpreadsheet; +use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; +use PhpOffice\PhpSpreadsheet\Helper\Size; use PhpOffice\PhpSpreadsheet\RichText\RichText; +use PhpOffice\PhpSpreadsheet\Shared\Drawing as SharedDrawing; +use PhpOffice\PhpSpreadsheet\Style\Alignment; +use PhpOffice\PhpSpreadsheet\Style\Color; +use PhpOffice\PhpSpreadsheet\Worksheet\Drawing; class Comment implements IComparable { @@ -58,7 +64,7 @@ class Comment implements IComparable /** * Comment fill color. * - * @var Style\Color + * @var Color */ private $fillColor; @@ -69,6 +75,13 @@ class Comment implements IComparable */ private $alignment; + /** + * Background image in comment. + * + * @var Drawing + */ + private $backgroundImage; + /** * Create a new Comment. */ @@ -77,28 +90,23 @@ class Comment implements IComparable // Initialise variables $this->author = 'Author'; $this->text = new RichText(); - $this->fillColor = new Style\Color('FFFFFFE1'); - $this->alignment = Style\Alignment::HORIZONTAL_GENERAL; + $this->fillColor = new Color('FFFFFFE1'); + $this->alignment = Alignment::HORIZONTAL_GENERAL; + $this->backgroundImage = new Drawing(); } /** * Get Author. - * - * @return string */ - public function getAuthor() + public function getAuthor(): string { return $this->author; } /** * Set Author. - * - * @param string $author - * - * @return Comment */ - public function setAuthor($author) + public function setAuthor(string $author): self { $this->author = $author; @@ -107,166 +115,146 @@ class Comment implements IComparable /** * Get Rich text comment. - * - * @return RichText */ - public function getText() + public function getText(): RichText { return $this->text; } /** * Set Rich text comment. - * - * @param RichText $pValue - * - * @return Comment */ - public function setText(RichText $pValue) + public function setText(RichText $text): self { - $this->text = $pValue; + $this->text = $text; return $this; } /** * Get comment width (CSS style, i.e. XXpx or YYpt). - * - * @return string */ - public function getWidth() + public function getWidth(): string { return $this->width; } /** - * Set comment width (CSS style, i.e. XXpx or YYpt). - * - * @param string $width - * - * @return Comment + * Set comment width (CSS style, i.e. XXpx or YYpt). Default unit is pt. */ - public function setWidth($width) + public function setWidth(string $width): self { - $this->width = $width; + $width = new Size($width); + if ($width->valid()) { + $this->width = (string) $width; + } return $this; } /** * Get comment height (CSS style, i.e. XXpx or YYpt). - * - * @return string */ - public function getHeight() + public function getHeight(): string { return $this->height; } /** - * Set comment height (CSS style, i.e. XXpx or YYpt). - * - * @param string $value - * - * @return Comment + * Set comment height (CSS style, i.e. XXpx or YYpt). Default unit is pt. */ - public function setHeight($value) + public function setHeight(string $height): self { - $this->height = $value; + $height = new Size($height); + if ($height->valid()) { + $this->height = (string) $height; + } return $this; } /** * Get left margin (CSS style, i.e. XXpx or YYpt). - * - * @return string */ - public function getMarginLeft() + public function getMarginLeft(): string { return $this->marginLeft; } /** - * Set left margin (CSS style, i.e. XXpx or YYpt). - * - * @param string $value - * - * @return Comment + * Set left margin (CSS style, i.e. XXpx or YYpt). Default unit is pt. */ - public function setMarginLeft($value) + public function setMarginLeft(string $margin): self { - $this->marginLeft = $value; + $margin = new Size($margin); + if ($margin->valid()) { + $this->marginLeft = (string) $margin; + } return $this; } /** * Get top margin (CSS style, i.e. XXpx or YYpt). - * - * @return string */ - public function getMarginTop() + public function getMarginTop(): string { return $this->marginTop; } /** - * Set top margin (CSS style, i.e. XXpx or YYpt). - * - * @param string $value - * - * @return Comment + * Set top margin (CSS style, i.e. XXpx or YYpt). Default unit is pt. */ - public function setMarginTop($value) + public function setMarginTop(string $margin): self { - $this->marginTop = $value; + $margin = new Size($margin); + if ($margin->valid()) { + $this->marginTop = (string) $margin; + } return $this; } /** * Is the comment visible by default? - * - * @return bool */ - public function getVisible() + public function getVisible(): bool { return $this->visible; } /** * Set comment default visibility. - * - * @param bool $value - * - * @return Comment */ - public function setVisible($value) + public function setVisible(bool $visibility): self { - $this->visible = $value; + $this->visible = $visibility; + + return $this; + } + + /** + * Set fill color. + */ + public function setFillColor(Color $color): self + { + $this->fillColor = $color; return $this; } /** * Get fill color. - * - * @return Style\Color */ - public function getFillColor() + public function getFillColor(): Color { return $this->fillColor; } /** * Set Alignment. - * - * @param string $alignment see Style\Alignment::HORIZONTAL_* - * - * @return Comment */ - public function setAlignment($alignment) + public function setAlignment(string $alignment): self { $this->alignment = $alignment; @@ -275,20 +263,16 @@ class Comment implements IComparable /** * Get Alignment. - * - * @return string */ - public function getAlignment() + public function getAlignment(): string { return $this->alignment; } /** * Get hash code. - * - * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { return md5( $this->author . @@ -300,6 +284,7 @@ class Comment implements IComparable ($this->visible ? 1 : 0) . $this->fillColor->getHashCode() . $this->alignment . + ($this->hasBackgroundImage() ? $this->backgroundImage->getHashCode() : '') . __CLASS__ ); } @@ -321,11 +306,57 @@ class Comment implements IComparable /** * Convert to string. - * - * @return string */ - public function __toString() + public function __toString(): string { return $this->text->getPlainText(); } + + /** + * Check is background image exists. + */ + public function hasBackgroundImage(): bool + { + $path = $this->backgroundImage->getPath(); + + if (empty($path)) { + return false; + } + + return getimagesize($path) !== false; + } + + /** + * Returns background image. + */ + public function getBackgroundImage(): Drawing + { + return $this->backgroundImage; + } + + /** + * Sets background image. + */ + public function setBackgroundImage(Drawing $objDrawing): self + { + if (!array_key_exists($objDrawing->getType(), Drawing::IMAGE_TYPES_CONVERTION_MAP)) { + throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.'); + } + $this->backgroundImage = $objDrawing; + + return $this; + } + + /** + * Sets size of comment as size of background image. + */ + public function setSizeAsBackgroundImage(): self + { + if ($this->hasBackgroundImage()) { + $this->setWidth(SharedDrawing::pixelsToPoints($this->backgroundImage->getWidth()) . 'pt'); + $this->setHeight(SharedDrawing::pixelsToPoints($this->backgroundImage->getHeight()) . 'pt'); + } + + return $this; + } } diff --git a/PhpOffice/PhpSpreadsheet/DefinedName.php b/PhpOffice/PhpSpreadsheet/DefinedName.php new file mode 100644 index 0000000..0f4df26 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/DefinedName.php @@ -0,0 +1,273 @@ +worksheet). + * + * @var bool + */ + protected $localOnly; + + /** + * Scope. + * + * @var Worksheet + */ + protected $scope; + + /** + * Whether this is a named range or a named formula. + * + * @var bool + */ + protected $isFormula; + + /** + * Create a new Defined Name. + */ + public function __construct( + string $name, + ?Worksheet $worksheet = null, + ?string $value = null, + bool $localOnly = false, + ?Worksheet $scope = null + ) { + if ($worksheet === null) { + $worksheet = $scope; + } + + // Set local members + $this->name = $name; + $this->worksheet = $worksheet; + $this->value = (string) $value; + $this->localOnly = $localOnly; + // If local only, then the scope will be set to worksheet unless a scope is explicitly set + $this->scope = ($localOnly === true) ? (($scope === null) ? $worksheet : $scope) : null; + // If the range string contains characters that aren't associated with the range definition (A-Z,1-9 + // for cell references, and $, or the range operators (colon comma or space), quotes and ! for + // worksheet names + // then this is treated as a named formula, and not a named range + $this->isFormula = self::testIfFormula($this->value); + } + + /** + * Create a new defined name, either a range or a formula. + */ + public static function createInstance( + string $name, + ?Worksheet $worksheet = null, + ?string $value = null, + bool $localOnly = false, + ?Worksheet $scope = null + ): self { + $value = (string) $value; + $isFormula = self::testIfFormula($value); + if ($isFormula) { + return new NamedFormula($name, $worksheet, $value, $localOnly, $scope); + } + + return new NamedRange($name, $worksheet, $value, $localOnly, $scope); + } + + public static function testIfFormula(string $value): bool + { + if (substr($value, 0, 1) === '=') { + $value = substr($value, 1); + } + + if (is_numeric($value)) { + return true; + } + + $segMatcher = false; + foreach (explode("'", $value) as $subVal) { + // Only test in alternate array entries (the non-quoted blocks) + $segMatcher = $segMatcher === false; + if ( + $segMatcher && + (preg_match('/' . self::REGEXP_IDENTIFY_FORMULA . '/miu', $subVal)) + ) { + return true; + } + } + + return false; + } + + /** + * Get name. + */ + public function getName(): string + { + return $this->name; + } + + /** + * Set name. + */ + public function setName(string $name): self + { + if (!empty($name)) { + // Old title + $oldTitle = $this->name; + + // Re-attach + if ($this->worksheet !== null) { + $this->worksheet->getParent()->removeNamedRange($this->name, $this->worksheet); + } + $this->name = $name; + + if ($this->worksheet !== null) { + $this->worksheet->getParent()->addNamedRange($this); + } + + // New title + $newTitle = $this->name; + ReferenceHelper::getInstance()->updateNamedFormulae($this->worksheet->getParent(), $oldTitle, $newTitle); + } + + return $this; + } + + /** + * Get worksheet. + */ + public function getWorksheet(): ?Worksheet + { + return $this->worksheet; + } + + /** + * Set worksheet. + */ + public function setWorksheet(?Worksheet $worksheet): self + { + $this->worksheet = $worksheet; + + return $this; + } + + /** + * Get range or formula value. + */ + public function getValue(): string + { + return $this->value; + } + + /** + * Set range or formula value. + */ + public function setValue(string $value): self + { + $this->value = $value; + + return $this; + } + + /** + * Get localOnly. + */ + public function getLocalOnly(): bool + { + return $this->localOnly; + } + + /** + * Set localOnly. + */ + public function setLocalOnly(bool $localScope): self + { + $this->localOnly = $localScope; + $this->scope = $localScope ? $this->worksheet : null; + + return $this; + } + + /** + * Get scope. + */ + public function getScope(): ?Worksheet + { + return $this->scope; + } + + /** + * Set scope. + */ + public function setScope(?Worksheet $worksheet): self + { + $this->scope = $worksheet; + $this->localOnly = $worksheet !== null; + + return $this; + } + + /** + * Identify whether this is a named range or a named formula. + */ + public function isFormula(): bool + { + return $this->isFormula; + } + + /** + * Resolve a named range to a regular cell range or formula. + */ + public static function resolveName(string $definedName, Worksheet $worksheet, string $sheetName = ''): ?self + { + if ($sheetName === '') { + $worksheet2 = $worksheet; + } else { + $worksheet2 = $worksheet->getParent()->getSheetByName($sheetName); + if ($worksheet2 === null) { + return null; + } + } + + return $worksheet->getParent()->getDefinedName($definedName, $worksheet2); + } + + /** + * Implement PHP __clone to create a deep clone, not just a shallow copy. + */ + public function __clone() + { + $vars = get_object_vars($this); + foreach ($vars as $key => $value) { + if (is_object($value)) { + $this->$key = clone $value; + } else { + $this->$key = $value; + } + } + } +} diff --git a/PhpOffice/PhpSpreadsheet/Document/Properties.php b/PhpOffice/PhpSpreadsheet/Document/Properties.php old mode 100755 new mode 100644 index 1a432db..afdeea9 --- a/PhpOffice/PhpSpreadsheet/Document/Properties.php +++ b/PhpOffice/PhpSpreadsheet/Document/Properties.php @@ -2,15 +2,26 @@ namespace PhpOffice\PhpSpreadsheet\Document; +use DateTime; +use PhpOffice\PhpSpreadsheet\Shared\IntOrFloat; + class Properties { /** constants */ - const PROPERTY_TYPE_BOOLEAN = 'b'; - const PROPERTY_TYPE_INTEGER = 'i'; - const PROPERTY_TYPE_FLOAT = 'f'; - const PROPERTY_TYPE_DATE = 'd'; - const PROPERTY_TYPE_STRING = 's'; - const PROPERTY_TYPE_UNKNOWN = 'u'; + public const PROPERTY_TYPE_BOOLEAN = 'b'; + public const PROPERTY_TYPE_INTEGER = 'i'; + public const PROPERTY_TYPE_FLOAT = 'f'; + public const PROPERTY_TYPE_DATE = 'd'; + public const PROPERTY_TYPE_STRING = 's'; + public const PROPERTY_TYPE_UNKNOWN = 'u'; + + private const VALID_PROPERTY_TYPE_LIST = [ + self::PROPERTY_TYPE_BOOLEAN, + self::PROPERTY_TYPE_INTEGER, + self::PROPERTY_TYPE_FLOAT, + self::PROPERTY_TYPE_DATE, + self::PROPERTY_TYPE_STRING, + ]; /** * Creator. @@ -29,14 +40,14 @@ class Properties /** * Created. * - * @var int + * @var float|int */ private $created; /** * Modified. * - * @var int + * @var float|int */ private $modified; @@ -87,12 +98,12 @@ class Properties * * @var string */ - private $company = 'Microsoft Corporation'; + private $company = ''; /** * Custom Properties. * - * @var string + * @var array{value: mixed, type: string}[] */ private $customProperties = []; @@ -103,16 +114,14 @@ class Properties { // Initialise values $this->lastModifiedBy = $this->creator; - $this->created = time(); - $this->modified = time(); + $this->created = self::intOrFloatTimestamp(null); + $this->modified = $this->created; } /** * Get Creator. - * - * @return string */ - public function getCreator() + public function getCreator(): string { return $this->creator; } @@ -120,11 +129,9 @@ class Properties /** * Set Creator. * - * @param string $creator - * - * @return Properties + * @return $this */ - public function setCreator($creator) + public function setCreator(string $creator): self { $this->creator = $creator; @@ -133,10 +140,8 @@ class Properties /** * Get Last Modified By. - * - * @return string */ - public function getLastModifiedBy() + public function getLastModifiedBy(): string { return $this->lastModifiedBy; } @@ -144,21 +149,42 @@ class Properties /** * Set Last Modified By. * - * @param string $pValue - * - * @return Properties + * @return $this */ - public function setLastModifiedBy($pValue) + public function setLastModifiedBy(string $modifiedBy): self { - $this->lastModifiedBy = $pValue; + $this->lastModifiedBy = $modifiedBy; return $this; } + /** + * @param null|float|int|string $timestamp + * + * @return float|int + */ + private static function intOrFloatTimestamp($timestamp) + { + if ($timestamp === null) { + $timestamp = (float) (new DateTime())->format('U'); + } elseif (is_string($timestamp)) { + if (is_numeric($timestamp)) { + $timestamp = (float) $timestamp; + } else { + $timestamp = (string) preg_replace('/[.][0-9]*$/', '', $timestamp); + $timestamp = (string) preg_replace('/^(\\d{4})- (\\d)/', '$1-0$2', $timestamp); + $timestamp = (string) preg_replace('/^(\\d{4}-\\d{2})- (\\d)/', '$1-0$2', $timestamp); + $timestamp = (float) (new DateTime($timestamp))->format('U'); + } + } + + return IntOrFloat::evaluate($timestamp); + } + /** * Get Created. * - * @return int + * @return float|int */ public function getCreated() { @@ -168,23 +194,13 @@ class Properties /** * Set Created. * - * @param int|string $time + * @param null|float|int|string $timestamp * - * @return Properties + * @return $this */ - public function setCreated($time) + public function setCreated($timestamp): self { - if ($time === null) { - $time = time(); - } elseif (is_string($time)) { - if (is_numeric($time)) { - $time = (int) $time; - } else { - $time = strtotime($time); - } - } - - $this->created = $time; + $this->created = self::intOrFloatTimestamp($timestamp); return $this; } @@ -192,7 +208,7 @@ class Properties /** * Get Modified. * - * @return int + * @return float|int */ public function getModified() { @@ -202,33 +218,21 @@ class Properties /** * Set Modified. * - * @param int|string $time + * @param null|float|int|string $timestamp * - * @return Properties + * @return $this */ - public function setModified($time) + public function setModified($timestamp): self { - if ($time === null) { - $time = time(); - } elseif (is_string($time)) { - if (is_numeric($time)) { - $time = (int) $time; - } else { - $time = strtotime($time); - } - } - - $this->modified = $time; + $this->modified = self::intOrFloatTimestamp($timestamp); return $this; } /** * Get Title. - * - * @return string */ - public function getTitle() + public function getTitle(): string { return $this->title; } @@ -236,11 +240,9 @@ class Properties /** * Set Title. * - * @param string $title - * - * @return Properties + * @return $this */ - public function setTitle($title) + public function setTitle(string $title): self { $this->title = $title; @@ -249,10 +251,8 @@ class Properties /** * Get Description. - * - * @return string */ - public function getDescription() + public function getDescription(): string { return $this->description; } @@ -260,11 +260,9 @@ class Properties /** * Set Description. * - * @param string $description - * - * @return Properties + * @return $this */ - public function setDescription($description) + public function setDescription(string $description): self { $this->description = $description; @@ -273,10 +271,8 @@ class Properties /** * Get Subject. - * - * @return string */ - public function getSubject() + public function getSubject(): string { return $this->subject; } @@ -284,11 +280,9 @@ class Properties /** * Set Subject. * - * @param string $subject - * - * @return Properties + * @return $this */ - public function setSubject($subject) + public function setSubject(string $subject): self { $this->subject = $subject; @@ -297,10 +291,8 @@ class Properties /** * Get Keywords. - * - * @return string */ - public function getKeywords() + public function getKeywords(): string { return $this->keywords; } @@ -308,11 +300,9 @@ class Properties /** * Set Keywords. * - * @param string $keywords - * - * @return Properties + * @return $this */ - public function setKeywords($keywords) + public function setKeywords(string $keywords): self { $this->keywords = $keywords; @@ -321,10 +311,8 @@ class Properties /** * Get Category. - * - * @return string */ - public function getCategory() + public function getCategory(): string { return $this->category; } @@ -332,11 +320,9 @@ class Properties /** * Set Category. * - * @param string $category - * - * @return Properties + * @return $this */ - public function setCategory($category) + public function setCategory(string $category): self { $this->category = $category; @@ -345,10 +331,8 @@ class Properties /** * Get Company. - * - * @return string */ - public function getCompany() + public function getCompany(): string { return $this->company; } @@ -356,11 +340,9 @@ class Properties /** * Set Company. * - * @param string $company - * - * @return Properties + * @return $this */ - public function setCompany($company) + public function setCompany(string $company): self { $this->company = $company; @@ -369,10 +351,8 @@ class Properties /** * Get Manager. - * - * @return string */ - public function getManager() + public function getManager(): string { return $this->manager; } @@ -380,11 +360,9 @@ class Properties /** * Set Manager. * - * @param string $manager - * - * @return Properties + * @return $this */ - public function setManager($manager) + public function setManager(string $manager): self { $this->manager = $manager; @@ -394,57 +372,66 @@ class Properties /** * Get a List of Custom Property Names. * - * @return array of string + * @return string[] */ - public function getCustomProperties() + public function getCustomProperties(): array { return array_keys($this->customProperties); } /** * Check if a Custom Property is defined. - * - * @param string $propertyName - * - * @return bool */ - public function isCustomPropertySet($propertyName) + public function isCustomPropertySet(string $propertyName): bool { - return isset($this->customProperties[$propertyName]); + return array_key_exists($propertyName, $this->customProperties); } /** * Get a Custom Property Value. * - * @param string $propertyName - * * @return mixed */ - public function getCustomPropertyValue($propertyName) + public function getCustomPropertyValue(string $propertyName) { if (isset($this->customProperties[$propertyName])) { return $this->customProperties[$propertyName]['value']; } + + return null; } /** * Get a Custom Property Type. * - * @param string $propertyName - * - * @return string + * @return null|string */ - public function getCustomPropertyType($propertyName) + public function getCustomPropertyType(string $propertyName) { - if (isset($this->customProperties[$propertyName])) { - return $this->customProperties[$propertyName]['type']; + return $this->customProperties[$propertyName]['type'] ?? null; + } + + /** + * @param mixed $propertyValue + */ + private function identifyPropertyType($propertyValue): string + { + if (is_float($propertyValue)) { + return self::PROPERTY_TYPE_FLOAT; } + if (is_int($propertyValue)) { + return self::PROPERTY_TYPE_INTEGER; + } + if (is_bool($propertyValue)) { + return self::PROPERTY_TYPE_BOOLEAN; + } + + return self::PROPERTY_TYPE_STRING; } /** * Set a Custom Property. * - * @param string $propertyName * @param mixed $propertyValue * @param string $propertyType * 'i' : Integer @@ -453,177 +440,98 @@ class Properties * 'd' : Date/Time * 'b' : Boolean * - * @return Properties + * @return $this */ - public function setCustomProperty($propertyName, $propertyValue = '', $propertyType = null) + public function setCustomProperty(string $propertyName, $propertyValue = '', $propertyType = null): self { - if (($propertyType === null) || (!in_array($propertyType, [self::PROPERTY_TYPE_INTEGER, - self::PROPERTY_TYPE_FLOAT, - self::PROPERTY_TYPE_STRING, - self::PROPERTY_TYPE_DATE, - self::PROPERTY_TYPE_BOOLEAN, ]))) { - if ($propertyValue === null) { - $propertyType = self::PROPERTY_TYPE_STRING; - } elseif (is_float($propertyValue)) { - $propertyType = self::PROPERTY_TYPE_FLOAT; - } elseif (is_int($propertyValue)) { - $propertyType = self::PROPERTY_TYPE_INTEGER; - } elseif (is_bool($propertyValue)) { - $propertyType = self::PROPERTY_TYPE_BOOLEAN; - } else { - $propertyType = self::PROPERTY_TYPE_STRING; - } + if (($propertyType === null) || (!in_array($propertyType, self::VALID_PROPERTY_TYPE_LIST))) { + $propertyType = $this->identifyPropertyType($propertyValue); } - $this->customProperties[$propertyName] = [ - 'value' => $propertyValue, - 'type' => $propertyType, - ]; + if (!is_object($propertyValue)) { + $this->customProperties[$propertyName] = [ + 'value' => self::convertProperty($propertyValue, $propertyType), + 'type' => $propertyType, + ]; + } return $this; } + private const PROPERTY_TYPE_ARRAY = [ + 'i' => self::PROPERTY_TYPE_INTEGER, // Integer + 'i1' => self::PROPERTY_TYPE_INTEGER, // 1-Byte Signed Integer + 'i2' => self::PROPERTY_TYPE_INTEGER, // 2-Byte Signed Integer + 'i4' => self::PROPERTY_TYPE_INTEGER, // 4-Byte Signed Integer + 'i8' => self::PROPERTY_TYPE_INTEGER, // 8-Byte Signed Integer + 'int' => self::PROPERTY_TYPE_INTEGER, // Integer + 'ui1' => self::PROPERTY_TYPE_INTEGER, // 1-Byte Unsigned Integer + 'ui2' => self::PROPERTY_TYPE_INTEGER, // 2-Byte Unsigned Integer + 'ui4' => self::PROPERTY_TYPE_INTEGER, // 4-Byte Unsigned Integer + 'ui8' => self::PROPERTY_TYPE_INTEGER, // 8-Byte Unsigned Integer + 'uint' => self::PROPERTY_TYPE_INTEGER, // Unsigned Integer + 'f' => self::PROPERTY_TYPE_FLOAT, // Real Number + 'r4' => self::PROPERTY_TYPE_FLOAT, // 4-Byte Real Number + 'r8' => self::PROPERTY_TYPE_FLOAT, // 8-Byte Real Number + 'decimal' => self::PROPERTY_TYPE_FLOAT, // Decimal + 's' => self::PROPERTY_TYPE_STRING, // String + 'empty' => self::PROPERTY_TYPE_STRING, // Empty + 'null' => self::PROPERTY_TYPE_STRING, // Null + 'lpstr' => self::PROPERTY_TYPE_STRING, // LPSTR + 'lpwstr' => self::PROPERTY_TYPE_STRING, // LPWSTR + 'bstr' => self::PROPERTY_TYPE_STRING, // Basic String + 'd' => self::PROPERTY_TYPE_DATE, // Date and Time + 'date' => self::PROPERTY_TYPE_DATE, // Date and Time + 'filetime' => self::PROPERTY_TYPE_DATE, // File Time + 'b' => self::PROPERTY_TYPE_BOOLEAN, // Boolean + 'bool' => self::PROPERTY_TYPE_BOOLEAN, // Boolean + ]; + + private const SPECIAL_TYPES = [ + 'empty' => '', + 'null' => null, + ]; + /** - * Implement PHP __clone to create a deep clone, not just a shallow copy. + * Convert property to form desired by Excel. + * + * @param mixed $propertyValue + * + * @return mixed */ - public function __clone() + public static function convertProperty($propertyValue, string $propertyType) { - $vars = get_object_vars($this); - foreach ($vars as $key => $value) { - if (is_object($value)) { - $this->$key = clone $value; - } else { - $this->$key = $value; - } - } + return self::SPECIAL_TYPES[$propertyType] ?? self::convertProperty2($propertyValue, $propertyType); } - public static function convertProperty($propertyValue, $propertyType) + /** + * Convert property to form desired by Excel. + * + * @param mixed $propertyValue + * + * @return mixed + */ + private static function convertProperty2($propertyValue, string $type) { + $propertyType = self::convertPropertyType($type); switch ($propertyType) { - case 'empty': // Empty - return ''; + case self::PROPERTY_TYPE_INTEGER: + $intValue = (int) $propertyValue; - break; - case 'null': // Null - return null; - - break; - case 'i1': // 1-Byte Signed Integer - case 'i2': // 2-Byte Signed Integer - case 'i4': // 4-Byte Signed Integer - case 'i8': // 8-Byte Signed Integer - case 'int': // Integer - return (int) $propertyValue; - - break; - case 'ui1': // 1-Byte Unsigned Integer - case 'ui2': // 2-Byte Unsigned Integer - case 'ui4': // 4-Byte Unsigned Integer - case 'ui8': // 8-Byte Unsigned Integer - case 'uint': // Unsigned Integer - return abs((int) $propertyValue); - - break; - case 'r4': // 4-Byte Real Number - case 'r8': // 8-Byte Real Number - case 'decimal': // Decimal + return ($type[0] === 'u') ? abs($intValue) : $intValue; + case self::PROPERTY_TYPE_FLOAT: return (float) $propertyValue; - - break; - case 'lpstr': // LPSTR - case 'lpwstr': // LPWSTR - case 'bstr': // Basic String + case self::PROPERTY_TYPE_DATE: + return self::intOrFloatTimestamp($propertyValue); + case self::PROPERTY_TYPE_BOOLEAN: + return is_bool($propertyValue) ? $propertyValue : ($propertyValue === 'true'); + default: // includes string return $propertyValue; - - break; - case 'date': // Date and Time - case 'filetime': // File Time - return strtotime($propertyValue); - - break; - case 'bool': // Boolean - return $propertyValue == 'true'; - - break; - case 'cy': // Currency - case 'error': // Error Status Code - case 'vector': // Vector - case 'array': // Array - case 'blob': // Binary Blob - case 'oblob': // Binary Blob Object - case 'stream': // Binary Stream - case 'ostream': // Binary Stream Object - case 'storage': // Binary Storage - case 'ostorage': // Binary Storage Object - case 'vstream': // Binary Versioned Stream - case 'clsid': // Class ID - case 'cf': // Clipboard Data - return $propertyValue; - - break; } - - return $propertyValue; } - public static function convertPropertyType($propertyType) + public static function convertPropertyType(string $propertyType): string { - switch ($propertyType) { - case 'i1': // 1-Byte Signed Integer - case 'i2': // 2-Byte Signed Integer - case 'i4': // 4-Byte Signed Integer - case 'i8': // 8-Byte Signed Integer - case 'int': // Integer - case 'ui1': // 1-Byte Unsigned Integer - case 'ui2': // 2-Byte Unsigned Integer - case 'ui4': // 4-Byte Unsigned Integer - case 'ui8': // 8-Byte Unsigned Integer - case 'uint': // Unsigned Integer - return self::PROPERTY_TYPE_INTEGER; - - break; - case 'r4': // 4-Byte Real Number - case 'r8': // 8-Byte Real Number - case 'decimal': // Decimal - return self::PROPERTY_TYPE_FLOAT; - - break; - case 'empty': // Empty - case 'null': // Null - case 'lpstr': // LPSTR - case 'lpwstr': // LPWSTR - case 'bstr': // Basic String - return self::PROPERTY_TYPE_STRING; - - break; - case 'date': // Date and Time - case 'filetime': // File Time - return self::PROPERTY_TYPE_DATE; - - break; - case 'bool': // Boolean - return self::PROPERTY_TYPE_BOOLEAN; - - break; - case 'cy': // Currency - case 'error': // Error Status Code - case 'vector': // Vector - case 'array': // Array - case 'blob': // Binary Blob - case 'oblob': // Binary Blob Object - case 'stream': // Binary Stream - case 'ostream': // Binary Stream Object - case 'storage': // Binary Storage - case 'ostorage': // Binary Storage Object - case 'vstream': // Binary Versioned Stream - case 'clsid': // Class ID - case 'cf': // Clipboard Data - return self::PROPERTY_TYPE_UNKNOWN; - - break; - } - - return self::PROPERTY_TYPE_UNKNOWN; + return self::PROPERTY_TYPE_ARRAY[$propertyType] ?? self::PROPERTY_TYPE_UNKNOWN; } } diff --git a/PhpOffice/PhpSpreadsheet/Document/Security.php b/PhpOffice/PhpSpreadsheet/Document/Security.php old mode 100755 new mode 100644 index 1682678..279aed4 --- a/PhpOffice/PhpSpreadsheet/Document/Security.php +++ b/PhpOffice/PhpSpreadsheet/Document/Security.php @@ -50,94 +50,57 @@ class Security /** * Is some sort of document security enabled? - * - * @return bool */ - public function isSecurityEnabled() + public function isSecurityEnabled(): bool { return $this->lockRevision || $this->lockStructure || $this->lockWindows; } - /** - * Get LockRevision. - * - * @return bool - */ - public function getLockRevision() + public function getLockRevision(): bool { return $this->lockRevision; } - /** - * Set LockRevision. - * - * @param bool $pValue - * - * @return Security - */ - public function setLockRevision($pValue) + public function setLockRevision(?bool $locked): self { - $this->lockRevision = $pValue; + if ($locked !== null) { + $this->lockRevision = $locked; + } return $this; } - /** - * Get LockStructure. - * - * @return bool - */ - public function getLockStructure() + public function getLockStructure(): bool { return $this->lockStructure; } - /** - * Set LockStructure. - * - * @param bool $pValue - * - * @return Security - */ - public function setLockStructure($pValue) + public function setLockStructure(?bool $locked): self { - $this->lockStructure = $pValue; + if ($locked !== null) { + $this->lockStructure = $locked; + } return $this; } - /** - * Get LockWindows. - * - * @return bool - */ - public function getLockWindows() + public function getLockWindows(): bool { return $this->lockWindows; } - /** - * Set LockWindows. - * - * @param bool $pValue - * - * @return Security - */ - public function setLockWindows($pValue) + public function setLockWindows(?bool $locked): self { - $this->lockWindows = $pValue; + if ($locked !== null) { + $this->lockWindows = $locked; + } return $this; } - /** - * Get RevisionsPassword (hashed). - * - * @return string - */ - public function getRevisionsPassword() + public function getRevisionsPassword(): string { return $this->revisionsPassword; } @@ -145,27 +108,24 @@ class Security /** * Set RevisionsPassword. * - * @param string $pValue - * @param bool $pAlreadyHashed If the password has already been hashed, set this to true + * @param string $password + * @param bool $alreadyHashed If the password has already been hashed, set this to true * - * @return Security + * @return $this */ - public function setRevisionsPassword($pValue, $pAlreadyHashed = false) + public function setRevisionsPassword(?string $password, bool $alreadyHashed = false) { - if (!$pAlreadyHashed) { - $pValue = PasswordHasher::hashPassword($pValue); + if ($password !== null) { + if (!$alreadyHashed) { + $password = PasswordHasher::hashPassword($password); + } + $this->revisionsPassword = $password; } - $this->revisionsPassword = $pValue; return $this; } - /** - * Get WorkbookPassword (hashed). - * - * @return string - */ - public function getWorkbookPassword() + public function getWorkbookPassword(): string { return $this->workbookPassword; } @@ -173,33 +133,20 @@ class Security /** * Set WorkbookPassword. * - * @param string $pValue - * @param bool $pAlreadyHashed If the password has already been hashed, set this to true + * @param string $password + * @param bool $alreadyHashed If the password has already been hashed, set this to true * - * @return Security + * @return $this */ - public function setWorkbookPassword($pValue, $pAlreadyHashed = false) + public function setWorkbookPassword(?string $password, bool $alreadyHashed = false) { - if (!$pAlreadyHashed) { - $pValue = PasswordHasher::hashPassword($pValue); + if ($password !== null) { + if (!$alreadyHashed) { + $password = PasswordHasher::hashPassword($password); + } + $this->workbookPassword = $password; } - $this->workbookPassword = $pValue; return $this; } - - /** - * Implement PHP __clone to create a deep clone, not just a shallow copy. - */ - public function __clone() - { - $vars = get_object_vars($this); - foreach ($vars as $key => $value) { - if (is_object($value)) { - $this->$key = clone $value; - } else { - $this->$key = $value; - } - } - } } diff --git a/PhpOffice/PhpSpreadsheet/Exception.php b/PhpOffice/PhpSpreadsheet/Exception.php old mode 100755 new mode 100644 diff --git a/PhpOffice/PhpSpreadsheet/HashTable.php b/PhpOffice/PhpSpreadsheet/HashTable.php old mode 100755 new mode 100644 index 4e8f0a5..6de7d03 --- a/PhpOffice/PhpSpreadsheet/HashTable.php +++ b/PhpOffice/PhpSpreadsheet/HashTable.php @@ -2,52 +2,51 @@ namespace PhpOffice\PhpSpreadsheet; +/** + * @template T of IComparable + */ class HashTable { /** * HashTable elements. * - * @var IComparable[] + * @var array */ protected $items = []; /** * HashTable key map. * - * @var string[] + * @var array */ protected $keyMap = []; /** - * Create a new \PhpOffice\PhpSpreadsheet\HashTable. + * Create a new HashTable. * - * @param IComparable[] $pSource Optional source array to create HashTable from - * - * @throws Exception + * @param T[] $source Optional source array to create HashTable from */ - public function __construct($pSource = null) + public function __construct($source = null) { - if ($pSource !== null) { + if ($source !== null) { // Create HashTable - $this->addFromSource($pSource); + $this->addFromSource($source); } } /** * Add HashTable items from source. * - * @param IComparable[] $pSource Source array to create HashTable from - * - * @throws Exception + * @param T[] $source Source array to create HashTable from */ - public function addFromSource(array $pSource = null) + public function addFromSource(?array $source = null): void { // Check if an array was passed - if ($pSource == null) { + if ($source === null) { return; } - foreach ($pSource as $item) { + foreach ($source as $item) { $this->add($item); } } @@ -55,13 +54,13 @@ class HashTable /** * Add HashTable item. * - * @param IComparable $pSource Item to add + * @param T $source Item to add */ - public function add(IComparable $pSource) + public function add(IComparable $source): void { - $hash = $pSource->getHashCode(); + $hash = $source->getHashCode(); if (!isset($this->items[$hash])) { - $this->items[$hash] = $pSource; + $this->items[$hash] = $source; $this->keyMap[count($this->items) - 1] = $hash; } } @@ -69,11 +68,11 @@ class HashTable /** * Remove HashTable item. * - * @param IComparable $pSource Item to remove + * @param T $source Item to remove */ - public function remove(IComparable $pSource) + public function remove(IComparable $source): void { - $hash = $pSource->getHashCode(); + $hash = $source->getHashCode(); if (isset($this->items[$hash])) { unset($this->items[$hash]); @@ -94,7 +93,7 @@ class HashTable /** * Clear HashTable. */ - public function clear() + public function clear(): void { $this->items = []; $this->keyMap = []; @@ -113,26 +112,23 @@ class HashTable /** * Get index for hash code. * - * @param string $pHashCode - * - * @return int Index + * @return false|int Index */ - public function getIndexForHashCode($pHashCode) + public function getIndexForHashCode(string $hashCode) { - return array_search($pHashCode, $this->keyMap); + // Scrutinizer thinks the following could return string. It is wrong. + return array_search($hashCode, $this->keyMap, true); } /** * Get by index. * - * @param int $pIndex - * - * @return IComparable + * @return null|T */ - public function getByIndex($pIndex) + public function getByIndex(int $index) { - if (isset($this->keyMap[$pIndex])) { - return $this->getByHashCode($this->keyMap[$pIndex]); + if (isset($this->keyMap[$index])) { + return $this->getByHashCode($this->keyMap[$index]); } return null; @@ -141,14 +137,12 @@ class HashTable /** * Get by hashcode. * - * @param string $pHashCode - * - * @return IComparable + * @return null|T */ - public function getByHashCode($pHashCode) + public function getByHashCode(string $hashCode) { - if (isset($this->items[$pHashCode])) { - return $this->items[$pHashCode]; + if (isset($this->items[$hashCode])) { + return $this->items[$hashCode]; } return null; @@ -157,7 +151,7 @@ class HashTable /** * HashTable to array. * - * @return IComparable[] + * @return T[] */ public function toArray() { @@ -171,8 +165,15 @@ class HashTable { $vars = get_object_vars($this); foreach ($vars as $key => $value) { - if (is_object($value)) { - $this->$key = clone $value; + // each member of this class is an array + if (is_array($value)) { + $array1 = $value; + foreach ($array1 as $key1 => $value1) { + if (is_object($value1)) { + $array1[$key1] = clone $value1; + } + } + $this->$key = $array1; } } } diff --git a/PhpOffice/PhpSpreadsheet/Helper/Dimension.php b/PhpOffice/PhpSpreadsheet/Helper/Dimension.php new file mode 100644 index 0000000..ff07ce5 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Helper/Dimension.php @@ -0,0 +1,115 @@ + 96.0 / 2.54, + self::UOM_MILLIMETERS => 96.0 / 25.4, + self::UOM_INCHES => 96.0, + self::UOM_PIXELS => 1.0, + self::UOM_POINTS => 96.0 / 72, + self::UOM_PICA => 96.0 * 12 / 72, + ]; + + /** + * Based on a standard column width of 8.54 units in MS Excel. + */ + const RELATIVE_UNITS = [ + 'em' => 10.0 / 8.54, + 'ex' => 10.0 / 8.54, + 'ch' => 10.0 / 8.54, + 'rem' => 10.0 / 8.54, + 'vw' => 8.54, + 'vh' => 8.54, + 'vmin' => 8.54, + 'vmax' => 8.54, + '%' => 8.54 / 100, + ]; + + /** + * @var float|int If this is a width, then size is measured in pixels (if is set) + * or in Excel's default column width units if $unit is null. + * If this is a height, then size is measured in pixels () + * or in points () if $unit is null. + */ + protected $size; + + /** + * @var null|string + */ + protected $unit; + + /** + * Phpstan bug has been fixed; this function allows us to + * pass Phpstan whether fixed or not. + * + * @param mixed $value + */ + private static function stanBugFixed($value): array + { + return is_array($value) ? $value : [null, null]; + } + + public function __construct(string $dimension) + { + [$size, $unit] = self::stanBugFixed(sscanf($dimension, '%[1234567890.]%s')); + $unit = strtolower(trim($unit ?? '')); + $size = (float) $size; + + // If a UoM is specified, then convert the size to pixels for internal storage + if (isset(self::ABSOLUTE_UNITS[$unit])) { + $size *= self::ABSOLUTE_UNITS[$unit]; + $this->unit = self::UOM_PIXELS; + } elseif (isset(self::RELATIVE_UNITS[$unit])) { + $size *= self::RELATIVE_UNITS[$unit]; + $size = round($size, 4); + } + + $this->size = $size; + } + + public function width(): float + { + return (float) ($this->unit === null) + ? $this->size + : round(Drawing::pixelsToCellDimension((int) $this->size, new Font(false)), 4); + } + + public function height(): float + { + return (float) ($this->unit === null) + ? $this->size + : $this->toUnit(self::UOM_POINTS); + } + + public function toUnit(string $unitOfMeasure): float + { + $unitOfMeasure = strtolower($unitOfMeasure); + if (!array_key_exists($unitOfMeasure, self::ABSOLUTE_UNITS)) { + throw new Exception("{$unitOfMeasure} is not a vaid unit of measure"); + } + + $size = $this->size; + if ($this->unit === null) { + $size = Drawing::cellDimensionToPixels($size, new Font(false)); + } + + return $size / self::ABSOLUTE_UNITS[$unitOfMeasure]; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Helper/Html.php b/PhpOffice/PhpSpreadsheet/Helper/Html.php old mode 100755 new mode 100644 index eaf7302..b8ed386 --- a/PhpOffice/PhpSpreadsheet/Helper/Html.php +++ b/PhpOffice/PhpSpreadsheet/Helper/Html.php @@ -12,7 +12,7 @@ use PhpOffice\PhpSpreadsheet\Style\Font; class Html { - protected static $colourMap = [ + private const COLOUR_MAP = [ 'aliceblue' => 'f0f8ff', 'antiquewhite' => 'faebd7', 'antiquewhite1' => 'ffefdb', @@ -532,25 +532,34 @@ class Html 'yellowgreen' => '9acd32', ]; - protected $face; + /** @var ?string */ + private $face; - protected $size; + /** @var ?string */ + private $size; - protected $color; + /** @var ?string */ + private $color; - protected $bold = false; + /** @var bool */ + private $bold = false; - protected $italic = false; + /** @var bool */ + private $italic = false; - protected $underline = false; + /** @var bool */ + private $underline = false; - protected $superscript = false; + /** @var bool */ + private $superscript = false; - protected $subscript = false; + /** @var bool */ + private $subscript = false; - protected $strikethrough = false; + /** @var bool */ + private $strikethrough = false; - protected $startTagCallbacks = [ + private const START_TAG_CALLBACKS = [ 'font' => 'startFontTag', 'b' => 'startBoldTag', 'strong' => 'startBoldTag', @@ -563,7 +572,7 @@ class Html 'sub' => 'startSubscriptTag', ]; - protected $endTagCallbacks = [ + private const END_TAG_CALLBACKS = [ 'font' => 'endFontTag', 'b' => 'endBoldTag', 'strong' => 'endBoldTag', @@ -584,16 +593,18 @@ class Html 'h6' => 'breakTag', ]; - protected $stack = []; + /** @var array */ + private $stack = []; - protected $stringData = ''; + /** @var string */ + private $stringData = ''; /** * @var RichText */ - protected $richTextObject; + private $richTextObject; - protected function initialise() + private function initialise(): void { $this->face = $this->size = $this->color = null; $this->bold = $this->italic = $this->underline = $this->superscript = $this->subscript = $this->strikethrough = false; @@ -619,6 +630,7 @@ class Html // Load the HTML file into the DOM object // Note the use of error suppression, because typically this will be an html fragment, so not fully valid markup $prefix = ''; + /** @scrutinizer ignore-unhandled */ @$dom->loadHTML($prefix . $html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); // Discard excess white space $dom->preserveWhiteSpace = false; @@ -632,7 +644,7 @@ class Html return $this->richTextObject; } - protected function cleanWhitespace() + private function cleanWhitespace(): void { foreach ($this->richTextObject->getRichTextElements() as $key => $element) { $text = $element->getText(); @@ -641,12 +653,12 @@ class Html $text = ltrim($text); } // Trim any spaces immediately after a line break - $text = preg_replace('/\n */mu', "\n", $text); + $text = (string) preg_replace('/\n */mu', "\n", $text); $element->setText($text); } } - protected function buildTextRun() + private function buildTextRun(): void { $text = $this->stringData; if (trim($text) === '') { @@ -654,181 +666,186 @@ class Html } $richtextRun = $this->richTextObject->createTextRun($this->stringData); - if ($this->face) { - $richtextRun->getFont()->setName($this->face); - } - if ($this->size) { - $richtextRun->getFont()->setSize($this->size); - } - if ($this->color) { - $richtextRun->getFont()->setColor(new Color('ff' . $this->color)); - } - if ($this->bold) { - $richtextRun->getFont()->setBold(true); - } - if ($this->italic) { - $richtextRun->getFont()->setItalic(true); - } - if ($this->underline) { - $richtextRun->getFont()->setUnderline(Font::UNDERLINE_SINGLE); - } - if ($this->superscript) { - $richtextRun->getFont()->setSuperscript(true); - } - if ($this->subscript) { - $richtextRun->getFont()->setSubscript(true); - } - if ($this->strikethrough) { - $richtextRun->getFont()->setStrikethrough(true); + $font = $richtextRun->getFont(); + if ($font !== null) { + if ($this->face) { + $font->setName($this->face); + } + if ($this->size) { + $font->setSize($this->size); + } + if ($this->color) { + $font->setColor(new Color('ff' . $this->color)); + } + if ($this->bold) { + $font->setBold(true); + } + if ($this->italic) { + $font->setItalic(true); + } + if ($this->underline) { + $font->setUnderline(Font::UNDERLINE_SINGLE); + } + if ($this->superscript) { + $font->setSuperscript(true); + } + if ($this->subscript) { + $font->setSubscript(true); + } + if ($this->strikethrough) { + $font->setStrikethrough(true); + } } $this->stringData = ''; } - protected function rgbToColour($rgb) + private function rgbToColour(string $rgbValue): string { - preg_match_all('/\d+/', $rgb, $values); + preg_match_all('/\d+/', $rgbValue, $values); foreach ($values[0] as &$value) { $value = str_pad(dechex($value), 2, '0', STR_PAD_LEFT); } - return implode($values[0]); + return implode('', $values[0]); } - protected function colourNameLookup($rgb) + public static function colourNameLookup(string $colorName): string { - return self::$colourMap[$rgb]; + return self::COLOUR_MAP[$colorName] ?? ''; } - protected function startFontTag($tag) + private function startFontTag(DOMElement $tag): void { - foreach ($tag->attributes as $attribute) { - $attributeName = strtolower($attribute->name); - $attributeValue = $attribute->value; + $attrs = $tag->attributes; + if ($attrs !== null) { + foreach ($attrs as $attribute) { + $attributeName = strtolower($attribute->name); + $attributeValue = $attribute->value; - if ($attributeName == 'color') { - if (preg_match('/rgb\s*\(/', $attributeValue)) { - $this->$attributeName = $this->rgbToColour($attributeValue); - } elseif (strpos(trim($attributeValue), '#') === 0) { - $this->$attributeName = ltrim($attributeValue, '#'); + if ($attributeName == 'color') { + if (preg_match('/rgb\s*\(/', $attributeValue)) { + $this->$attributeName = $this->rgbToColour($attributeValue); + } elseif (strpos(trim($attributeValue), '#') === 0) { + $this->$attributeName = ltrim($attributeValue, '#'); + } else { + $this->$attributeName = static::colourNameLookup($attributeValue); + } } else { - $this->$attributeName = $this->colourNameLookup($attributeValue); + $this->$attributeName = $attributeValue; } - } else { - $this->$attributeName = $attributeValue; } } } - protected function endFontTag() + private function endFontTag(): void { $this->face = $this->size = $this->color = null; } - protected function startBoldTag() + private function startBoldTag(): void { $this->bold = true; } - protected function endBoldTag() + private function endBoldTag(): void { $this->bold = false; } - protected function startItalicTag() + private function startItalicTag(): void { $this->italic = true; } - protected function endItalicTag() + private function endItalicTag(): void { $this->italic = false; } - protected function startUnderlineTag() + private function startUnderlineTag(): void { $this->underline = true; } - protected function endUnderlineTag() + private function endUnderlineTag(): void { $this->underline = false; } - protected function startSubscriptTag() + private function startSubscriptTag(): void { $this->subscript = true; } - protected function endSubscriptTag() + private function endSubscriptTag(): void { $this->subscript = false; } - protected function startSuperscriptTag() + private function startSuperscriptTag(): void { $this->superscript = true; } - protected function endSuperscriptTag() + private function endSuperscriptTag(): void { $this->superscript = false; } - protected function startStrikethruTag() + private function startStrikethruTag(): void { $this->strikethrough = true; } - protected function endStrikethruTag() + private function endStrikethruTag(): void { $this->strikethrough = false; } - protected function breakTag() + private function breakTag(): void { $this->stringData .= "\n"; } - protected function parseTextNode(DOMText $textNode) + private function parseTextNode(DOMText $textNode): void { - $domText = preg_replace( + $domText = (string) preg_replace( '/\s+/u', ' ', - str_replace(["\r", "\n"], ' ', $textNode->nodeValue) + str_replace(["\r", "\n"], ' ', $textNode->nodeValue ?? '') ); $this->stringData .= $domText; $this->buildTextRun(); } /** - * @param DOMElement $element * @param string $callbackTag - * @param array $callbacks */ - protected function handleCallback(DOMElement $element, $callbackTag, array $callbacks) + private function handleCallback(DOMElement $element, $callbackTag, array $callbacks): void { if (isset($callbacks[$callbackTag])) { $elementHandler = $callbacks[$callbackTag]; if (method_exists($this, $elementHandler)) { + /** @phpstan-ignore-next-line */ call_user_func([$this, $elementHandler], $element); } } } - protected function parseElementNode(DOMElement $element) + private function parseElementNode(DOMElement $element): void { $callbackTag = strtolower($element->nodeName); $this->stack[] = $callbackTag; - $this->handleCallback($element, $callbackTag, $this->startTagCallbacks); + $this->handleCallback($element, $callbackTag, self::START_TAG_CALLBACKS); $this->parseElements($element); array_pop($this->stack); - $this->handleCallback($element, $callbackTag, $this->endTagCallbacks); + $this->handleCallback($element, $callbackTag, self::END_TAG_CALLBACKS); } - protected function parseElements(DOMNode $element) + private function parseElements(DOMNode $element): void { foreach ($element->childNodes as $child) { if ($child instanceof DOMText) { diff --git a/PhpOffice/PhpSpreadsheet/Helper/Migrator.php b/PhpOffice/PhpSpreadsheet/Helper/Migrator.php deleted file mode 100755 index 26d5fce..0000000 --- a/PhpOffice/PhpSpreadsheet/Helper/Migrator.php +++ /dev/null @@ -1,333 +0,0 @@ -from = array_keys($this->getMapping()); - $this->to = array_values($this->getMapping()); - } - - /** - * Return the ordered mapping from old PHPExcel class names to new PhpSpreadsheet one. - * - * @return string[] - */ - public function getMapping() - { - // Order matters here, we should have the deepest namespaces first (the most "unique" strings) - $classes = [ - 'PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE_Blip' => \PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE\Blip::class, - 'PHPExcel_Shared_Escher_DgContainer_SpgrContainer_SpContainer' => \PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer\SpContainer::class, - 'PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE' => \PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE::class, - 'PHPExcel_Shared_Escher_DgContainer_SpgrContainer' => \PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer::class, - 'PHPExcel_Shared_Escher_DggContainer_BstoreContainer' => \PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer::class, - 'PHPExcel_Shared_OLE_PPS_File' => \PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\File::class, - 'PHPExcel_Shared_OLE_PPS_Root' => \PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\Root::class, - 'PHPExcel_Worksheet_AutoFilter_Column_Rule' => \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule::class, - 'PHPExcel_Writer_OpenDocument_Cell_Comment' => \PhpOffice\PhpSpreadsheet\Writer\Ods\Cell\Comment::class, - 'PHPExcel_Calculation_Token_Stack' => \PhpOffice\PhpSpreadsheet\Calculation\Token\Stack::class, - 'PHPExcel_Chart_Renderer_jpgraph' => \PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraph::class, - 'PHPExcel_Reader_Excel5_Escher' => \PhpOffice\PhpSpreadsheet\Reader\Xls\Escher::class, - 'PHPExcel_Reader_Excel5_MD5' => \PhpOffice\PhpSpreadsheet\Reader\Xls\MD5::class, - 'PHPExcel_Reader_Excel5_RC4' => \PhpOffice\PhpSpreadsheet\Reader\Xls\RC4::class, - 'PHPExcel_Reader_Excel2007_Chart' => \PhpOffice\PhpSpreadsheet\Reader\Xlsx\Chart::class, - 'PHPExcel_Reader_Excel2007_Theme' => \PhpOffice\PhpSpreadsheet\Reader\Xlsx\Theme::class, - 'PHPExcel_Shared_Escher_DgContainer' => \PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer::class, - 'PHPExcel_Shared_Escher_DggContainer' => \PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer::class, - 'CholeskyDecomposition' => \PhpOffice\PhpSpreadsheet\Shared\JAMA\CholeskyDecomposition::class, - 'EigenvalueDecomposition' => \PhpOffice\PhpSpreadsheet\Shared\JAMA\EigenvalueDecomposition::class, - 'PHPExcel_Shared_JAMA_LUDecomposition' => \PhpOffice\PhpSpreadsheet\Shared\JAMA\LUDecomposition::class, - 'PHPExcel_Shared_JAMA_Matrix' => \PhpOffice\PhpSpreadsheet\Shared\JAMA\Matrix::class, - 'QRDecomposition' => \PhpOffice\PhpSpreadsheet\Shared\JAMA\QRDecomposition::class, - 'PHPExcel_Shared_JAMA_QRDecomposition' => \PhpOffice\PhpSpreadsheet\Shared\JAMA\QRDecomposition::class, - 'SingularValueDecomposition' => \PhpOffice\PhpSpreadsheet\Shared\JAMA\SingularValueDecomposition::class, - 'PHPExcel_Shared_OLE_ChainedBlockStream' => \PhpOffice\PhpSpreadsheet\Shared\OLE\ChainedBlockStream::class, - 'PHPExcel_Shared_OLE_PPS' => \PhpOffice\PhpSpreadsheet\Shared\OLE\PPS::class, - 'PHPExcel_Best_Fit' => \PhpOffice\PhpSpreadsheet\Shared\Trend\BestFit::class, - 'PHPExcel_Exponential_Best_Fit' => \PhpOffice\PhpSpreadsheet\Shared\Trend\ExponentialBestFit::class, - 'PHPExcel_Linear_Best_Fit' => \PhpOffice\PhpSpreadsheet\Shared\Trend\LinearBestFit::class, - 'PHPExcel_Logarithmic_Best_Fit' => \PhpOffice\PhpSpreadsheet\Shared\Trend\LogarithmicBestFit::class, - 'polynomialBestFit' => \PhpOffice\PhpSpreadsheet\Shared\Trend\PolynomialBestFit::class, - 'PHPExcel_Polynomial_Best_Fit' => \PhpOffice\PhpSpreadsheet\Shared\Trend\PolynomialBestFit::class, - 'powerBestFit' => \PhpOffice\PhpSpreadsheet\Shared\Trend\PowerBestFit::class, - 'PHPExcel_Power_Best_Fit' => \PhpOffice\PhpSpreadsheet\Shared\Trend\PowerBestFit::class, - 'trendClass' => \PhpOffice\PhpSpreadsheet\Shared\Trend\Trend::class, - 'PHPExcel_Worksheet_AutoFilter_Column' => \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column::class, - 'PHPExcel_Worksheet_Drawing_Shadow' => \PhpOffice\PhpSpreadsheet\Worksheet\Drawing\Shadow::class, - 'PHPExcel_Writer_OpenDocument_Content' => \PhpOffice\PhpSpreadsheet\Writer\Ods\Content::class, - 'PHPExcel_Writer_OpenDocument_Meta' => \PhpOffice\PhpSpreadsheet\Writer\Ods\Meta::class, - 'PHPExcel_Writer_OpenDocument_MetaInf' => \PhpOffice\PhpSpreadsheet\Writer\Ods\MetaInf::class, - 'PHPExcel_Writer_OpenDocument_Mimetype' => \PhpOffice\PhpSpreadsheet\Writer\Ods\Mimetype::class, - 'PHPExcel_Writer_OpenDocument_Settings' => \PhpOffice\PhpSpreadsheet\Writer\Ods\Settings::class, - 'PHPExcel_Writer_OpenDocument_Styles' => \PhpOffice\PhpSpreadsheet\Writer\Ods\Styles::class, - 'PHPExcel_Writer_OpenDocument_Thumbnails' => \PhpOffice\PhpSpreadsheet\Writer\Ods\Thumbnails::class, - 'PHPExcel_Writer_OpenDocument_WriterPart' => \PhpOffice\PhpSpreadsheet\Writer\Ods\WriterPart::class, - 'PHPExcel_Writer_PDF_Core' => \PhpOffice\PhpSpreadsheet\Writer\Pdf::class, - 'PHPExcel_Writer_PDF_DomPDF' => \PhpOffice\PhpSpreadsheet\Writer\Pdf\Dompdf::class, - 'PHPExcel_Writer_PDF_mPDF' => \PhpOffice\PhpSpreadsheet\Writer\Pdf\Mpdf::class, - 'PHPExcel_Writer_PDF_tcPDF' => \PhpOffice\PhpSpreadsheet\Writer\Pdf\Tcpdf::class, - 'PHPExcel_Writer_Excel5_BIFFwriter' => \PhpOffice\PhpSpreadsheet\Writer\Xls\BIFFwriter::class, - 'PHPExcel_Writer_Excel5_Escher' => \PhpOffice\PhpSpreadsheet\Writer\Xls\Escher::class, - 'PHPExcel_Writer_Excel5_Font' => \PhpOffice\PhpSpreadsheet\Writer\Xls\Font::class, - 'PHPExcel_Writer_Excel5_Parser' => \PhpOffice\PhpSpreadsheet\Writer\Xls\Parser::class, - 'PHPExcel_Writer_Excel5_Workbook' => \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook::class, - 'PHPExcel_Writer_Excel5_Worksheet' => \PhpOffice\PhpSpreadsheet\Writer\Xls\Worksheet::class, - 'PHPExcel_Writer_Excel5_Xf' => \PhpOffice\PhpSpreadsheet\Writer\Xls\Xf::class, - 'PHPExcel_Writer_Excel2007_Chart' => \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Chart::class, - 'PHPExcel_Writer_Excel2007_Comments' => \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Comments::class, - 'PHPExcel_Writer_Excel2007_ContentTypes' => \PhpOffice\PhpSpreadsheet\Writer\Xlsx\ContentTypes::class, - 'PHPExcel_Writer_Excel2007_DocProps' => \PhpOffice\PhpSpreadsheet\Writer\Xlsx\DocProps::class, - 'PHPExcel_Writer_Excel2007_Drawing' => \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Drawing::class, - 'PHPExcel_Writer_Excel2007_Rels' => \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Rels::class, - 'PHPExcel_Writer_Excel2007_RelsRibbon' => \PhpOffice\PhpSpreadsheet\Writer\Xlsx\RelsRibbon::class, - 'PHPExcel_Writer_Excel2007_RelsVBA' => \PhpOffice\PhpSpreadsheet\Writer\Xlsx\RelsVBA::class, - 'PHPExcel_Writer_Excel2007_StringTable' => \PhpOffice\PhpSpreadsheet\Writer\Xlsx\StringTable::class, - 'PHPExcel_Writer_Excel2007_Style' => \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Style::class, - 'PHPExcel_Writer_Excel2007_Theme' => \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Theme::class, - 'PHPExcel_Writer_Excel2007_Workbook' => \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Workbook::class, - 'PHPExcel_Writer_Excel2007_Worksheet' => \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Worksheet::class, - 'PHPExcel_Writer_Excel2007_WriterPart' => \PhpOffice\PhpSpreadsheet\Writer\Xlsx\WriterPart::class, - 'PHPExcel_CachedObjectStorage_CacheBase' => \PhpOffice\PhpSpreadsheet\Collection\Cells::class, - 'PHPExcel_CalcEngine_CyclicReferenceStack' => \PhpOffice\PhpSpreadsheet\Calculation\Engine\CyclicReferenceStack::class, - 'PHPExcel_CalcEngine_Logger' => \PhpOffice\PhpSpreadsheet\Calculation\Engine\Logger::class, - 'PHPExcel_Calculation_Functions' => \PhpOffice\PhpSpreadsheet\Calculation\Functions::class, - 'PHPExcel_Calculation_Function' => \PhpOffice\PhpSpreadsheet\Calculation\Category::class, - 'PHPExcel_Calculation_Database' => \PhpOffice\PhpSpreadsheet\Calculation\Database::class, - 'PHPExcel_Calculation_DateTime' => \PhpOffice\PhpSpreadsheet\Calculation\DateTime::class, - 'PHPExcel_Calculation_Engineering' => \PhpOffice\PhpSpreadsheet\Calculation\Engineering::class, - 'PHPExcel_Calculation_Exception' => \PhpOffice\PhpSpreadsheet\Calculation\Exception::class, - 'PHPExcel_Calculation_ExceptionHandler' => \PhpOffice\PhpSpreadsheet\Calculation\ExceptionHandler::class, - 'PHPExcel_Calculation_Financial' => \PhpOffice\PhpSpreadsheet\Calculation\Financial::class, - 'PHPExcel_Calculation_FormulaParser' => \PhpOffice\PhpSpreadsheet\Calculation\FormulaParser::class, - 'PHPExcel_Calculation_FormulaToken' => \PhpOffice\PhpSpreadsheet\Calculation\FormulaToken::class, - 'PHPExcel_Calculation_Logical' => \PhpOffice\PhpSpreadsheet\Calculation\Logical::class, - 'PHPExcel_Calculation_LookupRef' => \PhpOffice\PhpSpreadsheet\Calculation\LookupRef::class, - 'PHPExcel_Calculation_MathTrig' => \PhpOffice\PhpSpreadsheet\Calculation\MathTrig::class, - 'PHPExcel_Calculation_Statistical' => \PhpOffice\PhpSpreadsheet\Calculation\Statistical::class, - 'PHPExcel_Calculation_TextData' => \PhpOffice\PhpSpreadsheet\Calculation\TextData::class, - 'PHPExcel_Cell_AdvancedValueBinder' => \PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder::class, - 'PHPExcel_Cell_DataType' => \PhpOffice\PhpSpreadsheet\Cell\DataType::class, - 'PHPExcel_Cell_DataValidation' => \PhpOffice\PhpSpreadsheet\Cell\DataValidation::class, - 'PHPExcel_Cell_DefaultValueBinder' => \PhpOffice\PhpSpreadsheet\Cell\DefaultValueBinder::class, - 'PHPExcel_Cell_Hyperlink' => \PhpOffice\PhpSpreadsheet\Cell\Hyperlink::class, - 'PHPExcel_Cell_IValueBinder' => \PhpOffice\PhpSpreadsheet\Cell\IValueBinder::class, - 'PHPExcel_Chart_Axis' => \PhpOffice\PhpSpreadsheet\Chart\Axis::class, - 'PHPExcel_Chart_DataSeries' => \PhpOffice\PhpSpreadsheet\Chart\DataSeries::class, - 'PHPExcel_Chart_DataSeriesValues' => \PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues::class, - 'PHPExcel_Chart_Exception' => \PhpOffice\PhpSpreadsheet\Chart\Exception::class, - 'PHPExcel_Chart_GridLines' => \PhpOffice\PhpSpreadsheet\Chart\GridLines::class, - 'PHPExcel_Chart_Layout' => \PhpOffice\PhpSpreadsheet\Chart\Layout::class, - 'PHPExcel_Chart_Legend' => \PhpOffice\PhpSpreadsheet\Chart\Legend::class, - 'PHPExcel_Chart_PlotArea' => \PhpOffice\PhpSpreadsheet\Chart\PlotArea::class, - 'PHPExcel_Properties' => \PhpOffice\PhpSpreadsheet\Chart\Properties::class, - 'PHPExcel_Chart_Title' => \PhpOffice\PhpSpreadsheet\Chart\Title::class, - 'PHPExcel_DocumentProperties' => \PhpOffice\PhpSpreadsheet\Document\Properties::class, - 'PHPExcel_DocumentSecurity' => \PhpOffice\PhpSpreadsheet\Document\Security::class, - 'PHPExcel_Helper_HTML' => \PhpOffice\PhpSpreadsheet\Helper\Html::class, - 'PHPExcel_Reader_Abstract' => \PhpOffice\PhpSpreadsheet\Reader\BaseReader::class, - 'PHPExcel_Reader_CSV' => \PhpOffice\PhpSpreadsheet\Reader\Csv::class, - 'PHPExcel_Reader_DefaultReadFilter' => \PhpOffice\PhpSpreadsheet\Reader\DefaultReadFilter::class, - 'PHPExcel_Reader_Excel2003XML' => \PhpOffice\PhpSpreadsheet\Reader\Xml::class, - 'PHPExcel_Reader_Exception' => \PhpOffice\PhpSpreadsheet\Reader\Exception::class, - 'PHPExcel_Reader_Gnumeric' => \PhpOffice\PhpSpreadsheet\Reader\Gnumeric::class, - 'PHPExcel_Reader_HTML' => \PhpOffice\PhpSpreadsheet\Reader\Html::class, - 'PHPExcel_Reader_IReadFilter' => \PhpOffice\PhpSpreadsheet\Reader\IReadFilter::class, - 'PHPExcel_Reader_IReader' => \PhpOffice\PhpSpreadsheet\Reader\IReader::class, - 'PHPExcel_Reader_OOCalc' => \PhpOffice\PhpSpreadsheet\Reader\Ods::class, - 'PHPExcel_Reader_SYLK' => \PhpOffice\PhpSpreadsheet\Reader\Slk::class, - 'PHPExcel_Reader_Excel5' => \PhpOffice\PhpSpreadsheet\Reader\Xls::class, - 'PHPExcel_Reader_Excel2007' => \PhpOffice\PhpSpreadsheet\Reader\Xlsx::class, - 'PHPExcel_RichText_ITextElement' => \PhpOffice\PhpSpreadsheet\RichText\ITextElement::class, - 'PHPExcel_RichText_Run' => \PhpOffice\PhpSpreadsheet\RichText\Run::class, - 'PHPExcel_RichText_TextElement' => \PhpOffice\PhpSpreadsheet\RichText\TextElement::class, - 'PHPExcel_Shared_CodePage' => \PhpOffice\PhpSpreadsheet\Shared\CodePage::class, - 'PHPExcel_Shared_Date' => \PhpOffice\PhpSpreadsheet\Shared\Date::class, - 'PHPExcel_Shared_Drawing' => \PhpOffice\PhpSpreadsheet\Shared\Drawing::class, - 'PHPExcel_Shared_Escher' => \PhpOffice\PhpSpreadsheet\Shared\Escher::class, - 'PHPExcel_Shared_File' => \PhpOffice\PhpSpreadsheet\Shared\File::class, - 'PHPExcel_Shared_Font' => \PhpOffice\PhpSpreadsheet\Shared\Font::class, - 'PHPExcel_Shared_OLE' => \PhpOffice\PhpSpreadsheet\Shared\OLE::class, - 'PHPExcel_Shared_OLERead' => \PhpOffice\PhpSpreadsheet\Shared\OLERead::class, - 'PHPExcel_Shared_PasswordHasher' => \PhpOffice\PhpSpreadsheet\Shared\PasswordHasher::class, - 'PHPExcel_Shared_String' => \PhpOffice\PhpSpreadsheet\Shared\StringHelper::class, - 'PHPExcel_Shared_TimeZone' => \PhpOffice\PhpSpreadsheet\Shared\TimeZone::class, - 'PHPExcel_Shared_XMLWriter' => \PhpOffice\PhpSpreadsheet\Shared\XMLWriter::class, - 'PHPExcel_Shared_Excel5' => \PhpOffice\PhpSpreadsheet\Shared\Xls::class, - 'PHPExcel_Style_Alignment' => \PhpOffice\PhpSpreadsheet\Style\Alignment::class, - 'PHPExcel_Style_Border' => \PhpOffice\PhpSpreadsheet\Style\Border::class, - 'PHPExcel_Style_Borders' => \PhpOffice\PhpSpreadsheet\Style\Borders::class, - 'PHPExcel_Style_Color' => \PhpOffice\PhpSpreadsheet\Style\Color::class, - 'PHPExcel_Style_Conditional' => \PhpOffice\PhpSpreadsheet\Style\Conditional::class, - 'PHPExcel_Style_Fill' => \PhpOffice\PhpSpreadsheet\Style\Fill::class, - 'PHPExcel_Style_Font' => \PhpOffice\PhpSpreadsheet\Style\Font::class, - 'PHPExcel_Style_NumberFormat' => \PhpOffice\PhpSpreadsheet\Style\NumberFormat::class, - 'PHPExcel_Style_Protection' => \PhpOffice\PhpSpreadsheet\Style\Protection::class, - 'PHPExcel_Style_Supervisor' => \PhpOffice\PhpSpreadsheet\Style\Supervisor::class, - 'PHPExcel_Worksheet_AutoFilter' => \PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter::class, - 'PHPExcel_Worksheet_BaseDrawing' => \PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing::class, - 'PHPExcel_Worksheet_CellIterator' => \PhpOffice\PhpSpreadsheet\Worksheet\CellIterator::class, - 'PHPExcel_Worksheet_Column' => \PhpOffice\PhpSpreadsheet\Worksheet\Column::class, - 'PHPExcel_Worksheet_ColumnCellIterator' => \PhpOffice\PhpSpreadsheet\Worksheet\ColumnCellIterator::class, - 'PHPExcel_Worksheet_ColumnDimension' => \PhpOffice\PhpSpreadsheet\Worksheet\ColumnDimension::class, - 'PHPExcel_Worksheet_ColumnIterator' => \PhpOffice\PhpSpreadsheet\Worksheet\ColumnIterator::class, - 'PHPExcel_Worksheet_Drawing' => \PhpOffice\PhpSpreadsheet\Worksheet\Drawing::class, - 'PHPExcel_Worksheet_HeaderFooter' => \PhpOffice\PhpSpreadsheet\Worksheet\HeaderFooter::class, - 'PHPExcel_Worksheet_HeaderFooterDrawing' => \PhpOffice\PhpSpreadsheet\Worksheet\HeaderFooterDrawing::class, - 'PHPExcel_WorksheetIterator' => \PhpOffice\PhpSpreadsheet\Worksheet\Iterator::class, - 'PHPExcel_Worksheet_MemoryDrawing' => \PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing::class, - 'PHPExcel_Worksheet_PageMargins' => \PhpOffice\PhpSpreadsheet\Worksheet\PageMargins::class, - 'PHPExcel_Worksheet_PageSetup' => \PhpOffice\PhpSpreadsheet\Worksheet\PageSetup::class, - 'PHPExcel_Worksheet_Protection' => \PhpOffice\PhpSpreadsheet\Worksheet\Protection::class, - 'PHPExcel_Worksheet_Row' => \PhpOffice\PhpSpreadsheet\Worksheet\Row::class, - 'PHPExcel_Worksheet_RowCellIterator' => \PhpOffice\PhpSpreadsheet\Worksheet\RowCellIterator::class, - 'PHPExcel_Worksheet_RowDimension' => \PhpOffice\PhpSpreadsheet\Worksheet\RowDimension::class, - 'PHPExcel_Worksheet_RowIterator' => \PhpOffice\PhpSpreadsheet\Worksheet\RowIterator::class, - 'PHPExcel_Worksheet_SheetView' => \PhpOffice\PhpSpreadsheet\Worksheet\SheetView::class, - 'PHPExcel_Writer_Abstract' => \PhpOffice\PhpSpreadsheet\Writer\BaseWriter::class, - 'PHPExcel_Writer_CSV' => \PhpOffice\PhpSpreadsheet\Writer\Csv::class, - 'PHPExcel_Writer_Exception' => \PhpOffice\PhpSpreadsheet\Writer\Exception::class, - 'PHPExcel_Writer_HTML' => \PhpOffice\PhpSpreadsheet\Writer\Html::class, - 'PHPExcel_Writer_IWriter' => \PhpOffice\PhpSpreadsheet\Writer\IWriter::class, - 'PHPExcel_Writer_OpenDocument' => \PhpOffice\PhpSpreadsheet\Writer\Ods::class, - 'PHPExcel_Writer_PDF' => \PhpOffice\PhpSpreadsheet\Writer\Pdf::class, - 'PHPExcel_Writer_Excel5' => \PhpOffice\PhpSpreadsheet\Writer\Xls::class, - 'PHPExcel_Writer_Excel2007' => \PhpOffice\PhpSpreadsheet\Writer\Xlsx::class, - 'PHPExcel_CachedObjectStorageFactory' => \PhpOffice\PhpSpreadsheet\Collection\CellsFactory::class, - 'PHPExcel_Calculation' => \PhpOffice\PhpSpreadsheet\Calculation\Calculation::class, - 'PHPExcel_Cell' => \PhpOffice\PhpSpreadsheet\Cell\Cell::class, - 'PHPExcel_Chart' => \PhpOffice\PhpSpreadsheet\Chart\Chart::class, - 'PHPExcel_Comment' => \PhpOffice\PhpSpreadsheet\Comment::class, - 'PHPExcel_Exception' => \PhpOffice\PhpSpreadsheet\Exception::class, - 'PHPExcel_HashTable' => \PhpOffice\PhpSpreadsheet\HashTable::class, - 'PHPExcel_IComparable' => \PhpOffice\PhpSpreadsheet\IComparable::class, - 'PHPExcel_IOFactory' => \PhpOffice\PhpSpreadsheet\IOFactory::class, - 'PHPExcel_NamedRange' => \PhpOffice\PhpSpreadsheet\NamedRange::class, - 'PHPExcel_ReferenceHelper' => \PhpOffice\PhpSpreadsheet\ReferenceHelper::class, - 'PHPExcel_RichText' => \PhpOffice\PhpSpreadsheet\RichText\RichText::class, - 'PHPExcel_Settings' => \PhpOffice\PhpSpreadsheet\Settings::class, - 'PHPExcel_Style' => \PhpOffice\PhpSpreadsheet\Style\Style::class, - 'PHPExcel_Worksheet' => \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::class, - ]; - - $methods = [ - 'MINUTEOFHOUR' => 'MINUTE', - 'SECONDOFMINUTE' => 'SECOND', - 'DAYOFWEEK' => 'WEEKDAY', - 'WEEKOFYEAR' => 'WEEKNUM', - 'ExcelToPHPObject' => 'excelToDateTimeObject', - 'ExcelToPHP' => 'excelToTimestamp', - 'FormattedPHPToExcel' => 'formattedPHPToExcel', - 'Cell::absoluteCoordinate' => 'Coordinate::absoluteCoordinate', - 'Cell::absoluteReference' => 'Coordinate::absoluteReference', - 'Cell::buildRange' => 'Coordinate::buildRange', - 'Cell::columnIndexFromString' => 'Coordinate::columnIndexFromString', - 'Cell::coordinateFromString' => 'Coordinate::coordinateFromString', - 'Cell::extractAllCellReferencesInRange' => 'Coordinate::extractAllCellReferencesInRange', - 'Cell::getRangeBoundaries' => 'Coordinate::getRangeBoundaries', - 'Cell::mergeRangesInCollection' => 'Coordinate::mergeRangesInCollection', - 'Cell::rangeBoundaries' => 'Coordinate::rangeBoundaries', - 'Cell::rangeDimension' => 'Coordinate::rangeDimension', - 'Cell::splitRange' => 'Coordinate::splitRange', - 'Cell::stringFromColumnIndex' => 'Coordinate::stringFromColumnIndex', - ]; - - // Keep '\' prefix for class names - $prefixedClasses = []; - foreach ($classes as $key => &$value) { - $value = str_replace('PhpOffice\\', '\\PhpOffice\\', $value); - $prefixedClasses['\\' . $key] = $value; - } - $mapping = $prefixedClasses + $classes + $methods; - - return $mapping; - } - - /** - * Search in all files in given directory. - * - * @param string $path - */ - private function recursiveReplace($path) - { - $patterns = [ - '/*.md', - '/*.txt', - '/*.TXT', - '/*.php', - '/*.phpt', - '/*.php3', - '/*.php4', - '/*.php5', - '/*.phtml', - ]; - - foreach ($patterns as $pattern) { - foreach (glob($path . $pattern) as $file) { - if (strpos($path, '/vendor/') !== false) { - echo $file . " skipped\n"; - - continue; - } - $original = file_get_contents($file); - $converted = $this->replace($original); - - if ($original !== $converted) { - echo $file . " converted\n"; - file_put_contents($file, $converted); - } - } - } - - // Do the recursion in subdirectory - foreach (glob($path . '/*', GLOB_ONLYDIR) as $subpath) { - if (strpos($subpath, $path . '/') === 0) { - $this->recursiveReplace($subpath); - } - } - } - - public function migrate() - { - $path = realpath(getcwd()); - echo 'This will search and replace recursively in ' . $path . PHP_EOL; - echo 'You MUST backup your files first, or you risk losing data.' . PHP_EOL; - echo 'Are you sure ? (y/n)'; - - $confirm = fread(STDIN, 1); - if ($confirm === 'y') { - $this->recursiveReplace($path); - } - } - - /** - * Migrate the given code from PHPExcel to PhpSpreadsheet. - * - * @param string $original - * - * @return string - */ - public function replace($original) - { - $converted = str_replace($this->from, $this->to, $original); - - // The string "PHPExcel" gets special treatment because of how common it might be. - // This regex requires a word boundary around the string, and it can't be - // preceded by $ or -> (goal is to filter out cases where a variable is named $PHPExcel or similar) - $converted = preg_replace('~(?)(\b|\\\\)PHPExcel\b~', '\\' . \PhpOffice\PhpSpreadsheet\Spreadsheet::class, $converted); - - return $converted; - } -} diff --git a/PhpOffice/PhpSpreadsheet/Helper/Sample.php b/PhpOffice/PhpSpreadsheet/Helper/Sample.php old mode 100755 new mode 100644 index e199c80..5ca546e --- a/PhpOffice/PhpSpreadsheet/Helper/Sample.php +++ b/PhpOffice/PhpSpreadsheet/Helper/Sample.php @@ -4,13 +4,14 @@ namespace PhpOffice\PhpSpreadsheet\Helper; use PhpOffice\PhpSpreadsheet\IOFactory; use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use PhpOffice\PhpSpreadsheet\Writer\IWriter; -use PhpOffice\PhpSpreadsheet\Writer\Pdf; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use RecursiveRegexIterator; use ReflectionClass; use RegexIterator; +use RuntimeException; /** * Helper class to be used in sample code. @@ -70,12 +71,17 @@ class Sample /** * Returns an array of all known samples. * - * @return string[] [$name => $path] + * @return string[][] [$name => $path] */ public function getSamples() { // Populate samples $baseDir = realpath(__DIR__ . '/../../../samples'); + if ($baseDir === false) { + // @codeCoverageIgnoreStart + throw new RuntimeException('realpath returned false'); + // @codeCoverageIgnoreEnd + } $directory = new RecursiveDirectoryIterator($baseDir); $iterator = new RecursiveIteratorIterator($directory); $regex = new RegexIterator($iterator, '/^.+\.php$/', RecursiveRegexIterator::GET_MATCH); @@ -83,9 +89,14 @@ class Sample $files = []; foreach ($regex as $file) { $file = str_replace(str_replace('\\', '/', $baseDir) . '/', '', str_replace('\\', '/', $file[0])); + if (is_array($file)) { + // @codeCoverageIgnoreStart + throw new RuntimeException('str_replace returned array'); + // @codeCoverageIgnoreEnd + } $info = pathinfo($file); - $category = str_replace('_', ' ', $info['dirname']); - $name = str_replace('_', ' ', preg_replace('/(|\.php)/', '', $info['filename'])); + $category = str_replace('_', ' ', $info['dirname'] ?? ''); + $name = str_replace('_', ' ', (string) preg_replace('/(|\.php)/', '', $info['filename'])); if (!in_array($category, ['.', 'boostrap', 'templates'])) { if (!isset($files[$category])) { $files[$category] = []; @@ -106,11 +117,10 @@ class Sample /** * Write documents. * - * @param Spreadsheet $spreadsheet * @param string $filename * @param string[] $writers */ - public function write(Spreadsheet $spreadsheet, $filename, array $writers = ['Xlsx', 'Xls']) + public function write(Spreadsheet $spreadsheet, $filename, array $writers = ['Xlsx', 'Xls']): void { // Set active sheet index to the first sheet, so Excel opens this as the first sheet $spreadsheet->setActiveSheetIndex(0); @@ -119,19 +129,19 @@ class Sample foreach ($writers as $writerType) { $path = $this->getFilename($filename, mb_strtolower($writerType)); $writer = IOFactory::createWriter($spreadsheet, $writerType); - if ($writer instanceof Pdf) { - // PDF writer needs temporary directory - $tempDir = $this->getTemporaryFolder(); - $writer->setTempDir($tempDir); - } $callStartTime = microtime(true); $writer->save($path); - $this->logWrite($writer, $path, $callStartTime); + $this->logWrite($writer, $path, /** @scrutinizer ignore-type */ $callStartTime); } $this->logEndingNotes(); } + protected function isDirOrMkdir(string $folder): bool + { + return \is_dir($folder) || \mkdir($folder); + } + /** * Returns the temporary directory and make sure it exists. * @@ -140,10 +150,8 @@ class Sample private function getTemporaryFolder() { $tempFolder = sys_get_temp_dir() . '/phpspreadsheet'; - if (!is_dir($tempFolder)) { - if (!mkdir($tempFolder) && !is_dir($tempFolder)) { - throw new \RuntimeException(sprintf('Directory "%s" was not created', $tempFolder)); - } + if (!$this->isDirOrMkdir($tempFolder)) { + throw new RuntimeException(sprintf('Directory "%s" was not created', $tempFolder)); } return $tempFolder; @@ -161,7 +169,7 @@ class Sample { $originalExtension = pathinfo($filename, PATHINFO_EXTENSION); - return $this->getTemporaryFolder() . '/' . str_replace('.' . $originalExtension, '.' . $extension, basename($filename)); + return $this->getTemporaryFolder() . '/' . str_replace('.' . /** @scrutinizer ignore-type */ $originalExtension, '.' . $extension, basename($filename)); } /** @@ -174,21 +182,53 @@ class Sample public function getTemporaryFilename($extension = 'xlsx') { $temporaryFilename = tempnam($this->getTemporaryFolder(), 'phpspreadsheet-'); + if ($temporaryFilename === false) { + // @codeCoverageIgnoreStart + throw new RuntimeException('tempnam returned false'); + // @codeCoverageIgnoreEnd + } unlink($temporaryFilename); return $temporaryFilename . '.' . $extension; } - public function log($message) + public function log(string $message): void { $eol = $this->isCli() ? PHP_EOL : '
'; echo date('H:i:s ') . $message . $eol; } + public function titles(string $category, string $functionName, ?string $description = null): void + { + $this->log(sprintf('%s Functions:', $category)); + $description === null + ? $this->log(sprintf('Function: %s()', rtrim($functionName, '()'))) + : $this->log(sprintf('Function: %s() - %s.', rtrim($functionName, '()'), rtrim($description, '.'))); + } + + public function displayGrid(array $matrix): void + { + $renderer = new TextGrid($matrix, $this->isCli()); + echo $renderer->render(); + } + + public function logCalculationResult( + Worksheet $worksheet, + string $functionName, + string $formulaCell, + ?string $descriptionCell = null + ): void { + if ($descriptionCell !== null) { + $this->log($worksheet->getCell($descriptionCell)->getValue()); + } + $this->log($worksheet->getCell($formulaCell)->getValue()); + $this->log(sprintf('%s() Result is ', $functionName) . $worksheet->getCell($formulaCell)->getCalculatedValue()); + } + /** * Log ending notes. */ - public function logEndingNotes() + public function logEndingNotes(): void { // Do not show execution time for index $this->log('Peak memory usage: ' . (memory_get_peak_usage(true) / 1024 / 1024) . 'MB'); @@ -197,11 +237,10 @@ class Sample /** * Log a line about the write operation. * - * @param IWriter $writer * @param string $path * @param float $callStartTime */ - public function logWrite(IWriter $writer, $path, $callStartTime) + public function logWrite(IWriter $writer, $path, $callStartTime): void { $callEndTime = microtime(true); $callTime = $callEndTime - $callStartTime; @@ -219,7 +258,7 @@ class Sample * @param string $path * @param float $callStartTime */ - public function logRead($format, $path, $callStartTime) + public function logRead($format, $path, $callStartTime): void { $callEndTime = microtime(true); $callTime = $callEndTime - $callStartTime; diff --git a/PhpOffice/PhpSpreadsheet/Helper/Size.php b/PhpOffice/PhpSpreadsheet/Helper/Size.php new file mode 100644 index 0000000..12ba4ef --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Helper/Size.php @@ -0,0 +1,52 @@ +\d*\.?\d+)(?Ppt|px|em)?$/i'; + + /** + * @var bool + */ + protected $valid; + + /** + * @var string + */ + protected $size = ''; + + /** + * @var string + */ + protected $unit = ''; + + public function __construct(string $size) + { + $this->valid = (bool) preg_match(self::REGEXP_SIZE_VALIDATION, $size, $matches); + if ($this->valid) { + $this->size = $matches['size']; + $this->unit = $matches['unit'] ?? 'pt'; + } + } + + public function valid(): bool + { + return $this->valid; + } + + public function size(): string + { + return $this->size; + } + + public function unit(): string + { + return $this->unit; + } + + public function __toString() + { + return $this->size . $this->unit; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Helper/TextGrid.php b/PhpOffice/PhpSpreadsheet/Helper/TextGrid.php new file mode 100644 index 0000000..ed146a5 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Helper/TextGrid.php @@ -0,0 +1,139 @@ +rows = array_keys($matrix); + $this->columns = array_keys($matrix[$this->rows[0]]); + + $matrix = array_values($matrix); + array_walk( + $matrix, + function (&$row): void { + $row = array_values($row); + } + ); + + $this->matrix = $matrix; + $this->isCli = $isCli; + } + + public function render(): string + { + $this->gridDisplay = $this->isCli ? '' : ''; + + $maxRow = max($this->rows); + $maxRowLength = strlen((string) $maxRow) + 1; + $columnWidths = $this->getColumnWidths(); + + $this->renderColumnHeader($maxRowLength, $columnWidths); + $this->renderRows($maxRowLength, $columnWidths); + $this->renderFooter($maxRowLength, $columnWidths); + + $this->gridDisplay .= $this->isCli ? '' : ''; + + return $this->gridDisplay; + } + + private function renderRows(int $maxRowLength, array $columnWidths): void + { + foreach ($this->matrix as $row => $rowData) { + $this->gridDisplay .= '|' . str_pad((string) $this->rows[$row], $maxRowLength, ' ', STR_PAD_LEFT) . ' '; + $this->renderCells($rowData, $columnWidths); + $this->gridDisplay .= '|' . PHP_EOL; + } + } + + private function renderCells(array $rowData, array $columnWidths): void + { + foreach ($rowData as $column => $cell) { + $cell = ($this->isCli) ? (string) $cell : htmlentities((string) $cell); + $this->gridDisplay .= '| '; + $this->gridDisplay .= str_pad($cell, $columnWidths[$column] + 1, ' '); + } + } + + private function renderColumnHeader(int $maxRowLength, array $columnWidths): void + { + $this->gridDisplay .= str_repeat(' ', $maxRowLength + 2); + foreach ($this->columns as $column => $reference) { + $this->gridDisplay .= '+-' . str_repeat('-', $columnWidths[$column] + 1); + } + $this->gridDisplay .= '+' . PHP_EOL; + + $this->gridDisplay .= str_repeat(' ', $maxRowLength + 2); + foreach ($this->columns as $column => $reference) { + $this->gridDisplay .= '| ' . str_pad((string) $reference, $columnWidths[$column] + 1, ' '); + } + $this->gridDisplay .= '|' . PHP_EOL; + + $this->renderFooter($maxRowLength, $columnWidths); + } + + private function renderFooter(int $maxRowLength, array $columnWidths): void + { + $this->gridDisplay .= '+' . str_repeat('-', $maxRowLength + 1); + foreach ($this->columns as $column => $reference) { + $this->gridDisplay .= '+-'; + $this->gridDisplay .= str_pad((string) '', $columnWidths[$column] + 1, '-'); + } + $this->gridDisplay .= '+' . PHP_EOL; + } + + private function getColumnWidths(): array + { + $columnCount = count($this->matrix, COUNT_RECURSIVE) / count($this->matrix); + $columnWidths = []; + for ($column = 0; $column < $columnCount; ++$column) { + $columnWidths[] = $this->getColumnWidth(array_column($this->matrix, $column)); + } + + return $columnWidths; + } + + private function getColumnWidth(array $columnData): int + { + $columnWidth = 0; + $columnData = array_values($columnData); + + foreach ($columnData as $columnValue) { + if (is_string($columnValue)) { + $columnWidth = max($columnWidth, strlen($columnValue)); + } elseif (is_bool($columnValue)) { + $columnWidth = max($columnWidth, strlen($columnValue ? 'TRUE' : 'FALSE')); + } + + $columnWidth = max($columnWidth, strlen((string) $columnWidth)); + } + + return $columnWidth; + } +} diff --git a/PhpOffice/PhpSpreadsheet/IComparable.php b/PhpOffice/PhpSpreadsheet/IComparable.php old mode 100755 new mode 100644 diff --git a/PhpOffice/PhpSpreadsheet/IOFactory.php b/PhpOffice/PhpSpreadsheet/IOFactory.php old mode 100755 new mode 100644 index a116334..e437a22 --- a/PhpOffice/PhpSpreadsheet/IOFactory.php +++ b/PhpOffice/PhpSpreadsheet/IOFactory.php @@ -2,7 +2,9 @@ namespace PhpOffice\PhpSpreadsheet; +use PhpOffice\PhpSpreadsheet\Reader\IReader; use PhpOffice\PhpSpreadsheet\Shared\File; +use PhpOffice\PhpSpreadsheet\Writer\IWriter; /** * Factory to create readers and writers easily. @@ -12,23 +14,39 @@ use PhpOffice\PhpSpreadsheet\Shared\File; */ abstract class IOFactory { + public const READER_XLSX = 'Xlsx'; + public const READER_XLS = 'Xls'; + public const READER_XML = 'Xml'; + public const READER_ODS = 'Ods'; + public const READER_SYLK = 'Slk'; + public const READER_SLK = 'Slk'; + public const READER_GNUMERIC = 'Gnumeric'; + public const READER_HTML = 'Html'; + public const READER_CSV = 'Csv'; + + public const WRITER_XLSX = 'Xlsx'; + public const WRITER_XLS = 'Xls'; + public const WRITER_ODS = 'Ods'; + public const WRITER_CSV = 'Csv'; + public const WRITER_HTML = 'Html'; + private static $readers = [ - 'Xlsx' => Reader\Xlsx::class, - 'Xls' => Reader\Xls::class, - 'Xml' => Reader\Xml::class, - 'Ods' => Reader\Ods::class, - 'Slk' => Reader\Slk::class, - 'Gnumeric' => Reader\Gnumeric::class, - 'Html' => Reader\Html::class, - 'Csv' => Reader\Csv::class, + self::READER_XLSX => Reader\Xlsx::class, + self::READER_XLS => Reader\Xls::class, + self::READER_XML => Reader\Xml::class, + self::READER_ODS => Reader\Ods::class, + self::READER_SLK => Reader\Slk::class, + self::READER_GNUMERIC => Reader\Gnumeric::class, + self::READER_HTML => Reader\Html::class, + self::READER_CSV => Reader\Csv::class, ]; private static $writers = [ - 'Xls' => Writer\Xls::class, - 'Xlsx' => Writer\Xlsx::class, - 'Ods' => Writer\Ods::class, - 'Csv' => Writer\Csv::class, - 'Html' => Writer\Html::class, + self::WRITER_XLS => Writer\Xls::class, + self::WRITER_XLSX => Writer\Xlsx::class, + self::WRITER_ODS => Writer\Ods::class, + self::WRITER_CSV => Writer\Csv::class, + self::WRITER_HTML => Writer\Html::class, 'Tcpdf' => Writer\Pdf\Tcpdf::class, 'Dompdf' => Writer\Pdf\Dompdf::class, 'Mpdf' => Writer\Pdf\Mpdf::class, @@ -36,15 +54,8 @@ abstract class IOFactory /** * Create Writer\IWriter. - * - * @param Spreadsheet $spreadsheet - * @param string $writerType Example: Xlsx - * - * @throws Writer\Exception - * - * @return Writer\IWriter */ - public static function createWriter(Spreadsheet $spreadsheet, $writerType) + public static function createWriter(Spreadsheet $spreadsheet, string $writerType): IWriter { if (!isset(self::$writers[$writerType])) { throw new Writer\Exception("No writer found for type $writerType"); @@ -52,21 +63,14 @@ abstract class IOFactory // Instantiate writer $className = self::$writers[$writerType]; - $writer = new $className($spreadsheet); - return $writer; + return new $className($spreadsheet); } /** - * Create Reader\IReader. - * - * @param string $readerType Example: Xlsx - * - * @throws Reader\Exception - * - * @return Reader\IReader + * Create IReader. */ - public static function createReader($readerType) + public static function createReader(string $readerType): IReader { if (!isset(self::$readers[$readerType])) { throw new Reader\Exception("No reader found for type $readerType"); @@ -74,39 +78,36 @@ abstract class IOFactory // Instantiate reader $className = self::$readers[$readerType]; - $reader = new $className(); - return $reader; + return new $className(); } /** * Loads Spreadsheet from file using automatic Reader\IReader resolution. * - * @param string $pFilename The name of the spreadsheet file - * - * @throws Reader\Exception - * - * @return Spreadsheet + * @param string $filename The name of the spreadsheet file + * @param int $flags the optional second parameter flags may be used to identify specific elements + * that should be loaded, but which won't be loaded by default, using these values: + * IReader::LOAD_WITH_CHARTS - Include any charts that are defined in the loaded file + * @param string[] $readers An array of Readers to use to identify the file type. By default, load() will try + * all possible Readers until it finds a match; but this allows you to pass in a + * list of Readers so it will only try the subset that you specify here. + * Values in this list can be any of the constant values defined in the set + * IOFactory::READER_*. */ - public static function load($pFilename) + public static function load(string $filename, int $flags = 0, ?array $readers = null): Spreadsheet { - $reader = self::createReaderForFile($pFilename); + $reader = self::createReaderForFile($filename, $readers); - return $reader->load($pFilename); + return $reader->load($filename, $flags); } /** - * Identify file type using automatic Reader\IReader resolution. - * - * @param string $pFilename The name of the spreadsheet file to identify - * - * @throws Reader\Exception - * - * @return string + * Identify file type using automatic IReader resolution. */ - public static function identify($pFilename) + public static function identify(string $filename, ?array $readers = null): string { - $reader = self::createReaderForFile($pFilename); + $reader = self::createReaderForFile($filename, $readers); $className = get_class($reader); $classType = explode('\\', $className); unset($reader); @@ -115,35 +116,47 @@ abstract class IOFactory } /** - * Create Reader\IReader for file using automatic Reader\IReader resolution. + * Create Reader\IReader for file using automatic IReader resolution. * - * @param string $filename The name of the spreadsheet file - * - * @throws Reader\Exception - * - * @return Reader\IReader + * @param string[] $readers An array of Readers to use to identify the file type. By default, load() will try + * all possible Readers until it finds a match; but this allows you to pass in a + * list of Readers so it will only try the subset that you specify here. + * Values in this list can be any of the constant values defined in the set + * IOFactory::READER_*. */ - public static function createReaderForFile($filename) + public static function createReaderForFile(string $filename, ?array $readers = null): IReader { File::assertFile($filename); + $testReaders = self::$readers; + if ($readers !== null) { + $readers = array_map('strtoupper', $readers); + $testReaders = array_filter( + self::$readers, + function (string $readerType) use ($readers) { + return in_array(strtoupper($readerType), $readers, true); + }, + ARRAY_FILTER_USE_KEY + ); + } + // First, lucky guess by inspecting file extension $guessedReader = self::getReaderTypeFromExtension($filename); - if ($guessedReader !== null) { + if (($guessedReader !== null) && array_key_exists($guessedReader, $testReaders)) { $reader = self::createReader($guessedReader); // Let's see if we are lucky - if (isset($reader) && $reader->canRead($filename)) { + if ($reader->canRead($filename)) { return $reader; } } // If we reach here then "lucky guess" didn't give any result - // Try walking through all the options in self::$autoResolveClasses - foreach (self::$readers as $type => $class) { + // Try walking through all the options in self::$readers (or the selected subset) + foreach ($testReaders as $readerType => $class) { // Ignore our original guess, we know that won't work - if ($type !== $guessedReader) { - $reader = self::createReader($type); + if ($readerType !== $guessedReader) { + $reader = self::createReader($readerType); if ($reader->canRead($filename)) { return $reader; } @@ -155,12 +168,8 @@ abstract class IOFactory /** * Guess a reader type from the file extension, if any. - * - * @param string $filename - * - * @return null|string */ - private static function getReaderTypeFromExtension($filename) + private static function getReaderTypeFromExtension(string $filename): ?string { $pathinfo = pathinfo($filename); if (!isset($pathinfo['extension'])) { @@ -200,14 +209,11 @@ abstract class IOFactory /** * Register a writer with its type and class name. - * - * @param string $writerType - * @param string $writerClass */ - public static function registerWriter($writerType, $writerClass) + public static function registerWriter(string $writerType, string $writerClass): void { - if (!is_a($writerClass, Writer\IWriter::class, true)) { - throw new Writer\Exception('Registered writers must implement ' . Writer\IWriter::class); + if (!is_a($writerClass, IWriter::class, true)) { + throw new Writer\Exception('Registered writers must implement ' . IWriter::class); } self::$writers[$writerType] = $writerClass; @@ -215,14 +221,11 @@ abstract class IOFactory /** * Register a reader with its type and class name. - * - * @param string $readerType - * @param string $readerClass */ - public static function registerReader($readerType, $readerClass) + public static function registerReader(string $readerType, string $readerClass): void { - if (!is_a($readerClass, Reader\IReader::class, true)) { - throw new Reader\Exception('Registered readers must implement ' . Reader\IReader::class); + if (!is_a($readerClass, IReader::class, true)) { + throw new Reader\Exception('Registered readers must implement ' . IReader::class); } self::$readers[$readerType] = $readerClass; diff --git a/PhpOffice/PhpSpreadsheet/NamedFormula.php b/PhpOffice/PhpSpreadsheet/NamedFormula.php new file mode 100644 index 0000000..500151f --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/NamedFormula.php @@ -0,0 +1,45 @@ +value; + } + + /** + * Set the formula value. + */ + public function setFormula(string $formula): self + { + if (!empty($formula)) { + $this->value = $formula; + } + + return $this; + } +} diff --git a/PhpOffice/PhpSpreadsheet/NamedRange.php b/PhpOffice/PhpSpreadsheet/NamedRange.php old mode 100755 new mode 100644 index 1f94d5a..db9c5f1 --- a/PhpOffice/PhpSpreadsheet/NamedRange.php +++ b/PhpOffice/PhpSpreadsheet/NamedRange.php @@ -2,239 +2,54 @@ namespace PhpOffice\PhpSpreadsheet; +use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; -class NamedRange +class NamedRange extends DefinedName { /** - * Range name. - * - * @var string + * Create a new Named Range. */ - private $name; - - /** - * Worksheet on which the named range can be resolved. - * - * @var Worksheet - */ - private $worksheet; - - /** - * Range of the referenced cells. - * - * @var string - */ - private $range; - - /** - * Is the named range local? (i.e. can only be used on $this->worksheet). - * - * @var bool - */ - private $localOnly; - - /** - * Scope. - * - * @var Worksheet - */ - private $scope; - - /** - * Create a new NamedRange. - * - * @param string $pName - * @param Worksheet $pWorksheet - * @param string $pRange - * @param bool $pLocalOnly - * @param null|Worksheet $pScope Scope. Only applies when $pLocalOnly = true. Null for global scope. - * - * @throws Exception - */ - public function __construct($pName, Worksheet $pWorksheet, $pRange = 'A1', $pLocalOnly = false, $pScope = null) - { - // Validate data - if (($pName === null) || ($pWorksheet === null) || ($pRange === null)) { - throw new Exception('Parameters can not be null.'); + public function __construct( + string $name, + ?Worksheet $worksheet = null, + string $range = 'A1', + bool $localOnly = false, + ?Worksheet $scope = null + ) { + if ($worksheet === null && $scope === null) { + throw new Exception('You must specify a worksheet or a scope for a Named Range'); } - - // Set local members - $this->name = $pName; - $this->worksheet = $pWorksheet; - $this->range = $pRange; - $this->localOnly = $pLocalOnly; - $this->scope = ($pLocalOnly == true) ? (($pScope == null) ? $pWorksheet : $pScope) : null; + parent::__construct($name, $worksheet, $range, $localOnly, $scope); } /** - * Get name. - * - * @return string + * Get the range value. */ - public function getName() + public function getRange(): string { - return $this->name; + return $this->value; } /** - * Set name. - * - * @param string $value - * - * @return NamedRange + * Set the range value. */ - public function setName($value) + public function setRange(string $range): self { - if ($value !== null) { - // Old title - $oldTitle = $this->name; - - // Re-attach - if ($this->worksheet !== null) { - $this->worksheet->getParent()->removeNamedRange($this->name, $this->worksheet); - } - $this->name = $value; - - if ($this->worksheet !== null) { - $this->worksheet->getParent()->addNamedRange($this); - } - - // New title - $newTitle = $this->name; - ReferenceHelper::getInstance()->updateNamedFormulas($this->worksheet->getParent(), $oldTitle, $newTitle); + if (!empty($range)) { + $this->value = $range; } return $this; } - /** - * Get worksheet. - * - * @return Worksheet - */ - public function getWorksheet() + public function getCellsInRange(): array { - return $this->worksheet; - } - - /** - * Set worksheet. - * - * @param Worksheet $value - * - * @return NamedRange - */ - public function setWorksheet(Worksheet $value = null) - { - if ($value !== null) { - $this->worksheet = $value; + $range = $this->value; + if (substr($range, 0, 1) === '=') { + $range = substr($range, 1); } - return $this; - } - - /** - * Get range. - * - * @return string - */ - public function getRange() - { - return $this->range; - } - - /** - * Set range. - * - * @param string $value - * - * @return NamedRange - */ - public function setRange($value) - { - if ($value !== null) { - $this->range = $value; - } - - return $this; - } - - /** - * Get localOnly. - * - * @return bool - */ - public function getLocalOnly() - { - return $this->localOnly; - } - - /** - * Set localOnly. - * - * @param bool $value - * - * @return NamedRange - */ - public function setLocalOnly($value) - { - $this->localOnly = $value; - $this->scope = $value ? $this->worksheet : null; - - return $this; - } - - /** - * Get scope. - * - * @return null|Worksheet - */ - public function getScope() - { - return $this->scope; - } - - /** - * Set scope. - * - * @param null|Worksheet $value - * - * @return NamedRange - */ - public function setScope(Worksheet $value = null) - { - $this->scope = $value; - $this->localOnly = $value != null; - - return $this; - } - - /** - * Resolve a named range to a regular cell range. - * - * @param string $pNamedRange Named range - * @param null|Worksheet $pSheet Scope. Use null for global scope - * - * @return NamedRange - */ - public static function resolveRange($pNamedRange, Worksheet $pSheet) - { - return $pSheet->getParent()->getNamedRange($pNamedRange, $pSheet); - } - - /** - * Implement PHP __clone to create a deep clone, not just a shallow copy. - */ - public function __clone() - { - $vars = get_object_vars($this); - foreach ($vars as $key => $value) { - if (is_object($value)) { - $this->$key = clone $value; - } else { - $this->$key = $value; - } - } + return Coordinate::extractAllCellReferencesInRange($range); } } diff --git a/PhpOffice/PhpSpreadsheet/Reader/BaseReader.php b/PhpOffice/PhpSpreadsheet/Reader/BaseReader.php old mode 100755 new mode 100644 index f7af155..42268d6 --- a/PhpOffice/PhpSpreadsheet/Reader/BaseReader.php +++ b/PhpOffice/PhpSpreadsheet/Reader/BaseReader.php @@ -2,8 +2,11 @@ namespace PhpOffice\PhpSpreadsheet\Reader; +use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; +use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException; use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner; use PhpOffice\PhpSpreadsheet\Shared\File; +use PhpOffice\PhpSpreadsheet\Spreadsheet; abstract class BaseReader implements IReader { @@ -37,7 +40,7 @@ abstract class BaseReader implements IReader * Restrict which sheets should be loaded? * This property holds an array of worksheet names to be loaded. If null, then all worksheets will be loaded. * - * @var array of string + * @var null|string[] */ protected $loadSheetsOnly; @@ -65,9 +68,9 @@ abstract class BaseReader implements IReader return $this->readDataOnly; } - public function setReadDataOnly($pValue) + public function setReadDataOnly($readCellValuesOnly) { - $this->readDataOnly = (bool) $pValue; + $this->readDataOnly = (bool) $readCellValuesOnly; return $this; } @@ -77,9 +80,9 @@ abstract class BaseReader implements IReader return $this->readEmptyCells; } - public function setReadEmptyCells($pValue) + public function setReadEmptyCells($readEmptyCells) { - $this->readEmptyCells = (bool) $pValue; + $this->readEmptyCells = (bool) $readEmptyCells; return $this; } @@ -89,9 +92,9 @@ abstract class BaseReader implements IReader return $this->includeCharts; } - public function setIncludeCharts($pValue) + public function setIncludeCharts($includeCharts) { - $this->includeCharts = (bool) $pValue; + $this->includeCharts = (bool) $includeCharts; return $this; } @@ -101,13 +104,13 @@ abstract class BaseReader implements IReader return $this->loadSheetsOnly; } - public function setLoadSheetsOnly($value) + public function setLoadSheetsOnly($sheetList) { - if ($value === null) { + if ($sheetList === null) { return $this->setLoadAllSheets(); } - $this->loadSheetsOnly = is_array($value) ? $value : [$value]; + $this->loadSheetsOnly = is_array($sheetList) ? $sheetList : [$sheetList]; return $this; } @@ -124,37 +127,70 @@ abstract class BaseReader implements IReader return $this->readFilter; } - public function setReadFilter(IReadFilter $pValue) + public function setReadFilter(IReadFilter $readFilter) { - $this->readFilter = $pValue; + $this->readFilter = $readFilter; return $this; } public function getSecurityScanner() { - if (property_exists($this, 'securityScanner')) { - return $this->securityScanner; - } + return $this->securityScanner; + } - return null; + protected function processFlags(int $flags): void + { + if (((bool) ($flags & self::LOAD_WITH_CHARTS)) === true) { + $this->setIncludeCharts(true); + } + if (((bool) ($flags & self::READ_DATA_ONLY)) === true) { + $this->setReadDataOnly(true); + } + if (((bool) ($flags & self::SKIP_EMPTY_CELLS)) === true) { + $this->setReadEmptyCells(false); + } + } + + protected function loadSpreadsheetFromFile(string $filename): Spreadsheet + { + throw new PhpSpreadsheetException('Reader classes must implement their own loadSpreadsheetFromFile() method'); + } + + /** + * Loads Spreadsheet from file. + * + * @param int $flags the optional second parameter flags may be used to identify specific elements + * that should be loaded, but which won't be loaded by default, using these values: + * IReader::LOAD_WITH_CHARTS - Include any charts that are defined in the loaded file + */ + public function load(string $filename, int $flags = 0): Spreadsheet + { + $this->processFlags($flags); + + try { + return $this->loadSpreadsheetFromFile($filename); + } catch (ReaderException $e) { + throw $e; + } } /** * Open file for reading. - * - * @param string $pFilename - * - * @throws Exception */ - protected function openFile($pFilename) + protected function openFile(string $filename): void { - File::assertFile($pFilename); + $fileHandle = false; + if ($filename) { + File::assertFile($filename); - // Open file - $this->fileHandle = fopen($pFilename, 'r'); - if ($this->fileHandle === false) { - throw new Exception('Could not open file ' . $pFilename . ' for reading.'); + // Open file + $fileHandle = fopen($filename, 'rb'); } + if ($fileHandle === false) { + throw new ReaderException('Could not open file ' . $filename . ' for reading.'); + } + + $this->fileHandle = $fileHandle; } } diff --git a/PhpOffice/PhpSpreadsheet/Reader/Csv.php b/PhpOffice/PhpSpreadsheet/Reader/Csv.php old mode 100755 new mode 100644 index 2125191..4f128d6 --- a/PhpOffice/PhpSpreadsheet/Reader/Csv.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Csv.php @@ -2,12 +2,34 @@ namespace PhpOffice\PhpSpreadsheet\Reader; +use PhpOffice\PhpSpreadsheet\Calculation\Calculation; +use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; +use PhpOffice\PhpSpreadsheet\Reader\Csv\Delimiter; +use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Style\NumberFormat; class Csv extends BaseReader { + const DEFAULT_FALLBACK_ENCODING = 'CP1252'; + const GUESS_ENCODING = 'guess'; + const UTF8_BOM = "\xEF\xBB\xBF"; + const UTF8_BOM_LEN = 3; + const UTF16BE_BOM = "\xfe\xff"; + const UTF16BE_BOM_LEN = 2; + const UTF16BE_LF = "\x00\x0a"; + const UTF16LE_BOM = "\xff\xfe"; + const UTF16LE_BOM_LEN = 2; + const UTF16LE_LF = "\x0a\x00"; + const UTF32BE_BOM = "\x00\x00\xfe\xff"; + const UTF32BE_BOM_LEN = 4; + const UTF32BE_LF = "\x00\x00\x00\x0a"; + const UTF32LE_BOM = "\xff\xfe\x00\x00"; + const UTF32LE_BOM_LEN = 4; + const UTF32LE_LF = "\x0a\x00\x00\x00"; + /** * Input encoding. * @@ -16,10 +38,17 @@ class Csv extends BaseReader private $inputEncoding = 'UTF-8'; /** - * Delimiter. + * Fallback encoding if guess strikes out. * * @var string */ + private $fallbackEncoding = self::DEFAULT_FALLBACK_ENCODING; + + /** + * Delimiter. + * + * @var ?string + */ private $delimiter; /** @@ -43,13 +72,6 @@ class Csv extends BaseReader */ private $contiguous = false; - /** - * Row counter for loading rows contiguously. - * - * @var int - */ - private $contiguousRow = -1; - /** * The character that can escape the enclosure. * @@ -57,80 +79,101 @@ class Csv extends BaseReader */ private $escapeCharacter = '\\'; + /** + * Callback for setting defaults in construction. + * + * @var ?callable + */ + private static $constructorCallback; + + /** + * Attempt autodetect line endings (deprecated after PHP8.1)? + * + * @var bool + */ + private $testAutodetect = true; + + /** + * @var bool + */ + protected $castFormattedNumberToNumeric = false; + + /** + * @var bool + */ + protected $preserveNumericFormatting = false; + + /** @var bool */ + private $preserveNullString = false; + /** * Create a new CSV Reader instance. */ public function __construct() { parent::__construct(); + $callback = self::$constructorCallback; + if ($callback !== null) { + $callback($this); + } } /** - * Set input encoding. + * Set a callback to change the defaults. * - * @param string $pValue Input encoding, eg: 'UTF-8' - * - * @return Csv + * The callback must accept the Csv Reader object as the first parameter, + * and it should return void. */ - public function setInputEncoding($pValue) + public static function setConstructorCallback(?callable $callback): void { - $this->inputEncoding = $pValue; + self::$constructorCallback = $callback; + } + + public static function getConstructorCallback(): ?callable + { + return self::$constructorCallback; + } + + public function setInputEncoding(string $encoding): self + { + $this->inputEncoding = $encoding; return $this; } - /** - * Get input encoding. - * - * @return string - */ - public function getInputEncoding() + public function getInputEncoding(): string { return $this->inputEncoding; } + public function setFallbackEncoding(string $fallbackEncoding): self + { + $this->fallbackEncoding = $fallbackEncoding; + + return $this; + } + + public function getFallbackEncoding(): string + { + return $this->fallbackEncoding; + } + /** * Move filepointer past any BOM marker. */ - protected function skipBOM() + protected function skipBOM(): void { rewind($this->fileHandle); - switch ($this->inputEncoding) { - case 'UTF-8': - fgets($this->fileHandle, 4) == "\xEF\xBB\xBF" ? - fseek($this->fileHandle, 3) : fseek($this->fileHandle, 0); - - break; - case 'UTF-16LE': - fgets($this->fileHandle, 3) == "\xFF\xFE" ? - fseek($this->fileHandle, 2) : fseek($this->fileHandle, 0); - - break; - case 'UTF-16BE': - fgets($this->fileHandle, 3) == "\xFE\xFF" ? - fseek($this->fileHandle, 2) : fseek($this->fileHandle, 0); - - break; - case 'UTF-32LE': - fgets($this->fileHandle, 5) == "\xFF\xFE\x00\x00" ? - fseek($this->fileHandle, 4) : fseek($this->fileHandle, 0); - - break; - case 'UTF-32BE': - fgets($this->fileHandle, 5) == "\x00\x00\xFE\xFF" ? - fseek($this->fileHandle, 4) : fseek($this->fileHandle, 0); - - break; - default: - break; + if (fgets($this->fileHandle, self::UTF8_BOM_LEN + 1) !== self::UTF8_BOM) { + rewind($this->fileHandle); } } /** * Identify any separator that is explicitly set in the file. */ - protected function checkSeparator() + protected function checkSeparator(): void { $line = fgets($this->fileHandle); if ($line === false) { @@ -149,140 +192,39 @@ class Csv extends BaseReader /** * Infer the separator if it isn't explicitly set in the file or specified by the user. */ - protected function inferSeparator() + protected function inferSeparator(): void { if ($this->delimiter !== null) { return; } - $potentialDelimiters = [',', ';', "\t", '|', ':', ' ', '~']; - $counts = []; - foreach ($potentialDelimiters as $delimiter) { - $counts[$delimiter] = []; - } - - // Count how many times each of the potential delimiters appears in each line - $numberLines = 0; - while (($line = $this->getNextLine()) !== false && (++$numberLines < 1000)) { - $countLine = []; - for ($i = strlen($line) - 1; $i >= 0; --$i) { - $char = $line[$i]; - if (isset($counts[$char])) { - if (!isset($countLine[$char])) { - $countLine[$char] = 0; - } - ++$countLine[$char]; - } - } - foreach ($potentialDelimiters as $delimiter) { - $counts[$delimiter][] = $countLine[$delimiter] - ?? 0; - } - } + $inferenceEngine = new Delimiter($this->fileHandle, $this->escapeCharacter, $this->enclosure); // If number of lines is 0, nothing to infer : fall back to the default - if ($numberLines === 0) { - $this->delimiter = reset($potentialDelimiters); + if ($inferenceEngine->linesCounted() === 0) { + $this->delimiter = $inferenceEngine->getDefaultDelimiter(); $this->skipBOM(); return; } - // Calculate the mean square deviations for each delimiter (ignoring delimiters that haven't been found consistently) - $meanSquareDeviations = []; - $middleIdx = floor(($numberLines - 1) / 2); - - foreach ($potentialDelimiters as $delimiter) { - $series = $counts[$delimiter]; - sort($series); - - $median = ($numberLines % 2) - ? $series[$middleIdx] - : ($series[$middleIdx] + $series[$middleIdx + 1]) / 2; - - if ($median === 0) { - continue; - } - - $meanSquareDeviations[$delimiter] = array_reduce( - $series, - function ($sum, $value) use ($median) { - return $sum + pow($value - $median, 2); - } - ) / count($series); - } - - // ... and pick the delimiter with the smallest mean square deviation (in case of ties, the order in potentialDelimiters is respected) - $min = INF; - foreach ($potentialDelimiters as $delimiter) { - if (!isset($meanSquareDeviations[$delimiter])) { - continue; - } - - if ($meanSquareDeviations[$delimiter] < $min) { - $min = $meanSquareDeviations[$delimiter]; - $this->delimiter = $delimiter; - } - } + $this->delimiter = $inferenceEngine->infer(); // If no delimiter could be detected, fall back to the default if ($this->delimiter === null) { - $this->delimiter = reset($potentialDelimiters); + $this->delimiter = $inferenceEngine->getDefaultDelimiter(); } $this->skipBOM(); } - /** - * Get the next full line from the file. - * - * @param string $line - * - * @return bool|string - */ - private function getNextLine($line = '') - { - // Get the next line in the file - $newLine = fgets($this->fileHandle); - - // Return false if there is no next line - if ($newLine === false) { - return false; - } - - // Add the new line to the line passed in - $line = $line . $newLine; - - // Drop everything that is enclosed to avoid counting false positives in enclosures - $enclosure = '(?escapeCharacter, '/') . ')' - . preg_quote($this->enclosure, '/'); - $line = preg_replace('/(' . $enclosure . '.*' . $enclosure . ')/Us', '', $line); - - // See if we have any enclosures left in the line - // if we still have an enclosure then we need to read the next line as well - if (preg_match('/(' . $enclosure . ')/', $line) > 0) { - $line = $this->getNextLine($line); - } - - return $line; - } - /** * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns). - * - * @param string $pFilename - * - * @throws Exception - * - * @return array */ - public function listWorksheetInfo($pFilename) + public function listWorksheetInfo(string $filename): array { // Open file - if (!$this->canRead($pFilename)) { - throw new Exception($pFilename . ' is an Invalid Spreadsheet file.'); - } - $this->openFile($pFilename); + $this->openFileOrMemory($filename); $fileHandle = $this->fileHandle; // Skip BOM, if any @@ -298,9 +240,11 @@ class Csv extends BaseReader $worksheetInfo[0]['totalColumns'] = 0; // Loop through each line of the file in turn - while (($rowData = fgetcsv($fileHandle, 0, $this->delimiter, $this->enclosure, $this->escapeCharacter)) !== false) { + $rowData = fgetcsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter); + while (is_array($rowData)) { ++$worksheetInfo[0]['totalRows']; $worksheetInfo[0]['lastColumnIndex'] = max($worksheetInfo[0]['lastColumnIndex'], count($rowData) - 1); + $rowData = fgetcsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter); } $worksheetInfo[0]['lastColumnLetter'] = Coordinate::stringFromColumnIndex($worksheetInfo[0]['lastColumnIndex'] + 1); @@ -314,42 +258,116 @@ class Csv extends BaseReader /** * Loads Spreadsheet from file. - * - * @param string $pFilename - * - * @throws Exception - * - * @return Spreadsheet */ - public function load($pFilename) + protected function loadSpreadsheetFromFile(string $filename): Spreadsheet { // Create new Spreadsheet $spreadsheet = new Spreadsheet(); // Load into this instance - return $this->loadIntoExisting($pFilename, $spreadsheet); + return $this->loadIntoExisting($filename, $spreadsheet); + } + + /** + * Loads Spreadsheet from string. + */ + public function loadSpreadsheetFromString(string $contents): Spreadsheet + { + // Create new Spreadsheet + $spreadsheet = new Spreadsheet(); + + // Load into this instance + return $this->loadStringOrFile('data://text/plain,' . urlencode($contents), $spreadsheet, true); + } + + private function openFileOrMemory(string $filename): void + { + // Open file + $fhandle = $this->canRead($filename); + if (!$fhandle) { + throw new Exception($filename . ' is an Invalid Spreadsheet file.'); + } + if ($this->inputEncoding === self::GUESS_ENCODING) { + $this->inputEncoding = self::guessEncoding($filename, $this->fallbackEncoding); + } + $this->openFile($filename); + if ($this->inputEncoding !== 'UTF-8') { + fclose($this->fileHandle); + $entireFile = file_get_contents($filename); + $this->fileHandle = fopen('php://memory', 'r+b'); + if ($this->fileHandle !== false && $entireFile !== false) { + $data = StringHelper::convertEncoding($entireFile, 'UTF-8', $this->inputEncoding); + fwrite($this->fileHandle, $data); + $this->skipBOM(); + } + } + } + + public function setTestAutoDetect(bool $value): self + { + $this->testAutodetect = $value; + + return $this; + } + + private function setAutoDetect(?string $value): ?string + { + $retVal = null; + if ($value !== null && $this->testAutodetect) { + $retVal2 = @ini_set('auto_detect_line_endings', $value); + if (is_string($retVal2)) { + $retVal = $retVal2; + } + } + + return $retVal; + } + + public function castFormattedNumberToNumeric( + bool $castFormattedNumberToNumeric, + bool $preserveNumericFormatting = false + ): void { + $this->castFormattedNumberToNumeric = $castFormattedNumberToNumeric; + $this->preserveNumericFormatting = $preserveNumericFormatting; + } + + /** + * Open data uri for reading. + */ + private function openDataUri(string $filename): void + { + $fileHandle = fopen($filename, 'rb'); + if ($fileHandle === false) { + // @codeCoverageIgnoreStart + throw new ReaderException('Could not open file ' . $filename . ' for reading.'); + // @codeCoverageIgnoreEnd + } + + $this->fileHandle = $fileHandle; } /** * Loads PhpSpreadsheet from file into PhpSpreadsheet instance. - * - * @param string $pFilename - * @param Spreadsheet $spreadsheet - * - * @throws Exception - * - * @return Spreadsheet */ - public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet) + public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Spreadsheet { - $lineEnding = ini_get('auto_detect_line_endings'); - ini_set('auto_detect_line_endings', true); + return $this->loadStringOrFile($filename, $spreadsheet, false); + } + + /** + * Loads PhpSpreadsheet from file into PhpSpreadsheet instance. + */ + private function loadStringOrFile(string $filename, Spreadsheet $spreadsheet, bool $dataUri): Spreadsheet + { + // Deprecated in Php8.1 + $iniset = $this->setAutoDetect('1'); // Open file - if (!$this->canRead($pFilename)) { - throw new Exception($pFilename . ' is an Invalid Spreadsheet file.'); + if ($dataUri) { + $this->openDataUri($filename); + } else { + $this->openFileOrMemory($filename); } - $this->openFile($pFilename); $fileHandle = $this->fileHandle; // Skip BOM, if any @@ -365,83 +383,118 @@ class Csv extends BaseReader // Set our starting row based on whether we're in contiguous mode or not $currentRow = 1; - if ($this->contiguous) { - $currentRow = ($this->contiguousRow == -1) ? $sheet->getHighestRow() : $this->contiguousRow; - } + $outRow = 0; // Loop through each line of the file in turn - while (($rowData = fgetcsv($fileHandle, 0, $this->delimiter, $this->enclosure, $this->escapeCharacter)) !== false) { + $rowData = fgetcsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter); + $valueBinder = Cell::getValueBinder(); + $preserveBooleanString = method_exists($valueBinder, 'getBooleanConversion') && $valueBinder->getBooleanConversion(); + while (is_array($rowData)) { + $noOutputYet = true; $columnLetter = 'A'; foreach ($rowData as $rowDatum) { - if ($rowDatum != '' && $this->readFilter->readCell($columnLetter, $currentRow)) { - // Convert encoding if necessary - if ($this->inputEncoding !== 'UTF-8') { - $rowDatum = StringHelper::convertEncoding($rowDatum, 'UTF-8', $this->inputEncoding); + $this->convertBoolean($rowDatum, $preserveBooleanString); + $numberFormatMask = $this->convertFormattedNumber($rowDatum); + if (($rowDatum !== '' || $this->preserveNullString) && $this->readFilter->readCell($columnLetter, $currentRow)) { + if ($this->contiguous) { + if ($noOutputYet) { + $noOutputYet = false; + ++$outRow; + } + } else { + $outRow = $currentRow; } - + // Set basic styling for the value (Note that this could be overloaded by styling in a value binder) + $sheet->getCell($columnLetter . $outRow)->getStyle() + ->getNumberFormat() + ->setFormatCode($numberFormatMask); // Set cell value - $sheet->getCell($columnLetter . $currentRow)->setValue($rowDatum); + $sheet->getCell($columnLetter . $outRow)->setValue($rowDatum); } ++$columnLetter; } + $rowData = fgetcsv($fileHandle, 0, $this->delimiter ?? '', $this->enclosure, $this->escapeCharacter); ++$currentRow; } // Close file fclose($fileHandle); - if ($this->contiguous) { - $this->contiguousRow = $currentRow; - } - - ini_set('auto_detect_line_endings', $lineEnding); + $this->setAutoDetect($iniset); // Return return $spreadsheet; } /** - * Get delimiter. + * Convert string true/false to boolean, and null to null-string. * - * @return string + * @param mixed $rowDatum */ - public function getDelimiter() + private function convertBoolean(&$rowDatum, bool $preserveBooleanString): void + { + if (is_string($rowDatum) && !$preserveBooleanString) { + if (strcasecmp(Calculation::getTRUE(), $rowDatum) === 0 || strcasecmp('true', $rowDatum) === 0) { + $rowDatum = true; + } elseif (strcasecmp(Calculation::getFALSE(), $rowDatum) === 0 || strcasecmp('false', $rowDatum) === 0) { + $rowDatum = false; + } + } else { + $rowDatum = $rowDatum ?? ''; + } + } + + /** + * Convert numeric strings to int or float values. + * + * @param mixed $rowDatum + */ + private function convertFormattedNumber(&$rowDatum): string + { + $numberFormatMask = NumberFormat::FORMAT_GENERAL; + if ($this->castFormattedNumberToNumeric === true && is_string($rowDatum)) { + $numeric = str_replace( + [StringHelper::getThousandsSeparator(), StringHelper::getDecimalSeparator()], + ['', '.'], + $rowDatum + ); + + if (is_numeric($numeric)) { + $decimalPos = strpos($rowDatum, StringHelper::getDecimalSeparator()); + if ($this->preserveNumericFormatting === true) { + $numberFormatMask = (strpos($rowDatum, StringHelper::getThousandsSeparator()) !== false) + ? '#,##0' : '0'; + if ($decimalPos !== false) { + $decimals = strlen($rowDatum) - $decimalPos - 1; + $numberFormatMask .= '.' . str_repeat('0', min($decimals, 6)); + } + } + + $rowDatum = ($decimalPos !== false) ? (float) $numeric : (int) $numeric; + } + } + + return $numberFormatMask; + } + + public function getDelimiter(): ?string { return $this->delimiter; } - /** - * Set delimiter. - * - * @param string $delimiter Delimiter, eg: ',' - * - * @return CSV - */ - public function setDelimiter($delimiter) + public function setDelimiter(?string $delimiter): self { $this->delimiter = $delimiter; return $this; } - /** - * Get enclosure. - * - * @return string - */ - public function getEnclosure() + public function getEnclosure(): string { return $this->enclosure; } - /** - * Set enclosure. - * - * @param string $enclosure Enclosure, defaults to " - * - * @return CSV - */ - public function setEnclosure($enclosure) + public function setEnclosure(string $enclosure): self { if ($enclosure == '') { $enclosure = '"'; @@ -451,108 +504,66 @@ class Csv extends BaseReader return $this; } - /** - * Get sheet index. - * - * @return int - */ - public function getSheetIndex() + public function getSheetIndex(): int { return $this->sheetIndex; } - /** - * Set sheet index. - * - * @param int $pValue Sheet index - * - * @return CSV - */ - public function setSheetIndex($pValue) + public function setSheetIndex(int $indexValue): self { - $this->sheetIndex = $pValue; + $this->sheetIndex = $indexValue; return $this; } - /** - * Set Contiguous. - * - * @param bool $contiguous - * - * @return Csv - */ - public function setContiguous($contiguous) + public function setContiguous(bool $contiguous): self { - $this->contiguous = (bool) $contiguous; - if (!$contiguous) { - $this->contiguousRow = -1; - } + $this->contiguous = $contiguous; return $this; } - /** - * Get Contiguous. - * - * @return bool - */ - public function getContiguous() + public function getContiguous(): bool { return $this->contiguous; } - /** - * Set escape backslashes. - * - * @param string $escapeCharacter - * - * @return $this - */ - public function setEscapeCharacter($escapeCharacter) + public function setEscapeCharacter(string $escapeCharacter): self { $this->escapeCharacter = $escapeCharacter; return $this; } - /** - * Get escape backslashes. - * - * @return string - */ - public function getEscapeCharacter() + public function getEscapeCharacter(): string { return $this->escapeCharacter; } /** * Can the current IReader read the file? - * - * @param string $pFilename - * - * @return bool */ - public function canRead($pFilename) + public function canRead(string $filename): bool { // Check if file exists try { - $this->openFile($pFilename); - } catch (Exception $e) { + $this->openFile($filename); + } catch (ReaderException $e) { return false; } fclose($this->fileHandle); // Trust file extension if any - $extension = strtolower(pathinfo($pFilename, PATHINFO_EXTENSION)); + $extension = strtolower(/** @scrutinizer ignore-type */ pathinfo($filename, PATHINFO_EXTENSION)); if (in_array($extension, ['csv', 'tsv'])) { return true; } // Attempt to guess mimetype - $type = mime_content_type($pFilename); + $type = mime_content_type($filename); $supportedTypes = [ + 'application/csv', 'text/csv', 'text/plain', 'inode/x-empty', @@ -560,4 +571,75 @@ class Csv extends BaseReader return in_array($type, $supportedTypes, true); } + + private static function guessEncodingTestNoBom(string &$encoding, string &$contents, string $compare, string $setEncoding): void + { + if ($encoding === '') { + $pos = strpos($contents, $compare); + if ($pos !== false && $pos % strlen($compare) === 0) { + $encoding = $setEncoding; + } + } + } + + private static function guessEncodingNoBom(string $filename): string + { + $encoding = ''; + $contents = file_get_contents($filename); + self::guessEncodingTestNoBom($encoding, $contents, self::UTF32BE_LF, 'UTF-32BE'); + self::guessEncodingTestNoBom($encoding, $contents, self::UTF32LE_LF, 'UTF-32LE'); + self::guessEncodingTestNoBom($encoding, $contents, self::UTF16BE_LF, 'UTF-16BE'); + self::guessEncodingTestNoBom($encoding, $contents, self::UTF16LE_LF, 'UTF-16LE'); + if ($encoding === '' && preg_match('//u', $contents) === 1) { + $encoding = 'UTF-8'; + } + + return $encoding; + } + + private static function guessEncodingTestBom(string &$encoding, string $first4, string $compare, string $setEncoding): void + { + if ($encoding === '') { + if ($compare === substr($first4, 0, strlen($compare))) { + $encoding = $setEncoding; + } + } + } + + private static function guessEncodingBom(string $filename): string + { + $encoding = ''; + $first4 = file_get_contents($filename, false, null, 0, 4); + if ($first4 !== false) { + self::guessEncodingTestBom($encoding, $first4, self::UTF8_BOM, 'UTF-8'); + self::guessEncodingTestBom($encoding, $first4, self::UTF16BE_BOM, 'UTF-16BE'); + self::guessEncodingTestBom($encoding, $first4, self::UTF32BE_BOM, 'UTF-32BE'); + self::guessEncodingTestBom($encoding, $first4, self::UTF32LE_BOM, 'UTF-32LE'); + self::guessEncodingTestBom($encoding, $first4, self::UTF16LE_BOM, 'UTF-16LE'); + } + + return $encoding; + } + + public static function guessEncoding(string $filename, string $dflt = self::DEFAULT_FALLBACK_ENCODING): string + { + $encoding = self::guessEncodingBom($filename); + if ($encoding === '') { + $encoding = self::guessEncodingNoBom($filename); + } + + return ($encoding === '') ? $dflt : $encoding; + } + + public function setPreserveNullString(bool $value): self + { + $this->preserveNullString = $value; + + return $this; + } + + public function getPreserveNullString(): bool + { + return $this->preserveNullString; + } } diff --git a/PhpOffice/PhpSpreadsheet/Reader/Csv/Delimiter.php b/PhpOffice/PhpSpreadsheet/Reader/Csv/Delimiter.php new file mode 100644 index 0000000..029d4a1 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Reader/Csv/Delimiter.php @@ -0,0 +1,151 @@ +fileHandle = $fileHandle; + $this->escapeCharacter = $escapeCharacter; + $this->enclosure = $enclosure; + + $this->countPotentialDelimiters(); + } + + public function getDefaultDelimiter(): string + { + return self::POTENTIAL_DELIMETERS[0]; + } + + public function linesCounted(): int + { + return $this->numberLines; + } + + protected function countPotentialDelimiters(): void + { + $this->counts = array_fill_keys(self::POTENTIAL_DELIMETERS, []); + $delimiterKeys = array_flip(self::POTENTIAL_DELIMETERS); + + // Count how many times each of the potential delimiters appears in each line + $this->numberLines = 0; + while (($line = $this->getNextLine()) !== false && (++$this->numberLines < 1000)) { + $this->countDelimiterValues($line, $delimiterKeys); + } + } + + protected function countDelimiterValues(string $line, array $delimiterKeys): void + { + $splitString = str_split($line, 1); + if (is_array($splitString)) { + $distribution = array_count_values($splitString); + $countLine = array_intersect_key($distribution, $delimiterKeys); + + foreach (self::POTENTIAL_DELIMETERS as $delimiter) { + $this->counts[$delimiter][] = $countLine[$delimiter] ?? 0; + } + } + } + + public function infer(): ?string + { + // Calculate the mean square deviations for each delimiter + // (ignoring delimiters that haven't been found consistently) + $meanSquareDeviations = []; + $middleIdx = floor(($this->numberLines - 1) / 2); + + foreach (self::POTENTIAL_DELIMETERS as $delimiter) { + $series = $this->counts[$delimiter]; + sort($series); + + $median = ($this->numberLines % 2) + ? $series[$middleIdx] + : ($series[$middleIdx] + $series[$middleIdx + 1]) / 2; + + if ($median === 0) { + continue; + } + + $meanSquareDeviations[$delimiter] = array_reduce( + $series, + function ($sum, $value) use ($median) { + return $sum + ($value - $median) ** 2; + } + ) / count($series); + } + + // ... and pick the delimiter with the smallest mean square deviation + // (in case of ties, the order in potentialDelimiters is respected) + $min = INF; + foreach (self::POTENTIAL_DELIMETERS as $delimiter) { + if (!isset($meanSquareDeviations[$delimiter])) { + continue; + } + + if ($meanSquareDeviations[$delimiter] < $min) { + $min = $meanSquareDeviations[$delimiter]; + $this->delimiter = $delimiter; + } + } + + return $this->delimiter; + } + + /** + * Get the next full line from the file. + * + * @return false|string + */ + public function getNextLine() + { + $line = ''; + $enclosure = ($this->escapeCharacter === '' ? '' + : ('(?escapeCharacter, '/') . ')')) + . preg_quote($this->enclosure, '/'); + + do { + // Get the next line in the file + $newLine = fgets($this->fileHandle); + + // Return false if there is no next line + if ($newLine === false) { + return false; + } + + // Add the new line to the line passed in + $line = $line . $newLine; + + // Drop everything that is enclosed to avoid counting false positives in enclosures + $line = (string) preg_replace('/(' . $enclosure . '.*' . $enclosure . ')/Us', '', $line); + + // See if we have any enclosures left in the line + // if we still have an enclosure then we need to read the next line as well + } while (preg_match('/(' . $enclosure . ')/', $line) > 0); + + return ($line !== '') ? $line : false; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Reader/DefaultReadFilter.php b/PhpOffice/PhpSpreadsheet/Reader/DefaultReadFilter.php old mode 100755 new mode 100644 index e104186..8fdb162 --- a/PhpOffice/PhpSpreadsheet/Reader/DefaultReadFilter.php +++ b/PhpOffice/PhpSpreadsheet/Reader/DefaultReadFilter.php @@ -7,13 +7,13 @@ class DefaultReadFilter implements IReadFilter /** * Should this cell be read? * - * @param string $column Column address (as a string value like "A", or "IV") + * @param string $columnAddress Column address (as a string value like "A", or "IV") * @param int $row Row number * @param string $worksheetName Optional worksheet name * * @return bool */ - public function readCell($column, $row, $worksheetName = '') + public function readCell($columnAddress, $row, $worksheetName = '') { return true; } diff --git a/PhpOffice/PhpSpreadsheet/Reader/Exception.php b/PhpOffice/PhpSpreadsheet/Reader/Exception.php old mode 100755 new mode 100644 diff --git a/PhpOffice/PhpSpreadsheet/Reader/Gnumeric.php b/PhpOffice/PhpSpreadsheet/Reader/Gnumeric.php old mode 100755 new mode 100644 index 44ab701..b33af24 --- a/PhpOffice/PhpSpreadsheet/Reader/Gnumeric.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Gnumeric.php @@ -4,24 +4,36 @@ namespace PhpOffice\PhpSpreadsheet\Reader; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Cell\DataType; -use PhpOffice\PhpSpreadsheet\NamedRange; +use PhpOffice\PhpSpreadsheet\DefinedName; +use PhpOffice\PhpSpreadsheet\Reader\Gnumeric\PageSetup; +use PhpOffice\PhpSpreadsheet\Reader\Gnumeric\Properties; +use PhpOffice\PhpSpreadsheet\Reader\Gnumeric\Styles; use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner; use PhpOffice\PhpSpreadsheet\ReferenceHelper; use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\Settings; -use PhpOffice\PhpSpreadsheet\Shared\Date; use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Spreadsheet; -use PhpOffice\PhpSpreadsheet\Style\Alignment; -use PhpOffice\PhpSpreadsheet\Style\Border; -use PhpOffice\PhpSpreadsheet\Style\Borders; -use PhpOffice\PhpSpreadsheet\Style\Fill; -use PhpOffice\PhpSpreadsheet\Style\Font; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use SimpleXMLElement; use XMLReader; class Gnumeric extends BaseReader { + const NAMESPACE_GNM = 'http://www.gnumeric.org/v10.dtd'; // gmr in old sheets + + const NAMESPACE_XSI = 'http://www.w3.org/2001/XMLSchema-instance'; + + const NAMESPACE_OFFICE = 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'; + + const NAMESPACE_XLINK = 'http://www.w3.org/1999/xlink'; + + const NAMESPACE_DC = 'http://purl.org/dc/elements/1.1/'; + + const NAMESPACE_META = 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0'; + + const NAMESPACE_OOO = 'http://openoffice.org/2004/office'; + /** * Shared Expressions. * @@ -29,8 +41,30 @@ class Gnumeric extends BaseReader */ private $expressions = []; + /** + * Spreadsheet shared across all functions. + * + * @var Spreadsheet + */ + private $spreadsheet; + + /** @var ReferenceHelper */ private $referenceHelper; + /** @var array */ + public static $mappings = [ + 'dataType' => [ + '10' => DataType::TYPE_NULL, + '20' => DataType::TYPE_BOOL, + '30' => DataType::TYPE_NUMERIC, // Integer doesn't exist in Excel + '40' => DataType::TYPE_NUMERIC, // Float + '50' => DataType::TYPE_ERROR, + '60' => DataType::TYPE_STRING, + //'70': // Cell Range + //'80': // Array + ], + ]; + /** * Create a new Gnumeric. */ @@ -43,51 +77,50 @@ class Gnumeric extends BaseReader /** * Can the current IReader read the file? - * - * @param string $pFilename - * - * @throws Exception - * - * @return bool */ - public function canRead($pFilename) + public function canRead(string $filename): bool { - File::assertFile($pFilename); - // Check if gzlib functions are available - if (!function_exists('gzread')) { - throw new Exception('gzlib library is not enabled'); + if (File::testFileNoThrow($filename) && function_exists('gzread')) { + // Read signature data (first 3 bytes) + $fh = fopen($filename, 'rb'); + if ($fh !== false) { + $data = fread($fh, 2); + fclose($fh); + } } - // Read signature data (first 3 bytes) - $fh = fopen($pFilename, 'r'); - $data = fread($fh, 2); - fclose($fh); + return isset($data) && $data === chr(0x1F) . chr(0x8B); + } - return $data == chr(0x1F) . chr(0x8B); + private static function matchXml(XMLReader $xml, string $expectedLocalName): bool + { + return $xml->namespaceURI === self::NAMESPACE_GNM + && $xml->localName === $expectedLocalName + && $xml->nodeType === XMLReader::ELEMENT; } /** * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object. * - * @param string $pFilename + * @param string $filename * * @return array */ - public function listWorksheetNames($pFilename) + public function listWorksheetNames($filename) { - File::assertFile($pFilename); + File::assertFile($filename); $xml = new XMLReader(); - $xml->xml($this->securityScanner->scanFile('compress.zlib://' . realpath($pFilename)), null, Settings::getLibXmlLoaderOptions()); + $xml->xml($this->securityScanner->scanFile('compress.zlib://' . realpath($filename)), null, Settings::getLibXmlLoaderOptions()); $xml->setParserProperty(2, true); $worksheetNames = []; while ($xml->read()) { - if ($xml->name == 'gnm:SheetName' && $xml->nodeType == XMLReader::ELEMENT) { + if (self::matchXml($xml, 'SheetName')) { $xml->read(); // Move onto the value node $worksheetNames[] = (string) $xml->value; - } elseif ($xml->name == 'gnm:Sheets') { + } elseif (self::matchXml($xml, 'Sheets')) { // break out of the loop once we've got our sheet names rather than parse the entire file break; } @@ -99,21 +132,21 @@ class Gnumeric extends BaseReader /** * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns). * - * @param string $pFilename + * @param string $filename * * @return array */ - public function listWorksheetInfo($pFilename) + public function listWorksheetInfo($filename) { - File::assertFile($pFilename); + File::assertFile($filename); $xml = new XMLReader(); - $xml->xml($this->securityScanner->scanFile('compress.zlib://' . realpath($pFilename)), null, Settings::getLibXmlLoaderOptions()); + $xml->xml($this->securityScanner->scanFile('compress.zlib://' . realpath($filename)), null, Settings::getLibXmlLoaderOptions()); $xml->setParserProperty(2, true); $worksheetInfo = []; while ($xml->read()) { - if ($xml->name == 'gnm:Sheet' && $xml->nodeType == XMLReader::ELEMENT) { + if (self::matchXml($xml, 'Sheet')) { $tmpInfo = [ 'worksheetName' => '', 'lastColumnLetter' => 'A', @@ -123,14 +156,14 @@ class Gnumeric extends BaseReader ]; while ($xml->read()) { - if ($xml->name == 'gnm:Name' && $xml->nodeType == XMLReader::ELEMENT) { + if (self::matchXml($xml, 'Name')) { $xml->read(); // Move onto the value node $tmpInfo['worksheetName'] = (string) $xml->value; - } elseif ($xml->name == 'gnm:MaxCol' && $xml->nodeType == XMLReader::ELEMENT) { + } elseif (self::matchXml($xml, 'MaxCol')) { $xml->read(); // Move onto the value node $tmpInfo['lastColumnIndex'] = (int) $xml->value; $tmpInfo['totalColumns'] = (int) $xml->value + 1; - } elseif ($xml->name == 'gnm:MaxRow' && $xml->nodeType == XMLReader::ELEMENT) { + } elseif (self::matchXml($xml, 'MaxRow')) { $xml->read(); // Move onto the value node $tmpInfo['totalRows'] = (int) $xml->value + 1; @@ -164,236 +197,100 @@ class Gnumeric extends BaseReader return $data; } + public static function gnumericMappings(): array + { + return array_merge(self::$mappings, Styles::$mappings); + } + + private function processComments(SimpleXMLElement $sheet): void + { + if ((!$this->readDataOnly) && (isset($sheet->Objects))) { + foreach ($sheet->Objects->children(self::NAMESPACE_GNM) as $key => $comment) { + $commentAttributes = $comment->attributes(); + // Only comment objects are handled at the moment + if ($commentAttributes && $commentAttributes->Text) { + $this->spreadsheet->getActiveSheet()->getComment((string) $commentAttributes->ObjectBound) + ->setAuthor((string) $commentAttributes->Author) + ->setText($this->parseRichText((string) $commentAttributes->Text)); + } + } + } + } + + /** + * @param mixed $value + */ + private static function testSimpleXml($value): SimpleXMLElement + { + return ($value instanceof SimpleXMLElement) ? $value : new SimpleXMLElement(''); + } + /** * Loads Spreadsheet from file. - * - * @param string $pFilename - * - * @throws Exception - * - * @return Spreadsheet */ - public function load($pFilename) + protected function loadSpreadsheetFromFile(string $filename): Spreadsheet { // Create new Spreadsheet $spreadsheet = new Spreadsheet(); + $spreadsheet->removeSheetByIndex(0); // Load into this instance - return $this->loadIntoExisting($pFilename, $spreadsheet); + return $this->loadIntoExisting($filename, $spreadsheet); } /** * Loads from file into Spreadsheet instance. - * - * @param string $pFilename - * @param Spreadsheet $spreadsheet - * - * @throws Exception - * - * @return Spreadsheet */ - public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet) + public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Spreadsheet { - File::assertFile($pFilename); + $this->spreadsheet = $spreadsheet; + File::assertFile($filename); - $gFileData = $this->gzfileGetContents($pFilename); + $gFileData = $this->gzfileGetContents($filename); - $xml = simplexml_load_string($this->securityScanner->scan($gFileData), 'SimpleXMLElement', Settings::getLibXmlLoaderOptions()); - $namespacesMeta = $xml->getNamespaces(true); + $xml2 = simplexml_load_string($this->securityScanner->scan($gFileData), 'SimpleXMLElement', Settings::getLibXmlLoaderOptions()); + $xml = self::testSimpleXml($xml2); - $gnmXML = $xml->children($namespacesMeta['gnm']); - - $docProps = $spreadsheet->getProperties(); - // Document Properties are held differently, depending on the version of Gnumeric - if (isset($namespacesMeta['office'])) { - $officeXML = $xml->children($namespacesMeta['office']); - $officeDocXML = $officeXML->{'document-meta'}; - $officeDocMetaXML = $officeDocXML->meta; - - foreach ($officeDocMetaXML as $officePropertyData) { - $officePropertyDC = []; - if (isset($namespacesMeta['dc'])) { - $officePropertyDC = $officePropertyData->children($namespacesMeta['dc']); - } - foreach ($officePropertyDC as $propertyName => $propertyValue) { - $propertyValue = (string) $propertyValue; - switch ($propertyName) { - case 'title': - $docProps->setTitle(trim($propertyValue)); - - break; - case 'subject': - $docProps->setSubject(trim($propertyValue)); - - break; - case 'creator': - $docProps->setCreator(trim($propertyValue)); - $docProps->setLastModifiedBy(trim($propertyValue)); - - break; - case 'date': - $creationDate = strtotime(trim($propertyValue)); - $docProps->setCreated($creationDate); - $docProps->setModified($creationDate); - - break; - case 'description': - $docProps->setDescription(trim($propertyValue)); - - break; - } - } - $officePropertyMeta = []; - if (isset($namespacesMeta['meta'])) { - $officePropertyMeta = $officePropertyData->children($namespacesMeta['meta']); - } - foreach ($officePropertyMeta as $propertyName => $propertyValue) { - $attributes = $propertyValue->attributes($namespacesMeta['meta']); - $propertyValue = (string) $propertyValue; - switch ($propertyName) { - case 'keyword': - $docProps->setKeywords(trim($propertyValue)); - - break; - case 'initial-creator': - $docProps->setCreator(trim($propertyValue)); - $docProps->setLastModifiedBy(trim($propertyValue)); - - break; - case 'creation-date': - $creationDate = strtotime(trim($propertyValue)); - $docProps->setCreated($creationDate); - $docProps->setModified($creationDate); - - break; - case 'user-defined': - [, $attrName] = explode(':', $attributes['name']); - switch ($attrName) { - case 'publisher': - $docProps->setCompany(trim($propertyValue)); - - break; - case 'category': - $docProps->setCategory(trim($propertyValue)); - - break; - case 'manager': - $docProps->setManager(trim($propertyValue)); - - break; - } - - break; - } - } - } - } elseif (isset($gnmXML->Summary)) { - foreach ($gnmXML->Summary->Item as $summaryItem) { - $propertyName = $summaryItem->name; - $propertyValue = $summaryItem->{'val-string'}; - switch ($propertyName) { - case 'title': - $docProps->setTitle(trim($propertyValue)); - - break; - case 'comments': - $docProps->setDescription(trim($propertyValue)); - - break; - case 'keywords': - $docProps->setKeywords(trim($propertyValue)); - - break; - case 'category': - $docProps->setCategory(trim($propertyValue)); - - break; - case 'manager': - $docProps->setManager(trim($propertyValue)); - - break; - case 'author': - $docProps->setCreator(trim($propertyValue)); - $docProps->setLastModifiedBy(trim($propertyValue)); - - break; - case 'company': - $docProps->setCompany(trim($propertyValue)); - - break; - } - } - } + $gnmXML = $xml->children(self::NAMESPACE_GNM); + (new Properties($this->spreadsheet))->readProperties($xml, $gnmXML); $worksheetID = 0; - foreach ($gnmXML->Sheets->Sheet as $sheet) { + foreach ($gnmXML->Sheets->Sheet as $sheetOrNull) { + $sheet = self::testSimpleXml($sheetOrNull); $worksheetName = (string) $sheet->Name; - if ((isset($this->loadSheetsOnly)) && (!in_array($worksheetName, $this->loadSheetsOnly))) { + if (is_array($this->loadSheetsOnly) && !in_array($worksheetName, $this->loadSheetsOnly, true)) { continue; } $maxRow = $maxCol = 0; // Create new Worksheet - $spreadsheet->createSheet(); - $spreadsheet->setActiveSheetIndex($worksheetID); + $this->spreadsheet->createSheet(); + $this->spreadsheet->setActiveSheetIndex($worksheetID); // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula // cells... during the load, all formulae should be correct, and we're simply bringing the worksheet // name in line with the formula, not the reverse - $spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false); + $this->spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false); - if ((!$this->readDataOnly) && (isset($sheet->PrintInformation))) { - if (isset($sheet->PrintInformation->Margins)) { - foreach ($sheet->PrintInformation->Margins->children('gnm', true) as $key => $margin) { - $marginAttributes = $margin->attributes(); - $marginSize = 72 / 100; // Default - switch ($marginAttributes['PrefUnit']) { - case 'mm': - $marginSize = (int) ($marginAttributes['Points']) / 100; - - break; - } - switch ($key) { - case 'top': - $spreadsheet->getActiveSheet()->getPageMargins()->setTop($marginSize); - - break; - case 'bottom': - $spreadsheet->getActiveSheet()->getPageMargins()->setBottom($marginSize); - - break; - case 'left': - $spreadsheet->getActiveSheet()->getPageMargins()->setLeft($marginSize); - - break; - case 'right': - $spreadsheet->getActiveSheet()->getPageMargins()->setRight($marginSize); - - break; - case 'header': - $spreadsheet->getActiveSheet()->getPageMargins()->setHeader($marginSize); - - break; - case 'footer': - $spreadsheet->getActiveSheet()->getPageMargins()->setFooter($marginSize); - - break; - } - } - } + $visibility = $sheet->attributes()['Visibility'] ?? 'GNM_SHEET_VISIBILITY_VISIBLE'; + if ((string) $visibility !== 'GNM_SHEET_VISIBILITY_VISIBLE') { + $this->spreadsheet->getActiveSheet()->setSheetState(Worksheet::SHEETSTATE_HIDDEN); } - foreach ($sheet->Cells->Cell as $cell) { - $cellAttributes = $cell->attributes(); + if (!$this->readDataOnly) { + (new PageSetup($this->spreadsheet)) + ->printInformation($sheet) + ->sheetMargins($sheet); + } + + foreach ($sheet->Cells->Cell as $cellOrNull) { + $cell = self::testSimpleXml($cellOrNull); + $cellAttributes = self::testSimpleXml($cell->attributes()); $row = (int) $cellAttributes->Row + 1; $column = (int) $cellAttributes->Col; - if ($row > $maxRow) { - $maxRow = $row; - } - if ($column > $maxCol) { - $maxCol = $column; - } + $maxRow = max($maxRow, $row); + $maxCol = max($maxCol, $column); $column = Coordinate::stringFromColumnIndex($column + 1); @@ -404,472 +301,235 @@ class Gnumeric extends BaseReader } } - $ValueType = $cellAttributes->ValueType; - $ExprID = (string) $cellAttributes->ExprID; - $type = DataType::TYPE_FORMULA; - if ($ExprID > '') { - if (((string) $cell) > '') { - $this->expressions[$ExprID] = [ - 'column' => $cellAttributes->Col, - 'row' => $cellAttributes->Row, - 'formula' => (string) $cell, - ]; - } else { - $expression = $this->expressions[$ExprID]; - - $cell = $this->referenceHelper->updateFormulaReferences( - $expression['formula'], - 'A1', - $cellAttributes->Col - $expression['column'], - $cellAttributes->Row - $expression['row'], - $worksheetName - ); - } - $type = DataType::TYPE_FORMULA; - } else { - switch ($ValueType) { - case '10': // NULL - $type = DataType::TYPE_NULL; - - break; - case '20': // Boolean - $type = DataType::TYPE_BOOL; - $cell = $cell == 'TRUE'; - - break; - case '30': // Integer - $cell = (int) $cell; - // Excel 2007+ doesn't differentiate between integer and float, so set the value and dropthru to the next (numeric) case - // no break - case '40': // Float - $type = DataType::TYPE_NUMERIC; - - break; - case '50': // Error - $type = DataType::TYPE_ERROR; - - break; - case '60': // String - $type = DataType::TYPE_STRING; - - break; - case '70': // Cell Range - case '80': // Array - } - } - $spreadsheet->getActiveSheet()->getCell($column . $row)->setValueExplicit($cell, $type); + $this->loadCell($cell, $worksheetName, $cellAttributes, $column, $row); } - if ((!$this->readDataOnly) && (isset($sheet->Objects))) { - foreach ($sheet->Objects->children('gnm', true) as $key => $comment) { - $commentAttributes = $comment->attributes(); - // Only comment objects are handled at the moment - if ($commentAttributes->Text) { - $spreadsheet->getActiveSheet()->getComment((string) $commentAttributes->ObjectBound)->setAuthor((string) $commentAttributes->Author)->setText($this->parseRichText((string) $commentAttributes->Text)); - } - } - } - foreach ($sheet->Styles->StyleRegion as $styleRegion) { - $styleAttributes = $styleRegion->attributes(); - if (($styleAttributes['startRow'] <= $maxRow) && - ($styleAttributes['startCol'] <= $maxCol)) { - $startColumn = Coordinate::stringFromColumnIndex((int) $styleAttributes['startCol'] + 1); - $startRow = $styleAttributes['startRow'] + 1; - - $endColumn = ($styleAttributes['endCol'] > $maxCol) ? $maxCol : (int) $styleAttributes['endCol']; - $endColumn = Coordinate::stringFromColumnIndex($endColumn + 1); - $endRow = ($styleAttributes['endRow'] > $maxRow) ? $maxRow : $styleAttributes['endRow']; - $endRow += 1; - $cellRange = $startColumn . $startRow . ':' . $endColumn . $endRow; - - $styleAttributes = $styleRegion->Style->attributes(); - - // We still set the number format mask for date/time values, even if readDataOnly is true - if ((!$this->readDataOnly) || - (Date::isDateTimeFormatCode((string) $styleAttributes['Format']))) { - $styleArray = []; - $styleArray['numberFormat']['formatCode'] = (string) $styleAttributes['Format']; - // If readDataOnly is false, we set all formatting information - if (!$this->readDataOnly) { - switch ($styleAttributes['HAlign']) { - case '1': - $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_GENERAL; - - break; - case '2': - $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_LEFT; - - break; - case '4': - $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_RIGHT; - - break; - case '8': - $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_CENTER; - - break; - case '16': - case '64': - $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_CENTER_CONTINUOUS; - - break; - case '32': - $styleArray['alignment']['horizontal'] = Alignment::HORIZONTAL_JUSTIFY; - - break; - } - - switch ($styleAttributes['VAlign']) { - case '1': - $styleArray['alignment']['vertical'] = Alignment::VERTICAL_TOP; - - break; - case '2': - $styleArray['alignment']['vertical'] = Alignment::VERTICAL_BOTTOM; - - break; - case '4': - $styleArray['alignment']['vertical'] = Alignment::VERTICAL_CENTER; - - break; - case '8': - $styleArray['alignment']['vertical'] = Alignment::VERTICAL_JUSTIFY; - - break; - } - - $styleArray['alignment']['wrapText'] = $styleAttributes['WrapText'] == '1'; - $styleArray['alignment']['shrinkToFit'] = $styleAttributes['ShrinkToFit'] == '1'; - $styleArray['alignment']['indent'] = ((int) ($styleAttributes['Indent']) > 0) ? $styleAttributes['indent'] : 0; - - $RGB = self::parseGnumericColour($styleAttributes['Fore']); - $styleArray['font']['color']['rgb'] = $RGB; - $RGB = self::parseGnumericColour($styleAttributes['Back']); - $shade = $styleAttributes['Shade']; - if (($RGB != '000000') || ($shade != '0')) { - $styleArray['fill']['color']['rgb'] = $styleArray['fill']['startColor']['rgb'] = $RGB; - $RGB2 = self::parseGnumericColour($styleAttributes['PatternColor']); - $styleArray['fill']['endColor']['rgb'] = $RGB2; - switch ($shade) { - case '1': - $styleArray['fill']['fillType'] = Fill::FILL_SOLID; - - break; - case '2': - $styleArray['fill']['fillType'] = Fill::FILL_GRADIENT_LINEAR; - - break; - case '3': - $styleArray['fill']['fillType'] = Fill::FILL_GRADIENT_PATH; - - break; - case '4': - $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKDOWN; - - break; - case '5': - $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKGRAY; - - break; - case '6': - $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKGRID; - - break; - case '7': - $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKHORIZONTAL; - - break; - case '8': - $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKTRELLIS; - - break; - case '9': - $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKUP; - - break; - case '10': - $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_DARKVERTICAL; - - break; - case '11': - $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_GRAY0625; - - break; - case '12': - $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_GRAY125; - - break; - case '13': - $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTDOWN; - - break; - case '14': - $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTGRAY; - - break; - case '15': - $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTGRID; - - break; - case '16': - $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTHORIZONTAL; - - break; - case '17': - $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTTRELLIS; - - break; - case '18': - $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTUP; - - break; - case '19': - $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_LIGHTVERTICAL; - - break; - case '20': - $styleArray['fill']['fillType'] = Fill::FILL_PATTERN_MEDIUMGRAY; - - break; - } - } - - $fontAttributes = $styleRegion->Style->Font->attributes(); - $styleArray['font']['name'] = (string) $styleRegion->Style->Font; - $styleArray['font']['size'] = (int) ($fontAttributes['Unit']); - $styleArray['font']['bold'] = $fontAttributes['Bold'] == '1'; - $styleArray['font']['italic'] = $fontAttributes['Italic'] == '1'; - $styleArray['font']['strikethrough'] = $fontAttributes['StrikeThrough'] == '1'; - switch ($fontAttributes['Underline']) { - case '1': - $styleArray['font']['underline'] = Font::UNDERLINE_SINGLE; - - break; - case '2': - $styleArray['font']['underline'] = Font::UNDERLINE_DOUBLE; - - break; - case '3': - $styleArray['font']['underline'] = Font::UNDERLINE_SINGLEACCOUNTING; - - break; - case '4': - $styleArray['font']['underline'] = Font::UNDERLINE_DOUBLEACCOUNTING; - - break; - default: - $styleArray['font']['underline'] = Font::UNDERLINE_NONE; - - break; - } - switch ($fontAttributes['Script']) { - case '1': - $styleArray['font']['superscript'] = true; - - break; - case '-1': - $styleArray['font']['subscript'] = true; - - break; - } - - if (isset($styleRegion->Style->StyleBorder)) { - if (isset($styleRegion->Style->StyleBorder->Top)) { - $styleArray['borders']['top'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Top->attributes()); - } - if (isset($styleRegion->Style->StyleBorder->Bottom)) { - $styleArray['borders']['bottom'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Bottom->attributes()); - } - if (isset($styleRegion->Style->StyleBorder->Left)) { - $styleArray['borders']['left'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Left->attributes()); - } - if (isset($styleRegion->Style->StyleBorder->Right)) { - $styleArray['borders']['right'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Right->attributes()); - } - if ((isset($styleRegion->Style->StyleBorder->Diagonal)) && (isset($styleRegion->Style->StyleBorder->{'Rev-Diagonal'}))) { - $styleArray['borders']['diagonal'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Diagonal->attributes()); - $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_BOTH; - } elseif (isset($styleRegion->Style->StyleBorder->Diagonal)) { - $styleArray['borders']['diagonal'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->Diagonal->attributes()); - $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_UP; - } elseif (isset($styleRegion->Style->StyleBorder->{'Rev-Diagonal'})) { - $styleArray['borders']['diagonal'] = self::parseBorderAttributes($styleRegion->Style->StyleBorder->{'Rev-Diagonal'}->attributes()); - $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_DOWN; - } - } - if (isset($styleRegion->Style->HyperLink)) { - // TO DO - $hyperlink = $styleRegion->Style->HyperLink->attributes(); - } - } - $spreadsheet->getActiveSheet()->getStyle($cellRange)->applyFromArray($styleArray); - } - } + if ($sheet->Styles !== null) { + (new Styles($this->spreadsheet, $this->readDataOnly))->read($sheet, $maxRow, $maxCol); } - if ((!$this->readDataOnly) && (isset($sheet->Cols))) { - // Column Widths - $columnAttributes = $sheet->Cols->attributes(); - $defaultWidth = $columnAttributes['DefaultSizePts'] / 5.4; - $c = 0; - foreach ($sheet->Cols->ColInfo as $columnOverride) { - $columnAttributes = $columnOverride->attributes(); - $column = $columnAttributes['No']; - $columnWidth = $columnAttributes['Unit'] / 5.4; - $hidden = (isset($columnAttributes['Hidden'])) && ($columnAttributes['Hidden'] == '1'); - $columnCount = (isset($columnAttributes['Count'])) ? $columnAttributes['Count'] : 1; - while ($c < $column) { - $spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($defaultWidth); - ++$c; - } - while (($c < ($column + $columnCount)) && ($c <= $maxCol)) { - $spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($columnWidth); - if ($hidden) { - $spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setVisible(false); - } - ++$c; - } - } - while ($c <= $maxCol) { - $spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($c + 1))->setWidth($defaultWidth); - ++$c; - } - } - - if ((!$this->readDataOnly) && (isset($sheet->Rows))) { - // Row Heights - $rowAttributes = $sheet->Rows->attributes(); - $defaultHeight = $rowAttributes['DefaultSizePts']; - $r = 0; - - foreach ($sheet->Rows->RowInfo as $rowOverride) { - $rowAttributes = $rowOverride->attributes(); - $row = $rowAttributes['No']; - $rowHeight = $rowAttributes['Unit']; - $hidden = (isset($rowAttributes['Hidden'])) && ($rowAttributes['Hidden'] == '1'); - $rowCount = (isset($rowAttributes['Count'])) ? $rowAttributes['Count'] : 1; - while ($r < $row) { - ++$r; - $spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($defaultHeight); - } - while (($r < ($row + $rowCount)) && ($r < $maxRow)) { - ++$r; - $spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($rowHeight); - if ($hidden) { - $spreadsheet->getActiveSheet()->getRowDimension($r)->setVisible(false); - } - } - } - while ($r < $maxRow) { - ++$r; - $spreadsheet->getActiveSheet()->getRowDimension($r)->setRowHeight($defaultHeight); - } - } - - // Handle Merged Cells in this worksheet - if (isset($sheet->MergedRegions)) { - foreach ($sheet->MergedRegions->Merge as $mergeCells) { - if (strpos($mergeCells, ':') !== false) { - $spreadsheet->getActiveSheet()->mergeCells($mergeCells); - } - } - } + $this->processComments($sheet); + $this->processColumnWidths($sheet, $maxCol); + $this->processRowHeights($sheet, $maxRow); + $this->processMergedCells($sheet); + $this->processAutofilter($sheet); + $this->setSelectedCells($sheet); ++$worksheetID; } - // Loop through definedNames (global named ranges) - if (isset($gnmXML->Names)) { - foreach ($gnmXML->Names->Name as $namedRange) { - $name = (string) $namedRange->name; - $range = (string) $namedRange->value; - if (stripos($range, '#REF!') !== false) { - continue; - } + $this->processDefinedNames($gnmXML); - $range = Worksheet::extractSheetTitle($range, true); - $range[0] = trim($range[0], "'"); - if ($worksheet = $spreadsheet->getSheetByName($range[0])) { - $extractedRange = str_replace('$', '', $range[1]); - $spreadsheet->addNamedRange(new NamedRange($name, $worksheet, $extractedRange)); + $this->setSelectedSheet($gnmXML); + + // Return + return $this->spreadsheet; + } + + private function setSelectedSheet(SimpleXMLElement $gnmXML): void + { + if (isset($gnmXML->UIData)) { + $attributes = self::testSimpleXml($gnmXML->UIData->attributes()); + $selectedSheet = (int) $attributes['SelectedTab']; + $this->spreadsheet->setActiveSheetIndex($selectedSheet); + } + } + + private function setSelectedCells(?SimpleXMLElement $sheet): void + { + if ($sheet !== null && isset($sheet->Selections)) { + foreach ($sheet->Selections as $selection) { + $startCol = (int) ($selection->StartCol ?? 0); + $startRow = (int) ($selection->StartRow ?? 0) + 1; + $endCol = (int) ($selection->EndCol ?? $startCol); + $endRow = (int) ($selection->endRow ?? 0) + 1; + + $startColumn = Coordinate::stringFromColumnIndex($startCol + 1); + $endColumn = Coordinate::stringFromColumnIndex($endCol + 1); + + $startCell = "{$startColumn}{$startRow}"; + $endCell = "{$endColumn}{$endRow}"; + $selectedRange = $startCell . (($endCell !== $startCell) ? ':' . $endCell : ''); + $this->spreadsheet->getActiveSheet()->setSelectedCell($selectedRange); + + break; + } + } + } + + private function processMergedCells(?SimpleXMLElement $sheet): void + { + // Handle Merged Cells in this worksheet + if ($sheet !== null && isset($sheet->MergedRegions)) { + foreach ($sheet->MergedRegions->Merge as $mergeCells) { + if (strpos((string) $mergeCells, ':') !== false) { + $this->spreadsheet->getActiveSheet()->mergeCells($mergeCells, Worksheet::MERGE_CELL_CONTENT_HIDE); } } } - - // Return - return $spreadsheet; } - private static function parseBorderAttributes($borderAttributes) + private function processAutofilter(?SimpleXMLElement $sheet): void { - $styleArray = []; - if (isset($borderAttributes['Color'])) { - $styleArray['color']['rgb'] = self::parseGnumericColour($borderAttributes['Color']); + if ($sheet !== null && isset($sheet->Filters)) { + foreach ($sheet->Filters->Filter as $autofilter) { + if ($autofilter !== null) { + $attributes = $autofilter->attributes(); + if (isset($attributes['Area'])) { + $this->spreadsheet->getActiveSheet()->setAutoFilter((string) $attributes['Area']); + } + } + } } - - switch ($borderAttributes['Style']) { - case '0': - $styleArray['borderStyle'] = Border::BORDER_NONE; - - break; - case '1': - $styleArray['borderStyle'] = Border::BORDER_THIN; - - break; - case '2': - $styleArray['borderStyle'] = Border::BORDER_MEDIUM; - - break; - case '3': - $styleArray['borderStyle'] = Border::BORDER_SLANTDASHDOT; - - break; - case '4': - $styleArray['borderStyle'] = Border::BORDER_DASHED; - - break; - case '5': - $styleArray['borderStyle'] = Border::BORDER_THICK; - - break; - case '6': - $styleArray['borderStyle'] = Border::BORDER_DOUBLE; - - break; - case '7': - $styleArray['borderStyle'] = Border::BORDER_DOTTED; - - break; - case '8': - $styleArray['borderStyle'] = Border::BORDER_MEDIUMDASHED; - - break; - case '9': - $styleArray['borderStyle'] = Border::BORDER_DASHDOT; - - break; - case '10': - $styleArray['borderStyle'] = Border::BORDER_MEDIUMDASHDOT; - - break; - case '11': - $styleArray['borderStyle'] = Border::BORDER_DASHDOTDOT; - - break; - case '12': - $styleArray['borderStyle'] = Border::BORDER_MEDIUMDASHDOTDOT; - - break; - case '13': - $styleArray['borderStyle'] = Border::BORDER_MEDIUMDASHDOTDOT; - - break; - } - - return $styleArray; } - private function parseRichText($is) + private function setColumnWidth(int $whichColumn, float $defaultWidth): void + { + $columnDimension = $this->spreadsheet->getActiveSheet() + ->getColumnDimension(Coordinate::stringFromColumnIndex($whichColumn + 1)); + if ($columnDimension !== null) { + $columnDimension->setWidth($defaultWidth); + } + } + + private function setColumnInvisible(int $whichColumn): void + { + $columnDimension = $this->spreadsheet->getActiveSheet() + ->getColumnDimension(Coordinate::stringFromColumnIndex($whichColumn + 1)); + if ($columnDimension !== null) { + $columnDimension->setVisible(false); + } + } + + private function processColumnLoop(int $whichColumn, int $maxCol, ?SimpleXMLElement $columnOverride, float $defaultWidth): int + { + $columnOverride = self::testSimpleXml($columnOverride); + $columnAttributes = self::testSimpleXml($columnOverride->attributes()); + $column = $columnAttributes['No']; + $columnWidth = ((float) $columnAttributes['Unit']) / 5.4; + $hidden = (isset($columnAttributes['Hidden'])) && ((string) $columnAttributes['Hidden'] == '1'); + $columnCount = (int) ($columnAttributes['Count'] ?? 1); + while ($whichColumn < $column) { + $this->setColumnWidth($whichColumn, $defaultWidth); + ++$whichColumn; + } + while (($whichColumn < ($column + $columnCount)) && ($whichColumn <= $maxCol)) { + $this->setColumnWidth($whichColumn, $columnWidth); + if ($hidden) { + $this->setColumnInvisible($whichColumn); + } + ++$whichColumn; + } + + return $whichColumn; + } + + private function processColumnWidths(?SimpleXMLElement $sheet, int $maxCol): void + { + if ((!$this->readDataOnly) && $sheet !== null && (isset($sheet->Cols))) { + // Column Widths + $defaultWidth = 0; + $columnAttributes = $sheet->Cols->attributes(); + if ($columnAttributes !== null) { + $defaultWidth = $columnAttributes['DefaultSizePts'] / 5.4; + } + $whichColumn = 0; + foreach ($sheet->Cols->ColInfo as $columnOverride) { + $whichColumn = $this->processColumnLoop($whichColumn, $maxCol, $columnOverride, $defaultWidth); + } + while ($whichColumn <= $maxCol) { + $this->setColumnWidth($whichColumn, $defaultWidth); + ++$whichColumn; + } + } + } + + private function setRowHeight(int $whichRow, float $defaultHeight): void + { + $rowDimension = $this->spreadsheet->getActiveSheet()->getRowDimension($whichRow); + if ($rowDimension !== null) { + $rowDimension->setRowHeight($defaultHeight); + } + } + + private function setRowInvisible(int $whichRow): void + { + $rowDimension = $this->spreadsheet->getActiveSheet()->getRowDimension($whichRow); + if ($rowDimension !== null) { + $rowDimension->setVisible(false); + } + } + + private function processRowLoop(int $whichRow, int $maxRow, ?SimpleXMLElement $rowOverride, float $defaultHeight): int + { + $rowOverride = self::testSimpleXml($rowOverride); + $rowAttributes = self::testSimpleXml($rowOverride->attributes()); + $row = $rowAttributes['No']; + $rowHeight = (float) $rowAttributes['Unit']; + $hidden = (isset($rowAttributes['Hidden'])) && ((string) $rowAttributes['Hidden'] == '1'); + $rowCount = (int) ($rowAttributes['Count'] ?? 1); + while ($whichRow < $row) { + ++$whichRow; + $this->setRowHeight($whichRow, $defaultHeight); + } + while (($whichRow < ($row + $rowCount)) && ($whichRow < $maxRow)) { + ++$whichRow; + $this->setRowHeight($whichRow, $rowHeight); + if ($hidden) { + $this->setRowInvisible($whichRow); + } + } + + return $whichRow; + } + + private function processRowHeights(?SimpleXMLElement $sheet, int $maxRow): void + { + if ((!$this->readDataOnly) && $sheet !== null && (isset($sheet->Rows))) { + // Row Heights + $defaultHeight = 0; + $rowAttributes = $sheet->Rows->attributes(); + if ($rowAttributes !== null) { + $defaultHeight = (float) $rowAttributes['DefaultSizePts']; + } + $whichRow = 0; + + foreach ($sheet->Rows->RowInfo as $rowOverride) { + $whichRow = $this->processRowLoop($whichRow, $maxRow, $rowOverride, $defaultHeight); + } + // never executed, I can't figure out any circumstances + // under which it would be executed, and, even if + // such exist, I'm not convinced this is needed. + //while ($whichRow < $maxRow) { + // ++$whichRow; + // $this->spreadsheet->getActiveSheet()->getRowDimension($whichRow)->setRowHeight($defaultHeight); + //} + } + } + + private function processDefinedNames(?SimpleXMLElement $gnmXML): void + { + // Loop through definedNames (global named ranges) + if ($gnmXML !== null && isset($gnmXML->Names)) { + foreach ($gnmXML->Names->Name as $definedName) { + $name = (string) $definedName->name; + $value = (string) $definedName->value; + if (stripos($value, '#REF!') !== false) { + continue; + } + + [$worksheetName] = Worksheet::extractSheetTitle($value, true); + $worksheetName = trim($worksheetName, "'"); + $worksheet = $this->spreadsheet->getSheetByName($worksheetName); + // Worksheet might still be null if we're only loading selected sheets rather than the full spreadsheet + if ($worksheet !== null) { + $this->spreadsheet->addDefinedName(DefinedName::createInstance($name, $worksheet, $value)); + } + } + } + } + + private function parseRichText(string $is): RichText { $value = new RichText(); $value->createText($is); @@ -877,13 +537,50 @@ class Gnumeric extends BaseReader return $value; } - private static function parseGnumericColour($gnmColour) - { - [$gnmR, $gnmG, $gnmB] = explode(':', $gnmColour); - $gnmR = substr(str_pad($gnmR, 4, '0', STR_PAD_RIGHT), 0, 2); - $gnmG = substr(str_pad($gnmG, 4, '0', STR_PAD_RIGHT), 0, 2); - $gnmB = substr(str_pad($gnmB, 4, '0', STR_PAD_RIGHT), 0, 2); + private function loadCell( + SimpleXMLElement $cell, + string $worksheetName, + SimpleXMLElement $cellAttributes, + string $column, + int $row + ): void { + $ValueType = $cellAttributes->ValueType; + $ExprID = (string) $cellAttributes->ExprID; + $type = DataType::TYPE_FORMULA; + if ($ExprID > '') { + if (((string) $cell) > '') { + $this->expressions[$ExprID] = [ + 'column' => $cellAttributes->Col, + 'row' => $cellAttributes->Row, + 'formula' => (string) $cell, + ]; + } else { + $expression = $this->expressions[$ExprID]; - return $gnmR . $gnmG . $gnmB; + $cell = $this->referenceHelper->updateFormulaReferences( + $expression['formula'], + 'A1', + $cellAttributes->Col - $expression['column'], + $cellAttributes->Row - $expression['row'], + $worksheetName + ); + } + $type = DataType::TYPE_FORMULA; + } else { + $vtype = (string) $ValueType; + if (array_key_exists($vtype, self::$mappings['dataType'])) { + $type = self::$mappings['dataType'][$vtype]; + } + if ($vtype === '20') { // Boolean + $cell = $cell == 'TRUE'; + } + } + + $this->spreadsheet->getActiveSheet()->getCell($column . $row)->setValueExplicit((string) $cell, $type); + if (isset($cellAttributes->ValueFormat)) { + $this->spreadsheet->getActiveSheet()->getCell($column . $row) + ->getStyle()->getNumberFormat() + ->setFormatCode((string) $cellAttributes->ValueFormat); + } } } diff --git a/PhpOffice/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php b/PhpOffice/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php new file mode 100644 index 0000000..204c730 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php @@ -0,0 +1,139 @@ +spreadsheet = $spreadsheet; + } + + public function printInformation(SimpleXMLElement $sheet): self + { + if (isset($sheet->PrintInformation, $sheet->PrintInformation[0])) { + $printInformation = $sheet->PrintInformation[0]; + + $scale = (string) $printInformation->Scale->attributes()['percentage']; + $pageOrder = (string) $printInformation->order; + $orientation = (string) $printInformation->orientation; + $horizontalCentered = (string) $printInformation->hcenter->attributes()['value']; + $verticalCentered = (string) $printInformation->vcenter->attributes()['value']; + + $this->spreadsheet->getActiveSheet()->getPageSetup() + ->setPageOrder($pageOrder === 'r_then_d' ? WorksheetPageSetup::PAGEORDER_OVER_THEN_DOWN : WorksheetPageSetup::PAGEORDER_DOWN_THEN_OVER) + ->setScale((int) $scale) + ->setOrientation($orientation ?? WorksheetPageSetup::ORIENTATION_DEFAULT) + ->setHorizontalCentered((bool) $horizontalCentered) + ->setVerticalCentered((bool) $verticalCentered); + } + + return $this; + } + + public function sheetMargins(SimpleXMLElement $sheet): self + { + if (isset($sheet->PrintInformation, $sheet->PrintInformation->Margins)) { + $marginSet = [ + // Default Settings + 'top' => 0.75, + 'header' => 0.3, + 'left' => 0.7, + 'right' => 0.7, + 'bottom' => 0.75, + 'footer' => 0.3, + ]; + + $marginSet = $this->buildMarginSet($sheet, $marginSet); + $this->adjustMargins($marginSet); + } + + return $this; + } + + private function buildMarginSet(SimpleXMLElement $sheet, array $marginSet): array + { + foreach ($sheet->PrintInformation->Margins->children(Gnumeric::NAMESPACE_GNM) as $key => $margin) { + $marginAttributes = $margin->attributes(); + $marginSize = ($marginAttributes['Points']) ?? 72; // Default is 72pt + // Convert value in points to inches + $marginSize = PageMargins::fromPoints((float) $marginSize); + $marginSet[$key] = $marginSize; + } + + return $marginSet; + } + + private function adjustMargins(array $marginSet): void + { + foreach ($marginSet as $key => $marginSize) { + // Gnumeric is quirky in the way it displays the header/footer values: + // header is actually the sum of top and header; footer is actually the sum of bottom and footer + // then top is actually the header value, and bottom is actually the footer value + switch ($key) { + case 'left': + case 'right': + $this->sheetMargin($key, $marginSize); + + break; + case 'top': + $this->sheetMargin($key, $marginSet['header'] ?? 0); + + break; + case 'bottom': + $this->sheetMargin($key, $marginSet['footer'] ?? 0); + + break; + case 'header': + $this->sheetMargin($key, ($marginSet['top'] ?? 0) - $marginSize); + + break; + case 'footer': + $this->sheetMargin($key, ($marginSet['bottom'] ?? 0) - $marginSize); + + break; + } + } + } + + private function sheetMargin(string $key, float $marginSize): void + { + switch ($key) { + case 'top': + $this->spreadsheet->getActiveSheet()->getPageMargins()->setTop($marginSize); + + break; + case 'bottom': + $this->spreadsheet->getActiveSheet()->getPageMargins()->setBottom($marginSize); + + break; + case 'left': + $this->spreadsheet->getActiveSheet()->getPageMargins()->setLeft($marginSize); + + break; + case 'right': + $this->spreadsheet->getActiveSheet()->getPageMargins()->setRight($marginSize); + + break; + case 'header': + $this->spreadsheet->getActiveSheet()->getPageMargins()->setHeader($marginSize); + + break; + case 'footer': + $this->spreadsheet->getActiveSheet()->getPageMargins()->setFooter($marginSize); + + break; + } + } +} diff --git a/PhpOffice/PhpSpreadsheet/Reader/Gnumeric/Properties.php b/PhpOffice/PhpSpreadsheet/Reader/Gnumeric/Properties.php new file mode 100644 index 0000000..23d4067 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Reader/Gnumeric/Properties.php @@ -0,0 +1,164 @@ +spreadsheet = $spreadsheet; + } + + private function docPropertiesOld(SimpleXMLElement $gnmXML): void + { + $docProps = $this->spreadsheet->getProperties(); + foreach ($gnmXML->Summary->Item as $summaryItem) { + $propertyName = $summaryItem->name; + $propertyValue = $summaryItem->{'val-string'}; + switch ($propertyName) { + case 'title': + $docProps->setTitle(trim($propertyValue)); + + break; + case 'comments': + $docProps->setDescription(trim($propertyValue)); + + break; + case 'keywords': + $docProps->setKeywords(trim($propertyValue)); + + break; + case 'category': + $docProps->setCategory(trim($propertyValue)); + + break; + case 'manager': + $docProps->setManager(trim($propertyValue)); + + break; + case 'author': + $docProps->setCreator(trim($propertyValue)); + $docProps->setLastModifiedBy(trim($propertyValue)); + + break; + case 'company': + $docProps->setCompany(trim($propertyValue)); + + break; + } + } + } + + private function docPropertiesDC(SimpleXMLElement $officePropertyDC): void + { + $docProps = $this->spreadsheet->getProperties(); + foreach ($officePropertyDC as $propertyName => $propertyValue) { + $propertyValue = trim((string) $propertyValue); + switch ($propertyName) { + case 'title': + $docProps->setTitle($propertyValue); + + break; + case 'subject': + $docProps->setSubject($propertyValue); + + break; + case 'creator': + $docProps->setCreator($propertyValue); + $docProps->setLastModifiedBy($propertyValue); + + break; + case 'date': + $creationDate = $propertyValue; + $docProps->setModified($creationDate); + + break; + case 'description': + $docProps->setDescription($propertyValue); + + break; + } + } + } + + private function docPropertiesMeta(SimpleXMLElement $officePropertyMeta): void + { + $docProps = $this->spreadsheet->getProperties(); + foreach ($officePropertyMeta as $propertyName => $propertyValue) { + if ($propertyValue !== null) { + $attributes = $propertyValue->attributes(Gnumeric::NAMESPACE_META); + $propertyValue = trim((string) $propertyValue); + switch ($propertyName) { + case 'keyword': + $docProps->setKeywords($propertyValue); + + break; + case 'initial-creator': + $docProps->setCreator($propertyValue); + $docProps->setLastModifiedBy($propertyValue); + + break; + case 'creation-date': + $creationDate = $propertyValue; + $docProps->setCreated($creationDate); + + break; + case 'user-defined': + if ($attributes) { + [, $attrName] = explode(':', (string) $attributes['name']); + $this->userDefinedProperties($attrName, $propertyValue); + } + + break; + } + } + } + } + + private function userDefinedProperties(string $attrName, string $propertyValue): void + { + $docProps = $this->spreadsheet->getProperties(); + switch ($attrName) { + case 'publisher': + $docProps->setCompany($propertyValue); + + break; + case 'category': + $docProps->setCategory($propertyValue); + + break; + case 'manager': + $docProps->setManager($propertyValue); + + break; + } + } + + public function readProperties(SimpleXMLElement $xml, SimpleXMLElement $gnmXML): void + { + $officeXML = $xml->children(Gnumeric::NAMESPACE_OFFICE); + if (!empty($officeXML)) { + $officeDocXML = $officeXML->{'document-meta'}; + $officeDocMetaXML = $officeDocXML->meta; + + foreach ($officeDocMetaXML as $officePropertyData) { + $officePropertyDC = $officePropertyData->children(Gnumeric::NAMESPACE_DC); + $this->docPropertiesDC($officePropertyDC); + + $officePropertyMeta = $officePropertyData->children(Gnumeric::NAMESPACE_META); + $this->docPropertiesMeta($officePropertyMeta); + } + } elseif (isset($gnmXML->Summary)) { + $this->docPropertiesOld($gnmXML); + } + } +} diff --git a/PhpOffice/PhpSpreadsheet/Reader/Gnumeric/Styles.php b/PhpOffice/PhpSpreadsheet/Reader/Gnumeric/Styles.php new file mode 100644 index 0000000..dc44dcc --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Reader/Gnumeric/Styles.php @@ -0,0 +1,281 @@ + [ + '0' => Border::BORDER_NONE, + '1' => Border::BORDER_THIN, + '2' => Border::BORDER_MEDIUM, + '3' => Border::BORDER_SLANTDASHDOT, + '4' => Border::BORDER_DASHED, + '5' => Border::BORDER_THICK, + '6' => Border::BORDER_DOUBLE, + '7' => Border::BORDER_DOTTED, + '8' => Border::BORDER_MEDIUMDASHED, + '9' => Border::BORDER_DASHDOT, + '10' => Border::BORDER_MEDIUMDASHDOT, + '11' => Border::BORDER_DASHDOTDOT, + '12' => Border::BORDER_MEDIUMDASHDOTDOT, + '13' => Border::BORDER_MEDIUMDASHDOTDOT, + ], + 'fillType' => [ + '1' => Fill::FILL_SOLID, + '2' => Fill::FILL_PATTERN_DARKGRAY, + '3' => Fill::FILL_PATTERN_MEDIUMGRAY, + '4' => Fill::FILL_PATTERN_LIGHTGRAY, + '5' => Fill::FILL_PATTERN_GRAY125, + '6' => Fill::FILL_PATTERN_GRAY0625, + '7' => Fill::FILL_PATTERN_DARKHORIZONTAL, // horizontal stripe + '8' => Fill::FILL_PATTERN_DARKVERTICAL, // vertical stripe + '9' => Fill::FILL_PATTERN_DARKDOWN, // diagonal stripe + '10' => Fill::FILL_PATTERN_DARKUP, // reverse diagonal stripe + '11' => Fill::FILL_PATTERN_DARKGRID, // diagoanl crosshatch + '12' => Fill::FILL_PATTERN_DARKTRELLIS, // thick diagonal crosshatch + '13' => Fill::FILL_PATTERN_LIGHTHORIZONTAL, + '14' => Fill::FILL_PATTERN_LIGHTVERTICAL, + '15' => Fill::FILL_PATTERN_LIGHTUP, + '16' => Fill::FILL_PATTERN_LIGHTDOWN, + '17' => Fill::FILL_PATTERN_LIGHTGRID, // thin horizontal crosshatch + '18' => Fill::FILL_PATTERN_LIGHTTRELLIS, // thin diagonal crosshatch + ], + 'horizontal' => [ + '1' => Alignment::HORIZONTAL_GENERAL, + '2' => Alignment::HORIZONTAL_LEFT, + '4' => Alignment::HORIZONTAL_RIGHT, + '8' => Alignment::HORIZONTAL_CENTER, + '16' => Alignment::HORIZONTAL_CENTER_CONTINUOUS, + '32' => Alignment::HORIZONTAL_JUSTIFY, + '64' => Alignment::HORIZONTAL_CENTER_CONTINUOUS, + ], + 'underline' => [ + '1' => Font::UNDERLINE_SINGLE, + '2' => Font::UNDERLINE_DOUBLE, + '3' => Font::UNDERLINE_SINGLEACCOUNTING, + '4' => Font::UNDERLINE_DOUBLEACCOUNTING, + ], + 'vertical' => [ + '1' => Alignment::VERTICAL_TOP, + '2' => Alignment::VERTICAL_BOTTOM, + '4' => Alignment::VERTICAL_CENTER, + '8' => Alignment::VERTICAL_JUSTIFY, + ], + ]; + + public function __construct(Spreadsheet $spreadsheet, bool $readDataOnly) + { + $this->spreadsheet = $spreadsheet; + $this->readDataOnly = $readDataOnly; + } + + public function read(SimpleXMLElement $sheet, int $maxRow, int $maxCol): void + { + if ($sheet->Styles->StyleRegion !== null) { + $this->readStyles($sheet->Styles->StyleRegion, $maxRow, $maxCol); + } + } + + private function readStyles(SimpleXMLElement $styleRegion, int $maxRow, int $maxCol): void + { + foreach ($styleRegion as $style) { + /** @scrutinizer ignore-call */ + $styleAttributes = $style->attributes(); + if ($styleAttributes !== null && ($styleAttributes['startRow'] <= $maxRow) && ($styleAttributes['startCol'] <= $maxCol)) { + $cellRange = $this->readStyleRange($styleAttributes, $maxCol, $maxRow); + + $styleAttributes = $style->Style->attributes(); + + $styleArray = []; + // We still set the number format mask for date/time values, even if readDataOnly is true + // so that we can identify whether a float is a float or a date value + $formatCode = $styleAttributes ? (string) $styleAttributes['Format'] : null; + if ($formatCode && Date::isDateTimeFormatCode($formatCode)) { + $styleArray['numberFormat']['formatCode'] = $formatCode; + } + if ($this->readDataOnly === false && $styleAttributes !== null) { + // If readDataOnly is false, we set all formatting information + $styleArray['numberFormat']['formatCode'] = $formatCode; + $styleArray = $this->readStyle($styleArray, $styleAttributes, /** @scrutinizer ignore-type */ $style); + } + $this->spreadsheet->getActiveSheet()->getStyle($cellRange)->applyFromArray($styleArray); + } + } + } + + private function addBorderDiagonal(SimpleXMLElement $srssb, array &$styleArray): void + { + if (isset($srssb->Diagonal, $srssb->{'Rev-Diagonal'})) { + $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->Diagonal->attributes()); + $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_BOTH; + } elseif (isset($srssb->Diagonal)) { + $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->Diagonal->attributes()); + $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_UP; + } elseif (isset($srssb->{'Rev-Diagonal'})) { + $styleArray['borders']['diagonal'] = self::parseBorderAttributes($srssb->{'Rev-Diagonal'}->attributes()); + $styleArray['borders']['diagonalDirection'] = Borders::DIAGONAL_DOWN; + } + } + + private function addBorderStyle(SimpleXMLElement $srssb, array &$styleArray, string $direction): void + { + $ucDirection = ucfirst($direction); + if (isset($srssb->$ucDirection)) { + $styleArray['borders'][$direction] = self::parseBorderAttributes($srssb->$ucDirection->attributes()); + } + } + + private function calcRotation(SimpleXMLElement $styleAttributes): int + { + $rotation = (int) $styleAttributes->Rotation; + if ($rotation >= 270 && $rotation <= 360) { + $rotation -= 360; + } + $rotation = (abs($rotation) > 90) ? 0 : $rotation; + + return $rotation; + } + + private static function addStyle(array &$styleArray, string $key, string $value): void + { + if (array_key_exists($value, self::$mappings[$key])) { + $styleArray[$key] = self::$mappings[$key][$value]; + } + } + + private static function addStyle2(array &$styleArray, string $key1, string $key, string $value): void + { + if (array_key_exists($value, self::$mappings[$key])) { + $styleArray[$key1][$key] = self::$mappings[$key][$value]; + } + } + + private static function parseBorderAttributes(?SimpleXMLElement $borderAttributes): array + { + $styleArray = []; + if ($borderAttributes !== null) { + if (isset($borderAttributes['Color'])) { + $styleArray['color']['rgb'] = self::parseGnumericColour($borderAttributes['Color']); + } + + self::addStyle($styleArray, 'borderStyle', (string) $borderAttributes['Style']); + } + + return $styleArray; + } + + private static function parseGnumericColour(string $gnmColour): string + { + [$gnmR, $gnmG, $gnmB] = explode(':', $gnmColour); + $gnmR = substr(str_pad($gnmR, 4, '0', STR_PAD_RIGHT), 0, 2); + $gnmG = substr(str_pad($gnmG, 4, '0', STR_PAD_RIGHT), 0, 2); + $gnmB = substr(str_pad($gnmB, 4, '0', STR_PAD_RIGHT), 0, 2); + + return $gnmR . $gnmG . $gnmB; + } + + private function addColors(array &$styleArray, SimpleXMLElement $styleAttributes): void + { + $RGB = self::parseGnumericColour((string) $styleAttributes['Fore']); + $styleArray['font']['color']['rgb'] = $RGB; + $RGB = self::parseGnumericColour((string) $styleAttributes['Back']); + $shade = (string) $styleAttributes['Shade']; + if (($RGB !== '000000') || ($shade !== '0')) { + $RGB2 = self::parseGnumericColour((string) $styleAttributes['PatternColor']); + if ($shade === '1') { + $styleArray['fill']['startColor']['rgb'] = $RGB; + $styleArray['fill']['endColor']['rgb'] = $RGB2; + } else { + $styleArray['fill']['endColor']['rgb'] = $RGB; + $styleArray['fill']['startColor']['rgb'] = $RGB2; + } + self::addStyle2($styleArray, 'fill', 'fillType', $shade); + } + } + + private function readStyleRange(SimpleXMLElement $styleAttributes, int $maxCol, int $maxRow): string + { + $startColumn = Coordinate::stringFromColumnIndex((int) $styleAttributes['startCol'] + 1); + $startRow = $styleAttributes['startRow'] + 1; + + $endColumn = ($styleAttributes['endCol'] > $maxCol) ? $maxCol : (int) $styleAttributes['endCol']; + $endColumn = Coordinate::stringFromColumnIndex($endColumn + 1); + + $endRow = 1 + (($styleAttributes['endRow'] > $maxRow) ? $maxRow : (int) $styleAttributes['endRow']); + $cellRange = $startColumn . $startRow . ':' . $endColumn . $endRow; + + return $cellRange; + } + + private function readStyle(array $styleArray, SimpleXMLElement $styleAttributes, SimpleXMLElement $style): array + { + self::addStyle2($styleArray, 'alignment', 'horizontal', (string) $styleAttributes['HAlign']); + self::addStyle2($styleArray, 'alignment', 'vertical', (string) $styleAttributes['VAlign']); + $styleArray['alignment']['wrapText'] = $styleAttributes['WrapText'] == '1'; + $styleArray['alignment']['textRotation'] = $this->calcRotation($styleAttributes); + $styleArray['alignment']['shrinkToFit'] = $styleAttributes['ShrinkToFit'] == '1'; + $styleArray['alignment']['indent'] = ((int) ($styleAttributes['Indent']) > 0) ? $styleAttributes['indent'] : 0; + + $this->addColors($styleArray, $styleAttributes); + + $fontAttributes = $style->Style->Font->attributes(); + if ($fontAttributes !== null) { + $styleArray['font']['name'] = (string) $style->Style->Font; + $styleArray['font']['size'] = (int) ($fontAttributes['Unit']); + $styleArray['font']['bold'] = $fontAttributes['Bold'] == '1'; + $styleArray['font']['italic'] = $fontAttributes['Italic'] == '1'; + $styleArray['font']['strikethrough'] = $fontAttributes['StrikeThrough'] == '1'; + self::addStyle2($styleArray, 'font', 'underline', (string) $fontAttributes['Underline']); + + switch ($fontAttributes['Script']) { + case '1': + $styleArray['font']['superscript'] = true; + + break; + case '-1': + $styleArray['font']['subscript'] = true; + + break; + } + } + + if (isset($style->Style->StyleBorder)) { + $srssb = $style->Style->StyleBorder; + $this->addBorderStyle($srssb, $styleArray, 'top'); + $this->addBorderStyle($srssb, $styleArray, 'bottom'); + $this->addBorderStyle($srssb, $styleArray, 'left'); + $this->addBorderStyle($srssb, $styleArray, 'right'); + $this->addBorderDiagonal($srssb, $styleArray); + } + // TO DO + /* + if (isset($style->Style->HyperLink)) { + $hyperlink = $style->Style->HyperLink->attributes(); + } + */ + + return $styleArray; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Reader/Html.php b/PhpOffice/PhpSpreadsheet/Reader/Html.php old mode 100755 new mode 100644 index bf9c603..76f128e --- a/PhpOffice/PhpSpreadsheet/Reader/Html.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Html.php @@ -7,6 +7,7 @@ use DOMElement; use DOMNode; use DOMText; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; +use PhpOffice\PhpSpreadsheet\Helper\Dimension as CssDimension; use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Style\Border; @@ -16,8 +17,8 @@ use PhpOffice\PhpSpreadsheet\Style\Font; use PhpOffice\PhpSpreadsheet\Style\Style; use PhpOffice\PhpSpreadsheet\Worksheet\Drawing; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use Throwable; -/** PhpSpreadsheet root directory */ class Html extends BaseReader { /** @@ -121,6 +122,7 @@ class Html extends BaseReader ], // Italic ]; + /** @var array */ protected $rowspan = []; /** @@ -134,16 +136,12 @@ class Html extends BaseReader /** * Validate that the current file is an HTML file. - * - * @param string $pFilename - * - * @return bool */ - public function canRead($pFilename) + public function canRead(string $filename): bool { // Check if file exists try { - $this->openFile($pFilename); + $this->openFile($filename); } catch (Exception $e) { return false; } @@ -158,19 +156,19 @@ class Html extends BaseReader return $startWithTag && $containsTags && $endsWithTag; } - private function readBeginning() + private function readBeginning(): string { fseek($this->fileHandle, 0); - return fread($this->fileHandle, self::TEST_SAMPLE_SIZE); + return (string) fread($this->fileHandle, self::TEST_SAMPLE_SIZE); } - private function readEnding() + private function readEnding(): string { $meta = stream_get_meta_data($this->fileHandle); $filename = $meta['uri']; - $size = filesize($filename); + $size = (int) filesize($filename); if ($size === 0) { return ''; } @@ -182,52 +180,50 @@ class Html extends BaseReader fseek($this->fileHandle, $size - $blockSize); - return fread($this->fileHandle, $blockSize); + return (string) fread($this->fileHandle, $blockSize); } - private static function startsWithTag($data) + private static function startsWithTag(string $data): bool { return '<' === substr(trim($data), 0, 1); } - private static function endsWithTag($data) + private static function endsWithTag(string $data): bool { return '>' === substr(trim($data), -1, 1); } - private static function containsTags($data) + private static function containsTags(string $data): bool { return strlen($data) !== strlen(strip_tags($data)); } /** * Loads Spreadsheet from file. - * - * @param string $pFilename - * - * @throws Exception - * - * @return Spreadsheet */ - public function load($pFilename) + public function loadSpreadsheetFromFile(string $filename): Spreadsheet { // Create new Spreadsheet $spreadsheet = new Spreadsheet(); // Load into this instance - return $this->loadIntoExisting($pFilename, $spreadsheet); + return $this->loadIntoExisting($filename, $spreadsheet); } /** * Set input encoding. * - * @param string $pValue Input encoding, eg: 'ANSI' + * @param string $inputEncoding Input encoding, eg: 'ANSI' * - * @return Html + * @return $this + * + * @codeCoverageIgnore + * + * @deprecated no use is made of this property */ - public function setInputEncoding($pValue) + public function setInputEncoding($inputEncoding) { - $this->inputEncoding = $pValue; + $this->inputEncoding = $inputEncoding; return $this; } @@ -236,6 +232,10 @@ class Html extends BaseReader * Get input encoding. * * @return string + * + * @codeCoverageIgnore + * + * @deprecated no use is made of this property */ public function getInputEncoding() { @@ -243,13 +243,17 @@ class Html extends BaseReader } // Data Array used for testing only, should write to Spreadsheet object on completion of tests + + /** @var array */ protected $dataArray = []; + /** @var int */ protected $tableLevel = 0; + /** @var array */ protected $nestedColumn = ['A']; - protected function setTableStartColumn($column) + protected function setTableStartColumn(string $column): string { if ($this->tableLevel == 0) { $column = 'A'; @@ -260,19 +264,26 @@ class Html extends BaseReader return $this->nestedColumn[$this->tableLevel]; } - protected function getTableStartColumn() + protected function getTableStartColumn(): string { return $this->nestedColumn[$this->tableLevel]; } - protected function releaseTableStartColumn() + protected function releaseTableStartColumn(): string { --$this->tableLevel; return array_pop($this->nestedColumn); } - protected function flushCell(Worksheet $sheet, $column, $row, &$cellContent) + /** + * Flush cell. + * + * @param string $column + * @param int|string $row + * @param mixed $cellContent + */ + protected function flushCell(Worksheet $sheet, $column, $row, &$cellContent): void { if (is_string($cellContent)) { // Simple String content @@ -291,18 +302,324 @@ class Html extends BaseReader $cellContent = (string) ''; } - /** - * @param DOMNode $element - * @param Worksheet $sheet - * @param int $row - * @param string $column - * @param string $cellContent - */ - protected function processDomElement(DOMNode $element, Worksheet $sheet, &$row, &$column, &$cellContent) + private function processDomElementBody(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child): void + { + $attributeArray = []; + foreach (($child->attributes ?? []) as $attribute) { + $attributeArray[$attribute->name] = $attribute->value; + } + + if ($child->nodeName === 'body') { + $row = 1; + $column = 'A'; + $cellContent = ''; + $this->tableLevel = 0; + $this->processDomElement($child, $sheet, $row, $column, $cellContent); + } else { + $this->processDomElementTitle($sheet, $row, $column, $cellContent, $child, $attributeArray); + } + } + + private function processDomElementTitle(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void + { + if ($child->nodeName === 'title') { + $this->processDomElement($child, $sheet, $row, $column, $cellContent); + $sheet->setTitle($cellContent, true, true); + $cellContent = ''; + } else { + $this->processDomElementSpanEtc($sheet, $row, $column, $cellContent, $child, $attributeArray); + } + } + + private const SPAN_ETC = ['span', 'div', 'font', 'i', 'em', 'strong', 'b']; + + private function processDomElementSpanEtc(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void + { + if (in_array((string) $child->nodeName, self::SPAN_ETC, true)) { + if (isset($attributeArray['class']) && $attributeArray['class'] === 'comment') { + $sheet->getComment($column . $row) + ->getText() + ->createTextRun($child->textContent); + } else { + $this->processDomElement($child, $sheet, $row, $column, $cellContent); + } + + if (isset($this->formats[$child->nodeName])) { + $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]); + } + } else { + $this->processDomElementHr($sheet, $row, $column, $cellContent, $child, $attributeArray); + } + } + + private function processDomElementHr(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void + { + if ($child->nodeName === 'hr') { + $this->flushCell($sheet, $column, $row, $cellContent); + ++$row; + if (isset($this->formats[$child->nodeName])) { + $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]); + } + ++$row; + } + // fall through to br + $this->processDomElementBr($sheet, $row, $column, $cellContent, $child, $attributeArray); + } + + private function processDomElementBr(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void + { + if ($child->nodeName === 'br' || $child->nodeName === 'hr') { + if ($this->tableLevel > 0) { + // If we're inside a table, replace with a \n and set the cell to wrap + $cellContent .= "\n"; + $sheet->getStyle($column . $row)->getAlignment()->setWrapText(true); + } else { + // Otherwise flush our existing content and move the row cursor on + $this->flushCell($sheet, $column, $row, $cellContent); + ++$row; + } + } else { + $this->processDomElementA($sheet, $row, $column, $cellContent, $child, $attributeArray); + } + } + + private function processDomElementA(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void + { + if ($child->nodeName === 'a') { + foreach ($attributeArray as $attributeName => $attributeValue) { + switch ($attributeName) { + case 'href': + $sheet->getCell($column . $row)->getHyperlink()->setUrl($attributeValue); + if (isset($this->formats[$child->nodeName])) { + $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]); + } + + break; + case 'class': + if ($attributeValue === 'comment-indicator') { + break; // Ignore - it's just a red square. + } + } + } + // no idea why this should be needed + //$cellContent .= ' '; + $this->processDomElement($child, $sheet, $row, $column, $cellContent); + } else { + $this->processDomElementH1Etc($sheet, $row, $column, $cellContent, $child, $attributeArray); + } + } + + private const H1_ETC = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'ul', 'p']; + + private function processDomElementH1Etc(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void + { + if (in_array((string) $child->nodeName, self::H1_ETC, true)) { + if ($this->tableLevel > 0) { + // If we're inside a table, replace with a \n + $cellContent .= $cellContent ? "\n" : ''; + $sheet->getStyle($column . $row)->getAlignment()->setWrapText(true); + $this->processDomElement($child, $sheet, $row, $column, $cellContent); + } else { + if ($cellContent > '') { + $this->flushCell($sheet, $column, $row, $cellContent); + ++$row; + } + $this->processDomElement($child, $sheet, $row, $column, $cellContent); + $this->flushCell($sheet, $column, $row, $cellContent); + + if (isset($this->formats[$child->nodeName])) { + $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]); + } + + ++$row; + $column = 'A'; + } + } else { + $this->processDomElementLi($sheet, $row, $column, $cellContent, $child, $attributeArray); + } + } + + private function processDomElementLi(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void + { + if ($child->nodeName === 'li') { + if ($this->tableLevel > 0) { + // If we're inside a table, replace with a \n + $cellContent .= $cellContent ? "\n" : ''; + $this->processDomElement($child, $sheet, $row, $column, $cellContent); + } else { + if ($cellContent > '') { + $this->flushCell($sheet, $column, $row, $cellContent); + } + ++$row; + $this->processDomElement($child, $sheet, $row, $column, $cellContent); + $this->flushCell($sheet, $column, $row, $cellContent); + $column = 'A'; + } + } else { + $this->processDomElementImg($sheet, $row, $column, $cellContent, $child, $attributeArray); + } + } + + private function processDomElementImg(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void + { + if ($child->nodeName === 'img') { + $this->insertImage($sheet, $column, $row, $attributeArray); + } else { + $this->processDomElementTable($sheet, $row, $column, $cellContent, $child, $attributeArray); + } + } + + private function processDomElementTable(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void + { + if ($child->nodeName === 'table') { + $this->flushCell($sheet, $column, $row, $cellContent); + $column = $this->setTableStartColumn($column); + if ($this->tableLevel > 1 && $row > 1) { + --$row; + } + $this->processDomElement($child, $sheet, $row, $column, $cellContent); + $column = $this->releaseTableStartColumn(); + if ($this->tableLevel > 1) { + ++$column; + } else { + ++$row; + } + } else { + $this->processDomElementTr($sheet, $row, $column, $cellContent, $child, $attributeArray); + } + } + + private function processDomElementTr(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void + { + if ($child->nodeName === 'tr') { + $column = $this->getTableStartColumn(); + $cellContent = ''; + $this->processDomElement($child, $sheet, $row, $column, $cellContent); + + if (isset($attributeArray['height'])) { + $sheet->getRowDimension($row)->setRowHeight($attributeArray['height']); + } + + ++$row; + } else { + $this->processDomElementThTdOther($sheet, $row, $column, $cellContent, $child, $attributeArray); + } + } + + private function processDomElementThTdOther(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void + { + if ($child->nodeName !== 'td' && $child->nodeName !== 'th') { + $this->processDomElement($child, $sheet, $row, $column, $cellContent); + } else { + $this->processDomElementThTd($sheet, $row, $column, $cellContent, $child, $attributeArray); + } + } + + private function processDomElementBgcolor(Worksheet $sheet, int $row, string $column, array $attributeArray): void + { + if (isset($attributeArray['bgcolor'])) { + $sheet->getStyle("$column$row")->applyFromArray( + [ + 'fill' => [ + 'fillType' => Fill::FILL_SOLID, + 'color' => ['rgb' => $this->getStyleColor($attributeArray['bgcolor'])], + ], + ] + ); + } + } + + private function processDomElementWidth(Worksheet $sheet, string $column, array $attributeArray): void + { + if (isset($attributeArray['width'])) { + $sheet->getColumnDimension($column)->setWidth((new CssDimension($attributeArray['width']))->width()); + } + } + + private function processDomElementHeight(Worksheet $sheet, int $row, array $attributeArray): void + { + if (isset($attributeArray['height'])) { + $sheet->getRowDimension($row)->setRowHeight((new CssDimension($attributeArray['height']))->height()); + } + } + + private function processDomElementAlign(Worksheet $sheet, int $row, string $column, array $attributeArray): void + { + if (isset($attributeArray['align'])) { + $sheet->getStyle($column . $row)->getAlignment()->setHorizontal($attributeArray['align']); + } + } + + private function processDomElementVAlign(Worksheet $sheet, int $row, string $column, array $attributeArray): void + { + if (isset($attributeArray['valign'])) { + $sheet->getStyle($column . $row)->getAlignment()->setVertical($attributeArray['valign']); + } + } + + private function processDomElementDataFormat(Worksheet $sheet, int $row, string $column, array $attributeArray): void + { + if (isset($attributeArray['data-format'])) { + $sheet->getStyle($column . $row)->getNumberFormat()->setFormatCode($attributeArray['data-format']); + } + } + + private function processDomElementThTd(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void + { + while (isset($this->rowspan[$column . $row])) { + ++$column; + } + $this->processDomElement($child, $sheet, $row, $column, $cellContent); + + // apply inline style + $this->applyInlineStyle($sheet, $row, $column, $attributeArray); + + $this->flushCell($sheet, $column, $row, $cellContent); + + $this->processDomElementBgcolor($sheet, $row, $column, $attributeArray); + $this->processDomElementWidth($sheet, $column, $attributeArray); + $this->processDomElementHeight($sheet, $row, $attributeArray); + $this->processDomElementAlign($sheet, $row, $column, $attributeArray); + $this->processDomElementVAlign($sheet, $row, $column, $attributeArray); + $this->processDomElementDataFormat($sheet, $row, $column, $attributeArray); + + if (isset($attributeArray['rowspan'], $attributeArray['colspan'])) { + //create merging rowspan and colspan + $columnTo = $column; + for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) { + ++$columnTo; + } + $range = $column . $row . ':' . $columnTo . ($row + (int) $attributeArray['rowspan'] - 1); + foreach (Coordinate::extractAllCellReferencesInRange($range) as $value) { + $this->rowspan[$value] = true; + } + $sheet->mergeCells($range); + $column = $columnTo; + } elseif (isset($attributeArray['rowspan'])) { + //create merging rowspan + $range = $column . $row . ':' . $column . ($row + (int) $attributeArray['rowspan'] - 1); + foreach (Coordinate::extractAllCellReferencesInRange($range) as $value) { + $this->rowspan[$value] = true; + } + $sheet->mergeCells($range); + } elseif (isset($attributeArray['colspan'])) { + //create merging colspan + $columnTo = $column; + for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) { + ++$columnTo; + } + $sheet->mergeCells($column . $row . ':' . $columnTo . $row); + $column = $columnTo; + } + + ++$column; + } + + protected function processDomElement(DOMNode $element, Worksheet $sheet, int &$row, string &$column, string &$cellContent): void { foreach ($element->childNodes as $child) { if ($child instanceof DOMText) { - $domText = preg_replace('/\s+/u', ' ', trim($child->nodeValue)); + $domText = (string) preg_replace('/\s+/u', ' ', trim($child->nodeValue ?? '')); if (is_string($cellContent)) { // simply append the text if the cell content is a plain text string $cellContent .= $domText; @@ -310,267 +627,7 @@ class Html extends BaseReader // but if we have a rich text run instead, we need to append it correctly // TODO } elseif ($child instanceof DOMElement) { - $attributeArray = []; - foreach ($child->attributes as $attribute) { - $attributeArray[$attribute->name] = $attribute->value; - } - - switch ($child->nodeName) { - case 'meta': - foreach ($attributeArray as $attributeName => $attributeValue) { - // Extract character set, so we can convert to UTF-8 if required - if ($attributeName === 'charset') { - $this->setInputEncoding($attributeValue); - } - } - $this->processDomElement($child, $sheet, $row, $column, $cellContent); - - break; - case 'title': - $this->processDomElement($child, $sheet, $row, $column, $cellContent); - $sheet->setTitle($cellContent, true, false); - $cellContent = ''; - - break; - case 'span': - case 'div': - case 'font': - case 'i': - case 'em': - case 'strong': - case 'b': - if (isset($attributeArray['class']) && $attributeArray['class'] === 'comment') { - $sheet->getComment($column . $row) - ->getText() - ->createTextRun($child->textContent); - - break; - } - - if ($cellContent > '') { - $cellContent .= ' '; - } - $this->processDomElement($child, $sheet, $row, $column, $cellContent); - if ($cellContent > '') { - $cellContent .= ' '; - } - - if (isset($this->formats[$child->nodeName])) { - $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]); - } - - break; - case 'hr': - $this->flushCell($sheet, $column, $row, $cellContent); - ++$row; - if (isset($this->formats[$child->nodeName])) { - $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]); - } else { - $cellContent = '----------'; - $this->flushCell($sheet, $column, $row, $cellContent); - } - ++$row; - // Add a break after a horizontal rule, simply by allowing the code to dropthru - // no break - case 'br': - if ($this->tableLevel > 0) { - // If we're inside a table, replace with a \n and set the cell to wrap - $cellContent .= "\n"; - $sheet->getStyle($column . $row)->getAlignment()->setWrapText(true); - } else { - // Otherwise flush our existing content and move the row cursor on - $this->flushCell($sheet, $column, $row, $cellContent); - ++$row; - } - - break; - case 'a': - foreach ($attributeArray as $attributeName => $attributeValue) { - switch ($attributeName) { - case 'href': - $sheet->getCell($column . $row)->getHyperlink()->setUrl($attributeValue); - if (isset($this->formats[$child->nodeName])) { - $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]); - } - - break; - case 'class': - if ($attributeValue === 'comment-indicator') { - break; // Ignore - it's just a red square. - } - } - } - $cellContent .= ' '; - $this->processDomElement($child, $sheet, $row, $column, $cellContent); - - break; - case 'h1': - case 'h2': - case 'h3': - case 'h4': - case 'h5': - case 'h6': - case 'ol': - case 'ul': - case 'p': - if ($this->tableLevel > 0) { - // If we're inside a table, replace with a \n - $cellContent .= "\n"; - $this->processDomElement($child, $sheet, $row, $column, $cellContent); - } else { - if ($cellContent > '') { - $this->flushCell($sheet, $column, $row, $cellContent); - ++$row; - } - $this->processDomElement($child, $sheet, $row, $column, $cellContent); - $this->flushCell($sheet, $column, $row, $cellContent); - - if (isset($this->formats[$child->nodeName])) { - $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]); - } - - ++$row; - $column = 'A'; - } - - break; - case 'li': - if ($this->tableLevel > 0) { - // If we're inside a table, replace with a \n - $cellContent .= "\n"; - $this->processDomElement($child, $sheet, $row, $column, $cellContent); - } else { - if ($cellContent > '') { - $this->flushCell($sheet, $column, $row, $cellContent); - } - ++$row; - $this->processDomElement($child, $sheet, $row, $column, $cellContent); - $this->flushCell($sheet, $column, $row, $cellContent); - $column = 'A'; - } - - break; - case 'img': - $this->insertImage($sheet, $column, $row, $attributeArray); - - break; - case 'table': - $this->flushCell($sheet, $column, $row, $cellContent); - $column = $this->setTableStartColumn($column); - if ($this->tableLevel > 1) { - --$row; - } - $this->processDomElement($child, $sheet, $row, $column, $cellContent); - $column = $this->releaseTableStartColumn(); - if ($this->tableLevel > 1) { - ++$column; - } else { - ++$row; - } - - break; - case 'thead': - case 'tbody': - $this->processDomElement($child, $sheet, $row, $column, $cellContent); - - break; - case 'tr': - $column = $this->getTableStartColumn(); - $cellContent = ''; - $this->processDomElement($child, $sheet, $row, $column, $cellContent); - - if (isset($attributeArray['height'])) { - $sheet->getRowDimension($row)->setRowHeight($attributeArray['height']); - } - - ++$row; - - break; - case 'th': - case 'td': - $this->processDomElement($child, $sheet, $row, $column, $cellContent); - - // apply inline style - $this->applyInlineStyle($sheet, $row, $column, $attributeArray); - - while (isset($this->rowspan[$column . $row])) { - ++$column; - } - - $this->flushCell($sheet, $column, $row, $cellContent); - - if (isset($attributeArray['rowspan'], $attributeArray['colspan'])) { - //create merging rowspan and colspan - $columnTo = $column; - for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) { - ++$columnTo; - } - $range = $column . $row . ':' . $columnTo . ($row + (int) $attributeArray['rowspan'] - 1); - foreach (Coordinate::extractAllCellReferencesInRange($range) as $value) { - $this->rowspan[$value] = true; - } - $sheet->mergeCells($range); - $column = $columnTo; - } elseif (isset($attributeArray['rowspan'])) { - //create merging rowspan - $range = $column . $row . ':' . $column . ($row + (int) $attributeArray['rowspan'] - 1); - foreach (Coordinate::extractAllCellReferencesInRange($range) as $value) { - $this->rowspan[$value] = true; - } - $sheet->mergeCells($range); - } elseif (isset($attributeArray['colspan'])) { - //create merging colspan - $columnTo = $column; - for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) { - ++$columnTo; - } - $sheet->mergeCells($column . $row . ':' . $columnTo . $row); - $column = $columnTo; - } elseif (isset($attributeArray['bgcolor'])) { - $sheet->getStyle($column . $row)->applyFromArray( - [ - 'fill' => [ - 'fillType' => Fill::FILL_SOLID, - 'color' => ['rgb' => $attributeArray['bgcolor']], - ], - ] - ); - } - - if (isset($attributeArray['width'])) { - $sheet->getColumnDimension($column)->setWidth($attributeArray['width']); - } - - if (isset($attributeArray['height'])) { - $sheet->getRowDimension($row)->setRowHeight($attributeArray['height']); - } - - if (isset($attributeArray['align'])) { - $sheet->getStyle($column . $row)->getAlignment()->setHorizontal($attributeArray['align']); - } - - if (isset($attributeArray['valign'])) { - $sheet->getStyle($column . $row)->getAlignment()->setVertical($attributeArray['valign']); - } - - if (isset($attributeArray['data-format'])) { - $sheet->getStyle($column . $row)->getNumberFormat()->setFormatCode($attributeArray['data-format']); - } - - ++$column; - - break; - case 'body': - $row = 1; - $column = 'A'; - $cellContent = ''; - $this->tableLevel = 0; - $this->processDomElement($child, $sheet, $row, $column, $cellContent); - - break; - default: - $this->processDomElement($child, $sheet, $row, $column, $cellContent); - } + $this->processDomElementBody($sheet, $row, $column, $cellContent, $child); } } } @@ -578,62 +635,75 @@ class Html extends BaseReader /** * Loads PhpSpreadsheet from file into PhpSpreadsheet instance. * - * @param string $pFilename - * @param Spreadsheet $spreadsheet - * - * @throws Exception + * @param string $filename * * @return Spreadsheet */ - public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet) + public function loadIntoExisting($filename, Spreadsheet $spreadsheet) { // Validate - if (!$this->canRead($pFilename)) { - throw new Exception($pFilename . ' is an Invalid HTML file.'); + if (!$this->canRead($filename)) { + throw new Exception($filename . ' is an Invalid HTML file.'); } // Create a new DOM object $dom = new DOMDocument(); // Reload the HTML file into the DOM object - $loaded = $dom->loadHTML(mb_convert_encoding($this->securityScanner->scanFile($pFilename), 'HTML-ENTITIES', 'UTF-8')); + try { + $convert = $this->securityScanner->scanFile($filename); + $lowend = "\u{80}"; + $highend = "\u{10ffff}"; + $regexp = "/[$lowend-$highend]/u"; + /** @var callable */ + $callback = [self::class, 'replaceNonAscii']; + $convert = preg_replace_callback($regexp, $callback, $convert); + $loaded = ($convert === null) ? false : $dom->loadHTML($convert); + } catch (Throwable $e) { + $loaded = false; + } if ($loaded === false) { - throw new Exception('Failed to load ' . $pFilename . ' as a DOM Document'); + throw new Exception('Failed to load ' . $filename . ' as a DOM Document', 0, $e ?? null); } return $this->loadDocument($dom, $spreadsheet); } + private static function replaceNonAscii(array $matches): string + { + return '&#' . mb_ord($matches[0], 'UTF-8') . ';'; + } + /** * Spreadsheet from content. * * @param string $content - * - * @throws Exception - * - * @return Spreadsheet */ - public function loadFromString($content): Spreadsheet + public function loadFromString($content, ?Spreadsheet $spreadsheet = null): Spreadsheet { // Create a new DOM object $dom = new DOMDocument(); // Reload the HTML file into the DOM object - $loaded = $dom->loadHTML(mb_convert_encoding($this->securityScanner->scan($content), 'HTML-ENTITIES', 'UTF-8')); + try { + $convert = $this->securityScanner->scan($content); + $lowend = "\u{80}"; + $highend = "\u{10ffff}"; + $regexp = "/[$lowend-$highend]/u"; + /** @var callable */ + $callback = [self::class, 'replaceNonAscii']; + $convert = preg_replace_callback($regexp, $callback, $convert); + $loaded = ($convert === null) ? false : $dom->loadHTML($convert); + } catch (Throwable $e) { + $loaded = false; + } if ($loaded === false) { - throw new Exception('Failed to load content as a DOM Document'); + throw new Exception('Failed to load content as a DOM Document', 0, $e ?? null); } - return $this->loadDocument($dom, new Spreadsheet()); + return $this->loadDocument($dom, $spreadsheet ?? new Spreadsheet()); } /** * Loads PhpSpreadsheet from DOMDocument into PhpSpreadsheet instance. - * - * @param DOMDocument $document - * @param Spreadsheet $spreadsheet - * - * @throws \PhpOffice\PhpSpreadsheet\Exception - * - * @return Spreadsheet */ private function loadDocument(DOMDocument $document, Spreadsheet $spreadsheet): Spreadsheet { @@ -668,13 +738,13 @@ class Html extends BaseReader /** * Set sheet index. * - * @param int $pValue Sheet index + * @param int $sheetIndex Sheet index * - * @return HTML + * @return $this */ - public function setSheetIndex($pValue) + public function setSheetIndex($sheetIndex) { - $this->sheetIndex = $pValue; + $this->sheetIndex = $sheetIndex; return $this; } @@ -689,18 +759,36 @@ class Html extends BaseReader * TODO : * - Implement to other propertie, such as border * - * @param Worksheet $sheet * @param int $row * @param string $column * @param array $attributeArray */ - private function applyInlineStyle(&$sheet, $row, $column, $attributeArray) + private function applyInlineStyle(Worksheet &$sheet, $row, $column, $attributeArray): void { if (!isset($attributeArray['style'])) { return; } - $cellStyle = $sheet->getStyle($column . $row); + if (isset($attributeArray['rowspan'], $attributeArray['colspan'])) { + $columnTo = $column; + for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) { + ++$columnTo; + } + $range = $column . $row . ':' . $columnTo . ($row + (int) $attributeArray['rowspan'] - 1); + $cellStyle = $sheet->getStyle($range); + } elseif (isset($attributeArray['rowspan'])) { + $range = $column . $row . ':' . $column . ($row + (int) $attributeArray['rowspan'] - 1); + $cellStyle = $sheet->getStyle($range); + } elseif (isset($attributeArray['colspan'])) { + $columnTo = $column; + for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) { + ++$columnTo; + } + $range = $column . $row . ':' . $columnTo . $row; + $cellStyle = $sheet->getStyle($range); + } else { + $cellStyle = $sheet->getStyle($column . $row); + } // add color styles (background & text) from dom element,currently support : td & th, using ONLY inline css style with RGB color $styles = explode(';', $attributeArray['style']); @@ -708,6 +796,7 @@ class Html extends BaseReader $value = explode(':', $st); $styleName = isset($value[0]) ? trim($value[0]) : null; $styleValue = isset($value[1]) ? trim($value[1]) : null; + $styleValueString = (string) $styleValue; if (!$styleName) { continue; @@ -716,7 +805,7 @@ class Html extends BaseReader switch ($styleName) { case 'background': case 'background-color': - $styleColor = $this->getStyleColor($styleValue); + $styleColor = $this->getStyleColor($styleValueString); if (!$styleColor) { continue 2; @@ -726,7 +815,7 @@ class Html extends BaseReader break; case 'color': - $styleColor = $this->getStyleColor($styleValue); + $styleColor = $this->getStyleColor($styleValueString); if (!$styleColor) { continue 2; @@ -737,27 +826,27 @@ class Html extends BaseReader break; case 'border': - $this->setBorderStyle($cellStyle, $styleValue, 'allBorders'); + $this->setBorderStyle($cellStyle, $styleValueString, 'allBorders'); break; case 'border-top': - $this->setBorderStyle($cellStyle, $styleValue, 'top'); + $this->setBorderStyle($cellStyle, $styleValueString, 'top'); break; case 'border-bottom': - $this->setBorderStyle($cellStyle, $styleValue, 'bottom'); + $this->setBorderStyle($cellStyle, $styleValueString, 'bottom'); break; case 'border-left': - $this->setBorderStyle($cellStyle, $styleValue, 'left'); + $this->setBorderStyle($cellStyle, $styleValueString, 'left'); break; case 'border-right': - $this->setBorderStyle($cellStyle, $styleValue, 'right'); + $this->setBorderStyle($cellStyle, $styleValueString, 'right'); break; @@ -783,7 +872,7 @@ class Html extends BaseReader break; case 'font-family': - $cellStyle->getFont()->setName(str_replace('\'', '', $styleValue)); + $cellStyle->getFont()->setName(str_replace('\'', '', $styleValueString)); break; @@ -802,25 +891,25 @@ class Html extends BaseReader break; case 'text-align': - $cellStyle->getAlignment()->setHorizontal($styleValue); + $cellStyle->getAlignment()->setHorizontal($styleValueString); break; case 'vertical-align': - $cellStyle->getAlignment()->setVertical($styleValue); + $cellStyle->getAlignment()->setVertical($styleValueString); break; case 'width': $sheet->getColumnDimension($column)->setWidth( - str_replace('px', '', $styleValue) + (new CssDimension($styleValue ?? ''))->width() ); break; case 'height': $sheet->getRowDimension($row)->setRowHeight( - str_replace('px', '', $styleValue) + (new CssDimension($styleValue ?? ''))->height() ); break; @@ -834,7 +923,7 @@ class Html extends BaseReader case 'text-indent': $cellStyle->getAlignment()->setIndent( - (int) str_replace(['px'], '', $styleValue) + (int) str_replace(['px'], '', $styleValueString) ); break; @@ -845,28 +934,25 @@ class Html extends BaseReader /** * Check if has #, so we can get clean hex. * - * @param $value + * @param mixed $value * * @return null|string */ public function getStyleColor($value) { - if (strpos($value, '#') === 0) { + $value = (string) $value; + if (strpos($value ?? '', '#') === 0) { return substr($value, 1); } - return null; + return \PhpOffice\PhpSpreadsheet\Helper\Html::colourNameLookup($value); } /** - * @param Worksheet $sheet * @param string $column * @param int $row - * @param array $attributes - * - * @throws \PhpOffice\PhpSpreadsheet\Exception */ - private function insertImage(Worksheet $sheet, $column, $row, array $attributes) + private function insertImage(Worksheet $sheet, $column, $row, array $attributes): void { if (!isset($attributes['src'])) { return; @@ -875,7 +961,7 @@ class Html extends BaseReader $src = urldecode($attributes['src']); $width = isset($attributes['width']) ? (float) $attributes['width'] : null; $height = isset($attributes['height']) ? (float) $attributes['height'] : null; - $name = isset($attributes['alt']) ? (float) $attributes['alt'] : null; + $name = $attributes['alt'] ?? null; $drawing = new Drawing(); $drawing->setPath($src); @@ -906,6 +992,28 @@ class Html extends BaseReader ); } + private const BORDER_MAPPINGS = [ + 'dash-dot' => Border::BORDER_DASHDOT, + 'dash-dot-dot' => Border::BORDER_DASHDOTDOT, + 'dashed' => Border::BORDER_DASHED, + 'dotted' => Border::BORDER_DOTTED, + 'double' => Border::BORDER_DOUBLE, + 'hair' => Border::BORDER_HAIR, + 'medium' => Border::BORDER_MEDIUM, + 'medium-dashed' => Border::BORDER_MEDIUMDASHED, + 'medium-dash-dot' => Border::BORDER_MEDIUMDASHDOT, + 'medium-dash-dot-dot' => Border::BORDER_MEDIUMDASHDOTDOT, + 'none' => Border::BORDER_NONE, + 'slant-dash-dot' => Border::BORDER_SLANTDASHDOT, + 'solid' => Border::BORDER_THIN, + 'thick' => Border::BORDER_THICK, + ]; + + public static function getBorderMappings(): array + { + return self::BORDER_MAPPINGS; + } + /** * Map html border style to PhpSpreadsheet border style. * @@ -915,48 +1023,29 @@ class Html extends BaseReader */ public function getBorderStyle($style) { - switch ($style) { - case 'solid': - return Border::BORDER_THIN; - case 'dashed': - return Border::BORDER_DASHED; - case 'dotted': - return Border::BORDER_DOTTED; - case 'medium': - return Border::BORDER_MEDIUM; - case 'thick': - return Border::BORDER_THICK; - case 'none': - return Border::BORDER_NONE; - case 'dash-dot': - return Border::BORDER_DASHDOT; - case 'dash-dot-dot': - return Border::BORDER_DASHDOTDOT; - case 'double': - return Border::BORDER_DOUBLE; - case 'hair': - return Border::BORDER_HAIR; - case 'medium-dash-dot': - return Border::BORDER_MEDIUMDASHDOT; - case 'medium-dash-dot-dot': - return Border::BORDER_MEDIUMDASHDOTDOT; - case 'medium-dashed': - return Border::BORDER_MEDIUMDASHED; - case 'slant-dash-dot': - return Border::BORDER_SLANTDASHDOT; - } - - return null; + return self::BORDER_MAPPINGS[$style] ?? null; } /** - * @param Style $cellStyle * @param string $styleValue * @param string $type */ - private function setBorderStyle(Style $cellStyle, $styleValue, $type) + private function setBorderStyle(Style $cellStyle, $styleValue, $type): void { - [, $borderStyle, $color] = explode(' ', $styleValue); + if (trim($styleValue) === Border::BORDER_NONE) { + $borderStyle = Border::BORDER_NONE; + $color = null; + } else { + $borderArray = explode(' ', $styleValue); + $borderCount = count($borderArray); + if ($borderCount >= 3) { + $borderStyle = $borderArray[1]; + $color = $borderArray[2]; + } else { + $borderStyle = $borderArray[0]; + $color = $borderArray[1] ?? null; + } + } $cellStyle->applyFromArray([ 'borders' => [ diff --git a/PhpOffice/PhpSpreadsheet/Reader/IReadFilter.php b/PhpOffice/PhpSpreadsheet/Reader/IReadFilter.php old mode 100755 new mode 100644 index ccfe05a..9f68a7f --- a/PhpOffice/PhpSpreadsheet/Reader/IReadFilter.php +++ b/PhpOffice/PhpSpreadsheet/Reader/IReadFilter.php @@ -7,11 +7,11 @@ interface IReadFilter /** * Should this cell be read? * - * @param string $column Column address (as a string value like "A", or "IV") + * @param string $columnAddress Column address (as a string value like "A", or "IV") * @param int $row Row number * @param string $worksheetName Optional worksheet name * * @return bool */ - public function readCell($column, $row, $worksheetName = ''); + public function readCell($columnAddress, $row, $worksheetName = ''); } diff --git a/PhpOffice/PhpSpreadsheet/Reader/IReader.php b/PhpOffice/PhpSpreadsheet/Reader/IReader.php old mode 100755 new mode 100644 index 70a7a20..d4a997b --- a/PhpOffice/PhpSpreadsheet/Reader/IReader.php +++ b/PhpOffice/PhpSpreadsheet/Reader/IReader.php @@ -4,6 +4,12 @@ namespace PhpOffice\PhpSpreadsheet\Reader; interface IReader { + public const LOAD_WITH_CHARTS = 1; + + public const READ_DATA_ONLY = 2; + + public const SKIP_EMPTY_CELLS = 4; + /** * IReader constructor. */ @@ -11,12 +17,8 @@ interface IReader /** * Can the current IReader read the file? - * - * @param string $pFilename - * - * @return bool */ - public function canRead($pFilename); + public function canRead(string $filename): bool; /** * Read data only? @@ -32,11 +34,11 @@ interface IReader * Set to true, to advise the Reader only to read data values for cells, and to ignore any formatting information. * Set to false (the default) to advise the Reader to read both data and formatting for cells. * - * @param bool $pValue + * @param bool $readDataOnly * * @return IReader */ - public function setReadDataOnly($pValue); + public function setReadDataOnly($readDataOnly); /** * Read empty cells? @@ -52,11 +54,11 @@ interface IReader * Set to true (the default) to advise the Reader read data values for all cells, irrespective of value. * Set to false to advise the Reader to ignore cells containing a null value or an empty string. * - * @param bool $pValue + * @param bool $readEmptyCells * * @return IReader */ - public function setReadEmptyCells($pValue); + public function setReadEmptyCells($readEmptyCells); /** * Read charts in workbook? @@ -74,11 +76,11 @@ interface IReader * Note that a ReadDataOnly value of false overrides, and charts won't be read regardless of the IncludeCharts value. * Set to false (the default) to discard charts. * - * @param bool $pValue + * @param bool $includeCharts * * @return IReader */ - public function setIncludeCharts($pValue); + public function setIncludeCharts($includeCharts); /** * Get which sheets to load @@ -118,20 +120,21 @@ interface IReader /** * Set read filter. * - * @param IReadFilter $pValue - * * @return IReader */ - public function setReadFilter(IReadFilter $pValue); + public function setReadFilter(IReadFilter $readFilter); /** * Loads PhpSpreadsheet from file. * - * @param string $pFilename - * - * @throws Exception + * @param string $filename The name of the file to load + * @param int $flags Flags that can change the behaviour of the Writer: + * self::LOAD_WITH_CHARTS Load any charts that are defined (if the Reader supports Charts) + * self::READ_DATA_ONLY Read only data, not style or structure information, from the file + * self::SKIP_EMPTY_CELLS Don't read empty cells (cells that contain a null value, + * empty string, or a string containing only whitespace characters) * * @return \PhpOffice\PhpSpreadsheet\Spreadsheet */ - public function load($pFilename); + public function load(string $filename, int $flags = 0); } diff --git a/PhpOffice/PhpSpreadsheet/Reader/Ods.php b/PhpOffice/PhpSpreadsheet/Reader/Ods.php old mode 100755 new mode 100644 index 5fff07a..3e0d803 --- a/PhpOffice/PhpSpreadsheet/Reader/Ods.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Ods.php @@ -2,11 +2,16 @@ namespace PhpOffice\PhpSpreadsheet\Reader; -use DateTime; -use DateTimeZone; -use PhpOffice\PhpSpreadsheet\Calculation\Calculation; +use DOMAttr; +use DOMDocument; +use DOMElement; +use DOMNode; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Cell\DataType; +use PhpOffice\PhpSpreadsheet\Reader\Ods\AutoFilter; +use PhpOffice\PhpSpreadsheet\Reader\Ods\DefinedNames; +use PhpOffice\PhpSpreadsheet\Reader\Ods\FormulaTranslator; +use PhpOffice\PhpSpreadsheet\Reader\Ods\PageSettings; use PhpOffice\PhpSpreadsheet\Reader\Ods\Properties as DocumentProperties; use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner; use PhpOffice\PhpSpreadsheet\RichText\RichText; @@ -15,11 +20,15 @@ use PhpOffice\PhpSpreadsheet\Shared\Date; use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Style\NumberFormat; +use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use Throwable; use XMLReader; use ZipArchive; class Ods extends BaseReader { + const INITIAL_FILE = 'content.xml'; + /** * Create a new Ods Reader instance. */ @@ -31,78 +40,64 @@ class Ods extends BaseReader /** * Can the current IReader read the file? - * - * @param string $pFilename - * - * @throws Exception - * - * @return bool */ - public function canRead($pFilename) + public function canRead(string $filename): bool { - File::assertFile($pFilename); - $mimeType = 'UNKNOWN'; // Load file - $zip = new ZipArchive(); - if ($zip->open($pFilename) === true) { - // check if it is an OOXML archive - $stat = $zip->statName('mimetype'); - if ($stat && ($stat['size'] <= 255)) { - $mimeType = $zip->getFromName($stat['name']); - } elseif ($zip->statName('META-INF/manifest.xml')) { - $xml = simplexml_load_string( - $this->securityScanner->scan($zip->getFromName('META-INF/manifest.xml')), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - $namespacesContent = $xml->getNamespaces(true); - if (isset($namespacesContent['manifest'])) { - $manifest = $xml->children($namespacesContent['manifest']); - foreach ($manifest as $manifestDataSet) { - $manifestAttributes = $manifestDataSet->attributes($namespacesContent['manifest']); - if ($manifestAttributes->{'full-path'} == '/') { - $mimeType = (string) $manifestAttributes->{'media-type'}; + if (File::testFileNoThrow($filename, '')) { + $zip = new ZipArchive(); + if ($zip->open($filename) === true) { + // check if it is an OOXML archive + $stat = $zip->statName('mimetype'); + if (!empty($stat) && ($stat['size'] <= 255)) { + $mimeType = $zip->getFromName($stat['name']); + } elseif ($zip->statName('META-INF/manifest.xml')) { + $xml = simplexml_load_string( + $this->securityScanner->scan($zip->getFromName('META-INF/manifest.xml')), + 'SimpleXMLElement', + Settings::getLibXmlLoaderOptions() + ); + $namespacesContent = $xml->getNamespaces(true); + if (isset($namespacesContent['manifest'])) { + $manifest = $xml->children($namespacesContent['manifest']); + foreach ($manifest as $manifestDataSet) { + /** @scrutinizer ignore-call */ + $manifestAttributes = $manifestDataSet->attributes($namespacesContent['manifest']); + if ($manifestAttributes && $manifestAttributes->{'full-path'} == '/') { + $mimeType = (string) $manifestAttributes->{'media-type'}; - break; + break; + } } } } + + $zip->close(); } - - $zip->close(); - - return $mimeType === 'application/vnd.oasis.opendocument.spreadsheet'; } - return false; + return $mimeType === 'application/vnd.oasis.opendocument.spreadsheet'; } /** * Reads names of the worksheets from a file, without parsing the whole file to a PhpSpreadsheet object. * - * @param string $pFilename - * - * @throws Exception + * @param string $filename * * @return string[] */ - public function listWorksheetNames($pFilename) + public function listWorksheetNames($filename) { - File::assertFile($pFilename); - - $zip = new ZipArchive(); - if (!$zip->open($pFilename)) { - throw new Exception('Could not open ' . $pFilename . ' for reading! Error opening file.'); - } + File::assertFile($filename, self::INITIAL_FILE); $worksheetNames = []; $xml = new XMLReader(); $xml->xml( - $this->securityScanner->scanFile('zip://' . realpath($pFilename) . '#content.xml'), + $this->securityScanner->scanFile('zip://' . realpath($filename) . '#' . self::INITIAL_FILE), null, Settings::getLibXmlLoaderOptions() ); @@ -112,7 +107,7 @@ class Ods extends BaseReader $xml->read(); while ($xml->read()) { // Quickly jump through to the office:body node - while ($xml->name !== 'office:body') { + while (self::getXmlName($xml) !== 'office:body') { if ($xml->isEmptyElement) { $xml->read(); } else { @@ -121,12 +116,13 @@ class Ods extends BaseReader } // Now read each node until we find our first table:table node while ($xml->read()) { - if ($xml->name == 'table:table' && $xml->nodeType == XMLReader::ELEMENT) { + $xmlName = self::getXmlName($xml); + if ($xmlName == 'table:table' && $xml->nodeType == XMLReader::ELEMENT) { // Loop through each table:table node reading the table:name attribute for each worksheet name do { $worksheetNames[] = $xml->getAttribute('table:name'); $xml->next(); - } while ($xml->name == 'table:table' && $xml->nodeType == XMLReader::ELEMENT); + } while (self::getXmlName($xml) == 'table:table' && $xml->nodeType == XMLReader::ELEMENT); } } } @@ -137,26 +133,19 @@ class Ods extends BaseReader /** * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns). * - * @param string $pFilename - * - * @throws Exception + * @param string $filename * * @return array */ - public function listWorksheetInfo($pFilename) + public function listWorksheetInfo($filename) { - File::assertFile($pFilename); + File::assertFile($filename, self::INITIAL_FILE); $worksheetInfo = []; - $zip = new ZipArchive(); - if (!$zip->open($pFilename)) { - throw new Exception('Could not open ' . $pFilename . ' for reading! Error opening file.'); - } - $xml = new XMLReader(); $xml->xml( - $this->securityScanner->scanFile('zip://' . realpath($pFilename) . '#content.xml'), + $this->securityScanner->scanFile('zip://' . realpath($filename) . '#' . self::INITIAL_FILE), null, Settings::getLibXmlLoaderOptions() ); @@ -166,7 +155,7 @@ class Ods extends BaseReader $xml->read(); while ($xml->read()) { // Quickly jump through to the office:body node - while ($xml->name !== 'office:body') { + while (self::getXmlName($xml) !== 'office:body') { if ($xml->isEmptyElement) { $xml->read(); } else { @@ -175,7 +164,7 @@ class Ods extends BaseReader } // Now read each node until we find our first table:table node while ($xml->read()) { - if ($xml->name == 'table:table' && $xml->nodeType == XMLReader::ELEMENT) { + if (self::getXmlName($xml) == 'table:table' && $xml->nodeType == XMLReader::ELEMENT) { $worksheetNames[] = $xml->getAttribute('table:name'); $tmpInfo = [ @@ -190,7 +179,7 @@ class Ods extends BaseReader $currCells = 0; do { $xml->read(); - if ($xml->name == 'table:table-row' && $xml->nodeType == XMLReader::ELEMENT) { + if (self::getXmlName($xml) == 'table:table-row' && $xml->nodeType == XMLReader::ELEMENT) { $rowspan = $xml->getAttribute('table:number-rows-repeated'); $rowspan = empty($rowspan) ? 1 : $rowspan; $tmpInfo['totalRows'] += $rowspan; @@ -199,23 +188,23 @@ class Ods extends BaseReader // Step into the row $xml->read(); do { - if ($xml->name == 'table:table-cell' && $xml->nodeType == XMLReader::ELEMENT) { + $doread = true; + if (self::getXmlName($xml) == 'table:table-cell' && $xml->nodeType == XMLReader::ELEMENT) { if (!$xml->isEmptyElement) { ++$currCells; $xml->next(); - } else { - $xml->read(); + $doread = false; } - } elseif ($xml->name == 'table:covered-table-cell' && $xml->nodeType == XMLReader::ELEMENT) { + } elseif (self::getXmlName($xml) == 'table:covered-table-cell' && $xml->nodeType == XMLReader::ELEMENT) { $mergeSize = $xml->getAttribute('table:number-columns-repeated'); $currCells += (int) $mergeSize; - $xml->read(); - } else { + } + if ($doread) { $xml->read(); } - } while ($xml->name != 'table:table-row'); + } while (self::getXmlName($xml) != 'table:table-row'); } - } while ($xml->name != 'table:table'); + } while (self::getXmlName($xml) != 'table:table'); $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells); $tmpInfo['lastColumnIndex'] = $tmpInfo['totalColumns'] - 1; @@ -229,48 +218,44 @@ class Ods extends BaseReader } /** - * Loads PhpSpreadsheet from file. + * Counteract Phpstan caching. * - * @param string $pFilename - * - * @throws Exception - * - * @return Spreadsheet + * @phpstan-impure */ - public function load($pFilename) + private static function getXmlName(XMLReader $xml): string + { + return $xml->name; + } + + /** + * Loads PhpSpreadsheet from file. + */ + protected function loadSpreadsheetFromFile(string $filename): Spreadsheet { // Create new Spreadsheet $spreadsheet = new Spreadsheet(); // Load into this instance - return $this->loadIntoExisting($pFilename, $spreadsheet); + return $this->loadIntoExisting($filename, $spreadsheet); } /** * Loads PhpSpreadsheet from file into PhpSpreadsheet instance. * - * @param string $pFilename - * @param Spreadsheet $spreadsheet - * - * @throws Exception + * @param string $filename * * @return Spreadsheet */ - public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet) + public function loadIntoExisting($filename, Spreadsheet $spreadsheet) { - File::assertFile($pFilename); - - $timezoneObj = new DateTimeZone('Europe/London'); - $GMT = new \DateTimeZone('UTC'); + File::assertFile($filename, self::INITIAL_FILE); $zip = new ZipArchive(); - if (!$zip->open($pFilename)) { - throw new Exception("Could not open {$pFilename} for reading! Error opening file."); - } + $zip->open($filename); // Meta - $xml = simplexml_load_string( + $xml = @simplexml_load_string( $this->securityScanner->scan($zip->getFromName('meta.xml')), 'SimpleXMLElement', Settings::getLibXmlLoaderOptions() @@ -283,11 +268,21 @@ class Ods extends BaseReader (new DocumentProperties($spreadsheet))->load($xml, $namespacesMeta); - // Content + // Styles - $dom = new \DOMDocument('1.01', 'UTF-8'); + $dom = new DOMDocument('1.01', 'UTF-8'); $dom->loadXML( - $this->securityScanner->scan($zip->getFromName('content.xml')), + $this->securityScanner->scan($zip->getFromName('styles.xml')), + Settings::getLibXmlLoaderOptions() + ); + + $pageSettings = new PageSettings($dom); + + // Main Content + + $dom = new DOMDocument('1.01', 'UTF-8'); + $dom->loadXML( + $this->securityScanner->scan($zip->getFromName(self::INITIAL_FILE)), Settings::getLibXmlLoaderOptions() ); @@ -296,43 +291,53 @@ class Ods extends BaseReader $textNs = $dom->lookupNamespaceUri('text'); $xlinkNs = $dom->lookupNamespaceUri('xlink'); + $pageSettings->readStyleCrossReferences($dom); + + $autoFilterReader = new AutoFilter($spreadsheet, $tableNs); + $definedNameReader = new DefinedNames($spreadsheet, $tableNs); + + // Content $spreadsheets = $dom->getElementsByTagNameNS($officeNs, 'body') ->item(0) ->getElementsByTagNameNS($officeNs, 'spreadsheet'); foreach ($spreadsheets as $workbookData) { - /** @var \DOMElement $workbookData */ + /** @var DOMElement $workbookData */ $tables = $workbookData->getElementsByTagNameNS($tableNs, 'table'); $worksheetID = 0; foreach ($tables as $worksheetDataSet) { - /** @var \DOMElement $worksheetDataSet */ + /** @var DOMElement $worksheetDataSet */ $worksheetName = $worksheetDataSet->getAttributeNS($tableNs, 'name'); // Check loadSheetsOnly - if (isset($this->loadSheetsOnly) + if ( + $this->loadSheetsOnly !== null && $worksheetName - && !in_array($worksheetName, $this->loadSheetsOnly)) { + && !in_array($worksheetName, $this->loadSheetsOnly) + ) { continue; } + $worksheetStyleName = $worksheetDataSet->getAttributeNS($tableNs, 'style-name'); + // Create sheet if ($worksheetID > 0) { $spreadsheet->createSheet(); // First sheet is added by default } $spreadsheet->setActiveSheetIndex($worksheetID); - if ($worksheetName) { + if ($worksheetName || is_numeric($worksheetName)) { // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in // formula cells... during the load, all formulae should be correct, and we're simply // bringing the worksheet name in line with the formula, not the reverse - $spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false); + $spreadsheet->getActiveSheet()->setTitle((string) $worksheetName, false, false); } // Go through every child of table element $rowID = 1; foreach ($worksheetDataSet->childNodes as $childNode) { - /** @var \DOMElement $childNode */ + /** @var DOMElement $childNode */ // Filter elements which are not under the "table" ns if ($childNode->namespaceURI != $tableNs) { @@ -360,18 +365,25 @@ class Ods extends BaseReader break; case 'table-row': if ($childNode->hasAttributeNS($tableNs, 'number-rows-repeated')) { - $rowRepeats = $childNode->getAttributeNS($tableNs, 'number-rows-repeated'); + $rowRepeats = (int) $childNode->getAttributeNS($tableNs, 'number-rows-repeated'); } else { $rowRepeats = 1; } $columnID = 'A'; - foreach ($childNode->childNodes as $key => $cellData) { - // @var \DOMElement $cellData - + /** @var DOMElement $cellData */ + foreach ($childNode->childNodes as $cellData) { if ($this->getReadFilter() !== null) { if (!$this->getReadFilter()->readCell($columnID, $rowID, $worksheetName)) { - ++$columnID; + if ($cellData->hasAttributeNS($tableNs, 'number-columns-repeated')) { + $colRepeats = (int) $cellData->getAttributeNS($tableNs, 'number-columns-repeated'); + } else { + $colRepeats = 1; + } + + for ($i = 0; $i < $colRepeats; ++$i) { + ++$columnID; + } continue; } @@ -405,11 +417,11 @@ class Ods extends BaseReader // Content - /** @var \DOMElement[] $paragraphs */ + /** @var DOMElement[] $paragraphs */ $paragraphs = []; foreach ($cellData->childNodes as $item) { - /** @var \DOMElement $item */ + /** @var DOMElement $item */ // Filter text:p elements if ($item->nodeName == 'text:p') { @@ -455,9 +467,10 @@ class Ods extends BaseReader $type = DataType::TYPE_NUMERIC; $dataValue = (float) $cellData->getAttributeNS($officeNs, 'value'); - if (floor($dataValue) == $dataValue) { - $dataValue = (int) $dataValue; - } + // percentage should always be float + //if (floor($dataValue) == $dataValue) { + // $dataValue = (int) $dataValue; + //} $formatting = NumberFormat::FORMAT_PERCENTAGE_00; break; @@ -478,8 +491,6 @@ class Ods extends BaseReader if (floor($dataValue) == $dataValue) { if ($dataValue == (int) $dataValue) { $dataValue = (int) $dataValue; - } else { - $dataValue = (float) $dataValue; } } @@ -487,22 +498,7 @@ class Ods extends BaseReader case 'date': $type = DataType::TYPE_NUMERIC; $value = $cellData->getAttributeNS($officeNs, 'date-value'); - - $dateObj = new DateTime($value, $GMT); - $dateObj->setTimeZone($timezoneObj); - [$year, $month, $day, $hour, $minute, $second] = explode( - ' ', - $dateObj->format('Y m d H i s') - ); - - $dataValue = Date::formattedPHPToExcel( - (int) $year, - (int) $month, - (int) $day, - (int) $hour, - (int) $minute, - (int) $second - ); + $dataValue = Date::convertIsoDate($value); if ($dataValue != floor($dataValue)) { $formatting = NumberFormat::FORMAT_DATE_XLSX15 @@ -520,7 +516,7 @@ class Ods extends BaseReader $dataValue = Date::PHPToExcel( strtotime( - '01-01-1970 ' . implode(':', sscanf($timeValue, 'PT%dH%dM%dS')) + '01-01-1970 ' . implode(':', /** @scrutinizer ignore-type */ sscanf($timeValue, 'PT%dH%dM%dS') ?? []) ) ); $formatting = NumberFormat::FORMAT_DATE_TIME4; @@ -537,30 +533,7 @@ class Ods extends BaseReader if ($hasCalculatedValue) { $type = DataType::TYPE_FORMULA; $cellDataFormula = substr($cellDataFormula, strpos($cellDataFormula, ':=') + 1); - $temp = explode('"', $cellDataFormula); - $tKey = false; - foreach ($temp as &$value) { - // Only replace in alternate array entries (i.e. non-quoted blocks) - if ($tKey = !$tKey) { - // Cell range reference in another sheet - $value = preg_replace('/\[([^\.]+)\.([^\.]+):\.([^\.]+)\]/U', '$1!$2:$3', $value); - - // Cell reference in another sheet - $value = preg_replace('/\[([^\.]+)\.([^\.]+)\]/U', '$1!$2', $value); - - // Cell range reference - $value = preg_replace('/\[\.([^\.]+):\.([^\.]+)\]/U', '$1:$2', $value); - - // Simple cell reference - $value = preg_replace('/\[\.([^\.]+)\]/U', '$1', $value); - - $value = Calculation::translateSeparator(';', ',', $value, $inBraces); - } - } - unset($value); - - // Then rebuild the formula string - $cellDataFormula = implode('"', $temp); + $cellDataFormula = FormulaTranslator::convertToExcelFormulaValue($cellDataFormula); } if ($cellData->hasAttributeNS($tableNs, 'number-columns-repeated')) { @@ -616,30 +589,7 @@ class Ods extends BaseReader } // Merged cells - if ($cellData->hasAttributeNS($tableNs, 'number-columns-spanned') - || $cellData->hasAttributeNS($tableNs, 'number-rows-spanned') - ) { - if (($type !== DataType::TYPE_NULL) || (!$this->readDataOnly)) { - $columnTo = $columnID; - - if ($cellData->hasAttributeNS($tableNs, 'number-columns-spanned')) { - $columnIndex = Coordinate::columnIndexFromString($columnID); - $columnIndex += (int) $cellData->getAttributeNS($tableNs, 'number-columns-spanned'); - $columnIndex -= 2; - - $columnTo = Coordinate::stringFromColumnIndex($columnIndex + 1); - } - - $rowTo = $rowID; - - if ($cellData->hasAttributeNS($tableNs, 'number-rows-spanned')) { - $rowTo = $rowTo + (int) $cellData->getAttributeNS($tableNs, 'number-rows-spanned') - 1; - } - - $cellRange = $columnID . $rowID . ':' . $columnTo . $rowTo; - $spreadsheet->getActiveSheet()->mergeCells($cellRange); - } - } + $this->processMergedCells($cellData, $tableNs, $type, $columnID, $rowID, $spreadsheet); ++$columnID; } @@ -648,40 +598,112 @@ class Ods extends BaseReader break; } } + $pageSettings->setVisibilityForWorksheet($spreadsheet->getActiveSheet(), $worksheetStyleName); + $pageSettings->setPrintSettingsForWorksheet($spreadsheet->getActiveSheet(), $worksheetStyleName); ++$worksheetID; } + + $autoFilterReader->read($workbookData); + $definedNameReader->read($workbookData); + } + $spreadsheet->setActiveSheetIndex(0); + + if ($zip->locateName('settings.xml') !== false) { + $this->processSettings($zip, $spreadsheet); } // Return return $spreadsheet; } + private function processSettings(ZipArchive $zip, Spreadsheet $spreadsheet): void + { + $dom = new DOMDocument('1.01', 'UTF-8'); + $dom->loadXML( + $this->securityScanner->scan($zip->getFromName('settings.xml')), + Settings::getLibXmlLoaderOptions() + ); + //$xlinkNs = $dom->lookupNamespaceUri('xlink'); + $configNs = $dom->lookupNamespaceUri('config'); + //$oooNs = $dom->lookupNamespaceUri('ooo'); + $officeNs = $dom->lookupNamespaceUri('office'); + $settings = $dom->getElementsByTagNameNS($officeNs, 'settings') + ->item(0); + $this->lookForActiveSheet($settings, $spreadsheet, $configNs); + $this->lookForSelectedCells($settings, $spreadsheet, $configNs); + } + + private function lookForActiveSheet(DOMElement $settings, Spreadsheet $spreadsheet, string $configNs): void + { + /** @var DOMElement $t */ + foreach ($settings->getElementsByTagNameNS($configNs, 'config-item') as $t) { + if ($t->getAttributeNs($configNs, 'name') === 'ActiveTable') { + try { + $spreadsheet->setActiveSheetIndexByName($t->nodeValue ?? ''); + } catch (Throwable $e) { + // do nothing + } + + break; + } + } + } + + private function lookForSelectedCells(DOMElement $settings, Spreadsheet $spreadsheet, string $configNs): void + { + /** @var DOMElement $t */ + foreach ($settings->getElementsByTagNameNS($configNs, 'config-item-map-named') as $t) { + if ($t->getAttributeNs($configNs, 'name') === 'Tables') { + foreach ($t->getElementsByTagNameNS($configNs, 'config-item-map-entry') as $ws) { + $setRow = $setCol = ''; + $wsname = $ws->getAttributeNs($configNs, 'name'); + foreach ($ws->getElementsByTagNameNS($configNs, 'config-item') as $configItem) { + $attrName = $configItem->getAttributeNs($configNs, 'name'); + if ($attrName === 'CursorPositionX') { + $setCol = $configItem->nodeValue; + } + if ($attrName === 'CursorPositionY') { + $setRow = $configItem->nodeValue; + } + } + $this->setSelected($spreadsheet, $wsname, "$setCol", "$setRow"); + } + + break; + } + } + } + + private function setSelected(Spreadsheet $spreadsheet, string $wsname, string $setCol, string $setRow): void + { + if (is_numeric($setCol) && is_numeric($setRow)) { + $sheet = $spreadsheet->getSheetByName($wsname); + if ($sheet !== null) { + $sheet->setSelectedCells([(int) $setCol + 1, (int) $setRow + 1]); + } + } + } + /** * Recursively scan element. * - * @param \DOMNode $element - * * @return string */ - protected function scanElementForText(\DOMNode $element) + protected function scanElementForText(DOMNode $element) { $str = ''; foreach ($element->childNodes as $child) { - /** @var \DOMNode $child */ + /** @var DOMNode $child */ if ($child->nodeType == XML_TEXT_NODE) { $str .= $child->nodeValue; } elseif ($child->nodeType == XML_ELEMENT_NODE && $child->nodeName == 'text:s') { // It's a space // Multiple spaces? - /** @var \DOMAttr $cAttr */ + /** @var DOMAttr $cAttr */ + /** @scrutinizer ignore-call */ $cAttr = $child->attributes->getNamedItem('c'); - if ($cAttr) { - $multiplier = (int) $cAttr->nodeValue; - } else { - $multiplier = 1; - } - + $multiplier = self::getMultiplier($cAttr); $str .= str_repeat(' ', $multiplier); } @@ -693,6 +715,17 @@ class Ods extends BaseReader return $str; } + private static function getMultiplier(?DOMAttr $cAttr): int + { + if ($cAttr) { + $multiplier = (int) $cAttr->nodeValue; + } else { + $multiplier = 1; + } + + return $multiplier; + } + /** * @param string $is * @@ -705,4 +738,39 @@ class Ods extends BaseReader return $value; } + + private function processMergedCells( + DOMElement $cellData, + string $tableNs, + string $type, + string $columnID, + int $rowID, + Spreadsheet $spreadsheet + ): void { + if ( + $cellData->hasAttributeNS($tableNs, 'number-columns-spanned') + || $cellData->hasAttributeNS($tableNs, 'number-rows-spanned') + ) { + if (($type !== DataType::TYPE_NULL) || ($this->readDataOnly === false)) { + $columnTo = $columnID; + + if ($cellData->hasAttributeNS($tableNs, 'number-columns-spanned')) { + $columnIndex = Coordinate::columnIndexFromString($columnID); + $columnIndex += (int) $cellData->getAttributeNS($tableNs, 'number-columns-spanned'); + $columnIndex -= 2; + + $columnTo = Coordinate::stringFromColumnIndex($columnIndex + 1); + } + + $rowTo = $rowID; + + if ($cellData->hasAttributeNS($tableNs, 'number-rows-spanned')) { + $rowTo = $rowTo + (int) $cellData->getAttributeNS($tableNs, 'number-rows-spanned') - 1; + } + + $cellRange = $columnID . $rowID . ':' . $columnTo . $rowTo; + $spreadsheet->getActiveSheet()->mergeCells($cellRange, Worksheet::MERGE_CELL_CONTENT_HIDE); + } + } + } } diff --git a/PhpOffice/PhpSpreadsheet/Reader/Ods/AutoFilter.php b/PhpOffice/PhpSpreadsheet/Reader/Ods/AutoFilter.php new file mode 100644 index 0000000..1f5f975 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Reader/Ods/AutoFilter.php @@ -0,0 +1,45 @@ +readAutoFilters($workbookData); + } + + protected function readAutoFilters(DOMElement $workbookData): void + { + $databases = $workbookData->getElementsByTagNameNS($this->tableNs, 'database-ranges'); + + foreach ($databases as $autofilters) { + foreach ($autofilters->childNodes as $autofilter) { + $autofilterRange = $this->getAttributeValue($autofilter, 'target-range-address'); + if ($autofilterRange !== null) { + $baseAddress = FormulaTranslator::convertToExcelAddressValue($autofilterRange); + $this->spreadsheet->getActiveSheet()->setAutoFilter($baseAddress); + } + } + } + } + + protected function getAttributeValue(?DOMNode $node, string $attributeName): ?string + { + if ($node !== null && $node->attributes !== null) { + $attribute = $node->attributes->getNamedItemNS( + $this->tableNs, + $attributeName + ); + + if ($attribute !== null) { + return $attribute->nodeValue; + } + } + + return null; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Reader/Ods/BaseLoader.php b/PhpOffice/PhpSpreadsheet/Reader/Ods/BaseLoader.php new file mode 100644 index 0000000..b06691f --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Reader/Ods/BaseLoader.php @@ -0,0 +1,27 @@ +spreadsheet = $spreadsheet; + $this->tableNs = $tableNs; + } + + abstract public function read(DOMElement $workbookData): void; +} diff --git a/PhpOffice/PhpSpreadsheet/Reader/Ods/DefinedNames.php b/PhpOffice/PhpSpreadsheet/Reader/Ods/DefinedNames.php new file mode 100644 index 0000000..e0ab890 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Reader/Ods/DefinedNames.php @@ -0,0 +1,66 @@ +readDefinedRanges($workbookData); + $this->readDefinedExpressions($workbookData); + } + + /** + * Read any Named Ranges that are defined in this spreadsheet. + */ + protected function readDefinedRanges(DOMElement $workbookData): void + { + $namedRanges = $workbookData->getElementsByTagNameNS($this->tableNs, 'named-range'); + foreach ($namedRanges as $definedNameElement) { + $definedName = $definedNameElement->getAttributeNS($this->tableNs, 'name'); + $baseAddress = $definedNameElement->getAttributeNS($this->tableNs, 'base-cell-address'); + $range = $definedNameElement->getAttributeNS($this->tableNs, 'cell-range-address'); + + $baseAddress = FormulaTranslator::convertToExcelAddressValue($baseAddress); + $range = FormulaTranslator::convertToExcelAddressValue($range); + + $this->addDefinedName($baseAddress, $definedName, $range); + } + } + + /** + * Read any Named Formulae that are defined in this spreadsheet. + */ + protected function readDefinedExpressions(DOMElement $workbookData): void + { + $namedExpressions = $workbookData->getElementsByTagNameNS($this->tableNs, 'named-expression'); + foreach ($namedExpressions as $definedNameElement) { + $definedName = $definedNameElement->getAttributeNS($this->tableNs, 'name'); + $baseAddress = $definedNameElement->getAttributeNS($this->tableNs, 'base-cell-address'); + $expression = $definedNameElement->getAttributeNS($this->tableNs, 'expression'); + + $baseAddress = FormulaTranslator::convertToExcelAddressValue($baseAddress); + $expression = substr($expression, strpos($expression, ':=') + 1); + $expression = FormulaTranslator::convertToExcelFormulaValue($expression); + + $this->addDefinedName($baseAddress, $definedName, $expression); + } + } + + /** + * Assess scope and store the Defined Name. + */ + private function addDefinedName(string $baseAddress, string $definedName, string $value): void + { + [$sheetReference] = Worksheet::extractSheetTitle($baseAddress, true); + $worksheet = $this->spreadsheet->getSheetByName($sheetReference); + // Worksheet might still be null if we're only loading selected sheets rather than the full spreadsheet + if ($worksheet !== null) { + $this->spreadsheet->addDefinedName(DefinedName::createInstance((string) $definedName, $worksheet, $value)); + } + } +} diff --git a/PhpOffice/PhpSpreadsheet/Reader/Ods/FormulaTranslator.php b/PhpOffice/PhpSpreadsheet/Reader/Ods/FormulaTranslator.php new file mode 100644 index 0000000..27862d7 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Reader/Ods/FormulaTranslator.php @@ -0,0 +1,97 @@ +setDomNameSpaces($styleDom); + $this->readPageSettingStyles($styleDom); + $this->readStyleMasterLookup($styleDom); + } + + private function setDomNameSpaces(DOMDocument $styleDom): void + { + $this->officeNs = $styleDom->lookupNamespaceUri('office'); + $this->stylesNs = $styleDom->lookupNamespaceUri('style'); + $this->stylesFo = $styleDom->lookupNamespaceUri('fo'); + $this->tableNs = $styleDom->lookupNamespaceUri('table'); + } + + private function readPageSettingStyles(DOMDocument $styleDom): void + { + $styles = $styleDom->getElementsByTagNameNS($this->officeNs, 'automatic-styles') + ->item(0) + ->getElementsByTagNameNS($this->stylesNs, 'page-layout'); + + foreach ($styles as $styleSet) { + $styleName = $styleSet->getAttributeNS($this->stylesNs, 'name'); + $pageLayoutProperties = $styleSet->getElementsByTagNameNS($this->stylesNs, 'page-layout-properties')[0]; + $styleOrientation = $pageLayoutProperties->getAttributeNS($this->stylesNs, 'print-orientation'); + $styleScale = $pageLayoutProperties->getAttributeNS($this->stylesNs, 'scale-to'); + $stylePrintOrder = $pageLayoutProperties->getAttributeNS($this->stylesNs, 'print-page-order'); + $centered = $pageLayoutProperties->getAttributeNS($this->stylesNs, 'table-centering'); + + $marginLeft = $pageLayoutProperties->getAttributeNS($this->stylesFo, 'margin-left'); + $marginRight = $pageLayoutProperties->getAttributeNS($this->stylesFo, 'margin-right'); + $marginTop = $pageLayoutProperties->getAttributeNS($this->stylesFo, 'margin-top'); + $marginBottom = $pageLayoutProperties->getAttributeNS($this->stylesFo, 'margin-bottom'); + $header = $styleSet->getElementsByTagNameNS($this->stylesNs, 'header-style')[0]; + $headerProperties = $header->getElementsByTagNameNS($this->stylesNs, 'header-footer-properties')[0]; + $marginHeader = isset($headerProperties) ? $headerProperties->getAttributeNS($this->stylesFo, 'min-height') : null; + $footer = $styleSet->getElementsByTagNameNS($this->stylesNs, 'footer-style')[0]; + $footerProperties = $footer->getElementsByTagNameNS($this->stylesNs, 'header-footer-properties')[0]; + $marginFooter = isset($footerProperties) ? $footerProperties->getAttributeNS($this->stylesFo, 'min-height') : null; + + $this->pageLayoutStyles[$styleName] = (object) [ + 'orientation' => $styleOrientation ?: PageSetup::ORIENTATION_DEFAULT, + 'scale' => $styleScale ?: 100, + 'printOrder' => $stylePrintOrder, + 'horizontalCentered' => $centered === 'horizontal' || $centered === 'both', + 'verticalCentered' => $centered === 'vertical' || $centered === 'both', + // margin size is already stored in inches, so no UOM conversion is required + 'marginLeft' => (float) $marginLeft ?? 0.7, + 'marginRight' => (float) $marginRight ?? 0.7, + 'marginTop' => (float) $marginTop ?? 0.3, + 'marginBottom' => (float) $marginBottom ?? 0.3, + 'marginHeader' => (float) $marginHeader ?? 0.45, + 'marginFooter' => (float) $marginFooter ?? 0.45, + ]; + } + } + + private function readStyleMasterLookup(DOMDocument $styleDom): void + { + $styleMasterLookup = $styleDom->getElementsByTagNameNS($this->officeNs, 'master-styles') + ->item(0) + ->getElementsByTagNameNS($this->stylesNs, 'master-page'); + + foreach ($styleMasterLookup as $styleMasterSet) { + $styleMasterName = $styleMasterSet->getAttributeNS($this->stylesNs, 'name'); + $pageLayoutName = $styleMasterSet->getAttributeNS($this->stylesNs, 'page-layout-name'); + $this->masterPrintStylesCrossReference[$styleMasterName] = $pageLayoutName; + } + } + + public function readStyleCrossReferences(DOMDocument $contentDom): void + { + $styleXReferences = $contentDom->getElementsByTagNameNS($this->officeNs, 'automatic-styles') + ->item(0) + ->getElementsByTagNameNS($this->stylesNs, 'style'); + + foreach ($styleXReferences as $styleXreferenceSet) { + $styleXRefName = $styleXreferenceSet->getAttributeNS($this->stylesNs, 'name'); + $stylePageLayoutName = $styleXreferenceSet->getAttributeNS($this->stylesNs, 'master-page-name'); + $styleFamilyName = $styleXreferenceSet->getAttributeNS($this->stylesNs, 'family'); + if (!empty($styleFamilyName) && $styleFamilyName === 'table') { + $styleVisibility = 'true'; + foreach ($styleXreferenceSet->getElementsByTagNameNS($this->stylesNs, 'table-properties') as $tableProperties) { + $styleVisibility = $tableProperties->getAttributeNS($this->tableNs, 'display'); + } + $this->tableStylesCrossReference[$styleXRefName] = $styleVisibility; + } + if (!empty($stylePageLayoutName)) { + $this->masterStylesCrossReference[$styleXRefName] = $stylePageLayoutName; + } + } + } + + public function setVisibilityForWorksheet(Worksheet $worksheet, string $styleName): void + { + if (!array_key_exists($styleName, $this->tableStylesCrossReference)) { + return; + } + + $worksheet->setSheetState( + $this->tableStylesCrossReference[$styleName] === 'false' + ? Worksheet::SHEETSTATE_HIDDEN + : Worksheet::SHEETSTATE_VISIBLE + ); + } + + public function setPrintSettingsForWorksheet(Worksheet $worksheet, string $styleName): void + { + if (!array_key_exists($styleName, $this->masterStylesCrossReference)) { + return; + } + $masterStyleName = $this->masterStylesCrossReference[$styleName]; + + if (!array_key_exists($masterStyleName, $this->masterPrintStylesCrossReference)) { + return; + } + $printSettingsIndex = $this->masterPrintStylesCrossReference[$masterStyleName]; + + if (!array_key_exists($printSettingsIndex, $this->pageLayoutStyles)) { + return; + } + $printSettings = $this->pageLayoutStyles[$printSettingsIndex]; + + $worksheet->getPageSetup() + ->setOrientation($printSettings->orientation ?? PageSetup::ORIENTATION_DEFAULT) + ->setPageOrder($printSettings->printOrder === 'ltr' ? PageSetup::PAGEORDER_OVER_THEN_DOWN : PageSetup::PAGEORDER_DOWN_THEN_OVER) + ->setScale((int) trim($printSettings->scale, '%')) + ->setHorizontalCentered($printSettings->horizontalCentered) + ->setVerticalCentered($printSettings->verticalCentered); + + $worksheet->getPageMargins() + ->setLeft($printSettings->marginLeft) + ->setRight($printSettings->marginRight) + ->setTop($printSettings->marginTop) + ->setBottom($printSettings->marginBottom) + ->setHeader($printSettings->marginHeader) + ->setFooter($printSettings->marginFooter); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Reader/Ods/Properties.php b/PhpOffice/PhpSpreadsheet/Reader/Ods/Properties.php old mode 100755 new mode 100644 index 8b6122c..1547a6b --- a/PhpOffice/PhpSpreadsheet/Reader/Ods/Properties.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Ods/Properties.php @@ -4,6 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Ods; use PhpOffice\PhpSpreadsheet\Document\Properties as DocumentProperties; use PhpOffice\PhpSpreadsheet\Spreadsheet; +use SimpleXMLElement; class Properties { @@ -14,29 +15,30 @@ class Properties $this->spreadsheet = $spreadsheet; } - public function load(\SimpleXMLElement $xml, $namespacesMeta) + public function load(SimpleXMLElement $xml, $namespacesMeta): void { $docProps = $this->spreadsheet->getProperties(); $officeProperty = $xml->children($namespacesMeta['office']); foreach ($officeProperty as $officePropertyData) { - /** @var \SimpleXMLElement $officePropertyData */ - $officePropertiesDC = (object) []; if (isset($namespacesMeta['dc'])) { + /** @scrutinizer ignore-call */ $officePropertiesDC = $officePropertyData->children($namespacesMeta['dc']); + $this->setCoreProperties($docProps, $officePropertiesDC); } - $this->setCoreProperties($docProps, $officePropertiesDC); - $officePropertyMeta = (object) []; + $officePropertyMeta = null; if (isset($namespacesMeta['dc'])) { + /** @scrutinizer ignore-call */ $officePropertyMeta = $officePropertyData->children($namespacesMeta['meta']); } + $officePropertyMeta = $officePropertyMeta ?? []; foreach ($officePropertyMeta as $propertyName => $propertyValue) { $this->setMetaProperties($namespacesMeta, $propertyValue, $propertyName, $docProps); } } } - private function setCoreProperties(DocumentProperties $docProps, \SimpleXMLElement $officePropertyDC) + private function setCoreProperties(DocumentProperties $docProps, SimpleXMLElement $officePropertyDC): void { foreach ($officePropertyDC as $propertyName => $propertyValue) { $propertyValue = (string) $propertyValue; @@ -54,14 +56,8 @@ class Properties $docProps->setLastModifiedBy($propertyValue); break; - case 'creation-date': - $creationDate = strtotime($propertyValue); - $docProps->setCreated($creationDate); - $docProps->setModified($creationDate); - - break; - case 'keyword': - $docProps->setKeywords($propertyValue); + case 'date': + $docProps->setModified($propertyValue); break; case 'description': @@ -74,10 +70,10 @@ class Properties private function setMetaProperties( $namespacesMeta, - \SimpleXMLElement $propertyValue, + SimpleXMLElement $propertyValue, $propertyName, DocumentProperties $docProps - ) { + ): void { $propertyValueAttributes = $propertyValue->attributes($namespacesMeta['meta']); $propertyValue = (string) $propertyValue; switch ($propertyName) { @@ -90,8 +86,7 @@ class Properties break; case 'creation-date': - $creationDate = strtotime($propertyValue); - $docProps->setCreated($creationDate); + $docProps->setCreated($propertyValue); break; case 'user-defined': @@ -101,7 +96,7 @@ class Properties } } - private function setUserDefinedProperty($propertyValueAttributes, $propertyValue, DocumentProperties $docProps) + private function setUserDefinedProperty($propertyValueAttributes, $propertyValue, DocumentProperties $docProps): void { $propertyValueName = ''; $propertyValueType = DocumentProperties::PROPERTY_TYPE_STRING; diff --git a/PhpOffice/PhpSpreadsheet/Reader/Security/XmlScanner.php b/PhpOffice/PhpSpreadsheet/Reader/Security/XmlScanner.php old mode 100755 new mode 100644 index 4798004..f4cf2db --- a/PhpOffice/PhpSpreadsheet/Reader/Security/XmlScanner.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Security/XmlScanner.php @@ -3,7 +3,6 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Security; use PhpOffice\PhpSpreadsheet\Reader; -use PhpOffice\PhpSpreadsheet\Settings; class XmlScanner { @@ -18,6 +17,11 @@ class XmlScanner private static $libxmlDisableEntityLoaderValue; + /** + * @var bool + */ + private static $shutdownRegistered = false; + public function __construct($pattern = 'pattern = $pattern; @@ -25,7 +29,10 @@ class XmlScanner $this->disableEntityLoaderCheck(); // A fatal error will bypass the destructor, so we register a shutdown here - register_shutdown_function([__CLASS__, 'shutdown']); + if (!self::$shutdownRegistered) { + self::$shutdownRegistered = true; + register_shutdown_function([__CLASS__, 'shutdown']); + } } public static function getInstance(Reader\IReader $reader) @@ -45,7 +52,7 @@ class XmlScanner public static function threadSafeLibxmlDisableEntityLoaderAvailability() { - if (PHP_MAJOR_VERSION == 7) { + if (PHP_MAJOR_VERSION === 7) { switch (PHP_MINOR_VERSION) { case 2: return PHP_RELEASE_VERSION >= 1; @@ -61,9 +68,9 @@ class XmlScanner return false; } - private function disableEntityLoaderCheck() + private function disableEntityLoaderCheck(): void { - if (Settings::getLibXmlDisableEntityLoader() && \PHP_VERSION_ID < 80000) { + if (\PHP_VERSION_ID < 80000) { $libxmlDisableEntityLoaderValue = libxml_disable_entity_loader(true); if (self::$libxmlDisableEntityLoaderValue === null) { @@ -72,7 +79,7 @@ class XmlScanner } } - public static function shutdown() + public static function shutdown(): void { if (self::$libxmlDisableEntityLoaderValue !== null && \PHP_VERSION_ID < 80000) { libxml_disable_entity_loader(self::$libxmlDisableEntityLoaderValue); @@ -85,11 +92,22 @@ class XmlScanner self::shutdown(); } - public function setAdditionalCallback(callable $callback) + public function setAdditionalCallback(callable $callback): void { $this->callback = $callback; } + /** @param mixed $arg */ + private static function forceString($arg): string + { + return is_string($arg) ? $arg : ''; + } + + /** + * @param string $xml + * + * @return string + */ private function toUtf8($xml) { $pattern = '/encoding="(.*?)"/'; @@ -97,7 +115,7 @@ class XmlScanner $charset = strtoupper($result ? $matches[1] : 'UTF-8'); if ($charset !== 'UTF-8') { - $xml = mb_convert_encoding($xml, 'UTF-8', $charset); + $xml = self::forceString(mb_convert_encoding($xml, 'UTF-8', $charset)); $result = preg_match($pattern, $xml, $matches); $charset = strtoupper($result ? $matches[1] : 'UTF-8'); @@ -112,20 +130,21 @@ class XmlScanner /** * Scan the XML for use of disableEntityLoaderCheck(); $xml = $this->toUtf8($xml); // Don't rely purely on libxml_disable_entity_loader() - $pattern = '/\\0?' . implode('\\0?', str_split($this->pattern)) . '\\0?/'; + $pattern = '/\\0?' . implode('\\0?', /** @scrutinizer ignore-type */ str_split($this->pattern)) . '\\0?/'; if (preg_match($pattern, $xml)) { throw new Reader\Exception('Detected use of ENTITY in XML, spreadsheet file load() aborted to prevent XXE/XEE attacks'); @@ -143,8 +162,6 @@ class XmlScanner * * @param string $filestream * - * @throws Reader\Exception - * * @return string */ public function scanFile($filestream) diff --git a/PhpOffice/PhpSpreadsheet/Reader/Slk.php b/PhpOffice/PhpSpreadsheet/Reader/Slk.php old mode 100755 new mode 100644 index d73f598..38eddbc --- a/PhpOffice/PhpSpreadsheet/Reader/Slk.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Slk.php @@ -4,9 +4,11 @@ namespace PhpOffice\PhpSpreadsheet\Reader; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; +use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Style\Border; +use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; class Slk extends BaseReader { @@ -38,6 +40,20 @@ class Slk extends BaseReader */ private $format = 0; + /** + * Fonts. + * + * @var array + */ + private $fonts = []; + + /** + * Font Count. + * + * @var int + */ + private $fontcount = 0; + /** * Create a new SYLK Reader instance. */ @@ -48,22 +64,17 @@ class Slk extends BaseReader /** * Validate that the current file is a SYLK file. - * - * @param string $pFilename - * - * @return bool */ - public function canRead($pFilename) + public function canRead(string $filename): bool { - // Check if file exists try { - $this->openFile($pFilename); - } catch (Exception $e) { + $this->openFile($filename); + } catch (ReaderException $e) { return false; } // Read sample data (first 2 KB will do) - $data = fread($this->fileHandle, 2048); + $data = (string) fread($this->fileHandle, 2048); // Count delimiters in file $delimiterCount = substr_count($data, ';'); @@ -78,16 +89,28 @@ class Slk extends BaseReader return $hasDelimiter && $hasId; } + private function canReadOrBust(string $filename): void + { + if (!$this->canRead($filename)) { + throw new ReaderException($filename . ' is an Invalid SYLK file.'); + } + $this->openFile($filename); + } + /** * Set input encoding. * - * @param string $pValue Input encoding, eg: 'ANSI' + * @deprecated no use is made of this property * - * @return Slk + * @param string $inputEncoding Input encoding, eg: 'ANSI' + * + * @return $this + * + * @codeCoverageIgnore */ - public function setInputEncoding($pValue) + public function setInputEncoding($inputEncoding) { - $this->inputEncoding = $pValue; + $this->inputEncoding = $inputEncoding; return $this; } @@ -95,7 +118,11 @@ class Slk extends BaseReader /** * Get input encoding. * + * @deprecated no use is made of this property + * * @return string + * + * @codeCoverageIgnore */ public function getInputEncoding() { @@ -105,31 +132,23 @@ class Slk extends BaseReader /** * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns). * - * @param string $pFilename - * - * @throws Exception + * @param string $filename * * @return array */ - public function listWorksheetInfo($pFilename) + public function listWorksheetInfo($filename) { // Open file - if (!$this->canRead($pFilename)) { - throw new Exception($pFilename . ' is an Invalid Spreadsheet file.'); - } - $this->openFile($pFilename); + $this->canReadOrBust($filename); $fileHandle = $this->fileHandle; rewind($fileHandle); $worksheetInfo = []; - $worksheetInfo[0]['worksheetName'] = 'Worksheet'; - $worksheetInfo[0]['lastColumnLetter'] = 'A'; - $worksheetInfo[0]['lastColumnIndex'] = 0; - $worksheetInfo[0]['totalRows'] = 0; - $worksheetInfo[0]['totalColumns'] = 0; + $worksheetInfo[0]['worksheetName'] = basename($filename, '.slk'); // loop through one row (line) at a time in the file $rowIndex = 0; + $columnIndex = 0; while (($rowData = fgets($fileHandle)) !== false) { $columnIndex = 0; @@ -141,28 +160,26 @@ class Slk extends BaseReader $rowData = explode("\t", str_replace('¤', ';', str_replace(';', "\t", str_replace(';;', '¤', rtrim($rowData))))); $dataType = array_shift($rowData); - if ($dataType == 'C') { - // Read cell value data + if ($dataType == 'B') { foreach ($rowData as $rowDatum) { switch ($rowDatum[0]) { - case 'C': case 'X': - $columnIndex = substr($rowDatum, 1) - 1; + $columnIndex = (int) substr($rowDatum, 1) - 1; break; - case 'R': case 'Y': $rowIndex = substr($rowDatum, 1); break; } - - $worksheetInfo[0]['totalRows'] = max($worksheetInfo[0]['totalRows'], $rowIndex); - $worksheetInfo[0]['lastColumnIndex'] = max($worksheetInfo[0]['lastColumnIndex'], $columnIndex); } + + break; } } + $worksheetInfo[0]['lastColumnIndex'] = $columnIndex; + $worksheetInfo[0]['totalRows'] = $rowIndex; $worksheetInfo[0]['lastColumnLetter'] = Coordinate::stringFromColumnIndex($worksheetInfo[0]['lastColumnIndex'] + 1); $worksheetInfo[0]['totalColumns'] = $worksheetInfo[0]['lastColumnIndex'] + 1; @@ -174,39 +191,325 @@ class Slk extends BaseReader /** * Loads PhpSpreadsheet from file. - * - * @param string $pFilename - * - * @throws Exception - * - * @return Spreadsheet */ - public function load($pFilename) + protected function loadSpreadsheetFromFile(string $filename): Spreadsheet { // Create new Spreadsheet $spreadsheet = new Spreadsheet(); // Load into this instance - return $this->loadIntoExisting($pFilename, $spreadsheet); + return $this->loadIntoExisting($filename, $spreadsheet); + } + + private const COLOR_ARRAY = [ + 'FF00FFFF', // 0 - cyan + 'FF000000', // 1 - black + 'FFFFFFFF', // 2 - white + 'FFFF0000', // 3 - red + 'FF00FF00', // 4 - green + 'FF0000FF', // 5 - blue + 'FFFFFF00', // 6 - yellow + 'FFFF00FF', // 7 - magenta + ]; + + private const FONT_STYLE_MAPPINGS = [ + 'B' => 'bold', + 'I' => 'italic', + 'U' => 'underline', + ]; + + private function processFormula(string $rowDatum, bool &$hasCalculatedValue, string &$cellDataFormula, string $row, string $column): void + { + $cellDataFormula = '=' . substr($rowDatum, 1); + // Convert R1C1 style references to A1 style references (but only when not quoted) + $temp = explode('"', $cellDataFormula); + $key = false; + foreach ($temp as &$value) { + // Only count/replace in alternate array entries + $key = $key === false; + if ($key) { + preg_match_all('/(R(\[?-?\d*\]?))(C(\[?-?\d*\]?))/', $value, $cellReferences, PREG_SET_ORDER + PREG_OFFSET_CAPTURE); + // Reverse the matches array, otherwise all our offsets will become incorrect if we modify our way + // through the formula from left to right. Reversing means that we work right to left.through + // the formula + $cellReferences = array_reverse($cellReferences); + // Loop through each R1C1 style reference in turn, converting it to its A1 style equivalent, + // then modify the formula to use that new reference + foreach ($cellReferences as $cellReference) { + $rowReference = $cellReference[2][0]; + // Empty R reference is the current row + if ($rowReference == '') { + $rowReference = $row; + } + // Bracketed R references are relative to the current row + if ($rowReference[0] == '[') { + $rowReference = (int) $row + (int) trim($rowReference, '[]'); + } + $columnReference = $cellReference[4][0]; + // Empty C reference is the current column + if ($columnReference == '') { + $columnReference = $column; + } + // Bracketed C references are relative to the current column + if ($columnReference[0] == '[') { + $columnReference = (int) $column + (int) trim($columnReference, '[]'); + } + $A1CellReference = Coordinate::stringFromColumnIndex((int) $columnReference) . $rowReference; + + $value = substr_replace($value, $A1CellReference, $cellReference[0][1], strlen($cellReference[0][0])); + } + } + } + unset($value); + // Then rebuild the formula string + $cellDataFormula = implode('"', $temp); + $hasCalculatedValue = true; + } + + private function processCRecord(array $rowData, Spreadsheet &$spreadsheet, string &$row, string &$column): void + { + // Read cell value data + $hasCalculatedValue = false; + $cellDataFormula = $cellData = ''; + foreach ($rowData as $rowDatum) { + switch ($rowDatum[0]) { + case 'C': + case 'X': + $column = substr($rowDatum, 1); + + break; + case 'R': + case 'Y': + $row = substr($rowDatum, 1); + + break; + case 'K': + $cellData = substr($rowDatum, 1); + + break; + case 'E': + $this->processFormula($rowDatum, $hasCalculatedValue, $cellDataFormula, $row, $column); + + break; + case 'A': + $comment = substr($rowDatum, 1); + $columnLetter = Coordinate::stringFromColumnIndex((int) $column); + $spreadsheet->getActiveSheet() + ->getComment("$columnLetter$row") + ->getText() + ->createText($comment); + + break; + } + } + $columnLetter = Coordinate::stringFromColumnIndex((int) $column); + $cellData = Calculation::unwrapResult($cellData); + + // Set cell value + $this->processCFinal($spreadsheet, $hasCalculatedValue, $cellDataFormula, $cellData, "$columnLetter$row"); + } + + private function processCFinal(Spreadsheet &$spreadsheet, bool $hasCalculatedValue, string $cellDataFormula, string $cellData, string $coordinate): void + { + // Set cell value + $spreadsheet->getActiveSheet()->getCell($coordinate)->setValue(($hasCalculatedValue) ? $cellDataFormula : $cellData); + if ($hasCalculatedValue) { + $cellData = Calculation::unwrapResult($cellData); + $spreadsheet->getActiveSheet()->getCell($coordinate)->setCalculatedValue($cellData); + } + } + + private function processFRecord(array $rowData, Spreadsheet &$spreadsheet, string &$row, string &$column): void + { + // Read cell formatting + $formatStyle = $columnWidth = ''; + $startCol = $endCol = ''; + $fontStyle = ''; + $styleData = []; + foreach ($rowData as $rowDatum) { + switch ($rowDatum[0]) { + case 'C': + case 'X': + $column = substr($rowDatum, 1); + + break; + case 'R': + case 'Y': + $row = substr($rowDatum, 1); + + break; + case 'P': + $formatStyle = $rowDatum; + + break; + case 'W': + [$startCol, $endCol, $columnWidth] = explode(' ', substr($rowDatum, 1)); + + break; + case 'S': + $this->styleSettings($rowDatum, $styleData, $fontStyle); + + break; + } + } + $this->addFormats($spreadsheet, $formatStyle, $row, $column); + $this->addFonts($spreadsheet, $fontStyle, $row, $column); + $this->addStyle($spreadsheet, $styleData, $row, $column); + $this->addWidth($spreadsheet, $columnWidth, $startCol, $endCol); + } + + private const STYLE_SETTINGS_FONT = ['D' => 'bold', 'I' => 'italic']; + + private const STYLE_SETTINGS_BORDER = [ + 'B' => 'bottom', + 'L' => 'left', + 'R' => 'right', + 'T' => 'top', + ]; + + private function styleSettings(string $rowDatum, array &$styleData, string &$fontStyle): void + { + $styleSettings = substr($rowDatum, 1); + $iMax = strlen($styleSettings); + for ($i = 0; $i < $iMax; ++$i) { + $char = $styleSettings[$i]; + if (array_key_exists($char, self::STYLE_SETTINGS_FONT)) { + $styleData['font'][self::STYLE_SETTINGS_FONT[$char]] = true; + } elseif (array_key_exists($char, self::STYLE_SETTINGS_BORDER)) { + $styleData['borders'][self::STYLE_SETTINGS_BORDER[$char]]['borderStyle'] = Border::BORDER_THIN; + } elseif ($char == 'S') { + $styleData['fill']['fillType'] = \PhpOffice\PhpSpreadsheet\Style\Fill::FILL_PATTERN_GRAY125; + } elseif ($char == 'M') { + if (preg_match('/M([1-9]\\d*)/', $styleSettings, $matches)) { + $fontStyle = $matches[1]; + } + } + } + } + + private function addFormats(Spreadsheet &$spreadsheet, string $formatStyle, string $row, string $column): void + { + if ($formatStyle && $column > '' && $row > '') { + $columnLetter = Coordinate::stringFromColumnIndex((int) $column); + if (isset($this->formats[$formatStyle])) { + $spreadsheet->getActiveSheet()->getStyle($columnLetter . $row)->applyFromArray($this->formats[$formatStyle]); + } + } + } + + private function addFonts(Spreadsheet &$spreadsheet, string $fontStyle, string $row, string $column): void + { + if ($fontStyle && $column > '' && $row > '') { + $columnLetter = Coordinate::stringFromColumnIndex((int) $column); + if (isset($this->fonts[$fontStyle])) { + $spreadsheet->getActiveSheet()->getStyle($columnLetter . $row)->applyFromArray($this->fonts[$fontStyle]); + } + } + } + + private function addStyle(Spreadsheet &$spreadsheet, array $styleData, string $row, string $column): void + { + if ((!empty($styleData)) && $column > '' && $row > '') { + $columnLetter = Coordinate::stringFromColumnIndex((int) $column); + $spreadsheet->getActiveSheet()->getStyle($columnLetter . $row)->applyFromArray($styleData); + } + } + + private function addWidth(Spreadsheet $spreadsheet, string $columnWidth, string $startCol, string $endCol): void + { + if ($columnWidth > '') { + if ($startCol == $endCol) { + $startCol = Coordinate::stringFromColumnIndex((int) $startCol); + $spreadsheet->getActiveSheet()->getColumnDimension($startCol)->setWidth((float) $columnWidth); + } else { + $startCol = Coordinate::stringFromColumnIndex((int) $startCol); + $endCol = Coordinate::stringFromColumnIndex((int) $endCol); + $spreadsheet->getActiveSheet()->getColumnDimension($startCol)->setWidth((float) $columnWidth); + do { + $spreadsheet->getActiveSheet()->getColumnDimension(++$startCol)->setWidth((float) $columnWidth); + } while ($startCol !== $endCol); + } + } + } + + private function processPRecord(array $rowData, Spreadsheet &$spreadsheet): void + { + // Read shared styles + $formatArray = []; + $fromFormats = ['\-', '\ ']; + $toFormats = ['-', ' ']; + foreach ($rowData as $rowDatum) { + switch ($rowDatum[0]) { + case 'P': + $formatArray['numberFormat']['formatCode'] = str_replace($fromFormats, $toFormats, substr($rowDatum, 1)); + + break; + case 'E': + case 'F': + $formatArray['font']['name'] = substr($rowDatum, 1); + + break; + case 'M': + $formatArray['font']['size'] = ((float) substr($rowDatum, 1)) / 20; + + break; + case 'L': + $this->processPColors($rowDatum, $formatArray); + + break; + case 'S': + $this->processPFontStyles($rowDatum, $formatArray); + + break; + } + } + $this->processPFinal($spreadsheet, $formatArray); + } + + private function processPColors(string $rowDatum, array &$formatArray): void + { + if (preg_match('/L([1-9]\\d*)/', $rowDatum, $matches)) { + $fontColor = $matches[1] % 8; + $formatArray['font']['color']['argb'] = self::COLOR_ARRAY[$fontColor]; + } + } + + private function processPFontStyles(string $rowDatum, array &$formatArray): void + { + $styleSettings = substr($rowDatum, 1); + $iMax = strlen($styleSettings); + for ($i = 0; $i < $iMax; ++$i) { + if (array_key_exists($styleSettings[$i], self::FONT_STYLE_MAPPINGS)) { + $formatArray['font'][self::FONT_STYLE_MAPPINGS[$styleSettings[$i]]] = true; + } + } + } + + private function processPFinal(Spreadsheet &$spreadsheet, array $formatArray): void + { + if (array_key_exists('numberFormat', $formatArray)) { + $this->formats['P' . $this->format] = $formatArray; + ++$this->format; + } elseif (array_key_exists('font', $formatArray)) { + ++$this->fontcount; + $this->fonts[$this->fontcount] = $formatArray; + if ($this->fontcount === 1) { + $spreadsheet->getDefaultStyle()->applyFromArray($formatArray); + } + } } /** * Loads PhpSpreadsheet from file into PhpSpreadsheet instance. * - * @param string $pFilename - * @param Spreadsheet $spreadsheet - * - * @throws Exception + * @param string $filename * * @return Spreadsheet */ - public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet) + public function loadIntoExisting($filename, Spreadsheet $spreadsheet) { // Open file - if (!$this->canRead($pFilename)) { - throw new Exception($pFilename . ' is an Invalid Spreadsheet file.'); - } - $this->openFile($pFilename); + $this->canReadOrBust($filename); $fileHandle = $this->fileHandle; rewind($fileHandle); @@ -215,251 +518,32 @@ class Slk extends BaseReader $spreadsheet->createSheet(); } $spreadsheet->setActiveSheetIndex($this->sheetIndex); - - $fromFormats = ['\-', '\ ']; - $toFormats = ['-', ' ']; + $spreadsheet->getActiveSheet()->setTitle(substr(basename($filename, '.slk'), 0, Worksheet::SHEET_TITLE_MAXIMUM_LENGTH)); // Loop through file $column = $row = ''; // loop through one row (line) at a time in the file - while (($rowData = fgets($fileHandle)) !== false) { + while (($rowDataTxt = fgets($fileHandle)) !== false) { // convert SYLK encoded $rowData to UTF-8 - $rowData = StringHelper::SYLKtoUTF8($rowData); + $rowDataTxt = StringHelper::SYLKtoUTF8($rowDataTxt); // explode each row at semicolons while taking into account that literal semicolon (;) // is escaped like this (;;) - $rowData = explode("\t", str_replace('¤', ';', str_replace(';', "\t", str_replace(';;', '¤', rtrim($rowData))))); + $rowData = explode("\t", str_replace('¤', ';', str_replace(';', "\t", str_replace(';;', '¤', rtrim($rowDataTxt))))); $dataType = array_shift($rowData); - // Read shared styles if ($dataType == 'P') { - $formatArray = []; - foreach ($rowData as $rowDatum) { - switch ($rowDatum[0]) { - case 'P': - $formatArray['numberFormat']['formatCode'] = str_replace($fromFormats, $toFormats, substr($rowDatum, 1)); - - break; - case 'E': - case 'F': - $formatArray['font']['name'] = substr($rowDatum, 1); - - break; - case 'L': - $formatArray['font']['size'] = substr($rowDatum, 1); - - break; - case 'S': - $styleSettings = substr($rowDatum, 1); - $iMax = strlen($styleSettings); - for ($i = 0; $i < $iMax; ++$i) { - switch ($styleSettings[$i]) { - case 'I': - $formatArray['font']['italic'] = true; - - break; - case 'D': - $formatArray['font']['bold'] = true; - - break; - case 'T': - $formatArray['borders']['top']['borderStyle'] = Border::BORDER_THIN; - - break; - case 'B': - $formatArray['borders']['bottom']['borderStyle'] = Border::BORDER_THIN; - - break; - case 'L': - $formatArray['borders']['left']['borderStyle'] = Border::BORDER_THIN; - - break; - case 'R': - $formatArray['borders']['right']['borderStyle'] = Border::BORDER_THIN; - - break; - } - } - - break; - } - } - $this->formats['P' . $this->format++] = $formatArray; - // Read cell value data + // Read shared styles + $this->processPRecord($rowData, $spreadsheet); } elseif ($dataType == 'C') { - $hasCalculatedValue = false; - $cellData = $cellDataFormula = ''; - foreach ($rowData as $rowDatum) { - switch ($rowDatum[0]) { - case 'C': - case 'X': - $column = substr($rowDatum, 1); - - break; - case 'R': - case 'Y': - $row = substr($rowDatum, 1); - - break; - case 'K': - $cellData = substr($rowDatum, 1); - - break; - case 'E': - $cellDataFormula = '=' . substr($rowDatum, 1); - // Convert R1C1 style references to A1 style references (but only when not quoted) - $temp = explode('"', $cellDataFormula); - $key = false; - foreach ($temp as &$value) { - // Only count/replace in alternate array entries - if ($key = !$key) { - preg_match_all('/(R(\[?-?\d*\]?))(C(\[?-?\d*\]?))/', $value, $cellReferences, PREG_SET_ORDER + PREG_OFFSET_CAPTURE); - // Reverse the matches array, otherwise all our offsets will become incorrect if we modify our way - // through the formula from left to right. Reversing means that we work right to left.through - // the formula - $cellReferences = array_reverse($cellReferences); - // Loop through each R1C1 style reference in turn, converting it to its A1 style equivalent, - // then modify the formula to use that new reference - foreach ($cellReferences as $cellReference) { - $rowReference = $cellReference[2][0]; - // Empty R reference is the current row - if ($rowReference == '') { - $rowReference = $row; - } - // Bracketed R references are relative to the current row - if ($rowReference[0] == '[') { - $rowReference = $row + trim($rowReference, '[]'); - } - $columnReference = $cellReference[4][0]; - // Empty C reference is the current column - if ($columnReference == '') { - $columnReference = $column; - } - // Bracketed C references are relative to the current column - if ($columnReference[0] == '[') { - $columnReference = $column + trim($columnReference, '[]'); - } - $A1CellReference = Coordinate::stringFromColumnIndex($columnReference) . $rowReference; - - $value = substr_replace($value, $A1CellReference, $cellReference[0][1], strlen($cellReference[0][0])); - } - } - } - unset($value); - // Then rebuild the formula string - $cellDataFormula = implode('"', $temp); - $hasCalculatedValue = true; - - break; - } - } - $columnLetter = Coordinate::stringFromColumnIndex($column); - $cellData = Calculation::unwrapResult($cellData); - - // Set cell value - $spreadsheet->getActiveSheet()->getCell($columnLetter . $row)->setValue(($hasCalculatedValue) ? $cellDataFormula : $cellData); - if ($hasCalculatedValue) { - $cellData = Calculation::unwrapResult($cellData); - $spreadsheet->getActiveSheet()->getCell($columnLetter . $row)->setCalculatedValue($cellData); - } - // Read cell formatting + // Read cell value data + $this->processCRecord($rowData, $spreadsheet, $row, $column); } elseif ($dataType == 'F') { - $formatStyle = $columnWidth = $styleSettings = ''; - $styleData = []; - foreach ($rowData as $rowDatum) { - switch ($rowDatum[0]) { - case 'C': - case 'X': - $column = substr($rowDatum, 1); - - break; - case 'R': - case 'Y': - $row = substr($rowDatum, 1); - - break; - case 'P': - $formatStyle = $rowDatum; - - break; - case 'W': - [$startCol, $endCol, $columnWidth] = explode(' ', substr($rowDatum, 1)); - - break; - case 'S': - $styleSettings = substr($rowDatum, 1); - $iMax = strlen($styleSettings); - for ($i = 0; $i < $iMax; ++$i) { - switch ($styleSettings[$i]) { - case 'I': - $styleData['font']['italic'] = true; - - break; - case 'D': - $styleData['font']['bold'] = true; - - break; - case 'T': - $styleData['borders']['top']['borderStyle'] = Border::BORDER_THIN; - - break; - case 'B': - $styleData['borders']['bottom']['borderStyle'] = Border::BORDER_THIN; - - break; - case 'L': - $styleData['borders']['left']['borderStyle'] = Border::BORDER_THIN; - - break; - case 'R': - $styleData['borders']['right']['borderStyle'] = Border::BORDER_THIN; - - break; - } - } - - break; - } - } - if (($formatStyle > '') && ($column > '') && ($row > '')) { - $columnLetter = Coordinate::stringFromColumnIndex($column); - if (isset($this->formats[$formatStyle])) { - $spreadsheet->getActiveSheet()->getStyle($columnLetter . $row)->applyFromArray($this->formats[$formatStyle]); - } - } - if ((!empty($styleData)) && ($column > '') && ($row > '')) { - $columnLetter = Coordinate::stringFromColumnIndex($column); - $spreadsheet->getActiveSheet()->getStyle($columnLetter . $row)->applyFromArray($styleData); - } - if ($columnWidth > '') { - if ($startCol == $endCol) { - $startCol = Coordinate::stringFromColumnIndex($startCol); - $spreadsheet->getActiveSheet()->getColumnDimension($startCol)->setWidth($columnWidth); - } else { - $startCol = Coordinate::stringFromColumnIndex($startCol); - $endCol = Coordinate::stringFromColumnIndex($endCol); - $spreadsheet->getActiveSheet()->getColumnDimension($startCol)->setWidth($columnWidth); - do { - $spreadsheet->getActiveSheet()->getColumnDimension(++$startCol)->setWidth($columnWidth); - } while ($startCol != $endCol); - } - } + // Read cell formatting + $this->processFRecord($rowData, $spreadsheet, $row, $column); } else { - foreach ($rowData as $rowDatum) { - switch ($rowDatum[0]) { - case 'C': - case 'X': - $column = substr($rowDatum, 1); - - break; - case 'R': - case 'Y': - $row = substr($rowDatum, 1); - - break; - } - } + $this->columnRowFromRowData($rowData, $column, $row); } } @@ -470,6 +554,18 @@ class Slk extends BaseReader return $spreadsheet; } + private function columnRowFromRowData(array $rowData, string &$column, string &$row): void + { + foreach ($rowData as $rowDatum) { + $char0 = $rowDatum[0]; + if ($char0 === 'X' || $char0 == 'C') { + $column = substr($rowDatum, 1); + } elseif ($char0 === 'Y' || $char0 == 'R') { + $row = substr($rowDatum, 1); + } + } + } + /** * Get sheet index. * @@ -483,13 +579,13 @@ class Slk extends BaseReader /** * Set sheet index. * - * @param int $pValue Sheet index + * @param int $sheetIndex Sheet index * - * @return Slk + * @return $this */ - public function setSheetIndex($pValue) + public function setSheetIndex($sheetIndex) { - $this->sheetIndex = $pValue; + $this->sheetIndex = $sheetIndex; return $this; } diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xls.php b/PhpOffice/PhpSpreadsheet/Reader/Xls.php old mode 100755 new mode 100644 index 36b50f0..5367eff --- a/PhpOffice/PhpSpreadsheet/Reader/Xls.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Xls.php @@ -7,6 +7,9 @@ use PhpOffice\PhpSpreadsheet\Cell\DataType; use PhpOffice\PhpSpreadsheet\Cell\DataValidation; use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; use PhpOffice\PhpSpreadsheet\NamedRange; +use PhpOffice\PhpSpreadsheet\Reader\Xls\ConditionalFormatting; +use PhpOffice\PhpSpreadsheet\Reader\Xls\Style\CellFont; +use PhpOffice\PhpSpreadsheet\Reader\Xls\Style\FillPattern; use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\Shared\CodePage; use PhpOffice\PhpSpreadsheet\Shared\Date; @@ -16,9 +19,12 @@ use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Shared\OLE; use PhpOffice\PhpSpreadsheet\Shared\OLERead; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; +use PhpOffice\PhpSpreadsheet\Shared\Xls as SharedXls; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Style\Alignment; use PhpOffice\PhpSpreadsheet\Style\Borders; +use PhpOffice\PhpSpreadsheet\Style\Conditional; +use PhpOffice\PhpSpreadsheet\Style\Fill; use PhpOffice\PhpSpreadsheet\Style\Font; use PhpOffice\PhpSpreadsheet\Style\NumberFormat; use PhpOffice\PhpSpreadsheet\Style\Protection; @@ -140,6 +146,8 @@ class Xls extends BaseReader const XLS_TYPE_SHEETLAYOUT = 0x0862; const XLS_TYPE_XFEXT = 0x087d; const XLS_TYPE_PAGELAYOUTVIEW = 0x088b; + const XLS_TYPE_CFHEADER = 0x01b0; + const XLS_TYPE_CFRULE = 0x01b1; const XLS_TYPE_UNKNOWN = 0xffff; // Encryption type @@ -224,7 +232,7 @@ class Xls extends BaseReader /** * Shared fonts. * - * @var array + * @var Font[] */ private $objFonts; @@ -374,7 +382,7 @@ class Xls extends BaseReader * * @var int */ - private $encryptionStartPos = false; + private $encryptionStartPos = 0; /** * The current RC4 decryption object. @@ -417,21 +425,19 @@ class Xls extends BaseReader /** * Can the current IReader read the file? - * - * @param string $pFilename - * - * @return bool */ - public function canRead($pFilename) + public function canRead(string $filename): bool { - File::assertFile($pFilename); + if (!File::testFileNoThrow($filename)) { + return false; + } try { // Use ParseXL for the hard work. $ole = new OLERead(); // get excel data - $ole->read($pFilename); + $ole->read($filename); return true; } catch (PhpSpreadsheetException $e) { @@ -439,23 +445,30 @@ class Xls extends BaseReader } } + public function setCodepage(string $codepage): void + { + if (!CodePage::validate($codepage)) { + throw new PhpSpreadsheetException('Unknown codepage: ' . $codepage); + } + + $this->codepage = $codepage; + } + /** * Reads names of the worksheets from a file, without parsing the whole file to a PhpSpreadsheet object. * - * @param string $pFilename - * - * @throws Exception + * @param string $filename * * @return array */ - public function listWorksheetNames($pFilename) + public function listWorksheetNames($filename) { - File::assertFile($pFilename); + File::assertFile($filename); $worksheetNames = []; // Read the OLE file - $this->loadOLE($pFilename); + $this->loadOLE($filename); // total byte size of Excel data (workbook global substream + sheet substreams) $this->dataSize = strlen($this->data); @@ -502,20 +515,18 @@ class Xls extends BaseReader /** * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns). * - * @param string $pFilename - * - * @throws Exception + * @param string $filename * * @return array */ - public function listWorksheetInfo($pFilename) + public function listWorksheetInfo($filename) { - File::assertFile($pFilename); + File::assertFile($filename); $worksheetInfo = []; // Read the OLE file - $this->loadOLE($pFilename); + $this->loadOLE($filename); // total byte size of Excel data (workbook global substream + sheet substreams) $this->dataSize = strlen($this->data); @@ -615,17 +626,11 @@ class Xls extends BaseReader /** * Loads PhpSpreadsheet from file. - * - * @param string $pFilename - * - * @throws Exception - * - * @return Spreadsheet */ - public function load($pFilename) + protected function loadSpreadsheetFromFile(string $filename): Spreadsheet { // Read the OLE file - $this->loadOLE($pFilename); + $this->loadOLE($filename); // Initialisations $this->spreadsheet = new Spreadsheet(); @@ -646,7 +651,7 @@ class Xls extends BaseReader // initialize $this->pos = 0; - $this->codepage = 'CP1252'; + $this->codepage = $this->codepage ?: CodePage::DEFAULT_CODE_PAGE; $this->formats = []; $this->objFonts = []; $this->palette = []; @@ -656,7 +661,7 @@ class Xls extends BaseReader $this->definedname = []; $this->sst = []; $this->drawingGroupData = ''; - $this->xfIndex = ''; + $this->xfIndex = 0; $this->mapCellXfIndex = []; $this->mapCellStyleXfIndex = []; @@ -798,9 +803,10 @@ class Xls extends BaseReader } // treat MSODRAWINGGROUP records, workbook-level Escher + $escherWorkbook = null; if (!$this->readDataOnly && $this->drawingGroupData) { - $escherWorkbook = new Escher(); - $reader = new Xls\Escher($escherWorkbook); + $escher = new Escher(); + $reader = new Xls\Escher($escher); $escherWorkbook = $reader->load($this->drawingGroupData); } @@ -1031,6 +1037,14 @@ class Xls extends BaseReader case self::XLS_TYPE_DATAVALIDATION: $this->readDataValidation(); + break; + case self::XLS_TYPE_CFHEADER: + $cellRangeAddresses = $this->readCFHeader(); + + break; + case self::XLS_TYPE_CFRULE: + $this->readCFRule($cellRangeAddresses ?? []); + break; case self::XLS_TYPE_SHEETLAYOUT: $this->readSheetLayout(); @@ -1097,12 +1111,12 @@ class Xls extends BaseReader $endOffsetX = $spContainer->getEndOffsetX(); $endOffsetY = $spContainer->getEndOffsetY(); - $width = \PhpOffice\PhpSpreadsheet\Shared\Xls::getDistanceX($this->phpSheet, $startColumn, $startOffsetX, $endColumn, $endOffsetX); - $height = \PhpOffice\PhpSpreadsheet\Shared\Xls::getDistanceY($this->phpSheet, $startRow, $startOffsetY, $endRow, $endOffsetY); + $width = SharedXls::getDistanceX($this->phpSheet, $startColumn, $startOffsetX, $endColumn, $endOffsetX); + $height = SharedXls::getDistanceY($this->phpSheet, $startRow, $startOffsetY, $endRow, $endOffsetY); // calculate offsetX and offsetY of the shape - $offsetX = $startOffsetX * \PhpOffice\PhpSpreadsheet\Shared\Xls::sizeCol($this->phpSheet, $startColumn) / 1024; - $offsetY = $startOffsetY * \PhpOffice\PhpSpreadsheet\Shared\Xls::sizeRow($this->phpSheet, $startRow) / 256; + $offsetX = (int) ($startOffsetX * SharedXls::sizeCol($this->phpSheet, $startColumn) / 1024); + $offsetY = (int) ($startOffsetY * SharedXls::sizeRow($this->phpSheet, $startRow) / 256); switch ($obj['otObjType']) { case 0x19: @@ -1130,38 +1144,44 @@ class Xls extends BaseReader continue 2; } - $BSECollection = $escherWorkbook->getDggContainer()->getBstoreContainer()->getBSECollection(); - $BSE = $BSECollection[$BSEindex - 1]; - $blipType = $BSE->getBlipType(); + if ($escherWorkbook) { + $BSECollection = $escherWorkbook->getDggContainer()->getBstoreContainer()->getBSECollection(); + $BSE = $BSECollection[$BSEindex - 1]; + $blipType = $BSE->getBlipType(); - // need check because some blip types are not supported by Escher reader such as EMF - if ($blip = $BSE->getBlip()) { - $ih = imagecreatefromstring($blip->getData()); - $drawing = new MemoryDrawing(); - $drawing->setImageResource($ih); + // need check because some blip types are not supported by Escher reader such as EMF + if ($blip = $BSE->getBlip()) { + $ih = imagecreatefromstring($blip->getData()); + if ($ih !== false) { + $drawing = new MemoryDrawing(); + $drawing->setImageResource($ih); - // width, height, offsetX, offsetY - $drawing->setResizeProportional(false); - $drawing->setWidth($width); - $drawing->setHeight($height); - $drawing->setOffsetX($offsetX); - $drawing->setOffsetY($offsetY); + // width, height, offsetX, offsetY + $drawing->setResizeProportional(false); + $drawing->setWidth($width); + $drawing->setHeight($height); + $drawing->setOffsetX($offsetX); + $drawing->setOffsetY($offsetY); - switch ($blipType) { - case BSE::BLIPTYPE_JPEG: - $drawing->setRenderingFunction(MemoryDrawing::RENDERING_JPEG); - $drawing->setMimeType(MemoryDrawing::MIMETYPE_JPEG); + switch ($blipType) { + case BSE::BLIPTYPE_JPEG: + $drawing->setRenderingFunction(MemoryDrawing::RENDERING_JPEG); + $drawing->setMimeType(MemoryDrawing::MIMETYPE_JPEG); - break; - case BSE::BLIPTYPE_PNG: - $drawing->setRenderingFunction(MemoryDrawing::RENDERING_PNG); - $drawing->setMimeType(MemoryDrawing::MIMETYPE_PNG); + break; + case BSE::BLIPTYPE_PNG: + imagealphablending($ih, false); + imagesavealpha($ih, true); + $drawing->setRenderingFunction(MemoryDrawing::RENDERING_PNG); + $drawing->setMimeType(MemoryDrawing::MIMETYPE_PNG); - break; + break; + } + + $drawing->setWorksheet($this->phpSheet); + $drawing->setCoordinates($spContainer->getStartCoordinates()); + } } - - $drawing->setWorksheet($this->phpSheet); - $drawing->setCoordinates($spContainer->getStartCoordinates()); } break; @@ -1254,10 +1274,10 @@ class Xls extends BaseReader [$firstColumn, $firstRow] = Coordinate::coordinateFromString($coordinateStrings[0]); [$lastColumn, $lastRow] = Coordinate::coordinateFromString($coordinateStrings[1]); - if ($firstColumn == 'A' and $lastColumn == 'IV') { + if ($firstColumn == 'A' && $lastColumn == 'IV') { // then we have repeating rows $docSheet->getPageSetup()->setRowsToRepeatAtTop([$firstRow, $lastRow]); - } elseif ($firstRow == 1 and $lastRow == 65536) { + } elseif ($firstRow == 1 && $lastRow == 65536) { // then we have repeating columns $docSheet->getPageSetup()->setColumnsToRepeatAtLeft([$firstColumn, $lastColumn]); } @@ -1272,14 +1292,15 @@ class Xls extends BaseReader // Extract range if (strpos($definedName['formula'], '!') !== false) { $explodes = Worksheet::extractSheetTitle($definedName['formula'], true); - if (($docSheet = $this->spreadsheet->getSheetByName($explodes[0])) || - ($docSheet = $this->spreadsheet->getSheetByName(trim($explodes[0], "'")))) { + if ( + ($docSheet = $this->spreadsheet->getSheetByName($explodes[0])) || + ($docSheet = $this->spreadsheet->getSheetByName(trim($explodes[0], "'"))) + ) { $extractedRange = $explodes[1]; - $extractedRange = str_replace('$', '', $extractedRange); - $localOnly = ($definedName['scope'] == 0) ? false : true; + $localOnly = ($definedName['scope'] === 0) ? false : true; - $scope = ($definedName['scope'] == 0) ? null : $this->spreadsheet->getSheetByName($this->sheets[$definedName['scope'] - 1]['name']); + $scope = ($definedName['scope'] === 0) ? null : $this->spreadsheet->getSheetByName($this->sheets[$definedName['scope'] - 1]['name']); $this->spreadsheet->addNamedRange(new NamedRange((string) $definedName['name'], $docSheet, $extractedRange, $localOnly, $scope)); } @@ -1288,7 +1309,7 @@ class Xls extends BaseReader // TODO Provide support for named values } } - $this->data = null; + $this->data = ''; return $this->spreadsheet; } @@ -1352,14 +1373,14 @@ class Xls extends BaseReader /** * Use OLE reader to extract the relevant data streams from the OLE file. * - * @param string $pFilename + * @param string $filename */ - private function loadOLE($pFilename) + private function loadOLE($filename): void { // OLE reader $ole = new OLERead(); // get excel data, - $ole->read($pFilename); + $ole->read($filename); // Get workbook data: workbook stream + sheet streams $this->data = $ole->getStream($ole->wrkbook); // Get summary information data @@ -1371,7 +1392,7 @@ class Xls extends BaseReader /** * Read summary information. */ - private function readSummaryInformation() + private function readSummaryInformation(): void { if (!isset($this->summaryInformation)) { return; @@ -1446,34 +1467,34 @@ class Xls extends BaseReader switch ($id) { case 0x01: // Code Page - $codePage = CodePage::numberToName($value); + $codePage = CodePage::numberToName((int) $value); break; case 0x02: // Title - $this->spreadsheet->getProperties()->setTitle($value); + $this->spreadsheet->getProperties()->setTitle("$value"); break; case 0x03: // Subject - $this->spreadsheet->getProperties()->setSubject($value); + $this->spreadsheet->getProperties()->setSubject("$value"); break; case 0x04: // Author (Creator) - $this->spreadsheet->getProperties()->setCreator($value); + $this->spreadsheet->getProperties()->setCreator("$value"); break; case 0x05: // Keywords - $this->spreadsheet->getProperties()->setKeywords($value); + $this->spreadsheet->getProperties()->setKeywords("$value"); break; case 0x06: // Comments (Description) - $this->spreadsheet->getProperties()->setDescription($value); + $this->spreadsheet->getProperties()->setDescription("$value"); break; case 0x07: // Template // Not supported by PhpSpreadsheet break; case 0x08: // Last Saved By (LastModifiedBy) - $this->spreadsheet->getProperties()->setLastModifiedBy($value); + $this->spreadsheet->getProperties()->setLastModifiedBy("$value"); break; case 0x09: // Revision @@ -1518,7 +1539,7 @@ class Xls extends BaseReader /** * Read additional document summary information. */ - private function readDocumentSummaryInformation() + private function readDocumentSummaryInformation(): void { if (!isset($this->documentSummaryInformation)) { return; @@ -1598,11 +1619,11 @@ class Xls extends BaseReader switch ($id) { case 0x01: // Code Page - $codePage = CodePage::numberToName($value); + $codePage = CodePage::numberToName((int) $value); break; case 0x02: // Category - $this->spreadsheet->getProperties()->setCategory($value); + $this->spreadsheet->getProperties()->setCategory("$value"); break; case 0x03: // Presentation Target @@ -1639,11 +1660,11 @@ class Xls extends BaseReader // Not supported by PhpSpreadsheet break; case 0x0E: // Manager - $this->spreadsheet->getProperties()->setManager($value); + $this->spreadsheet->getProperties()->setManager("$value"); break; case 0x0F: // Company - $this->spreadsheet->getProperties()->setCompany($value); + $this->spreadsheet->getProperties()->setCompany("$value"); break; case 0x10: // Links up-to-date @@ -1656,7 +1677,7 @@ class Xls extends BaseReader /** * Reads a general type of BIFF record. Does nothing except for moving stream pointer forward to next record. */ - private function readDefault() + private function readDefault(): void { $length = self::getUInt2d($this->data, $this->pos + 2); @@ -1668,7 +1689,7 @@ class Xls extends BaseReader * The NOTE record specifies a comment associated with a particular cell. In Excel 95 (BIFF7) and earlier versions, * this record stores a note (cell note). This feature was significantly enhanced in Excel 97. */ - private function readNote() + private function readNote(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -1698,7 +1719,8 @@ class Xls extends BaseReader // max 2048 bytes will probably throw a wobbly. $row = self::getUInt2d($recordData, 0); $extension = true; - $cellAddress = array_pop(array_keys($this->phpSheet->getComments())); + $arrayKeys = array_keys($this->phpSheet->getComments()); + $cellAddress = array_pop($arrayKeys); } $cellAddress = str_replace('$', '', $cellAddress); @@ -1721,7 +1743,7 @@ class Xls extends BaseReader /** * The TEXT Object record contains the text associated with a cell annotation. */ - private function readTextObject() + private function readTextObject(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -1768,7 +1790,7 @@ class Xls extends BaseReader /** * Read BOF. */ - private function readBof() + private function readBof(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = substr($this->data, $this->pos + 4, $length); @@ -1819,7 +1841,7 @@ class Xls extends BaseReader * are based on the source of Spreadsheet-ParseExcel: * https://metacpan.org/release/Spreadsheet-ParseExcel */ - private function readFilepass() + private function readFilepass(): void { $length = self::getUInt2d($this->data, $this->pos + 2); @@ -1969,7 +1991,7 @@ class Xls extends BaseReader * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ - private function readCodepage() + private function readCodepage(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -1995,7 +2017,7 @@ class Xls extends BaseReader * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ - private function readDateMode() + private function readDateMode(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -2013,7 +2035,7 @@ class Xls extends BaseReader /** * Read a FONT record. */ - private function readFont() + private function readFont(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -2058,39 +2080,11 @@ class Xls extends BaseReader // offset: 8; size: 2; escapement type $escapement = self::getUInt2d($recordData, 8); - switch ($escapement) { - case 0x0001: - $objFont->setSuperscript(true); - - break; - case 0x0002: - $objFont->setSubscript(true); - - break; - } + CellFont::escapement($objFont, $escapement); // offset: 10; size: 1; underline type $underlineType = ord($recordData[10]); - switch ($underlineType) { - case 0x00: - break; // no underline - case 0x01: - $objFont->setUnderline(Font::UNDERLINE_SINGLE); - - break; - case 0x02: - $objFont->setUnderline(Font::UNDERLINE_DOUBLE); - - break; - case 0x21: - $objFont->setUnderline(Font::UNDERLINE_SINGLEACCOUNTING); - - break; - case 0x22: - $objFont->setUnderline(Font::UNDERLINE_DOUBLEACCOUNTING); - - break; - } + CellFont::underline($objFont, $underlineType); // offset: 11; size: 1; font family // offset: 12; size: 1; character set @@ -2121,7 +2115,7 @@ class Xls extends BaseReader * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ - private function readFormat() + private function readFormat(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -2140,6 +2134,10 @@ class Xls extends BaseReader } $formatString = $string['value']; + // Apache Open Office sets wrong case writing to xls - issue 2239 + if ($formatString === 'GENERAL') { + $formatString = NumberFormat::FORMAT_GENERAL; + } $this->formats[$indexCode] = $formatString; } } @@ -2158,7 +2156,7 @@ class Xls extends BaseReader * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ - private function readXf() + private function readXf(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -2189,7 +2187,7 @@ class Xls extends BaseReader $numberFormat = ['formatCode' => $code]; } else { // we set the general format code - $numberFormat = ['formatCode' => 'General']; + $numberFormat = ['formatCode' => NumberFormat::FORMAT_GENERAL]; } $objStyle->getNumberFormat()->setFormatCode($numberFormat['formatCode']); @@ -2210,68 +2208,15 @@ class Xls extends BaseReader // offset: 6; size: 1; Alignment and text break // bit 2-0, mask 0x07; horizontal alignment $horAlign = (0x07 & ord($recordData[6])) >> 0; - switch ($horAlign) { - case 0: - $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_GENERAL); + Xls\Style\CellAlignment::horizontal($objStyle->getAlignment(), $horAlign); - break; - case 1: - $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_LEFT); - - break; - case 2: - $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); - - break; - case 3: - $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT); - - break; - case 4: - $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_FILL); - - break; - case 5: - $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_JUSTIFY); - - break; - case 6: - $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER_CONTINUOUS); - - break; - } // bit 3, mask 0x08; wrap text $wrapText = (0x08 & ord($recordData[6])) >> 3; - switch ($wrapText) { - case 0: - $objStyle->getAlignment()->setWrapText(false); + Xls\Style\CellAlignment::wrap($objStyle->getAlignment(), $wrapText); - break; - case 1: - $objStyle->getAlignment()->setWrapText(true); - - break; - } // bit 6-4, mask 0x70; vertical alignment $vertAlign = (0x70 & ord($recordData[6])) >> 4; - switch ($vertAlign) { - case 0: - $objStyle->getAlignment()->setVertical(Alignment::VERTICAL_TOP); - - break; - case 1: - $objStyle->getAlignment()->setVertical(Alignment::VERTICAL_CENTER); - - break; - case 2: - $objStyle->getAlignment()->setVertical(Alignment::VERTICAL_BOTTOM); - - break; - case 3: - $objStyle->getAlignment()->setVertical(Alignment::VERTICAL_JUSTIFY); - - break; - } + Xls\Style\CellAlignment::vertical($objStyle->getAlignment(), $vertAlign); if ($this->version == self::XLS_BIFF8) { // offset: 7; size: 1; XF_ROTATION: Text rotation angle @@ -2281,8 +2226,8 @@ class Xls extends BaseReader $rotation = $angle; } elseif ($angle <= 180) { $rotation = 90 - $angle; - } elseif ($angle == 255) { - $rotation = -165; + } elseif ($angle == Alignment::TEXTROTATION_STACK_EXCEL) { + $rotation = Alignment::TEXTROTATION_STACK_PHPSPREADSHEET; } $objStyle->getAlignment()->setTextRotation($rotation); @@ -2333,15 +2278,17 @@ class Xls extends BaseReader $diagonalDown = (0x40000000 & self::getInt4d($recordData, 10)) >> 30 ? true : false; // bit: 31; mask: 0x80000000; 1 = diagonal line from bottom left to top right - $diagonalUp = (0x80000000 & self::getInt4d($recordData, 10)) >> 31 ? true : false; + $diagonalUp = ((int) 0x80000000 & self::getInt4d($recordData, 10)) >> 31 ? true : false; - if ($diagonalUp == false && $diagonalDown == false) { - $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_NONE); - } elseif ($diagonalUp == true && $diagonalDown == false) { + if ($diagonalUp === false) { + if ($diagonalDown == false) { + $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_NONE); + } else { + $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_DOWN); + } + } elseif ($diagonalDown == false) { $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_UP); - } elseif ($diagonalUp == false && $diagonalDown == true) { - $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_DOWN); - } elseif ($diagonalUp == true && $diagonalDown == true) { + } else { $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_BOTH); } @@ -2361,7 +2308,7 @@ class Xls extends BaseReader } // bit: 31-26; mask: 0xFC000000 fill pattern - if ($fillType = Xls\Style\FillPattern::lookup((0xFC000000 & self::getInt4d($recordData, 14)) >> 26)) { + if ($fillType = Xls\Style\FillPattern::lookup(((int) 0xFC000000 & self::getInt4d($recordData, 14)) >> 26)) { $objStyle->getFill()->setFillType($fillType); } // offset: 18; size: 2; pattern and background colour @@ -2384,7 +2331,7 @@ class Xls extends BaseReader break; case 1: - $objStyle->getAlignment()->setTextRotation(-165); + $objStyle->getAlignment()->setTextRotation(Alignment::TEXTROTATION_STACK_PHPSPREADSHEET); break; case 2: @@ -2413,7 +2360,7 @@ class Xls extends BaseReader $objStyle->getBorders()->getBottom()->setBorderStyle(Xls\Style\Border::lookup((0x01C00000 & $borderAndBackground) >> 22)); // bit: 31-25; mask: 0xFE000000; bottom line color - $objStyle->getBorders()->getBottom()->colorIndex = (0xFE000000 & $borderAndBackground) >> 25; + $objStyle->getBorders()->getBottom()->colorIndex = ((int) 0xFE000000 & $borderAndBackground) >> 25; // offset: 12; size: 4; cell border lines $borderLines = self::getInt4d($recordData, 12); @@ -2455,7 +2402,7 @@ class Xls extends BaseReader } } - private function readXfExt() + private function readXfExt(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -2504,7 +2451,7 @@ class Xls extends BaseReader if (isset($this->mapCellXfIndex[$ixfe])) { $fill = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFill(); $fill->getStartColor()->setRGB($rgb); - unset($fill->startcolorIndex); // normal color index does not apply, discard + $fill->startcolorIndex = null; // normal color index does not apply, discard } } @@ -2520,7 +2467,7 @@ class Xls extends BaseReader if (isset($this->mapCellXfIndex[$ixfe])) { $fill = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFill(); $fill->getEndColor()->setRGB($rgb); - unset($fill->endcolorIndex); // normal color index does not apply, discard + $fill->endcolorIndex = null; // normal color index does not apply, discard } } @@ -2536,7 +2483,7 @@ class Xls extends BaseReader if (isset($this->mapCellXfIndex[$ixfe])) { $top = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getTop(); $top->getColor()->setRGB($rgb); - unset($top->colorIndex); // normal color index does not apply, discard + $top->colorIndex = null; // normal color index does not apply, discard } } @@ -2552,7 +2499,7 @@ class Xls extends BaseReader if (isset($this->mapCellXfIndex[$ixfe])) { $bottom = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getBottom(); $bottom->getColor()->setRGB($rgb); - unset($bottom->colorIndex); // normal color index does not apply, discard + $bottom->colorIndex = null; // normal color index does not apply, discard } } @@ -2568,7 +2515,7 @@ class Xls extends BaseReader if (isset($this->mapCellXfIndex[$ixfe])) { $left = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getLeft(); $left->getColor()->setRGB($rgb); - unset($left->colorIndex); // normal color index does not apply, discard + $left->colorIndex = null; // normal color index does not apply, discard } } @@ -2584,7 +2531,7 @@ class Xls extends BaseReader if (isset($this->mapCellXfIndex[$ixfe])) { $right = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getRight(); $right->getColor()->setRGB($rgb); - unset($right->colorIndex); // normal color index does not apply, discard + $right->colorIndex = null; // normal color index does not apply, discard } } @@ -2600,7 +2547,7 @@ class Xls extends BaseReader if (isset($this->mapCellXfIndex[$ixfe])) { $diagonal = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getDiagonal(); $diagonal->getColor()->setRGB($rgb); - unset($diagonal->colorIndex); // normal color index does not apply, discard + $diagonal->colorIndex = null; // normal color index does not apply, discard } } @@ -2616,7 +2563,7 @@ class Xls extends BaseReader if (isset($this->mapCellXfIndex[$ixfe])) { $font = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFont(); $font->getColor()->setRGB($rgb); - unset($font->colorIndex); // normal color index does not apply, discard + $font->colorIndex = null; // normal color index does not apply, discard } } @@ -2631,7 +2578,7 @@ class Xls extends BaseReader /** * Read STYLE record. */ - private function readStyle() + private function readStyle(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -2668,7 +2615,7 @@ class Xls extends BaseReader /** * Read PALETTE record. */ - private function readPalette() + private function readPalette(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -2700,7 +2647,7 @@ class Xls extends BaseReader * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ - private function readSheet() + private function readSheet(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -2736,6 +2683,7 @@ class Xls extends BaseReader $sheetType = ord($recordData[5]); // offset: 6; size: var; sheet name + $rec_name = null; if ($this->version == self::XLS_BIFF8) { $string = self::readUnicodeStringShort(substr($recordData, 6)); $rec_name = $string['value']; @@ -2755,7 +2703,7 @@ class Xls extends BaseReader /** * Read EXTERNALBOOK record. */ - private function readExternalBook() + private function readExternalBook(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -2817,7 +2765,7 @@ class Xls extends BaseReader /** * Read EXTERNNAME record. */ - private function readExternName() + private function readExternName(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -2851,7 +2799,7 @@ class Xls extends BaseReader /** * Read EXTERNSHEET record. */ - private function readExternSheet() + private function readExternSheet(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -2887,7 +2835,7 @@ class Xls extends BaseReader * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ - private function readDefinedName() + private function readDefinedName(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -2941,7 +2889,7 @@ class Xls extends BaseReader /** * Read MSODRAWINGGROUP record. */ - private function readMsoDrawingGroup() + private function readMsoDrawingGroup(): void { $length = self::getUInt2d($this->data, $this->pos + 2); @@ -2963,11 +2911,14 @@ class Xls extends BaseReader * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ - private function readSst() + private function readSst(): void { // offset within (spliced) record data $pos = 0; + // Limit global SST position, further control for bad SST Length in BIFF8 data + $limitposSST = 0; + // get spliced record data $splicedRecordData = $this->getSplicedRecordData(); @@ -2981,8 +2932,17 @@ class Xls extends BaseReader $nm = self::getInt4d($recordData, 4); $pos += 4; + // look up limit position + foreach ($spliceOffsets as $spliceOffset) { + // it can happen that the string is empty, therefore we need + // <= and not just < + if ($pos <= $spliceOffset) { + $limitposSST = $spliceOffset; + } + } + // loop through the Unicode strings (16-bit length) - for ($i = 0; $i < $nm; ++$i) { + for ($i = 0; $i < $nm && $pos < $limitposSST; ++$i) { // number of characters in the Unicode string $numChars = self::getUInt2d($recordData, $pos); $pos += 2; @@ -3000,12 +2960,14 @@ class Xls extends BaseReader // bit: 3; mask: 0x03; 0 = ordinary; 1 = Rich-Text $hasRichText = (($optionFlags & 0x08) != 0); + $formattingRuns = 0; if ($hasRichText) { // number of Rich-Text formatting runs $formattingRuns = self::getUInt2d($recordData, $pos); $pos += 2; } + $extendedRunLength = 0; if ($hasAsian) { // size of Asian phonetic setting $extendedRunLength = self::getInt4d($recordData, $pos); @@ -3015,7 +2977,8 @@ class Xls extends BaseReader // expected byte length of character array if not split $len = ($isCompressed) ? $numChars : $numChars * 2; - // look up limit position + // look up limit position - Check it again to be sure that no error occurs when parsing SST structure + $limitpos = null; foreach ($spliceOffsets as $spliceOffset) { // it can happen that the string is empty, therefore we need // <= and not just < @@ -3144,7 +3107,7 @@ class Xls extends BaseReader /** * Read PRINTGRIDLINES record. */ - private function readPrintGridlines() + private function readPrintGridlines(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -3162,7 +3125,7 @@ class Xls extends BaseReader /** * Read DEFAULTROWHEIGHT record. */ - private function readDefaultRowHeight() + private function readDefaultRowHeight(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -3179,7 +3142,7 @@ class Xls extends BaseReader /** * Read SHEETPR record. */ - private function readSheetPr() + private function readSheetPr(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -3205,7 +3168,7 @@ class Xls extends BaseReader /** * Read HORIZONTALPAGEBREAKS record. */ - private function readHorizontalPageBreaks() + private function readHorizontalPageBreaks(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -3224,7 +3187,7 @@ class Xls extends BaseReader $cl = self::getUInt2d($recordData, 2 + 6 * $i + 4); // not sure why two column indexes are necessary? - $this->phpSheet->setBreakByColumnAndRow($cf + 1, $r, Worksheet::BREAK_ROW); + $this->phpSheet->setBreak([$cf + 1, $r], Worksheet::BREAK_ROW); } } } @@ -3232,7 +3195,7 @@ class Xls extends BaseReader /** * Read VERTICALPAGEBREAKS record. */ - private function readVerticalPageBreaks() + private function readVerticalPageBreaks(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -3251,7 +3214,7 @@ class Xls extends BaseReader $rl = self::getUInt2d($recordData, 2 + 6 * $i + 4); // not sure why two row indexes are necessary? - $this->phpSheet->setBreakByColumnAndRow($c + 1, $rf, Worksheet::BREAK_COLUMN); + $this->phpSheet->setBreak([$c + 1, $rf], Worksheet::BREAK_COLUMN); } } } @@ -3259,7 +3222,7 @@ class Xls extends BaseReader /** * Read HEADER record. */ - private function readHeader() + private function readHeader(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -3286,7 +3249,7 @@ class Xls extends BaseReader /** * Read FOOTER record. */ - private function readFooter() + private function readFooter(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -3312,7 +3275,7 @@ class Xls extends BaseReader /** * Read HCENTER record. */ - private function readHcenter() + private function readHcenter(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -3331,7 +3294,7 @@ class Xls extends BaseReader /** * Read VCENTER record. */ - private function readVcenter() + private function readVcenter(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -3350,7 +3313,7 @@ class Xls extends BaseReader /** * Read LEFTMARGIN record. */ - private function readLeftMargin() + private function readLeftMargin(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -3367,7 +3330,7 @@ class Xls extends BaseReader /** * Read RIGHTMARGIN record. */ - private function readRightMargin() + private function readRightMargin(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -3384,7 +3347,7 @@ class Xls extends BaseReader /** * Read TOPMARGIN record. */ - private function readTopMargin() + private function readTopMargin(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -3401,7 +3364,7 @@ class Xls extends BaseReader /** * Read BOTTOMMARGIN record. */ - private function readBottomMargin() + private function readBottomMargin(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -3418,7 +3381,7 @@ class Xls extends BaseReader /** * Read PAGESETUP record. */ - private function readPageSetup() + private function readPageSetup(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -3441,6 +3404,9 @@ class Xls extends BaseReader // offset: 10; size: 2; option flags + // bit: 0; mask: 0x0001; 0=down then over, 1=over then down + $isOverThenDown = (0x0001 & self::getUInt2d($recordData, 10)); + // bit: 1; mask: 0x0002; 0=landscape, 1=portrait $isPortrait = (0x0002 & self::getUInt2d($recordData, 10)) >> 1; @@ -3450,16 +3416,8 @@ class Xls extends BaseReader if (!$isNotInit) { $this->phpSheet->getPageSetup()->setPaperSize($paperSize); - switch ($isPortrait) { - case 0: - $this->phpSheet->getPageSetup()->setOrientation(PageSetup::ORIENTATION_LANDSCAPE); - - break; - case 1: - $this->phpSheet->getPageSetup()->setOrientation(PageSetup::ORIENTATION_PORTRAIT); - - break; - } + $this->phpSheet->getPageSetup()->setPageOrder(((bool) $isOverThenDown) ? PageSetup::PAGEORDER_OVER_THEN_DOWN : PageSetup::PAGEORDER_DOWN_THEN_OVER); + $this->phpSheet->getPageSetup()->setOrientation(((bool) $isPortrait) ? PageSetup::ORIENTATION_PORTRAIT : PageSetup::ORIENTATION_LANDSCAPE); $this->phpSheet->getPageSetup()->setScale($scale, false); $this->phpSheet->getPageSetup()->setFitToPage((bool) $this->isFitToPages); @@ -3481,7 +3439,7 @@ class Xls extends BaseReader * PROTECT - Sheet protection (BIFF2 through BIFF8) * if this record is omitted, then it also means no sheet protection. */ - private function readProtect() + private function readProtect(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -3503,7 +3461,7 @@ class Xls extends BaseReader /** * SCENPROTECT. */ - private function readScenProtect() + private function readScenProtect(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -3526,7 +3484,7 @@ class Xls extends BaseReader /** * OBJECTPROTECT. */ - private function readObjectProtect() + private function readObjectProtect(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -3549,7 +3507,7 @@ class Xls extends BaseReader /** * PASSWORD - Sheet protection (hashed) password (BIFF2 through BIFF8). */ - private function readPassword() + private function readPassword(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -3567,7 +3525,7 @@ class Xls extends BaseReader /** * Read DEFCOLWIDTH record. */ - private function readDefColWidth() + private function readDefColWidth(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -3585,7 +3543,7 @@ class Xls extends BaseReader /** * Read COLINFO record. */ - private function readColInfo() + private function readColInfo(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -3614,7 +3572,7 @@ class Xls extends BaseReader $level = (0x0700 & self::getUInt2d($recordData, 8)) >> 8; // bit: 12; mask: 0x1000; 1 = collapsed - $isCollapsed = (0x1000 & self::getUInt2d($recordData, 8)) >> 12; + $isCollapsed = (bool) ((0x1000 & self::getUInt2d($recordData, 8)) >> 12); // offset: 10; size: 2; not used @@ -3628,7 +3586,9 @@ class Xls extends BaseReader $this->phpSheet->getColumnDimensionByColumn($i)->setVisible(!$isHidden); $this->phpSheet->getColumnDimensionByColumn($i)->setOutlineLevel($level); $this->phpSheet->getColumnDimensionByColumn($i)->setCollapsed($isCollapsed); - $this->phpSheet->getColumnDimensionByColumn($i)->setXfIndex($this->mapCellXfIndex[$xfIndex]); + if (isset($this->mapCellXfIndex[$xfIndex])) { + $this->phpSheet->getColumnDimensionByColumn($i)->setXfIndex($this->mapCellXfIndex[$xfIndex]); + } } } } @@ -3643,7 +3603,7 @@ class Xls extends BaseReader * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ - private function readRow() + private function readRow(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -3682,7 +3642,7 @@ class Xls extends BaseReader $this->phpSheet->getRowDimension($r + 1)->setOutlineLevel($level); // bit: 4; mask: 0x00000010; 1 = outline group start or ends here... and is collapsed - $isCollapsed = (0x00000010 & self::getInt4d($recordData, 12)) >> 4; + $isCollapsed = (bool) ((0x00000010 & self::getInt4d($recordData, 12)) >> 4); $this->phpSheet->getRowDimension($r + 1)->setCollapsed($isCollapsed); // bit: 5; mask: 0x00000020; 1 = row is hidden @@ -3712,7 +3672,7 @@ class Xls extends BaseReader * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ - private function readRk() + private function readRk(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -3737,7 +3697,7 @@ class Xls extends BaseReader $numValue = self::getIEEE754($rknum); $cell = $this->phpSheet->getCell($columnString . ($row + 1)); - if (!$this->readDataOnly) { + if (!$this->readDataOnly && isset($this->mapCellXfIndex[$xfIndex])) { // add style information $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]); } @@ -3756,7 +3716,7 @@ class Xls extends BaseReader * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ - private function readLabelSst() + private function readLabelSst(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -3803,10 +3763,13 @@ class Xls extends BaseReader if ($fmtRuns[$i - 1]['fontIndex'] < 4) { $fontIndex = $fmtRuns[$i - 1]['fontIndex']; } else { - // this has to do with that index 4 is omitted in all BIFF versions for some strange reason + // this has to do with that index 4 is omitted in all BIFF versions for some stra nge reason // check the OpenOffice documentation of the FONT record $fontIndex = $fmtRuns[$i - 1]['fontIndex'] - 1; } + if (array_key_exists($fontIndex, $this->objFonts) === false) { + $fontIndex = count($this->objFonts) - 1; + } $textRun->setFont(clone $this->objFonts[$fontIndex]); } } @@ -3825,7 +3788,7 @@ class Xls extends BaseReader } } - if (!$this->readDataOnly && !$emptyCell) { + if (!$this->readDataOnly && !$emptyCell && isset($this->mapCellXfIndex[$xfIndex])) { // add style information $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]); } @@ -3840,7 +3803,7 @@ class Xls extends BaseReader * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ - private function readMulRk() + private function readMulRk(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -3872,7 +3835,7 @@ class Xls extends BaseReader // offset: var; size: 4; RK value $numValue = self::getIEEE754(self::getInt4d($recordData, $offset + 2)); $cell = $this->phpSheet->getCell($columnString . ($row + 1)); - if (!$this->readDataOnly) { + if (!$this->readDataOnly && isset($this->mapCellXfIndex[$xfIndex])) { // add style $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]); } @@ -3893,7 +3856,7 @@ class Xls extends BaseReader * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ - private function readNumber() + private function readNumber(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -3916,7 +3879,7 @@ class Xls extends BaseReader $numValue = self::extractNumber(substr($recordData, 6, 8)); $cell = $this->phpSheet->getCell($columnString . ($row + 1)); - if (!$this->readDataOnly) { + if (!$this->readDataOnly && isset($this->mapCellXfIndex[$xfIndex])) { // add cell style $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]); } @@ -3934,7 +3897,7 @@ class Xls extends BaseReader * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ - private function readFormula() + private function readFormula(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -3999,21 +3962,27 @@ class Xls extends BaseReader // read STRING record $value = $this->readString(); - } elseif ((ord($recordData[6]) == 1) + } elseif ( + (ord($recordData[6]) == 1) && (ord($recordData[12]) == 255) - && (ord($recordData[13]) == 255)) { + && (ord($recordData[13]) == 255) + ) { // Boolean formula. Result is in +2; 0=false, 1=true $dataType = DataType::TYPE_BOOL; $value = (bool) ord($recordData[8]); - } elseif ((ord($recordData[6]) == 2) + } elseif ( + (ord($recordData[6]) == 2) && (ord($recordData[12]) == 255) - && (ord($recordData[13]) == 255)) { + && (ord($recordData[13]) == 255) + ) { // Error formula. Error code is in +2 $dataType = DataType::TYPE_ERROR; $value = Xls\ErrorCode::lookup(ord($recordData[8])); - } elseif ((ord($recordData[6]) == 3) + } elseif ( + (ord($recordData[6]) == 3) && (ord($recordData[12]) == 255) - && (ord($recordData[13]) == 255)) { + && (ord($recordData[13]) == 255) + ) { // Formula result is a null string $dataType = DataType::TYPE_NULL; $value = ''; @@ -4024,7 +3993,7 @@ class Xls extends BaseReader } $cell = $this->phpSheet->getCell($columnString . ($row + 1)); - if (!$this->readDataOnly) { + if (!$this->readDataOnly && isset($this->mapCellXfIndex[$xfIndex])) { // add cell style $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]); } @@ -4060,7 +4029,7 @@ class Xls extends BaseReader * which usually contains relative references. * These will be used to construct the formula in each shared formula part after the sheet is read. */ - private function readSharedFmla() + private function readSharedFmla(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -4118,7 +4087,7 @@ class Xls extends BaseReader * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ - private function readBoolErr() + private function readBoolErr(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -4162,7 +4131,7 @@ class Xls extends BaseReader break; } - if (!$this->readDataOnly) { + if (!$this->readDataOnly && isset($this->mapCellXfIndex[$xfIndex])) { // add cell style $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]); } @@ -4177,7 +4146,7 @@ class Xls extends BaseReader * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ - private function readMulBlank() + private function readMulBlank(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -4200,7 +4169,9 @@ class Xls extends BaseReader // Read cell? if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) { $xfIndex = self::getUInt2d($recordData, 4 + 2 * $i); - $this->phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->mapCellXfIndex[$xfIndex]); + if (isset($this->mapCellXfIndex[$xfIndex])) { + $this->phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->mapCellXfIndex[$xfIndex]); + } } } } @@ -4218,7 +4189,7 @@ class Xls extends BaseReader * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ - private function readLabel() + private function readLabel(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -4251,7 +4222,7 @@ class Xls extends BaseReader $cell = $this->phpSheet->getCell($columnString . ($row + 1)); $cell->setValueExplicit($value, DataType::TYPE_STRING); - if (!$this->readDataOnly) { + if (!$this->readDataOnly && isset($this->mapCellXfIndex[$xfIndex])) { // add cell style $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]); } @@ -4262,7 +4233,7 @@ class Xls extends BaseReader /** * Read BLANK record. */ - private function readBlank() + private function readBlank(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -4283,7 +4254,7 @@ class Xls extends BaseReader $xfIndex = self::getUInt2d($recordData, 4); // add style information - if (!$this->readDataOnly && $this->readEmptyCells) { + if (!$this->readDataOnly && $this->readEmptyCells && isset($this->mapCellXfIndex[$xfIndex])) { $this->phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->mapCellXfIndex[$xfIndex]); } } @@ -4292,7 +4263,7 @@ class Xls extends BaseReader /** * Read MSODRAWING record. */ - private function readMsoDrawing() + private function readMsoDrawing(): void { $length = self::getUInt2d($this->data, $this->pos + 2); @@ -4306,7 +4277,7 @@ class Xls extends BaseReader /** * Read OBJ record. */ - private function readObj() + private function readObj(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -4346,7 +4317,7 @@ class Xls extends BaseReader /** * Read WINDOW2 record. */ - private function readWindow2() + private function readWindow2(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -4362,16 +4333,29 @@ class Xls extends BaseReader // offset: 4; size: 2; index to first visible colum $firstVisibleColumn = self::getUInt2d($recordData, 4); + $zoomscaleInPageBreakPreview = 0; + $zoomscaleInNormalView = 0; if ($this->version === self::XLS_BIFF8) { // offset: 8; size: 2; not used // offset: 10; size: 2; cached magnification factor in page break preview (in percent); 0 = Default (60%) // offset: 12; size: 2; cached magnification factor in normal view (in percent); 0 = Default (100%) // offset: 14; size: 4; not used - $zoomscaleInPageBreakPreview = self::getUInt2d($recordData, 10); + if (!isset($recordData[10])) { + $zoomscaleInPageBreakPreview = 0; + } else { + $zoomscaleInPageBreakPreview = self::getUInt2d($recordData, 10); + } + if ($zoomscaleInPageBreakPreview === 0) { $zoomscaleInPageBreakPreview = 60; } - $zoomscaleInNormalView = self::getUInt2d($recordData, 12); + + if (!isset($recordData[12])) { + $zoomscaleInNormalView = 0; + } else { + $zoomscaleInNormalView = self::getUInt2d($recordData, 12); + } + if ($zoomscaleInNormalView === 0) { $zoomscaleInNormalView = 100; } @@ -4417,7 +4401,7 @@ class Xls extends BaseReader /** * Read PLV Record(Created by Excel2007 or upper). */ - private function readPageLayoutView() + private function readPageLayoutView(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -4454,7 +4438,7 @@ class Xls extends BaseReader /** * Read SCL record. */ - private function readScl() + private function readScl(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -4475,7 +4459,7 @@ class Xls extends BaseReader /** * Read PANE record. */ - private function readPane() + private function readPane(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -4509,7 +4493,7 @@ class Xls extends BaseReader /** * Read SELECTION record. There is one such record for each pane in the sheet. */ - private function readSelection() + private function readSelection(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -4539,17 +4523,17 @@ class Xls extends BaseReader // first row '1' + last row '16384' indicates that full column is selected (apparently also in BIFF8!) if (preg_match('/^([A-Z]+1\:[A-Z]+)16384$/', $selectedCells)) { - $selectedCells = preg_replace('/^([A-Z]+1\:[A-Z]+)16384$/', '${1}1048576', $selectedCells); + $selectedCells = (string) preg_replace('/^([A-Z]+1\:[A-Z]+)16384$/', '${1}1048576', $selectedCells); } // first row '1' + last row '65536' indicates that full column is selected if (preg_match('/^([A-Z]+1\:[A-Z]+)65536$/', $selectedCells)) { - $selectedCells = preg_replace('/^([A-Z]+1\:[A-Z]+)65536$/', '${1}1048576', $selectedCells); + $selectedCells = (string) preg_replace('/^([A-Z]+1\:[A-Z]+)65536$/', '${1}1048576', $selectedCells); } // first column 'A' + last column 'IV' indicates that full row is selected if (preg_match('/^(A\d+\:)IV(\d+)$/', $selectedCells)) { - $selectedCells = preg_replace('/^(A\d+\:)IV(\d+)$/', '${1}XFD${2}', $selectedCells); + $selectedCells = (string) preg_replace('/^(A\d+\:)IV(\d+)$/', '${1}XFD${2}', $selectedCells); } $this->phpSheet->setSelectedCells($selectedCells); @@ -4586,7 +4570,7 @@ class Xls extends BaseReader * -- "OpenOffice.org's Documentation of the Microsoft * Excel File Format" */ - private function readMergedCells() + private function readMergedCells(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -4597,9 +4581,11 @@ class Xls extends BaseReader if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) { $cellRangeAddressList = $this->readBIFF8CellRangeAddressList($recordData); foreach ($cellRangeAddressList['cellRangeAddresses'] as $cellRangeAddress) { - if ((strpos($cellRangeAddress, ':') !== false) && - ($this->includeCellRangeFiltered($cellRangeAddress))) { - $this->phpSheet->mergeCells($cellRangeAddress); + if ( + (strpos($cellRangeAddress, ':') !== false) && + ($this->includeCellRangeFiltered($cellRangeAddress)) + ) { + $this->phpSheet->mergeCells($cellRangeAddress, Worksheet::MERGE_CELL_CONTENT_HIDE); } } } @@ -4608,7 +4594,7 @@ class Xls extends BaseReader /** * Read HYPERLINK record. */ - private function readHyperLink() + private function readHyperLink(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -4781,7 +4767,7 @@ class Xls extends BaseReader /** * Read DATAVALIDATIONS record. */ - private function readDataValidations() + private function readDataValidations(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -4793,7 +4779,7 @@ class Xls extends BaseReader /** * Read DATAVALIDATION record. */ - private function readDataValidation() + private function readDataValidation(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -4810,57 +4796,11 @@ class Xls extends BaseReader // bit: 0-3; mask: 0x0000000F; type $type = (0x0000000F & $options) >> 0; - switch ($type) { - case 0x00: - $type = DataValidation::TYPE_NONE; - - break; - case 0x01: - $type = DataValidation::TYPE_WHOLE; - - break; - case 0x02: - $type = DataValidation::TYPE_DECIMAL; - - break; - case 0x03: - $type = DataValidation::TYPE_LIST; - - break; - case 0x04: - $type = DataValidation::TYPE_DATE; - - break; - case 0x05: - $type = DataValidation::TYPE_TIME; - - break; - case 0x06: - $type = DataValidation::TYPE_TEXTLENGTH; - - break; - case 0x07: - $type = DataValidation::TYPE_CUSTOM; - - break; - } + $type = Xls\DataValidationHelper::type($type); // bit: 4-6; mask: 0x00000070; error type $errorStyle = (0x00000070 & $options) >> 4; - switch ($errorStyle) { - case 0x00: - $errorStyle = DataValidation::STYLE_STOP; - - break; - case 0x01: - $errorStyle = DataValidation::STYLE_WARNING; - - break; - case 0x02: - $errorStyle = DataValidation::STYLE_INFORMATION; - - break; - } + $errorStyle = Xls\DataValidationHelper::errorStyle($errorStyle); // bit: 7; mask: 0x00000080; 1= formula is explicit (only applies to list) // I have only seen cases where this is 1 @@ -4880,39 +4820,10 @@ class Xls extends BaseReader // bit: 20-23; mask: 0x00F00000; condition operator $operator = (0x00F00000 & $options) >> 20; - switch ($operator) { - case 0x00: - $operator = DataValidation::OPERATOR_BETWEEN; + $operator = Xls\DataValidationHelper::operator($operator); - break; - case 0x01: - $operator = DataValidation::OPERATOR_NOTBETWEEN; - - break; - case 0x02: - $operator = DataValidation::OPERATOR_EQUAL; - - break; - case 0x03: - $operator = DataValidation::OPERATOR_NOTEQUAL; - - break; - case 0x04: - $operator = DataValidation::OPERATOR_GREATERTHAN; - - break; - case 0x05: - $operator = DataValidation::OPERATOR_LESSTHAN; - - break; - case 0x06: - $operator = DataValidation::OPERATOR_GREATERTHANOREQUAL; - - break; - case 0x07: - $operator = DataValidation::OPERATOR_LESSTHANOREQUAL; - - break; + if ($type === null || $errorStyle === null || $operator === null) { + return; } // offset: 4; size: var; title of the prompt box @@ -4946,6 +4857,7 @@ class Xls extends BaseReader // offset: var; size: $sz1; formula data for first condition (without size field) $formula1 = substr($recordData, $offset, $sz1); $formula1 = pack('v', $sz1) . $formula1; // prepend the length + try { $formula1 = $this->getFormulaFromStructure($formula1); @@ -4968,6 +4880,7 @@ class Xls extends BaseReader // offset: var; size: $sz2; formula data for second condition (without size field) $formula2 = substr($recordData, $offset, $sz2); $formula2 = pack('v', $sz2) . $formula2; // prepend the length + try { $formula2 = $this->getFormulaFromStructure($formula2); } catch (PhpSpreadsheetException $e) { @@ -5003,7 +4916,7 @@ class Xls extends BaseReader /** * Read SHEETLAYOUT record. Stores sheet tab color information. */ - private function readSheetLayout() + private function readSheetLayout(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -5034,8 +4947,6 @@ class Xls extends BaseReader case 0x28: // TODO: Investigate structure for .xls SHEETLAYOUT record as saved by MS Office Excel 2007 return; - - break; } } } @@ -5043,7 +4954,7 @@ class Xls extends BaseReader /** * Read SHEETPROTECTION record (FEATHEADR). */ - private function readSheetProtection() + private function readSheetProtection(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -5076,12 +4987,14 @@ class Xls extends BaseReader $options = self::getUInt2d($recordData, 19); // bit: 0; mask 0x0001; 1 = user may edit objects, 0 = users must not edit objects + // Note - do not negate $bool $bool = (0x0001 & $options) >> 0; - $this->phpSheet->getProtection()->setObjects(!$bool); + $this->phpSheet->getProtection()->setObjects((bool) $bool); // bit: 1; mask 0x0002; edit scenarios + // Note - do not negate $bool $bool = (0x0002 & $options) >> 1; - $this->phpSheet->getProtection()->setScenarios(!$bool); + $this->phpSheet->getProtection()->setScenarios((bool) $bool); // bit: 2; mask 0x0004; format cells $bool = (0x0004 & $options) >> 2; @@ -5116,8 +5029,9 @@ class Xls extends BaseReader $this->phpSheet->getProtection()->setDeleteRows(!$bool); // bit: 10; mask 0x0400; select locked cells + // Note that this is opposite of most of above. $bool = (0x0400 & $options) >> 10; - $this->phpSheet->getProtection()->setSelectLockedCells(!$bool); + $this->phpSheet->getProtection()->setSelectLockedCells((bool) $bool); // bit: 11; mask 0x0800; sort cell range $bool = (0x0800 & $options) >> 11; @@ -5132,8 +5046,9 @@ class Xls extends BaseReader $this->phpSheet->getProtection()->setPivotTables(!$bool); // bit: 14; mask 0x4000; select unlocked cells + // Note that this is opposite of most of above. $bool = (0x4000 & $options) >> 14; - $this->phpSheet->getProtection()->setSelectUnlockedCells(!$bool); + $this->phpSheet->getProtection()->setSelectUnlockedCells((bool) $bool); // offset: 21; size: 2; not used } @@ -5143,7 +5058,7 @@ class Xls extends BaseReader * Reading of this record is based on Microsoft Office Excel 97-2000 Binary File Format Specification, * where it is referred to as FEAT record. */ - private function readRangeProtection() + private function readRangeProtection(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -5205,7 +5120,7 @@ class Xls extends BaseReader * When MSODRAWING data on a sheet exceeds 8224 bytes, CONTINUE records are used instead. Undocumented. * In this case, we must treat the CONTINUE record as a MSODRAWING record. */ - private function readContinue() + private function readContinue(): void { $length = self::getUInt2d($this->data, $this->pos + 2); $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); @@ -5279,12 +5194,10 @@ class Xls extends BaseReader $nextIdentifier = self::getUInt2d($this->data, $this->pos); } while ($nextIdentifier == self::XLS_TYPE_CONTINUE); - $splicedData = [ + return [ 'recordData' => $data, 'spliceOffsets' => $spliceOffsets, ]; - - return $splicedData; } /** @@ -5327,7 +5240,7 @@ class Xls extends BaseReader // start parsing the formula data $tokens = []; - while (strlen($formulaData) > 0 and $token = $this->getNextToken($formulaData, $baseCell)) { + while (strlen($formulaData) > 0 && $token = $this->getNextToken($formulaData, $baseCell)) { $tokens[] = $token; $formulaData = substr($formulaData, $token['size']); } @@ -5518,8 +5431,6 @@ class Xls extends BaseReader * @param string $formulaData Formula data * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas * - * @throws Exception - * * @return array */ private function getNextToken($formulaData, $baseCell = 'A1') @@ -5769,6 +5680,7 @@ class Xls extends BaseReader $size = 9; $data = self::extractNumber(substr($formulaData, 1)); $data = str_replace(',', '.', (string) $data); // in case non-English locale + break; case 0x20: // array constant case 0x40: @@ -6972,7 +6884,7 @@ class Xls extends BaseReader // offset: 1; size: 2; one-based index to definedname record $definedNameIndex = self::getUInt2d($formulaData, 1) - 1; // offset: 2; size: 2; not used - $data = $this->definedname[$definedNameIndex]['name']; + $data = $this->definedname[$definedNameIndex]['name'] ?? ''; break; case 0x24: // single cell reference e.g. A5 @@ -7048,7 +6960,7 @@ class Xls extends BaseReader // offset: 3; size: 2; one-based index to DEFINEDNAME or EXTERNNAME record $index = self::getUInt2d($formulaData, 3); // assume index is to EXTERNNAME record - $data = $this->externalNames[$index - 1]['name']; + $data = $this->externalNames[$index - 1]['name'] ?? ''; // offset: 5; size: 2; not used break; case 0x3A: // 3d reference to cell @@ -7089,7 +7001,7 @@ class Xls extends BaseReader } break; - // Unknown cases // don't know how to deal with + // Unknown cases // don't know how to deal with default: throw new Exception('Unrecognized token ' . sprintf('%02X', $id) . ' in formula'); @@ -7147,6 +7059,7 @@ class Xls extends BaseReader { [$baseCol, $baseRow] = Coordinate::coordinateFromString($baseCell); $baseCol = Coordinate::columnIndexFromString($baseCol) - 1; + $baseRow = (int) $baseRow; // offset: 0; size: 2; index to row (0... 65535) (or offset (-32768... 32767)) $rowIndex = self::getUInt2d($cellAddressStructure, 0); @@ -7188,8 +7101,6 @@ class Xls extends BaseReader * * @param string $subData * - * @throws Exception - * * @return string */ private function readBIFF5CellRangeAddressFixed($subData) @@ -7215,7 +7126,7 @@ class Xls extends BaseReader $fc = Coordinate::stringFromColumnIndex($fc + 1); $lc = Coordinate::stringFromColumnIndex($lc + 1); - if ($fr == $lr and $fc == $lc) { + if ($fr == $lr && $fc == $lc) { return "$fc$fr"; } @@ -7229,8 +7140,6 @@ class Xls extends BaseReader * * @param string $subData * - * @throws Exception - * * @return string */ private function readBIFF8CellRangeAddressFixed($subData) @@ -7256,7 +7165,7 @@ class Xls extends BaseReader $fc = Coordinate::stringFromColumnIndex($fc + 1); $lc = Coordinate::stringFromColumnIndex($lc + 1); - if ($fr == $lr and $fc == $lc) { + if ($fr == $lr && $fc == $lc) { return "$fc$fr"; } @@ -7328,8 +7237,8 @@ class Xls extends BaseReader */ private function readBIFF8CellRangeAddressB($subData, $baseCell = 'A1') { - [$baseCol, $baseRow] = Coordinate::coordinateFromString($baseCell); - $baseCol = Coordinate::columnIndexFromString($baseCol) - 1; + [$baseCol, $baseRow] = Coordinate::indexesFromString($baseCell); + $baseCol = $baseCol - 1; // TODO: if cell range is just a single cell, should this funciton // not just return e.g. 'A1' and not 'A1:A1' ? @@ -7467,8 +7376,6 @@ class Xls extends BaseReader * * @param int $index * - * @throws Exception - * * @return false|string */ private function readSheetRangeByRefIndex($index) @@ -7479,7 +7386,7 @@ class Xls extends BaseReader switch ($type) { case 'internal': // check if we have a deleted 3d reference - if ($this->ref[$index]['firstSheetIndex'] == 0xFFFF or $this->ref[$index]['lastSheetIndex'] == 0xFFFF) { + if ($this->ref[$index]['firstSheetIndex'] == 0xFFFF || $this->ref[$index]['lastSheetIndex'] == 0xFFFF) { throw new Exception('Deleted sheet reference'); } @@ -7607,6 +7514,8 @@ class Xls extends BaseReader $size = 9; break; + default: + throw new PhpSpreadsheetException('Unsupported BIFF8 constant'); } return [ @@ -7704,7 +7613,7 @@ class Xls extends BaseReader $string = self::readUnicodeString(substr($subData, 1), $characterCount); // add 1 for the string length - $string['size'] += 1; + ++$string['size']; return $string; } @@ -7792,18 +7701,18 @@ class Xls extends BaseReader { $rknumhigh = self::getInt4d($data, 4); $rknumlow = self::getInt4d($data, 0); - $sign = ($rknumhigh & 0x80000000) >> 31; + $sign = ($rknumhigh & (int) 0x80000000) >> 31; $exp = (($rknumhigh & 0x7ff00000) >> 20) - 1023; $mantissa = (0x100000 | ($rknumhigh & 0x000fffff)); - $mantissalow1 = ($rknumlow & 0x80000000) >> 31; + $mantissalow1 = ($rknumlow & (int) 0x80000000) >> 31; $mantissalow2 = ($rknumlow & 0x7fffffff); - $value = $mantissa / pow(2, (20 - $exp)); + $value = $mantissa / 2 ** (20 - $exp); if ($mantissalow1 != 0) { - $value += 1 / pow(2, (21 - $exp)); + $value += 1 / 2 ** (21 - $exp); } - $value += $mantissalow2 / pow(2, (52 - $exp)); + $value += $mantissalow2 / 2 ** (52 - $exp); if ($sign) { $value *= -1; } @@ -7826,10 +7735,10 @@ class Xls extends BaseReader // The RK format calls for using only the most significant 30 bits // of the 64 bit floating point value. The other 34 bits are assumed // to be 0 so we use the upper 30 bits of $rknum as follows... - $sign = ($rknum & 0x80000000) >> 31; + $sign = ($rknum & (int) 0x80000000) >> 31; $exp = ($rknum & 0x7ff00000) >> 20; $mantissa = (0x100000 | ($rknum & 0x000ffffc)); - $value = $mantissa / pow(2, (20 - ($exp - 1023))); + $value = $mantissa / 2 ** (20 - ($exp - 1023)); if ($sign) { $value = -1 * $value; } @@ -7946,4 +7855,242 @@ class Xls extends BaseReader return $value; } + + /** + * Phpstan 1.4.4 complains that this property is never read. + * So, we might be able to get rid of it altogether. + * For now, however, this function makes it readable, + * which satisfies Phpstan. + * + * @codeCoverageIgnore + */ + public function getMapCellStyleXfIndex(): array + { + return $this->mapCellStyleXfIndex; + } + + private function readCFHeader(): array + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer forward to next record + $this->pos += 4 + $length; + + if ($this->readDataOnly) { + return []; + } + + // offset: 0; size: 2; Rule Count +// $ruleCount = self::getUInt2d($recordData, 0); + + // offset: var; size: var; cell range address list with + $cellRangeAddressList = ($this->version == self::XLS_BIFF8) + ? $this->readBIFF8CellRangeAddressList(substr($recordData, 12)) + : $this->readBIFF5CellRangeAddressList(substr($recordData, 12)); + $cellRangeAddresses = $cellRangeAddressList['cellRangeAddresses']; + + return $cellRangeAddresses; + } + + private function readCFRule(array $cellRangeAddresses): void + { + $length = self::getUInt2d($this->data, $this->pos + 2); + $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); + + // move stream pointer forward to next record + $this->pos += 4 + $length; + + if ($this->readDataOnly) { + return; + } + + // offset: 0; size: 2; Options + $cfRule = self::getUInt2d($recordData, 0); + + // bit: 8-15; mask: 0x00FF; type + $type = (0x00FF & $cfRule) >> 0; + $type = ConditionalFormatting::type($type); + + // bit: 0-7; mask: 0xFF00; type + $operator = (0xFF00 & $cfRule) >> 8; + $operator = ConditionalFormatting::operator($operator); + + if ($type === null || $operator === null) { + return; + } + + // offset: 2; size: 2; Size1 + $size1 = self::getUInt2d($recordData, 2); + + // offset: 4; size: 2; Size2 + $size2 = self::getUInt2d($recordData, 4); + + // offset: 6; size: 4; Options + $options = self::getInt4d($recordData, 6); + + $style = new Style(); + $this->getCFStyleOptions($options, $style); + + $hasFontRecord = (bool) ((0x04000000 & $options) >> 26); + $hasAlignmentRecord = (bool) ((0x08000000 & $options) >> 27); + $hasBorderRecord = (bool) ((0x10000000 & $options) >> 28); + $hasFillRecord = (bool) ((0x20000000 & $options) >> 29); + $hasProtectionRecord = (bool) ((0x40000000 & $options) >> 30); + + $offset = 12; + + if ($hasFontRecord === true) { + $fontStyle = substr($recordData, $offset, 118); + $this->getCFFontStyle($fontStyle, $style); + $offset += 118; + } + + if ($hasAlignmentRecord === true) { + $alignmentStyle = substr($recordData, $offset, 8); + $this->getCFAlignmentStyle($alignmentStyle, $style); + $offset += 8; + } + + if ($hasBorderRecord === true) { + $borderStyle = substr($recordData, $offset, 8); + $this->getCFBorderStyle($borderStyle, $style); + $offset += 8; + } + + if ($hasFillRecord === true) { + $fillStyle = substr($recordData, $offset, 4); + $this->getCFFillStyle($fillStyle, $style); + $offset += 4; + } + + if ($hasProtectionRecord === true) { + $protectionStyle = substr($recordData, $offset, 4); + $this->getCFProtectionStyle($protectionStyle, $style); + $offset += 2; + } + + $formula1 = $formula2 = null; + if ($size1 > 0) { + $formula1 = $this->readCFFormula($recordData, $offset, $size1); + if ($formula1 === null) { + return; + } + + $offset += $size1; + } + + if ($size2 > 0) { + $formula2 = $this->readCFFormula($recordData, $offset, $size2); + if ($formula2 === null) { + return; + } + + $offset += $size2; + } + + $this->setCFRules($cellRangeAddresses, $type, $operator, $formula1, $formula2, $style); + } + + private function getCFStyleOptions(int $options, Style $style): void + { + } + + private function getCFFontStyle(string $options, Style $style): void + { + $fontSize = self::getInt4d($options, 64); + if ($fontSize !== -1) { + $style->getFont()->setSize($fontSize / 20); // Convert twips to points + } + + $bold = self::getUInt2d($options, 72) === 700; // 400 = normal, 700 = bold + $style->getFont()->setBold($bold); + + $color = self::getInt4d($options, 80); + + if ($color !== -1) { + $style->getFont()->getColor()->setRGB(Xls\Color::map($color, $this->palette, $this->version)['rgb']); + } + } + + private function getCFAlignmentStyle(string $options, Style $style): void + { + } + + private function getCFBorderStyle(string $options, Style $style): void + { + } + + private function getCFFillStyle(string $options, Style $style): void + { + $fillPattern = self::getUInt2d($options, 0); + // bit: 10-15; mask: 0xFC00; type + $fillPattern = (0xFC00 & $fillPattern) >> 10; + $fillPattern = FillPattern::lookup($fillPattern); + $fillPattern = $fillPattern === Fill::FILL_NONE ? Fill::FILL_SOLID : $fillPattern; + + if ($fillPattern !== Fill::FILL_NONE) { + $style->getFill()->setFillType($fillPattern); + + $fillColors = self::getUInt2d($options, 2); + + // bit: 0-6; mask: 0x007F; type + $color1 = (0x007F & $fillColors) >> 0; + $style->getFill()->getStartColor()->setRGB(Xls\Color::map($color1, $this->palette, $this->version)['rgb']); + + // bit: 7-13; mask: 0x3F80; type + $color2 = (0x3F80 & $fillColors) >> 7; + $style->getFill()->getEndColor()->setRGB(Xls\Color::map($color2, $this->palette, $this->version)['rgb']); + } + } + + private function getCFProtectionStyle(string $options, Style $style): void + { + } + + /** + * @return null|float|int|string + */ + private function readCFFormula(string $recordData, int $offset, int $size) + { + try { + $formula = substr($recordData, $offset, $size); + $formula = pack('v', $size) . $formula; // prepend the length + + $formula = $this->getFormulaFromStructure($formula); + if (is_numeric($formula)) { + return (strpos($formula, '.') !== false) ? (float) $formula : (int) $formula; + } + + return $formula; + } catch (PhpSpreadsheetException $e) { + } + + return null; + } + + /** + * @param null|float|int|string $formula1 + * @param null|float|int|string $formula2 + */ + private function setCFRules(array $cellRanges, string $type, string $operator, $formula1, $formula2, Style $style): void + { + foreach ($cellRanges as $cellRange) { + $conditional = new Conditional(); + $conditional->setConditionType($type); + $conditional->setOperatorType($operator); + if ($formula1 !== null) { + $conditional->addCondition($formula1); + } + if ($formula2 !== null) { + $conditional->addCondition($formula2); + } + $conditional->setStyle($style); + + $conditionalStyles = $this->phpSheet->getStyle($cellRange)->getConditionalStyles(); + $conditionalStyles[] = $conditional; + + $this->phpSheet->getStyle($cellRange)->setConditionalStyles($conditionalStyles); + } + } } diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xls/Color.php b/PhpOffice/PhpSpreadsheet/Reader/Xls/Color.php old mode 100755 new mode 100644 index c45f88c..06c2d0b --- a/PhpOffice/PhpSpreadsheet/Reader/Xls/Color.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Xls/Color.php @@ -20,7 +20,7 @@ class Color if ($color <= 0x07 || $color >= 0x40) { // special built-in color return Color\BuiltIn::lookup($color); - } elseif (isset($palette, $palette[$color - 8])) { + } elseif (isset($palette[$color - 8])) { // palette color, color index 0x08 maps to pallete index 0 return $palette[$color - 8]; } diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xls/Color/BIFF5.php b/PhpOffice/PhpSpreadsheet/Reader/Xls/Color/BIFF5.php old mode 100755 new mode 100644 index 743d938..15d0b73 --- a/PhpOffice/PhpSpreadsheet/Reader/Xls/Color/BIFF5.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Xls/Color/BIFF5.php @@ -4,7 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xls\Color; class BIFF5 { - protected static $map = [ + private const BIFF5_COLOR_MAP = [ 0x08 => '000000', 0x09 => 'FFFFFF', 0x0A => 'FF0000', @@ -72,10 +72,6 @@ class BIFF5 */ public static function lookup($color) { - if (isset(self::$map[$color])) { - return ['rgb' => self::$map[$color]]; - } - - return ['rgb' => '000000']; + return ['rgb' => self::BIFF5_COLOR_MAP[$color] ?? '000000']; } } diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xls/Color/BIFF8.php b/PhpOffice/PhpSpreadsheet/Reader/Xls/Color/BIFF8.php old mode 100755 new mode 100644 index 5c109fb..019ec79 --- a/PhpOffice/PhpSpreadsheet/Reader/Xls/Color/BIFF8.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Xls/Color/BIFF8.php @@ -4,7 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xls\Color; class BIFF8 { - protected static $map = [ + private const BIFF8_COLOR_MAP = [ 0x08 => '000000', 0x09 => 'FFFFFF', 0x0A => 'FF0000', @@ -72,10 +72,6 @@ class BIFF8 */ public static function lookup($color) { - if (isset(self::$map[$color])) { - return ['rgb' => self::$map[$color]]; - } - - return ['rgb' => '000000']; + return ['rgb' => self::BIFF8_COLOR_MAP[$color] ?? '000000']; } } diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xls/Color/BuiltIn.php b/PhpOffice/PhpSpreadsheet/Reader/Xls/Color/BuiltIn.php old mode 100755 new mode 100644 index 90d50e3..b6a96af --- a/PhpOffice/PhpSpreadsheet/Reader/Xls/Color/BuiltIn.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Xls/Color/BuiltIn.php @@ -4,7 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xls\Color; class BuiltIn { - protected static $map = [ + private const BUILTIN_COLOR_MAP = [ 0x00 => '000000', 0x01 => 'FFFFFF', 0x02 => 'FF0000', @@ -26,10 +26,6 @@ class BuiltIn */ public static function lookup($color) { - if (isset(self::$map[$color])) { - return ['rgb' => self::$map[$color]]; - } - - return ['rgb' => '000000']; + return ['rgb' => self::BUILTIN_COLOR_MAP[$color] ?? '000000']; } } diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xls/ConditionalFormatting.php b/PhpOffice/PhpSpreadsheet/Reader/Xls/ConditionalFormatting.php new file mode 100644 index 0000000..8400efb --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Reader/Xls/ConditionalFormatting.php @@ -0,0 +1,49 @@ + + */ + private static $types = [ + 0x01 => Conditional::CONDITION_CELLIS, + 0x02 => Conditional::CONDITION_EXPRESSION, + ]; + + /** + * @var array + */ + private static $operators = [ + 0x00 => Conditional::OPERATOR_NONE, + 0x01 => Conditional::OPERATOR_BETWEEN, + 0x02 => Conditional::OPERATOR_NOTBETWEEN, + 0x03 => Conditional::OPERATOR_EQUAL, + 0x04 => Conditional::OPERATOR_NOTEQUAL, + 0x05 => Conditional::OPERATOR_GREATERTHAN, + 0x06 => Conditional::OPERATOR_LESSTHAN, + 0x07 => Conditional::OPERATOR_GREATERTHANOREQUAL, + 0x08 => Conditional::OPERATOR_LESSTHANOREQUAL, + ]; + + public static function type(int $type): ?string + { + if (isset(self::$types[$type])) { + return self::$types[$type]; + } + + return null; + } + + public static function operator(int $operator): ?string + { + if (isset(self::$operators[$operator])) { + return self::$operators[$operator]; + } + + return null; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xls/DataValidationHelper.php b/PhpOffice/PhpSpreadsheet/Reader/Xls/DataValidationHelper.php new file mode 100644 index 0000000..02f844e --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Reader/Xls/DataValidationHelper.php @@ -0,0 +1,72 @@ + + */ + private static $types = [ + 0x00 => DataValidation::TYPE_NONE, + 0x01 => DataValidation::TYPE_WHOLE, + 0x02 => DataValidation::TYPE_DECIMAL, + 0x03 => DataValidation::TYPE_LIST, + 0x04 => DataValidation::TYPE_DATE, + 0x05 => DataValidation::TYPE_TIME, + 0x06 => DataValidation::TYPE_TEXTLENGTH, + 0x07 => DataValidation::TYPE_CUSTOM, + ]; + + /** + * @var array + */ + private static $errorStyles = [ + 0x00 => DataValidation::STYLE_STOP, + 0x01 => DataValidation::STYLE_WARNING, + 0x02 => DataValidation::STYLE_INFORMATION, + ]; + + /** + * @var array + */ + private static $operators = [ + 0x00 => DataValidation::OPERATOR_BETWEEN, + 0x01 => DataValidation::OPERATOR_NOTBETWEEN, + 0x02 => DataValidation::OPERATOR_EQUAL, + 0x03 => DataValidation::OPERATOR_NOTEQUAL, + 0x04 => DataValidation::OPERATOR_GREATERTHAN, + 0x05 => DataValidation::OPERATOR_LESSTHAN, + 0x06 => DataValidation::OPERATOR_GREATERTHANOREQUAL, + 0x07 => DataValidation::OPERATOR_LESSTHANOREQUAL, + ]; + + public static function type(int $type): ?string + { + if (isset(self::$types[$type])) { + return self::$types[$type]; + } + + return null; + } + + public static function errorStyle(int $errorStyle): ?string + { + if (isset(self::$errorStyles[$errorStyle])) { + return self::$errorStyles[$errorStyle]; + } + + return null; + } + + public static function operator(int $operator): ?string + { + if (isset(self::$operators[$operator])) { + return self::$operators[$operator]; + } + + return null; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xls/ErrorCode.php b/PhpOffice/PhpSpreadsheet/Reader/Xls/ErrorCode.php old mode 100755 new mode 100644 index 7daf723..0b79366 --- a/PhpOffice/PhpSpreadsheet/Reader/Xls/ErrorCode.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Xls/ErrorCode.php @@ -4,7 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xls; class ErrorCode { - protected static $map = [ + private const ERROR_CODE_MAP = [ 0x00 => '#NULL!', 0x07 => '#DIV/0!', 0x0F => '#VALUE!', @@ -23,10 +23,6 @@ class ErrorCode */ public static function lookup($code) { - if (isset(self::$map[$code])) { - return self::$map[$code]; - } - - return false; + return self::ERROR_CODE_MAP[$code] ?? false; } } diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xls/Escher.php b/PhpOffice/PhpSpreadsheet/Reader/Xls/Escher.php old mode 100755 new mode 100644 index 858d6bb..e9c95c8 --- a/PhpOffice/PhpSpreadsheet/Reader/Xls/Escher.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Xls/Escher.php @@ -71,6 +71,27 @@ class Escher $this->object = $object; } + private const WHICH_ROUTINE = [ + self::DGGCONTAINER => 'readDggContainer', + self::DGG => 'readDgg', + self::BSTORECONTAINER => 'readBstoreContainer', + self::BSE => 'readBSE', + self::BLIPJPEG => 'readBlipJPEG', + self::BLIPPNG => 'readBlipPNG', + self::OPT => 'readOPT', + self::TERTIARYOPT => 'readTertiaryOPT', + self::SPLITMENUCOLORS => 'readSplitMenuColors', + self::DGCONTAINER => 'readDgContainer', + self::DG => 'readDg', + self::SPGRCONTAINER => 'readSpgrContainer', + self::SPCONTAINER => 'readSpContainer', + self::SPGR => 'readSpgr', + self::SP => 'readSp', + self::CLIENTTEXTBOX => 'readClientTextbox', + self::CLIENTANCHOR => 'readClientAnchor', + self::CLIENTDATA => 'readClientData', + ]; + /** * Load Escher stream data. May be a partial Escher stream. * @@ -91,84 +112,9 @@ class Escher while ($this->pos < $this->dataSize) { // offset: 2; size: 2: Record Type $fbt = Xls::getUInt2d($this->data, $this->pos + 2); - - switch ($fbt) { - case self::DGGCONTAINER: - $this->readDggContainer(); - - break; - case self::DGG: - $this->readDgg(); - - break; - case self::BSTORECONTAINER: - $this->readBstoreContainer(); - - break; - case self::BSE: - $this->readBSE(); - - break; - case self::BLIPJPEG: - $this->readBlipJPEG(); - - break; - case self::BLIPPNG: - $this->readBlipPNG(); - - break; - case self::OPT: - $this->readOPT(); - - break; - case self::TERTIARYOPT: - $this->readTertiaryOPT(); - - break; - case self::SPLITMENUCOLORS: - $this->readSplitMenuColors(); - - break; - case self::DGCONTAINER: - $this->readDgContainer(); - - break; - case self::DG: - $this->readDg(); - - break; - case self::SPGRCONTAINER: - $this->readSpgrContainer(); - - break; - case self::SPCONTAINER: - $this->readSpContainer(); - - break; - case self::SPGR: - $this->readSpgr(); - - break; - case self::SP: - $this->readSp(); - - break; - case self::CLIENTTEXTBOX: - $this->readClientTextbox(); - - break; - case self::CLIENTANCHOR: - $this->readClientAnchor(); - - break; - case self::CLIENTDATA: - $this->readClientData(); - - break; - default: - $this->readDefault(); - - break; + $routine = self::WHICH_ROUTINE[$fbt] ?? 'readDefault'; + if (method_exists($this, $routine)) { + $this->$routine(); } } @@ -178,19 +124,19 @@ class Escher /** * Read a generic record. */ - private function readDefault() + private function readDefault(): void { // offset 0; size: 2; recVer and recInstance - $verInstance = Xls::getUInt2d($this->data, $this->pos); + //$verInstance = Xls::getUInt2d($this->data, $this->pos); // offset: 2; size: 2: Record Type - $fbt = Xls::getUInt2d($this->data, $this->pos + 2); + //$fbt = Xls::getUInt2d($this->data, $this->pos + 2); // bit: 0-3; mask: 0x000F; recVer - $recVer = (0x000F & $verInstance) >> 0; + //$recVer = (0x000F & $verInstance) >> 0; $length = Xls::getInt4d($this->data, $this->pos + 4); - $recordData = substr($this->data, $this->pos + 8, $length); + //$recordData = substr($this->data, $this->pos + 8, $length); // move stream pointer to next record $this->pos += 8 + $length; @@ -199,7 +145,7 @@ class Escher /** * Read DggContainer record (Drawing Group Container). */ - private function readDggContainer() + private function readDggContainer(): void { $length = Xls::getInt4d($this->data, $this->pos + 4); $recordData = substr($this->data, $this->pos + 8, $length); @@ -209,7 +155,7 @@ class Escher // record is a container, read contents $dggContainer = new DggContainer(); - $this->object->setDggContainer($dggContainer); + $this->applyAttribute('setDggContainer', $dggContainer); $reader = new self($dggContainer); $reader->load($recordData); } @@ -217,10 +163,10 @@ class Escher /** * Read Dgg record (Drawing Group). */ - private function readDgg() + private function readDgg(): void { $length = Xls::getInt4d($this->data, $this->pos + 4); - $recordData = substr($this->data, $this->pos + 8, $length); + //$recordData = substr($this->data, $this->pos + 8, $length); // move stream pointer to next record $this->pos += 8 + $length; @@ -229,7 +175,7 @@ class Escher /** * Read BstoreContainer record (Blip Store Container). */ - private function readBstoreContainer() + private function readBstoreContainer(): void { $length = Xls::getInt4d($this->data, $this->pos + 4); $recordData = substr($this->data, $this->pos + 8, $length); @@ -239,7 +185,7 @@ class Escher // record is a container, read contents $bstoreContainer = new BstoreContainer(); - $this->object->setBstoreContainer($bstoreContainer); + $this->applyAttribute('setBstoreContainer', $bstoreContainer); $reader = new self($bstoreContainer); $reader->load($recordData); } @@ -247,7 +193,7 @@ class Escher /** * Read BSE record. */ - private function readBSE() + private function readBSE(): void { // offset: 0; size: 2; recVer and recInstance @@ -262,45 +208,45 @@ class Escher // add BSE to BstoreContainer $BSE = new BSE(); - $this->object->addBSE($BSE); + $this->applyAttribute('addBSE', $BSE); $BSE->setBLIPType($recInstance); // offset: 0; size: 1; btWin32 (MSOBLIPTYPE) - $btWin32 = ord($recordData[0]); + //$btWin32 = ord($recordData[0]); // offset: 1; size: 1; btWin32 (MSOBLIPTYPE) - $btMacOS = ord($recordData[1]); + //$btMacOS = ord($recordData[1]); // offset: 2; size: 16; MD4 digest - $rgbUid = substr($recordData, 2, 16); + //$rgbUid = substr($recordData, 2, 16); // offset: 18; size: 2; tag - $tag = Xls::getUInt2d($recordData, 18); + //$tag = Xls::getUInt2d($recordData, 18); // offset: 20; size: 4; size of BLIP in bytes - $size = Xls::getInt4d($recordData, 20); + //$size = Xls::getInt4d($recordData, 20); // offset: 24; size: 4; number of references to this BLIP - $cRef = Xls::getInt4d($recordData, 24); + //$cRef = Xls::getInt4d($recordData, 24); // offset: 28; size: 4; MSOFO file offset - $foDelay = Xls::getInt4d($recordData, 28); + //$foDelay = Xls::getInt4d($recordData, 28); // offset: 32; size: 1; unused1 - $unused1 = ord($recordData[32]); + //$unused1 = ord($recordData[32]); // offset: 33; size: 1; size of nameData in bytes (including null terminator) $cbName = ord($recordData[33]); // offset: 34; size: 1; unused2 - $unused2 = ord($recordData[34]); + //$unused2 = ord($recordData[34]); // offset: 35; size: 1; unused3 - $unused3 = ord($recordData[35]); + //$unused3 = ord($recordData[35]); // offset: 36; size: $cbName; nameData - $nameData = substr($recordData, 36, $cbName); + //$nameData = substr($recordData, 36, $cbName); // offset: 36 + $cbName, size: var; the BLIP data $blipData = substr($recordData, 36 + $cbName); @@ -313,7 +259,7 @@ class Escher /** * Read BlipJPEG record. Holds raw JPEG image data. */ - private function readBlipJPEG() + private function readBlipJPEG(): void { // offset: 0; size: 2; recVer and recInstance @@ -329,18 +275,18 @@ class Escher $pos = 0; // offset: 0; size: 16; rgbUid1 (MD4 digest of) - $rgbUid1 = substr($recordData, 0, 16); + //$rgbUid1 = substr($recordData, 0, 16); $pos += 16; // offset: 16; size: 16; rgbUid2 (MD4 digest), only if $recInstance = 0x46B or 0x6E3 if (in_array($recInstance, [0x046B, 0x06E3])) { - $rgbUid2 = substr($recordData, 16, 16); + //$rgbUid2 = substr($recordData, 16, 16); $pos += 16; } // offset: var; size: 1; tag - $tag = ord($recordData[$pos]); - $pos += 1; + //$tag = ord($recordData[$pos]); + ++$pos; // offset: var; size: var; the raw image data $data = substr($recordData, $pos); @@ -348,13 +294,13 @@ class Escher $blip = new Blip(); $blip->setData($data); - $this->object->setBlip($blip); + $this->applyAttribute('setBlip', $blip); } /** * Read BlipPNG record. Holds raw PNG image data. */ - private function readBlipPNG() + private function readBlipPNG(): void { // offset: 0; size: 2; recVer and recInstance @@ -370,18 +316,18 @@ class Escher $pos = 0; // offset: 0; size: 16; rgbUid1 (MD4 digest of) - $rgbUid1 = substr($recordData, 0, 16); + //$rgbUid1 = substr($recordData, 0, 16); $pos += 16; // offset: 16; size: 16; rgbUid2 (MD4 digest), only if $recInstance = 0x46B or 0x6E3 if ($recInstance == 0x06E1) { - $rgbUid2 = substr($recordData, 16, 16); + //$rgbUid2 = substr($recordData, 16, 16); $pos += 16; } // offset: var; size: 1; tag - $tag = ord($recordData[$pos]); - $pos += 1; + //$tag = ord($recordData[$pos]); + ++$pos; // offset: var; size: var; the raw image data $data = substr($recordData, $pos); @@ -389,13 +335,13 @@ class Escher $blip = new Blip(); $blip->setData($data); - $this->object->setBlip($blip); + $this->applyAttribute('setBlip', $blip); } /** * Read OPT record. This record may occur within DggContainer record or SpContainer. */ - private function readOPT() + private function readOPT(): void { // offset: 0; size: 2; recVer and recInstance @@ -414,15 +360,15 @@ class Escher /** * Read TertiaryOPT record. */ - private function readTertiaryOPT() + private function readTertiaryOPT(): void { // offset: 0; size: 2; recVer and recInstance // bit: 4-15; mask: 0xFFF0; recInstance - $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4; + //$recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4; $length = Xls::getInt4d($this->data, $this->pos + 4); - $recordData = substr($this->data, $this->pos + 8, $length); + //$recordData = substr($this->data, $this->pos + 8, $length); // move stream pointer to next record $this->pos += 8 + $length; @@ -431,10 +377,10 @@ class Escher /** * Read SplitMenuColors record. */ - private function readSplitMenuColors() + private function readSplitMenuColors(): void { $length = Xls::getInt4d($this->data, $this->pos + 4); - $recordData = substr($this->data, $this->pos + 8, $length); + //$recordData = substr($this->data, $this->pos + 8, $length); // move stream pointer to next record $this->pos += 8 + $length; @@ -443,7 +389,7 @@ class Escher /** * Read DgContainer record (Drawing Container). */ - private function readDgContainer() + private function readDgContainer(): void { $length = Xls::getInt4d($this->data, $this->pos + 4); $recordData = substr($this->data, $this->pos + 8, $length); @@ -453,18 +399,18 @@ class Escher // record is a container, read contents $dgContainer = new DgContainer(); - $this->object->setDgContainer($dgContainer); + $this->applyAttribute('setDgContainer', $dgContainer); $reader = new self($dgContainer); - $escher = $reader->load($recordData); + $reader->load($recordData); } /** * Read Dg record (Drawing). */ - private function readDg() + private function readDg(): void { $length = Xls::getInt4d($this->data, $this->pos + 4); - $recordData = substr($this->data, $this->pos + 8, $length); + //$recordData = substr($this->data, $this->pos + 8, $length); // move stream pointer to next record $this->pos += 8 + $length; @@ -473,7 +419,7 @@ class Escher /** * Read SpgrContainer record (Shape Group Container). */ - private function readSpgrContainer() + private function readSpgrContainer(): void { // context is either context DgContainer or SpgrContainer @@ -489,42 +435,42 @@ class Escher if ($this->object instanceof DgContainer) { // DgContainer $this->object->setSpgrContainer($spgrContainer); - } else { + } elseif ($this->object instanceof SpgrContainer) { // SpgrContainer $this->object->addChild($spgrContainer); } $reader = new self($spgrContainer); - $escher = $reader->load($recordData); + $reader->load($recordData); } /** * Read SpContainer record (Shape Container). */ - private function readSpContainer() + private function readSpContainer(): void { $length = Xls::getInt4d($this->data, $this->pos + 4); $recordData = substr($this->data, $this->pos + 8, $length); // add spContainer to spgrContainer $spContainer = new SpContainer(); - $this->object->addChild($spContainer); + $this->applyAttribute('addChild', $spContainer); // move stream pointer to next record $this->pos += 8 + $length; // record is a container, read contents $reader = new self($spContainer); - $escher = $reader->load($recordData); + $reader->load($recordData); } /** * Read Spgr record (Shape Group). */ - private function readSpgr() + private function readSpgr(): void { $length = Xls::getInt4d($this->data, $this->pos + 4); - $recordData = substr($this->data, $this->pos + 8, $length); + //$recordData = substr($this->data, $this->pos + 8, $length); // move stream pointer to next record $this->pos += 8 + $length; @@ -533,15 +479,15 @@ class Escher /** * Read Sp record (Shape). */ - private function readSp() + private function readSp(): void { // offset: 0; size: 2; recVer and recInstance // bit: 4-15; mask: 0xFFF0; recInstance - $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4; + //$recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4; $length = Xls::getInt4d($this->data, $this->pos + 4); - $recordData = substr($this->data, $this->pos + 8, $length); + //$recordData = substr($this->data, $this->pos + 8, $length); // move stream pointer to next record $this->pos += 8 + $length; @@ -550,15 +496,15 @@ class Escher /** * Read ClientTextbox record. */ - private function readClientTextbox() + private function readClientTextbox(): void { // offset: 0; size: 2; recVer and recInstance // bit: 4-15; mask: 0xFFF0; recInstance - $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4; + //$recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4; $length = Xls::getInt4d($this->data, $this->pos + 4); - $recordData = substr($this->data, $this->pos + 8, $length); + //$recordData = substr($this->data, $this->pos + 8, $length); // move stream pointer to next record $this->pos += 8 + $length; @@ -567,7 +513,7 @@ class Escher /** * Read ClientAnchor record. This record holds information about where the shape is anchored in worksheet. */ - private function readClientAnchor() + private function readClientAnchor(): void { $length = Xls::getInt4d($this->data, $this->pos + 4); $recordData = substr($this->data, $this->pos + 8, $length); @@ -599,32 +545,31 @@ class Escher // offset: 16; size: 2; bottom-right corner vertical offset in 1/256 of row height $endOffsetY = Xls::getUInt2d($recordData, 16); - // set the start coordinates - $this->object->setStartCoordinates(Coordinate::stringFromColumnIndex($c1 + 1) . ($r1 + 1)); + $this->applyAttribute('setStartCoordinates', Coordinate::stringFromColumnIndex($c1 + 1) . ($r1 + 1)); + $this->applyAttribute('setStartOffsetX', $startOffsetX); + $this->applyAttribute('setStartOffsetY', $startOffsetY); + $this->applyAttribute('setEndCoordinates', Coordinate::stringFromColumnIndex($c2 + 1) . ($r2 + 1)); + $this->applyAttribute('setEndOffsetX', $endOffsetX); + $this->applyAttribute('setEndOffsetY', $endOffsetY); + } - // set the start offsetX - $this->object->setStartOffsetX($startOffsetX); - - // set the start offsetY - $this->object->setStartOffsetY($startOffsetY); - - // set the end coordinates - $this->object->setEndCoordinates(Coordinate::stringFromColumnIndex($c2 + 1) . ($r2 + 1)); - - // set the end offsetX - $this->object->setEndOffsetX($endOffsetX); - - // set the end offsetY - $this->object->setEndOffsetY($endOffsetY); + /** + * @param mixed $value + */ + private function applyAttribute(string $name, $value): void + { + if (method_exists($this->object, $name)) { + $this->object->$name($value); + } } /** * Read ClientData record. */ - private function readClientData() + private function readClientData(): void { $length = Xls::getInt4d($this->data, $this->pos + 4); - $recordData = substr($this->data, $this->pos + 8, $length); + //$recordData = substr($this->data, $this->pos + 8, $length); // move stream pointer to next record $this->pos += 8 + $length; @@ -636,7 +581,7 @@ class Escher * @param string $data Binary data * @param int $n Number of properties */ - private function readOfficeArtRGFOPTE($data, $n) + private function readOfficeArtRGFOPTE($data, $n): void { $splicedComplexData = substr($data, 6 * $n); @@ -652,7 +597,7 @@ class Escher $opidOpid = (0x3FFF & $opid) >> 0; // bit: 14; mask 0x4000; 1 = value in op field is BLIP identifier - $opidFBid = (0x4000 & $opid) >> 14; + //$opidFBid = (0x4000 & $opid) >> 14; // bit: 15; mask 0x8000; 1 = this is a complex property, op field specifies size of complex data $opidFComplex = (0x8000 & $opid) >> 15; @@ -671,7 +616,9 @@ class Escher $value = $op; } - $this->object->setOPT($opidOpid, $value); + if (method_exists($this->object, 'setOPT')) { + $this->object->setOPT($opidOpid, $value); + } } } } diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xls/MD5.php b/PhpOffice/PhpSpreadsheet/Reader/Xls/MD5.php old mode 100755 new mode 100644 index 6a10e59..d376c45 --- a/PhpOffice/PhpSpreadsheet/Reader/Xls/MD5.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Xls/MD5.php @@ -4,31 +4,48 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xls; class MD5 { - // Context + /** + * @var int + */ private $a; + /** + * @var int + */ private $b; + /** + * @var int + */ private $c; + /** + * @var int + */ private $d; + /** + * @var int + */ + private static $allOneBits; + /** * MD5 stream constructor. */ public function __construct() { + self::$allOneBits = self::signedInt(0xffffffff); $this->reset(); } /** * Reset the MD5 stream context. */ - public function reset() + public function reset(): void { $this->a = 0x67452301; - $this->b = 0xEFCDAB89; - $this->c = 0x98BADCFE; + $this->b = self::signedInt(0xEFCDAB89); + $this->c = self::signedInt(0x98BADCFE); $this->d = 0x10325476; } @@ -56,8 +73,9 @@ class MD5 * * @param string $data Data to add */ - public function add($data) + public function add(string $data): void { + // @phpstan-ignore-next-line $words = array_values(unpack('V16', $data)); $A = $this->a; @@ -65,10 +83,10 @@ class MD5 $C = $this->c; $D = $this->d; - $F = ['self', 'f']; - $G = ['self', 'g']; - $H = ['self', 'h']; - $I = ['self', 'i']; + $F = [self::class, 'f']; + $G = [self::class, 'g']; + $H = [self::class, 'h']; + $I = [self::class, 'i']; // ROUND 1 self::step($F, $A, $B, $C, $D, $words[0], 7, 0xd76aa478); @@ -142,43 +160,51 @@ class MD5 self::step($I, $C, $D, $A, $B, $words[2], 15, 0x2ad7d2bb); self::step($I, $B, $C, $D, $A, $words[9], 21, 0xeb86d391); - $this->a = ($this->a + $A) & 0xffffffff; - $this->b = ($this->b + $B) & 0xffffffff; - $this->c = ($this->c + $C) & 0xffffffff; - $this->d = ($this->d + $D) & 0xffffffff; + $this->a = ($this->a + $A) & self::$allOneBits; + $this->b = ($this->b + $B) & self::$allOneBits; + $this->c = ($this->c + $C) & self::$allOneBits; + $this->d = ($this->d + $D) & self::$allOneBits; } - private static function f($X, $Y, $Z) + private static function f(int $X, int $Y, int $Z): int { return ($X & $Y) | ((~$X) & $Z); // X AND Y OR NOT X AND Z } - private static function g($X, $Y, $Z) + private static function g(int $X, int $Y, int $Z): int { return ($X & $Z) | ($Y & (~$Z)); // X AND Z OR Y AND NOT Z } - private static function h($X, $Y, $Z) + private static function h(int $X, int $Y, int $Z): int { return $X ^ $Y ^ $Z; // X XOR Y XOR Z } - private static function i($X, $Y, $Z) + private static function i(int $X, int $Y, int $Z): int { return $Y ^ ($X | (~$Z)); // Y XOR (X OR NOT Z) } - private static function step($func, &$A, $B, $C, $D, $M, $s, $t) + /** @param float|int $t may be float on 32-bit system */ + private static function step(callable $func, int &$A, int $B, int $C, int $D, int $M, int $s, $t): void { - $A = ($A + call_user_func($func, $B, $C, $D) + $M + $t) & 0xffffffff; + $t = self::signedInt($t); + $A = (int) ($A + call_user_func($func, $B, $C, $D) + $M + $t) & self::$allOneBits; $A = self::rotate($A, $s); - $A = ($B + $A) & 0xffffffff; + $A = (int) ($B + $A) & self::$allOneBits; } - private static function rotate($decimal, $bits) + /** @param float|int $result may be float on 32-bit system */ + private static function signedInt($result): int + { + return is_int($result) ? $result : (int) (PHP_INT_MIN + $result - 1 - PHP_INT_MAX); + } + + private static function rotate(int $decimal, int $bits): int { $binary = str_pad(decbin($decimal), 32, '0', STR_PAD_LEFT); - return bindec(substr($binary, $bits) . substr($binary, 0, $bits)); + return self::signedInt(bindec(substr($binary, $bits) . substr($binary, 0, $bits))); } } diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xls/RC4.php b/PhpOffice/PhpSpreadsheet/Reader/Xls/RC4.php old mode 100755 new mode 100644 index 691aca7..b7c7c90 --- a/PhpOffice/PhpSpreadsheet/Reader/Xls/RC4.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Xls/RC4.php @@ -4,11 +4,13 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xls; class RC4 { - // Context - protected $s = []; + /** @var int[] */ + protected $s = []; // Context + /** @var int */ protected $i = 0; + /** @var int */ protected $j = 0; /** diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xls/Style/Border.php b/PhpOffice/PhpSpreadsheet/Reader/Xls/Style/Border.php old mode 100755 new mode 100644 index 91cbe36..d832eb0 --- a/PhpOffice/PhpSpreadsheet/Reader/Xls/Style/Border.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Xls/Style/Border.php @@ -6,7 +6,10 @@ use PhpOffice\PhpSpreadsheet\Style\Border as StyleBorder; class Border { - protected static $map = [ + /** + * @var array + */ + protected static $borderStyleMap = [ 0x00 => StyleBorder::BORDER_NONE, 0x01 => StyleBorder::BORDER_THIN, 0x02 => StyleBorder::BORDER_MEDIUM, @@ -23,18 +26,10 @@ class Border 0x0D => StyleBorder::BORDER_SLANTDASHDOT, ]; - /** - * Map border style - * OpenOffice documentation: 2.5.11. - * - * @param int $index - * - * @return string - */ - public static function lookup($index) + public static function lookup(int $index): string { - if (isset(self::$map[$index])) { - return self::$map[$index]; + if (isset(self::$borderStyleMap[$index])) { + return self::$borderStyleMap[$index]; } return StyleBorder::BORDER_NONE; diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xls/Style/CellAlignment.php b/PhpOffice/PhpSpreadsheet/Reader/Xls/Style/CellAlignment.php new file mode 100644 index 0000000..f03d47f --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Reader/Xls/Style/CellAlignment.php @@ -0,0 +1,50 @@ + + */ + protected static $horizontalAlignmentMap = [ + 0 => Alignment::HORIZONTAL_GENERAL, + 1 => Alignment::HORIZONTAL_LEFT, + 2 => Alignment::HORIZONTAL_CENTER, + 3 => Alignment::HORIZONTAL_RIGHT, + 4 => Alignment::HORIZONTAL_FILL, + 5 => Alignment::HORIZONTAL_JUSTIFY, + 6 => Alignment::HORIZONTAL_CENTER_CONTINUOUS, + ]; + + /** + * @var array + */ + protected static $verticalAlignmentMap = [ + 0 => Alignment::VERTICAL_TOP, + 1 => Alignment::VERTICAL_CENTER, + 2 => Alignment::VERTICAL_BOTTOM, + 3 => Alignment::VERTICAL_JUSTIFY, + ]; + + public static function horizontal(Alignment $alignment, int $horizontal): void + { + if (array_key_exists($horizontal, self::$horizontalAlignmentMap)) { + $alignment->setHorizontal(self::$horizontalAlignmentMap[$horizontal]); + } + } + + public static function vertical(Alignment $alignment, int $vertical): void + { + if (array_key_exists($vertical, self::$verticalAlignmentMap)) { + $alignment->setVertical(self::$verticalAlignmentMap[$vertical]); + } + } + + public static function wrap(Alignment $alignment, int $wrap): void + { + $alignment->setWrapText((bool) $wrap); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xls/Style/CellFont.php b/PhpOffice/PhpSpreadsheet/Reader/Xls/Style/CellFont.php new file mode 100644 index 0000000..e975be4 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Reader/Xls/Style/CellFont.php @@ -0,0 +1,39 @@ +setSuperscript(true); + + break; + case 0x0002: + $font->setSubscript(true); + + break; + } + } + + /** + * @var array + */ + protected static $underlineMap = [ + 0x01 => Font::UNDERLINE_SINGLE, + 0x02 => Font::UNDERLINE_DOUBLE, + 0x21 => Font::UNDERLINE_SINGLEACCOUNTING, + 0x22 => Font::UNDERLINE_DOUBLEACCOUNTING, + ]; + + public static function underline(Font $font, int $underline): void + { + if (array_key_exists($underline, self::$underlineMap)) { + $font->setUnderline(self::$underlineMap[$underline]); + } + } +} diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xls/Style/FillPattern.php b/PhpOffice/PhpSpreadsheet/Reader/Xls/Style/FillPattern.php old mode 100755 new mode 100644 index 7b85c08..32ab5c8 --- a/PhpOffice/PhpSpreadsheet/Reader/Xls/Style/FillPattern.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Xls/Style/FillPattern.php @@ -6,7 +6,10 @@ use PhpOffice\PhpSpreadsheet\Style\Fill; class FillPattern { - protected static $map = [ + /** + * @var array + */ + protected static $fillPatternMap = [ 0x00 => Fill::FILL_NONE, 0x01 => Fill::FILL_SOLID, 0x02 => Fill::FILL_PATTERN_MEDIUMGRAY, @@ -38,8 +41,8 @@ class FillPattern */ public static function lookup($index) { - if (isset(self::$map[$index])) { - return self::$map[$index]; + if (isset(self::$fillPatternMap[$index])) { + return self::$fillPatternMap[$index]; } return Fill::FILL_NONE; diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xlsx.php b/PhpOffice/PhpSpreadsheet/Reader/Xlsx.php old mode 100755 new mode 100644 index 1c1351f..0d769ea --- a/PhpOffice/PhpSpreadsheet/Reader/Xlsx.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Xlsx.php @@ -3,8 +3,9 @@ namespace PhpOffice\PhpSpreadsheet\Reader; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; +use PhpOffice\PhpSpreadsheet\Cell\DataType; use PhpOffice\PhpSpreadsheet\Cell\Hyperlink; -use PhpOffice\PhpSpreadsheet\NamedRange; +use PhpOffice\PhpSpreadsheet\DefinedName; use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\AutoFilter; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Chart; @@ -12,11 +13,15 @@ use PhpOffice\PhpSpreadsheet\Reader\Xlsx\ColumnAndRowAttributes; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\ConditionalStyles; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\DataValidations; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Hyperlinks; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\PageSetup; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Properties as PropertyReader; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\SheetViewOptions; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\SheetViews; use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Styles; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\TableReader; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Theme; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\WorkbookView; use PhpOffice\PhpSpreadsheet\ReferenceHelper; use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\Settings; @@ -26,20 +31,21 @@ use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Shared\Font; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Spreadsheet; -use PhpOffice\PhpSpreadsheet\Style\Border; -use PhpOffice\PhpSpreadsheet\Style\Borders; use PhpOffice\PhpSpreadsheet\Style\Color; +use PhpOffice\PhpSpreadsheet\Style\Font as StyleFont; use PhpOffice\PhpSpreadsheet\Style\NumberFormat; -use PhpOffice\PhpSpreadsheet\Style\Protection; use PhpOffice\PhpSpreadsheet\Style\Style; use PhpOffice\PhpSpreadsheet\Worksheet\HeaderFooterDrawing; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use SimpleXMLElement; +use Throwable; use XMLReader; use ZipArchive; class Xlsx extends BaseReader { + const INITIAL_FILE = '_rels/.rels'; + /** * ReferenceHelper instance. * @@ -48,11 +54,12 @@ class Xlsx extends BaseReader private $referenceHelper; /** - * Xlsx\Theme instance. - * - * @var Xlsx\Theme + * @var ZipArchive */ - private static $theme = null; + private $zip; + + /** @var Styles */ + private $styleReader; /** * Create a new Xlsx Reader instance. @@ -66,84 +73,130 @@ class Xlsx extends BaseReader /** * Can the current IReader read the file? - * - * @param string $pFilename - * - * @throws Exception - * - * @return bool */ - public function canRead($pFilename) + public function canRead(string $filename): bool { - File::assertFile($pFilename); + if (!File::testFileNoThrow($filename, self::INITIAL_FILE)) { + return false; + } - $xl = false; - // Load file - $zip = new ZipArchive(); - if ($zip->open($pFilename) === true) { - // check if it is an OOXML archive - $rels = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, '_rels/.rels') - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - if ($rels !== false) { - foreach ($rels->Relationship as $rel) { - switch ($rel['Type']) { - case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument': - if (basename($rel['Target']) == 'workbook.xml') { - $xl = true; - } + $result = false; + $this->zip = $zip = new ZipArchive(); + + if ($zip->open($filename) === true) { + [$workbookBasename] = $this->getWorkbookBaseName(); + $result = !empty($workbookBasename); - break; - } - } - } $zip->close(); } - return $xl; + return $result; } + /** + * @param mixed $value + */ + public static function testSimpleXml($value): SimpleXMLElement + { + return ($value instanceof SimpleXMLElement) ? $value : new SimpleXMLElement(''); + } + + public static function getAttributes(?SimpleXMLElement $value, string $ns = ''): SimpleXMLElement + { + return self::testSimpleXml($value === null ? $value : $value->attributes($ns)); + } + + // Phpstan thinks, correctly, that xpath can return false. + // Scrutinizer thinks it can't. + // Sigh. + private static function xpathNoFalse(SimpleXmlElement $sxml, string $path): array + { + return self::falseToArray($sxml->xpath($path)); + } + + /** + * @param mixed $value + */ + public static function falseToArray($value): array + { + return is_array($value) ? $value : []; + } + + private function loadZip(string $filename, string $ns = '', bool $replaceUnclosedBr = false): SimpleXMLElement + { + $contents = $this->getFromZipArchive($this->zip, $filename); + if ($replaceUnclosedBr) { + $contents = str_replace('
', '
', $contents); + } + $rels = simplexml_load_string( + $this->securityScanner->scan($contents), + 'SimpleXMLElement', + Settings::getLibXmlLoaderOptions(), + $ns + ); + + return self::testSimpleXml($rels); + } + + // This function is just to identify cases where I'm not sure + // why empty namespace is required. + private function loadZipNonamespace(string $filename, string $ns): SimpleXMLElement + { + $contents = $this->getFromZipArchive($this->zip, $filename); + $rels = simplexml_load_string( + $this->securityScanner->scan($contents), + 'SimpleXMLElement', + Settings::getLibXmlLoaderOptions(), + ($ns === '' ? $ns : '') + ); + + return self::testSimpleXml($rels); + } + + private const REL_TO_MAIN = [ + Namespaces::PURL_OFFICE_DOCUMENT => Namespaces::PURL_MAIN, + Namespaces::THUMBNAIL => '', + ]; + + private const REL_TO_DRAWING = [ + Namespaces::PURL_RELATIONSHIPS => Namespaces::PURL_DRAWING, + ]; + + private const REL_TO_CHART = [ + Namespaces::PURL_RELATIONSHIPS => Namespaces::PURL_CHART, + ]; + /** * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object. * - * @param string $pFilename - * - * @throws Exception + * @param string $filename * * @return array */ - public function listWorksheetNames($pFilename) + public function listWorksheetNames($filename) { - File::assertFile($pFilename); + File::assertFile($filename, self::INITIAL_FILE); $worksheetNames = []; - $zip = new ZipArchive(); - $zip->open($pFilename); + $this->zip = $zip = new ZipArchive(); + $zip->open($filename); // The files we're looking at here are small enough that simpleXML is more efficient than XMLReader - //~ http://schemas.openxmlformats.org/package/2006/relationships"); - $rels = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, '_rels/.rels')) - ); - foreach ($rels->Relationship as $rel) { - switch ($rel['Type']) { - case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument': - //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main" - $xmlWorkbook = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, "{$rel['Target']}")) - ); + $rels = $this->loadZip(self::INITIAL_FILE, Namespaces::RELATIONSHIPS); + foreach ($rels->Relationship as $relx) { + $rel = self::getAttributes($relx); + $relType = (string) $rel['Type']; + $mainNS = self::REL_TO_MAIN[$relType] ?? Namespaces::MAIN; + if ($mainNS !== '') { + $xmlWorkbook = $this->loadZip((string) $rel['Target'], $mainNS); - if ($xmlWorkbook->sheets) { - foreach ($xmlWorkbook->sheets->sheet as $eleSheet) { - // Check if sheet should be skipped - $worksheetNames[] = (string) $eleSheet['name']; - } + if ($xmlWorkbook->sheets) { + foreach ($xmlWorkbook->sheets->sheet as $eleSheet) { + // Check if sheet should be skipped + $worksheetNames[] = (string) self::getAttributes($eleSheet)['name']; } + } } } @@ -155,74 +208,61 @@ class Xlsx extends BaseReader /** * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns). * - * @param string $pFilename - * - * @throws Exception + * @param string $filename * * @return array */ - public function listWorksheetInfo($pFilename) + public function listWorksheetInfo($filename) { - File::assertFile($pFilename); + File::assertFile($filename, self::INITIAL_FILE); $worksheetInfo = []; - $zip = new ZipArchive(); - $zip->open($pFilename); + $this->zip = $zip = new ZipArchive(); + $zip->open($filename); - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $rels = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, '_rels/.rels')), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - foreach ($rels->Relationship as $rel) { - if ($rel['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument') { - $dir = dirname($rel['Target']); - - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $relsWorkbook = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, "$dir/_rels/" . basename($rel['Target']) . '.rels') - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - $relsWorkbook->registerXPathNamespace('rel', 'http://schemas.openxmlformats.org/package/2006/relationships'); + $rels = $this->loadZip(self::INITIAL_FILE, Namespaces::RELATIONSHIPS); + foreach ($rels->Relationship as $relx) { + $rel = self::getAttributes($relx); + $relType = (string) $rel['Type']; + $mainNS = self::REL_TO_MAIN[$relType] ?? Namespaces::MAIN; + if ($mainNS !== '') { + $relTarget = (string) $rel['Target']; + $dir = dirname($relTarget); + $namespace = dirname($relType); + $relsWorkbook = $this->loadZip("$dir/_rels/" . basename($relTarget) . '.rels', ''); $worksheets = []; - foreach ($relsWorkbook->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet') { + foreach ($relsWorkbook->Relationship as $elex) { + $ele = self::getAttributes($elex); + if ( + ((string) $ele['Type'] === "$namespace/worksheet") || + ((string) $ele['Type'] === "$namespace/chartsheet") + ) { $worksheets[(string) $ele['Id']] = $ele['Target']; } } - //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main" - $xmlWorkbook = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, "{$rel['Target']}") - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $xmlWorkbook = $this->loadZip($relTarget, $mainNS); if ($xmlWorkbook->sheets) { - $dir = dirname($rel['Target']); + $dir = dirname($relTarget); /** @var SimpleXMLElement $eleSheet */ foreach ($xmlWorkbook->sheets->sheet as $eleSheet) { $tmpInfo = [ - 'worksheetName' => (string) $eleSheet['name'], + 'worksheetName' => (string) self::getAttributes($eleSheet)['name'], 'lastColumnLetter' => 'A', 'lastColumnIndex' => 0, 'totalRows' => 0, 'totalColumns' => 0, ]; - $fileWorksheet = $worksheets[(string) self::getArrayItem($eleSheet->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id')]; + $fileWorksheet = (string) $worksheets[(string) self::getArrayItem(self::getAttributes($eleSheet, $namespace), 'id')]; + $fileWorksheetPath = strpos($fileWorksheet, '/') === 0 ? substr($fileWorksheet, 1) : "$dir/$fileWorksheet"; $xml = new XMLReader(); $xml->xml( $this->securityScanner->scanFile( - 'zip://' . File::realpath($pFilename) . '#' . "$dir/$fileWorksheet" + 'zip://' . File::realpath($filename) . '#' . $fileWorksheetPath ), null, Settings::getLibXmlLoaderOptions() @@ -231,13 +271,14 @@ class Xlsx extends BaseReader $currCells = 0; while ($xml->read()) { - if ($xml->name == 'row' && $xml->nodeType == XMLReader::ELEMENT) { + if ($xml->localName == 'row' && $xml->nodeType == XMLReader::ELEMENT && $xml->namespaceURI === $mainNS) { $row = $xml->getAttribute('r'); $tmpInfo['totalRows'] = $row; $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells); $currCells = 0; - } elseif ($xml->name == 'c' && $xml->nodeType == XMLReader::ELEMENT) { - ++$currCells; + } elseif ($xml->localName == 'c' && $xml->nodeType == XMLReader::ELEMENT && $xml->namespaceURI === $mainNS) { + $cell = $xml->getAttribute('r'); + $currCells = $cell ? max($currCells, Coordinate::indexesFromString($cell)[0]) : ($currCells + 1); } } $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells); @@ -257,7 +298,7 @@ class Xlsx extends BaseReader return $worksheetInfo; } - private static function castToBoolean($c) + private static function castToBoolean(SimpleXMLElement $c): bool { $value = isset($c->v) ? (string) $c->v : null; if ($value == '0') { @@ -269,34 +310,42 @@ class Xlsx extends BaseReader return (bool) $c->v; } - private static function castToError($c) + private static function castToError(?SimpleXMLElement $c): ?string { - return isset($c->v) ? (string) $c->v : null; + return isset($c, $c->v) ? (string) $c->v : null; } - private static function castToString($c) + private static function castToString(?SimpleXMLElement $c): ?string { - return isset($c->v) ? (string) $c->v : null; + return isset($c, $c->v) ? (string) $c->v : null; } - private function castToFormula($c, $r, &$cellDataType, &$value, &$calculatedValue, &$sharedFormulas, $castBaseType) + /** + * @param mixed $value + * @param mixed $calculatedValue + */ + private function castToFormula(?SimpleXMLElement $c, string $r, string &$cellDataType, &$value, &$calculatedValue, array &$sharedFormulas, string $castBaseType): void { + if ($c === null) { + return; + } + $attr = $c->f->attributes(); $cellDataType = 'f'; $value = "={$c->f}"; $calculatedValue = self::$castBaseType($c); // Shared formula? - if (isset($c->f['t']) && strtolower((string) $c->f['t']) == 'shared') { - $instance = (string) $c->f['si']; + if (isset($attr['t']) && strtolower((string) $attr['t']) == 'shared') { + $instance = (string) $attr['si']; - if (!isset($sharedFormulas[(string) $c->f['si']])) { + if (!isset($sharedFormulas[(string) $attr['si']])) { $sharedFormulas[$instance] = ['master' => $r, 'formula' => $value]; } else { - $master = Coordinate::coordinateFromString($sharedFormulas[$instance]['master']); - $current = Coordinate::coordinateFromString($r); + $master = Coordinate::indexesFromString($sharedFormulas[$instance]['master']); + $current = Coordinate::indexesFromString($r); $difference = [0, 0]; - $difference[0] = Coordinate::columnIndexFromString($current[0]) - Coordinate::columnIndexFromString($master[0]); + $difference[0] = $current[0] - $master[0]; $difference[1] = $current[1] - $master[1]; $value = $this->referenceHelper->updateFormulaReferences($sharedFormulas[$instance]['formula'], 'A1', $difference[0], $difference[1]); @@ -305,12 +354,9 @@ class Xlsx extends BaseReader } /** - * @param ZipArchive $archive * @param string $fileName - * - * @return string */ - private function getFromZipArchive(ZipArchive $archive, $fileName = '') + private function fileExistsInArchive(ZipArchive $archive, $fileName = ''): bool { // Root-relative paths if (strpos($fileName, '//') !== false) { @@ -321,156 +367,184 @@ class Xlsx extends BaseReader // Sadly, some 3rd party xlsx generators don't use consistent case for filenaming // so we need to load case-insensitively from the zip file + // Apache POI fixes + $contents = $archive->locateName($fileName, ZipArchive::FL_NOCASE); + if ($contents === false) { + $contents = $archive->locateName(substr($fileName, 1), ZipArchive::FL_NOCASE); + } + + return $contents !== false; + } + + /** + * @param string $fileName + * + * @return string + */ + private function getFromZipArchive(ZipArchive $archive, $fileName = '') + { + // Root-relative paths + if (strpos($fileName, '//') !== false) { + $fileName = substr($fileName, strpos($fileName, '//') + 1); + } + // Relative paths generated by dirname($filename) when $filename + // has no path (i.e.files in root of the zip archive) + $fileName = (string) preg_replace('/^\.\//', '', $fileName); + $fileName = File::realpath($fileName); + + // Sadly, some 3rd party xlsx generators don't use consistent case for filenaming + // so we need to load case-insensitively from the zip file + // Apache POI fixes $contents = $archive->getFromName($fileName, 0, ZipArchive::FL_NOCASE); if ($contents === false) { $contents = $archive->getFromName(substr($fileName, 1), 0, ZipArchive::FL_NOCASE); } - return $contents; + return ($contents === false) ? '' : $contents; } /** * Loads Spreadsheet from file. - * - * @param string $pFilename - * - * @throws Exception - * - * @return Spreadsheet */ - public function load($pFilename) + protected function loadSpreadsheetFromFile(string $filename): Spreadsheet { - File::assertFile($pFilename); + File::assertFile($filename, self::INITIAL_FILE); // Initialisations $excel = new Spreadsheet(); $excel->removeSheetByIndex(0); - if (!$this->readDataOnly) { - $excel->removeCellStyleXfByIndex(0); // remove the default style - $excel->removeCellXfByIndex(0); // remove the default style - } + $addingFirstCellStyleXf = true; + $addingFirstCellXf = true; + $unparsedLoadedData = []; - $zip = new ZipArchive(); - $zip->open($pFilename); + $this->zip = $zip = new ZipArchive(); + $zip->open($filename); // Read the theme first, because we need the colour scheme when reading the styles - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $wbRels = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, 'xl/_rels/workbook.xml.rels')), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - foreach ($wbRels->Relationship as $rel) { + [$workbookBasename, $xmlNamespaceBase] = $this->getWorkbookBaseName(); + $drawingNS = self::REL_TO_DRAWING[$xmlNamespaceBase] ?? Namespaces::DRAWINGML; + $chartNS = self::REL_TO_CHART[$xmlNamespaceBase] ?? Namespaces::CHART; + $wbRels = $this->loadZip("xl/_rels/{$workbookBasename}.rels", Namespaces::RELATIONSHIPS); + $theme = null; + $this->styleReader = new Styles(); + foreach ($wbRels->Relationship as $relx) { + $rel = self::getAttributes($relx); + $relTarget = (string) $rel['Target']; + if (substr($relTarget, 0, 4) === '/xl/') { + $relTarget = substr($relTarget, 4); + } switch ($rel['Type']) { - case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme': + case "$xmlNamespaceBase/theme": $themeOrderArray = ['lt1', 'dk1', 'lt2', 'dk2']; $themeOrderAdditional = count($themeOrderArray); - $xmlTheme = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, "xl/{$rel['Target']}")), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - if (is_object($xmlTheme)) { - $xmlThemeName = $xmlTheme->attributes(); - $xmlTheme = $xmlTheme->children('http://schemas.openxmlformats.org/drawingml/2006/main'); - $themeName = (string) $xmlThemeName['name']; + $xmlTheme = $this->loadZip("xl/{$relTarget}", $drawingNS); + $xmlThemeName = self::getAttributes($xmlTheme); + $xmlTheme = $xmlTheme->children($drawingNS); + $themeName = (string) $xmlThemeName['name']; - $colourScheme = $xmlTheme->themeElements->clrScheme->attributes(); - $colourSchemeName = (string) $colourScheme['name']; - $colourScheme = $xmlTheme->themeElements->clrScheme->children('http://schemas.openxmlformats.org/drawingml/2006/main'); + $colourScheme = self::getAttributes($xmlTheme->themeElements->clrScheme); + $colourSchemeName = (string) $colourScheme['name']; + $colourScheme = $xmlTheme->themeElements->clrScheme->children($drawingNS); - $themeColours = []; - foreach ($colourScheme as $k => $xmlColour) { - $themePos = array_search($k, $themeOrderArray); - if ($themePos === false) { - $themePos = $themeOrderAdditional++; - } - if (isset($xmlColour->sysClr)) { - $xmlColourData = $xmlColour->sysClr->attributes(); - $themeColours[$themePos] = $xmlColourData['lastClr']; - } elseif (isset($xmlColour->srgbClr)) { - $xmlColourData = $xmlColour->srgbClr->attributes(); - $themeColours[$themePos] = $xmlColourData['val']; - } + $themeColours = []; + foreach ($colourScheme as $k => $xmlColour) { + $themePos = array_search($k, $themeOrderArray); + if ($themePos === false) { + $themePos = $themeOrderAdditional++; + } + if (isset($xmlColour->sysClr)) { + $xmlColourData = self::getAttributes($xmlColour->sysClr); + $themeColours[$themePos] = (string) $xmlColourData['lastClr']; + } elseif (isset($xmlColour->srgbClr)) { + $xmlColourData = self::getAttributes($xmlColour->srgbClr); + $themeColours[$themePos] = (string) $xmlColourData['val']; } - self::$theme = new Xlsx\Theme($themeName, $colourSchemeName, $themeColours); } + $theme = new Theme($themeName, $colourSchemeName, $themeColours); + $this->styleReader->setTheme($theme); break; } } - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $rels = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, '_rels/.rels')), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $rels = $this->loadZip(self::INITIAL_FILE, Namespaces::RELATIONSHIPS); $propertyReader = new PropertyReader($this->securityScanner, $excel->getProperties()); - foreach ($rels->Relationship as $rel) { - switch ($rel['Type']) { - case 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties': - $propertyReader->readCoreProperties($this->getFromZipArchive($zip, "{$rel['Target']}")); + $chartDetails = []; + foreach ($rels->Relationship as $relx) { + $rel = self::getAttributes($relx); + $relTarget = (string) $rel['Target']; + $relType = (string) $rel['Type']; + $mainNS = self::REL_TO_MAIN[$relType] ?? Namespaces::MAIN; + switch ($relType) { + case Namespaces::CORE_PROPERTIES: + $propertyReader->readCoreProperties($this->getFromZipArchive($zip, $relTarget)); break; - case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties': - $propertyReader->readExtendedProperties($this->getFromZipArchive($zip, "{$rel['Target']}")); + case "$xmlNamespaceBase/extended-properties": + $propertyReader->readExtendedProperties($this->getFromZipArchive($zip, $relTarget)); break; - case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties': - $propertyReader->readCustomProperties($this->getFromZipArchive($zip, "{$rel['Target']}")); + case "$xmlNamespaceBase/custom-properties": + $propertyReader->readCustomProperties($this->getFromZipArchive($zip, $relTarget)); break; - //Ribbon - case 'http://schemas.microsoft.com/office/2006/relationships/ui/extensibility': - $customUI = $rel['Target']; - if ($customUI !== null) { + //Ribbon + case Namespaces::EXTENSIBILITY: + $customUI = $relTarget; + if ($customUI) { $this->readRibbon($excel, $customUI, $zip); } break; - case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument': - $dir = dirname($rel['Target']); - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $relsWorkbook = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/_rels/" . basename($rel['Target']) . '.rels')), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - $relsWorkbook->registerXPathNamespace('rel', 'http://schemas.openxmlformats.org/package/2006/relationships'); + case "$xmlNamespaceBase/officeDocument": + $dir = dirname($relTarget); + + // Do not specify namespace in next stmt - do it in Xpath + $relsWorkbook = $this->loadZip("$dir/_rels/" . basename($relTarget) . '.rels', ''); + $relsWorkbook->registerXPathNamespace('rel', Namespaces::RELATIONSHIPS); $sharedStrings = []; - $xpath = self::getArrayItem($relsWorkbook->xpath("rel:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings']")); - //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main" - $xmlStrings = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/$xpath[Target]")), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - if (isset($xmlStrings, $xmlStrings->si)) { - foreach ($xmlStrings->si as $val) { - if (isset($val->t)) { - $sharedStrings[] = StringHelper::controlCharacterOOXML2PHP((string) $val->t); - } elseif (isset($val->r)) { - $sharedStrings[] = $this->parseRichText($val); + $relType = "rel:Relationship[@Type='" + //. Namespaces::SHARED_STRINGS + . "$xmlNamespaceBase/sharedStrings" + . "']"; + $xpath = self::getArrayItem($relsWorkbook->xpath($relType)); + + if ($xpath) { + $xmlStrings = $this->loadZip("$dir/$xpath[Target]", $mainNS); + if (isset($xmlStrings->si)) { + foreach ($xmlStrings->si as $val) { + if (isset($val->t)) { + $sharedStrings[] = StringHelper::controlCharacterOOXML2PHP((string) $val->t); + } elseif (isset($val->r)) { + $sharedStrings[] = $this->parseRichText($val); + } } } } $worksheets = []; $macros = $customUI = null; - foreach ($relsWorkbook->Relationship as $ele) { + foreach ($relsWorkbook->Relationship as $elex) { + $ele = self::getAttributes($elex); switch ($ele['Type']) { - case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet': + case Namespaces::WORKSHEET: + case Namespaces::PURL_WORKSHEET: $worksheets[(string) $ele['Id']] = $ele['Target']; break; - // a vbaProject ? (: some macros) - case 'http://schemas.microsoft.com/office/2006/relationships/vbaProject': + case Namespaces::CHARTSHEET: + if ($this->includeCharts === true) { + $worksheets[(string) $ele['Id']] = $ele['Target']; + } + + break; + // a vbaProject ? (: some macros) + case Namespaces::VBA: $macros = $ele['Target']; break; @@ -490,26 +564,39 @@ class Xlsx extends BaseReader } } - $xpath = self::getArrayItem($relsWorkbook->xpath("rel:Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles']")); - //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main" - $xmlStyles = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/$xpath[Target]")), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $relType = "rel:Relationship[@Type='" + . "$xmlNamespaceBase/styles" + . "']"; + $xpath = self::getArrayItem(self::xpathNoFalse($relsWorkbook, $relType)); + + if ($xpath === null) { + $xmlStyles = self::testSimpleXml(null); + } else { + $xmlStyles = $this->loadZip("$dir/$xpath[Target]", $mainNS); + } + + $palette = self::extractPalette($xmlStyles); + $this->styleReader->setWorkbookPalette($palette); + $fills = self::extractStyles($xmlStyles, 'fills', 'fill'); + $fonts = self::extractStyles($xmlStyles, 'fonts', 'font'); + $borders = self::extractStyles($xmlStyles, 'borders', 'border'); + $xfTags = self::extractStyles($xmlStyles, 'cellXfs', 'xf'); + $cellXfTags = self::extractStyles($xmlStyles, 'cellStyleXfs', 'xf'); $styles = []; $cellStyles = []; $numFmts = null; - if ($xmlStyles && $xmlStyles->numFmts[0]) { + if (/*$xmlStyles && */ $xmlStyles->numFmts[0]) { $numFmts = $xmlStyles->numFmts[0]; } if (isset($numFmts) && ($numFmts !== null)) { - $numFmts->registerXPathNamespace('sml', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'); + $numFmts->registerXPathNamespace('sml', $mainNS); } - if (!$this->readDataOnly && $xmlStyles) { - foreach ($xmlStyles->cellXfs->xf as $xf) { - $numFmt = NumberFormat::FORMAT_GENERAL; + $this->styleReader->setNamespace($mainNS); + if (!$this->readDataOnly/* && $xmlStyles*/) { + foreach ($xfTags as $xfTag) { + $xf = self::getAttributes($xfTag); + $numFmt = null; if ($xf['numFmtId']) { if (isset($numFmts)) { @@ -523,34 +610,39 @@ class Xlsx extends BaseReader // We shouldn't override any of the built-in MS Excel values (values below id 164) // But there's a lot of naughty homebrew xlsx writers that do use "reserved" id values that aren't actually used // So we make allowance for them rather than lose formatting masks - if ((int) $xf['numFmtId'] < 164 && - NumberFormat::builtInFormatCode((int) $xf['numFmtId']) !== '') { + if ( + $numFmt === null && + (int) $xf['numFmtId'] < 164 && + NumberFormat::builtInFormatCode((int) $xf['numFmtId']) !== '' + ) { $numFmt = NumberFormat::builtInFormatCode((int) $xf['numFmtId']); } } - $quotePrefix = false; - if (isset($xf['quotePrefix'])) { - $quotePrefix = (bool) $xf['quotePrefix']; - } + $quotePrefix = (bool) ($xf['quotePrefix'] ?? false); $style = (object) [ - 'numFmt' => $numFmt, - 'font' => $xmlStyles->fonts->font[(int) ($xf['fontId'])], - 'fill' => $xmlStyles->fills->fill[(int) ($xf['fillId'])], - 'border' => $xmlStyles->borders->border[(int) ($xf['borderId'])], - 'alignment' => $xf->alignment, - 'protection' => $xf->protection, + 'numFmt' => $numFmt ?? NumberFormat::FORMAT_GENERAL, + 'font' => $fonts[(int) ($xf['fontId'])], + 'fill' => $fills[(int) ($xf['fillId'])], + 'border' => $borders[(int) ($xf['borderId'])], + 'alignment' => $xfTag->alignment, + 'protection' => $xfTag->protection, 'quotePrefix' => $quotePrefix, ]; $styles[] = $style; // add style to cellXf collection $objStyle = new Style(); - self::readStyle($objStyle, $style); + $this->styleReader->readStyle($objStyle, $style); + if ($addingFirstCellXf) { + $excel->removeCellXfByIndex(0); // remove the default style + $addingFirstCellXf = false; + } $excel->addCellXf($objStyle); } - foreach (isset($xmlStyles->cellStyleXfs->xf) ? $xmlStyles->cellStyleXfs->xf : [] as $xf) { + foreach ($cellXfTags as $xfTag) { + $xf = self::getAttributes($xfTag); $numFmt = NumberFormat::FORMAT_GENERAL; if ($numFmts && $xf['numFmtId']) { $tmpNumFmt = self::getArrayItem($numFmts->xpath("sml:numFmt[@numFmtId=$xf[numFmtId]]")); @@ -561,41 +653,44 @@ class Xlsx extends BaseReader } } + $quotePrefix = (bool) ($xf['quotePrefix'] ?? false); + $cellStyle = (object) [ 'numFmt' => $numFmt, - 'font' => $xmlStyles->fonts->font[(int) ($xf['fontId'])], - 'fill' => $xmlStyles->fills->fill[(int) ($xf['fillId'])], - 'border' => $xmlStyles->borders->border[(int) ($xf['borderId'])], - 'alignment' => $xf->alignment, - 'protection' => $xf->protection, + 'font' => $fonts[(int) ($xf['fontId'])], + 'fill' => $fills[((int) $xf['fillId'])], + 'border' => $borders[(int) ($xf['borderId'])], + 'alignment' => $xfTag->alignment, + 'protection' => $xfTag->protection, 'quotePrefix' => $quotePrefix, ]; $cellStyles[] = $cellStyle; // add style to cellStyleXf collection $objStyle = new Style(); - self::readStyle($objStyle, $cellStyle); + $this->styleReader->readStyle($objStyle, $cellStyle); + if ($addingFirstCellStyleXf) { + $excel->removeCellStyleXfByIndex(0); // remove the default style + $addingFirstCellStyleXf = false; + } $excel->addCellStyleXf($objStyle); } } + $this->styleReader->setStyleXml($xmlStyles); + $this->styleReader->setNamespace($mainNS); + $this->styleReader->setStyleBaseData($theme, $styles, $cellStyles); + $dxfs = $this->styleReader->dxfs($this->readDataOnly); + $styles = $this->styleReader->styles(); - $styleReader = new Styles($xmlStyles); - $styleReader->setStyleBaseData(self::$theme, $styles, $cellStyles); - $dxfs = $styleReader->dxfs($this->readDataOnly); - $styles = $styleReader->styles(); - - //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main" - $xmlWorkbook = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, "{$rel['Target']}")), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $xmlWorkbook = $this->loadZipNoNamespace($relTarget, $mainNS); + $xmlWorkbookNS = $this->loadZip($relTarget, $mainNS); // Set base date - if ($xmlWorkbook->workbookPr) { + if ($xmlWorkbookNS->workbookPr) { Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - if (isset($xmlWorkbook->workbookPr['date1904'])) { - if (self::boolean((string) $xmlWorkbook->workbookPr['date1904'])) { + $attrs1904 = self::getAttributes($xmlWorkbookNS->workbookPr); + if (isset($attrs1904['date1904'])) { + if (self::boolean((string) $attrs1904['date1904'])) { Date::setExcelCalendar(Date::CALENDAR_MAC_1904); } } @@ -611,19 +706,27 @@ class Xlsx extends BaseReader $charts = $chartDetails = []; - if ($xmlWorkbook->sheets) { + if ($xmlWorkbookNS->sheets) { /** @var SimpleXMLElement $eleSheet */ - foreach ($xmlWorkbook->sheets->sheet as $eleSheet) { + foreach ($xmlWorkbookNS->sheets->sheet as $eleSheet) { + $eleSheetAttr = self::getAttributes($eleSheet); ++$oldSheetId; // Check if sheet should be skipped - if (isset($this->loadSheetsOnly) && !in_array((string) $eleSheet['name'], $this->loadSheetsOnly)) { + if (is_array($this->loadSheetsOnly) && !in_array((string) $eleSheetAttr['name'], $this->loadSheetsOnly)) { ++$countSkippedSheets; $mapSheetId[$oldSheetId] = null; continue; } + $sheetReferenceId = (string) self::getArrayItem(self::getAttributes($eleSheet, $xmlNamespaceBase), 'id'); + if (isset($worksheets[$sheetReferenceId]) === false) { + ++$countSkippedSheets; + $mapSheetId[$oldSheetId] = null; + + continue; + } // Map old sheet id in original workbook to new sheet id. // They will differ if loadSheetsOnly() is being used $mapSheetId[$oldSheetId] = $oldSheetId - $countSkippedSheets; @@ -634,44 +737,50 @@ class Xlsx extends BaseReader // references in formula cells... during the load, all formulae should be correct, // and we're simply bringing the worksheet name in line with the formula, not the // reverse - $docSheet->setTitle((string) $eleSheet['name'], false, false); - $fileWorksheet = $worksheets[(string) self::getArrayItem($eleSheet->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id')]; - //~ http://schemas.openxmlformats.org/spreadsheetml/2006/main" - $xmlSheet = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, "$dir/$fileWorksheet")), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $docSheet->setTitle((string) $eleSheetAttr['name'], false, false); + + $fileWorksheet = (string) $worksheets[$sheetReferenceId]; + $xmlSheet = $this->loadZipNoNamespace("$dir/$fileWorksheet", $mainNS); + $xmlSheetNS = $this->loadZip("$dir/$fileWorksheet", $mainNS); $sharedFormulas = []; - if (isset($eleSheet['state']) && (string) $eleSheet['state'] != '') { - $docSheet->setSheetState((string) $eleSheet['state']); + if (isset($eleSheetAttr['state']) && (string) $eleSheetAttr['state'] != '') { + $docSheet->setSheetState((string) $eleSheetAttr['state']); } - - if ($xmlSheet) { - if (isset($xmlSheet->sheetViews, $xmlSheet->sheetViews->sheetView)) { - $sheetViews = new SheetViews($xmlSheet->sheetViews->sheetView, $docSheet); + if ($xmlSheetNS) { + $xmlSheetMain = $xmlSheetNS->children($mainNS); + // Setting Conditional Styles adjusts selected cells, so we need to execute this + // before reading the sheet view data to get the actual selected cells + if (!$this->readDataOnly && ($xmlSheet->conditionalFormatting)) { + (new ConditionalStyles($docSheet, $xmlSheet, $dxfs))->load(); + } + if (!$this->readDataOnly && $xmlSheet->extLst) { + (new ConditionalStyles($docSheet, $xmlSheet, $dxfs))->loadFromExt($this->styleReader); + } + if (isset($xmlSheetMain->sheetViews, $xmlSheetMain->sheetViews->sheetView)) { + $sheetViews = new SheetViews($xmlSheetMain->sheetViews->sheetView, $docSheet); $sheetViews->load(); } - $sheetViewOptions = new SheetViewOptions($docSheet, $xmlSheet); - $sheetViewOptions->load($this->getReadDataOnly()); + $sheetViewOptions = new SheetViewOptions($docSheet, $xmlSheetNS); + $sheetViewOptions->load($this->getReadDataOnly(), $this->styleReader); - (new ColumnAndRowAttributes($docSheet, $xmlSheet)) + (new ColumnAndRowAttributes($docSheet, $xmlSheetNS)) ->load($this->getReadFilter(), $this->getReadDataOnly()); } - if ($xmlSheet && $xmlSheet->sheetData && $xmlSheet->sheetData->row) { + if ($xmlSheetNS && $xmlSheetNS->sheetData && $xmlSheetNS->sheetData->row) { $cIndex = 1; // Cell Start from 1 - foreach ($xmlSheet->sheetData->row as $row) { + foreach ($xmlSheetNS->sheetData->row as $row) { $rowIndex = 1; foreach ($row->c as $c) { - $r = (string) $c['r']; + $cAttr = self::getAttributes($c); + $r = (string) $cAttr['r']; if ($r == '') { $r = Coordinate::stringFromColumnIndex($rowIndex) . $cIndex; } - $cellDataType = (string) $c['t']; + $cellDataType = (string) $cAttr['t']; $value = null; $calculatedValue = null; @@ -680,7 +789,10 @@ class Xlsx extends BaseReader $coordinates = Coordinate::coordinateFromString($r); if (!$this->getReadFilter()->readCell($coordinates[0], (int) $coordinates[1], $docSheet->getTitle())) { - $rowIndex += 1; + if (isset($cAttr->f)) { + $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToError'); + } + ++$rowIndex; continue; } @@ -702,7 +814,12 @@ class Xlsx extends BaseReader break; case 'b': if (!isset($c->f)) { - $value = self::castToBoolean($c); + if (isset($c->v)) { + $value = self::castToBoolean($c); + } else { + $value = null; + $cellDataType = DATATYPE::TYPE_NULL; + } } else { // Formula $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToBoolean'); @@ -736,6 +853,10 @@ class Xlsx extends BaseReader } else { // Formula $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToString'); + if (isset($c->f['t'])) { + $attributes = $c->f['t']; + $docSheet->getCell($r)->setFormulaAttributes(['t' => (string) $attributes]); + } } break; @@ -743,15 +864,6 @@ class Xlsx extends BaseReader // read empty cells or the cells are not empty if ($this->readEmptyCells || ($value !== null && $value !== '')) { - // Check for numeric values - if (is_numeric($value) && $cellDataType != 's') { - if ($value == (int) $value) { - $value = (int) $value; - } elseif ($value == (float) $value) { - $value = (float) $value; - } - } - // Rich text? if ($value instanceof RichText && $this->readDataOnly) { $value = $value->getPlainText(); @@ -760,7 +872,13 @@ class Xlsx extends BaseReader $cell = $docSheet->getCell($r); // Assign value if ($cellDataType != '') { - $cell->setValueExplicit($value, $cellDataType); + // it is possible, that datatype is numeric but with an empty string, which result in an error + if ($cellDataType === DataType::TYPE_NUMERIC && ($value === '' || $value === null)) { + $cellDataType = DataType::TYPE_NULL; + } + if ($cellDataType !== DataType::TYPE_NULL) { + $cell->setValueExplicit($value, $cellDataType); + } } else { $cell->setValue($value); } @@ -769,48 +887,42 @@ class Xlsx extends BaseReader } // Style information? - if ($c['s'] && !$this->readDataOnly) { + if ($cAttr['s'] && !$this->readDataOnly) { // no style index means 0, it seems - $cell->setXfIndex(isset($styles[(int) ($c['s'])]) ? - (int) ($c['s']) : 0); + $cell->setXfIndex(isset($styles[(int) ($cAttr['s'])]) ? + (int) ($cAttr['s']) : 0); } } - $rowIndex += 1; + ++$rowIndex; } - $cIndex += 1; + ++$cIndex; } } - if (!$this->readDataOnly && $xmlSheet && $xmlSheet->conditionalFormatting) { - (new ConditionalStyles($docSheet, $xmlSheet, $dxfs))->load(); - } - - $aKeys = ['sheet', 'objects', 'scenarios', 'formatCells', 'formatColumns', 'formatRows', 'insertColumns', 'insertRows', 'insertHyperlinks', 'deleteColumns', 'deleteRows', 'selectLockedCells', 'sort', 'autoFilter', 'pivotTables', 'selectUnlockedCells']; - if (!$this->readDataOnly && $xmlSheet && $xmlSheet->sheetProtection) { - foreach ($aKeys as $key) { + if (!$this->readDataOnly && $xmlSheetNS && $xmlSheetNS->sheetProtection) { + $protAttr = $xmlSheetNS->sheetProtection->attributes() ?? []; + foreach ($protAttr as $key => $value) { $method = 'set' . ucfirst($key); - $docSheet->getProtection()->$method(self::boolean((string) $xmlSheet->sheetProtection[$key])); + $docSheet->getProtection()->$method(self::boolean((string) $value)); } } - if (!$this->readDataOnly && $xmlSheet && $xmlSheet->sheetProtection) { - $docSheet->getProtection()->setPassword((string) $xmlSheet->sheetProtection['password'], true); - if ($xmlSheet->protectedRanges->protectedRange) { - foreach ($xmlSheet->protectedRanges->protectedRange as $protectedRange) { - $docSheet->protectCells((string) $protectedRange['sqref'], (string) $protectedRange['password'], true); - } - } + if ($xmlSheet) { + $this->readSheetProtection($docSheet, $xmlSheet); } - if ($xmlSheet && $xmlSheet->autoFilter && !$this->readDataOnly) { - (new AutoFilter($docSheet, $xmlSheet))->load(); + if ($this->readDataOnly === false) { + $this->readAutoFilter($xmlSheet, $docSheet); + $this->readTables($xmlSheet, $docSheet, $dir, $fileWorksheet, $zip); } - if ($xmlSheet && $xmlSheet->mergeCells && $xmlSheet->mergeCells->mergeCell && !$this->readDataOnly) { - foreach ($xmlSheet->mergeCells->mergeCell as $mergeCell) { - $mergeRef = (string) $mergeCell['ref']; + if ($xmlSheetNS && $xmlSheetNS->mergeCells && $xmlSheetNS->mergeCells->mergeCell && !$this->readDataOnly) { + foreach ($xmlSheetNS->mergeCells->mergeCell as $mergeCellx) { + /** @scrutinizer ignore-call */ + $mergeCell = $mergeCellx->attributes(); + $mergeRef = (string) ($mergeCell['ref'] ?? ''); if (strpos($mergeRef, ':') !== false) { - $docSheet->mergeCells((string) $mergeCell['ref']); + $docSheet->mergeCells($mergeRef, Worksheet::MERGE_CELL_CONTENT_HIDE); } } } @@ -819,15 +931,38 @@ class Xlsx extends BaseReader $unparsedLoadedData = (new PageSetup($docSheet, $xmlSheet))->load($unparsedLoadedData); } + if ($xmlSheet !== false && isset($xmlSheet->extLst, $xmlSheet->extLst->ext, $xmlSheet->extLst->ext['uri']) && ($xmlSheet->extLst->ext['uri'] == '{CCE6A557-97BC-4b89-ADB6-D9C93CAAB3DF}')) { + // Create dataValidations node if does not exists, maybe is better inside the foreach ? + if (!$xmlSheet->dataValidations) { + $xmlSheet->addChild('dataValidations'); + } + + foreach ($xmlSheet->extLst->ext->children(Namespaces::DATA_VALIDATIONS1)->dataValidations->dataValidation as $item) { + $item = self::testSimpleXml($item); + $node = self::testSimpleXml($xmlSheet->dataValidations)->addChild('dataValidation'); + foreach ($item->attributes() ?? [] as $attr) { + $node->addAttribute($attr->getName(), $attr); + } + $node->addAttribute('sqref', $item->children(Namespaces::DATA_VALIDATIONS2)->sqref); + if (isset($item->formula1)) { + $childNode = $node->addChild('formula1'); + if ($childNode !== null) { // null should never happen + $childNode[0] = (string) $item->formula1->children(Namespaces::DATA_VALIDATIONS2)->f; // @phpstan-ignore-line + } + } + } + } + if ($xmlSheet && $xmlSheet->dataValidations && !$this->readDataOnly) { (new DataValidations($docSheet, $xmlSheet))->load(); } // unparsed sheet AlternateContent if ($xmlSheet && !$this->readDataOnly) { - $mc = $xmlSheet->children('http://schemas.openxmlformats.org/markup-compatibility/2006'); + $mc = $xmlSheet->children(Namespaces::COMPATIBILITY); if ($mc->AlternateContent) { foreach ($mc->AlternateContent as $alternateContent) { + $alternateContent = self::testSimpleXml($alternateContent); $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['AlternateContents'][] = $alternateContent->asXML(); } } @@ -839,20 +974,13 @@ class Xlsx extends BaseReader // Locate hyperlink relations $relationsFileName = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'; if ($zip->locateName($relationsFileName)) { - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $relsWorksheet = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, $relationsFileName) - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $relsWorksheet = $this->loadZip($relationsFileName, Namespaces::RELATIONSHIPS); $hyperlinkReader->readHyperlinks($relsWorksheet); } // Loop through hyperlinks - if ($xmlSheet && $xmlSheet->hyperlinks) { - $hyperlinkReader->setHyperlinks($xmlSheet->hyperlinks); + if ($xmlSheetNS && $xmlSheetNS->children($mainNS)->hyperlinks) { + $hyperlinkReader->setHyperlinks($xmlSheetNS->children($mainNS)->hyperlinks); } } @@ -861,20 +989,15 @@ class Xlsx extends BaseReader $vmlComments = []; if (!$this->readDataOnly) { // Locate comment relations - if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $relsWorksheet = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels') - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - foreach ($relsWorksheet->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments') { + $commentRelations = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'; + if ($zip->locateName($commentRelations)) { + $relsWorksheet = $this->loadZip($commentRelations, Namespaces::RELATIONSHIPS); + foreach ($relsWorksheet->Relationship as $elex) { + $ele = self::getAttributes($elex); + if ($ele['Type'] == Namespaces::COMMENTS) { $comments[(string) $ele['Id']] = (string) $ele['Target']; } - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing') { + if ($ele['Type'] == Namespaces::VML) { $vmlComments[(string) $ele['Id']] = (string) $ele['Target']; } } @@ -884,31 +1007,32 @@ class Xlsx extends BaseReader foreach ($comments as $relName => $relPath) { // Load comments file $relPath = File::realpath(dirname("$dir/$fileWorksheet") . '/' . $relPath); - $commentsFile = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, $relPath)), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + // okay to ignore namespace - using xpath + $commentsFile = $this->loadZip($relPath, ''); // Utility variables $authors = []; - - // Loop through authors - foreach ($commentsFile->authors->author as $author) { + $commentsFile->registerXpathNamespace('com', $mainNS); + $authorPath = self::xpathNoFalse($commentsFile, 'com:authors/com:author'); + foreach ($authorPath as $author) { $authors[] = (string) $author; } // Loop through contents - foreach ($commentsFile->commentList->comment as $comment) { - if (!empty($comment['authorId'])) { - $docSheet->getComment((string) $comment['ref'])->setAuthor($authors[(string) $comment['authorId']]); + $contentPath = self::xpathNoFalse($commentsFile, 'com:commentList/com:comment'); + foreach ($contentPath as $comment) { + $commentx = $comment->attributes(); + $commentModel = $docSheet->getComment((string) $commentx['ref']); + if (isset($commentx['authorId'])) { + $commentModel->setAuthor($authors[(int) $commentx['authorId']]); } - $docSheet->getComment((string) $comment['ref'])->setText($this->parseRichText($comment->text)); + $commentModel->setText($this->parseRichText($comment->children($mainNS)->text)); } } // later we will remove from it real vmlComments $unparsedVmlDrawings = $vmlComments; + $vmlDrawingContents = []; // Loop through VML comments foreach ($vmlComments as $relName => $relPath) { @@ -916,26 +1040,39 @@ class Xlsx extends BaseReader $relPath = File::realpath(dirname("$dir/$fileWorksheet") . '/' . $relPath); try { - $vmlCommentsFile = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, $relPath)), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - $vmlCommentsFile->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml'); - } catch (\Throwable $ex) { + // no namespace okay - processed with Xpath + $vmlCommentsFile = $this->loadZip($relPath, '', true); + $vmlCommentsFile->registerXPathNamespace('v', Namespaces::URN_VML); + } catch (Throwable $ex) { //Ignore unparsable vmlDrawings. Later they will be moved from $unparsedVmlDrawings to $unparsedLoadedData continue; } - $shapes = $vmlCommentsFile->xpath('//v:shape'); + // Locate VML drawings image relations + $drowingImages = []; + $VMLDrawingsRelations = dirname($relPath) . '/_rels/' . basename($relPath) . '.rels'; + $vmlDrawingContents[$relName] = $this->securityScanner->scan($this->getFromZipArchive($zip, $relPath)); + if ($zip->locateName($VMLDrawingsRelations)) { + $relsVMLDrawing = $this->loadZip($VMLDrawingsRelations, Namespaces::RELATIONSHIPS); + foreach ($relsVMLDrawing->Relationship as $elex) { + $ele = self::getAttributes($elex); + if ($ele['Type'] == Namespaces::IMAGE) { + $drowingImages[(string) $ele['Id']] = (string) $ele['Target']; + } + } + } + + $shapes = self::xpathNoFalse($vmlCommentsFile, '//v:shape'); foreach ($shapes as $shape) { - $shape->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml'); + $shape->registerXPathNamespace('v', Namespaces::URN_VML); if (isset($shape['style'])) { $style = (string) $shape['style']; $fillColor = strtoupper(substr((string) $shape['fillcolor'], 1)); $column = null; $row = null; + $fillImageRelId = null; + $fillImageTitle = ''; $clientData = $shape->xpath('.//x:ClientData'); if (is_array($clientData) && !empty($clientData)) { @@ -954,10 +1091,39 @@ class Xlsx extends BaseReader } } + $fillImageRelNode = $shape->xpath('.//v:fill/@o:relid'); + if (is_array($fillImageRelNode) && !empty($fillImageRelNode)) { + $fillImageRelNode = $fillImageRelNode[0]; + + if (isset($fillImageRelNode['relid'])) { + $fillImageRelId = (string) $fillImageRelNode['relid']; + } + } + + $fillImageTitleNode = $shape->xpath('.//v:fill/@o:title'); + if (is_array($fillImageTitleNode) && !empty($fillImageTitleNode)) { + $fillImageTitleNode = $fillImageTitleNode[0]; + + if (isset($fillImageTitleNode['title'])) { + $fillImageTitle = (string) $fillImageTitleNode['title']; + } + } + if (($column !== null) && ($row !== null)) { // Set comment properties - $comment = $docSheet->getCommentByColumnAndRow($column + 1, $row + 1); + $comment = $docSheet->getComment([$column + 1, $row + 1]); $comment->getFillColor()->setRGB($fillColor); + if (isset($drowingImages[$fillImageRelId])) { + $objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing(); + $objDrawing->setName($fillImageTitle); + $imagePath = str_replace('../', 'xl/', $drowingImages[$fillImageRelId]); + $objDrawing->setPath( + 'zip://' . File::realpath($filename) . '#' . $imagePath, + true, + $zip + ); + $comment->setBackgroundImage($objDrawing); + } // Parse style $styleArray = explode(';', str_replace(' ', '', $style)); @@ -1001,79 +1167,72 @@ class Xlsx extends BaseReader } // Header/footer images - if ($xmlSheet && $xmlSheet->legacyDrawingHF && !$this->readDataOnly) { + if ($xmlSheetNS && $xmlSheetNS->legacyDrawingHF) { + $vmlHfRid = ''; + $vmlHfRidAttr = $xmlSheetNS->legacyDrawingHF->attributes(Namespaces::SCHEMA_OFFICE_DOCUMENT); + if ($vmlHfRidAttr !== null && isset($vmlHfRidAttr['id'])) { + $vmlHfRid = (string) $vmlHfRidAttr['id'][0]; + } if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $relsWorksheet = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels') - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $relsWorksheet = $this->loadZipNoNamespace(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels', Namespaces::RELATIONSHIPS); $vmlRelationship = ''; foreach ($relsWorksheet->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing') { + if ((string) $ele['Type'] == Namespaces::VML && (string) $ele['Id'] === $vmlHfRid) { $vmlRelationship = self::dirAdd("$dir/$fileWorksheet", $ele['Target']); + + break; } } if ($vmlRelationship != '') { // Fetch linked images - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $relsVML = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, dirname($vmlRelationship) . '/_rels/' . basename($vmlRelationship) . '.rels') - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $relsVML = $this->loadZipNoNamespace(dirname($vmlRelationship) . '/_rels/' . basename($vmlRelationship) . '.rels', Namespaces::RELATIONSHIPS); $drawings = []; - foreach ($relsVML->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image') { - $drawings[(string) $ele['Id']] = self::dirAdd($vmlRelationship, $ele['Target']); + if (isset($relsVML->Relationship)) { + foreach ($relsVML->Relationship as $ele) { + if ($ele['Type'] == Namespaces::IMAGE) { + $drawings[(string) $ele['Id']] = self::dirAdd($vmlRelationship, $ele['Target']); + } } } - // Fetch VML document - $vmlDrawing = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, $vmlRelationship)), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - $vmlDrawing->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml'); + $vmlDrawing = $this->loadZipNoNamespace($vmlRelationship, ''); + $vmlDrawing->registerXPathNamespace('v', Namespaces::URN_VML); $hfImages = []; - $shapes = $vmlDrawing->xpath('//v:shape'); + $shapes = self::xpathNoFalse($vmlDrawing, '//v:shape'); foreach ($shapes as $idx => $shape) { - $shape->registerXPathNamespace('v', 'urn:schemas-microsoft-com:vml'); + $shape->registerXPathNamespace('v', Namespaces::URN_VML); $imageData = $shape->xpath('//v:imagedata'); - if (!$imageData) { + if (empty($imageData)) { continue; } $imageData = $imageData[$idx]; - $imageData = $imageData->attributes('urn:schemas-microsoft-com:office:office'); + $imageData = self::getAttributes($imageData, Namespaces::URN_MSOFFICE); $style = self::toCSSArray((string) $shape['style']); - $hfImages[(string) $shape['id']] = new HeaderFooterDrawing(); - if (isset($imageData['title'])) { - $hfImages[(string) $shape['id']]->setName((string) $imageData['title']); - } + if (array_key_exists((string) $imageData['relid'], $drawings)) { + $shapeId = (string) $shape['id']; + $hfImages[$shapeId] = new HeaderFooterDrawing(); + if (isset($imageData['title'])) { + $hfImages[$shapeId]->setName((string) $imageData['title']); + } - $hfImages[(string) $shape['id']]->setPath('zip://' . File::realpath($pFilename) . '#' . $drawings[(string) $imageData['relid']], false); - $hfImages[(string) $shape['id']]->setResizeProportional(false); - $hfImages[(string) $shape['id']]->setWidth($style['width']); - $hfImages[(string) $shape['id']]->setHeight($style['height']); - if (isset($style['margin-left'])) { - $hfImages[(string) $shape['id']]->setOffsetX($style['margin-left']); + $hfImages[$shapeId]->setPath('zip://' . File::realpath($filename) . '#' . $drawings[(string) $imageData['relid']], false); + $hfImages[$shapeId]->setResizeProportional(false); + $hfImages[$shapeId]->setWidth($style['width']); + $hfImages[$shapeId]->setHeight($style['height']); + if (isset($style['margin-left'])) { + $hfImages[$shapeId]->setOffsetX($style['margin-left']); + } + $hfImages[$shapeId]->setOffsetY($style['margin-top']); + $hfImages[$shapeId]->setResizeProportional(true); } - $hfImages[(string) $shape['id']]->setOffsetY($style['margin-top']); - $hfImages[(string) $shape['id']]->setResizeProportional(true); } $docSheet->getHeaderFooter()->setImages($hfImages); @@ -1083,46 +1242,64 @@ class Xlsx extends BaseReader } // TODO: Autoshapes from twoCellAnchors! - if ($zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $relsWorksheet = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels') - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $drawingFilename = dirname("$dir/$fileWorksheet") + . '/_rels/' + . basename($fileWorksheet) + . '.rels'; + if (substr($drawingFilename, 0, 7) === 'xl//xl/') { + $drawingFilename = substr($drawingFilename, 4); + } + if (substr($drawingFilename, 0, 8) === '/xl//xl/') { + $drawingFilename = substr($drawingFilename, 5); + } + if ($zip->locateName($drawingFilename)) { + $relsWorksheet = $this->loadZipNoNamespace($drawingFilename, Namespaces::RELATIONSHIPS); $drawings = []; foreach ($relsWorksheet->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing') { - $drawings[(string) $ele['Id']] = self::dirAdd("$dir/$fileWorksheet", $ele['Target']); + if ((string) $ele['Type'] === "$xmlNamespaceBase/drawing") { + $eleTarget = (string) $ele['Target']; + if (substr($eleTarget, 0, 4) === '/xl/') { + $drawings[(string) $ele['Id']] = substr($eleTarget, 1); + } else { + $drawings[(string) $ele['Id']] = self::dirAdd("$dir/$fileWorksheet", $ele['Target']); + } } } - if ($xmlSheet->drawing && !$this->readDataOnly) { + + if ($xmlSheetNS->drawing && !$this->readDataOnly) { $unparsedDrawings = []; - foreach ($xmlSheet->drawing as $drawing) { - $drawingRelId = (string) self::getArrayItem($drawing->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), 'id'); + $fileDrawing = null; + foreach ($xmlSheetNS->drawing as $drawing) { + $drawingRelId = (string) self::getArrayItem(self::getAttributes($drawing, $xmlNamespaceBase), 'id'); $fileDrawing = $drawings[$drawingRelId]; - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $relsDrawing = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, dirname($fileDrawing) . '/_rels/' . basename($fileDrawing) . '.rels') - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $drawingFilename = dirname($fileDrawing) . '/_rels/' . basename($fileDrawing) . '.rels'; + $relsDrawing = $this->loadZipNoNamespace($drawingFilename, $xmlNamespaceBase); + $images = []; $hyperlinks = []; if ($relsDrawing && $relsDrawing->Relationship) { foreach ($relsDrawing->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink') { + $eleType = (string) $ele['Type']; + if ($eleType === Namespaces::HYPERLINK) { $hyperlinks[(string) $ele['Id']] = (string) $ele['Target']; } - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image') { - $images[(string) $ele['Id']] = self::dirAdd($fileDrawing, $ele['Target']); - } elseif ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart') { + if ($eleType === "$xmlNamespaceBase/image") { + $eleTarget = (string) $ele['Target']; + if (substr($eleTarget, 0, 4) === '/xl/') { + $eleTarget = substr($eleTarget, 1); + $images[(string) $ele['Id']] = $eleTarget; + } else { + $images[(string) $ele['Id']] = self::dirAdd($fileDrawing, $eleTarget); + } + } elseif ($eleType === "$xmlNamespaceBase/chart") { if ($this->includeCharts) { - $charts[self::dirAdd($fileDrawing, $ele['Target'])] = [ + $eleTarget = (string) $ele['Target']; + if (substr($eleTarget, 0, 4) === '/xl/') { + $index = substr($eleTarget, 1); + } else { + $index = self::dirAdd($fileDrawing, $eleTarget); + } + $charts[$index] = [ 'id' => (string) $ele['Id'], 'sheet' => $docSheet->getTitle(), ]; @@ -1130,124 +1307,172 @@ class Xlsx extends BaseReader } } } - $xmlDrawing = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, $fileDrawing)), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - $xmlDrawingChildren = $xmlDrawing->children('http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing'); + + $xmlDrawing = $this->loadZipNoNamespace($fileDrawing, ''); + $xmlDrawingChildren = $xmlDrawing->children(Namespaces::SPREADSHEET_DRAWING); if ($xmlDrawingChildren->oneCellAnchor) { foreach ($xmlDrawingChildren->oneCellAnchor as $oneCellAnchor) { + $oneCellAnchor = self::testSimpleXml($oneCellAnchor); if ($oneCellAnchor->pic->blipFill) { /** @var SimpleXMLElement $blip */ - $blip = $oneCellAnchor->pic->blipFill->children('http://schemas.openxmlformats.org/drawingml/2006/main')->blip; + $blip = $oneCellAnchor->pic->blipFill->children(Namespaces::DRAWINGML)->blip; /** @var SimpleXMLElement $xfrm */ - $xfrm = $oneCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->xfrm; + $xfrm = $oneCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->xfrm; /** @var SimpleXMLElement $outerShdw */ - $outerShdw = $oneCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->effectLst->outerShdw; - /** @var \SimpleXMLElement $hlinkClick */ - $hlinkClick = $oneCellAnchor->pic->nvPicPr->cNvPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->hlinkClick; + $outerShdw = $oneCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->effectLst->outerShdw; $objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing(); - $objDrawing->setName((string) self::getArrayItem($oneCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'name')); - $objDrawing->setDescription((string) self::getArrayItem($oneCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'descr')); - $objDrawing->setPath( - 'zip://' . File::realpath($pFilename) . '#' . - $images[(string) self::getArrayItem( - $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), - 'embed' - )], - false + $objDrawing->setName((string) self::getArrayItem(self::getAttributes($oneCellAnchor->pic->nvPicPr->cNvPr), 'name')); + $objDrawing->setDescription((string) self::getArrayItem(self::getAttributes($oneCellAnchor->pic->nvPicPr->cNvPr), 'descr')); + $embedImageKey = (string) self::getArrayItem( + self::getAttributes($blip, $xmlNamespaceBase), + 'embed' ); - $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((string) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1)); - $objDrawing->setOffsetX(Drawing::EMUToPixels($oneCellAnchor->from->colOff)); + if (isset($images[$embedImageKey])) { + $objDrawing->setPath( + 'zip://' . File::realpath($filename) . '#' . + $images[$embedImageKey], + false + ); + } else { + $linkImageKey = (string) self::getArrayItem( + $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), + 'link' + ); + if (isset($images[$linkImageKey])) { + $url = str_replace('xl/drawings/', '', $images[$linkImageKey]); + $objDrawing->setPath($url); + } + } + $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((int) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1)); + + $objDrawing->setOffsetX((int) Drawing::EMUToPixels($oneCellAnchor->from->colOff)); $objDrawing->setOffsetY(Drawing::EMUToPixels($oneCellAnchor->from->rowOff)); $objDrawing->setResizeProportional(false); - $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cx'))); - $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cy'))); + $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($oneCellAnchor->ext), 'cx'))); + $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($oneCellAnchor->ext), 'cy'))); if ($xfrm) { - $objDrawing->setRotation(Drawing::angleToDegrees(self::getArrayItem($xfrm->attributes(), 'rot'))); + $objDrawing->setRotation((int) Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($xfrm), 'rot'))); } if ($outerShdw) { $shadow = $objDrawing->getShadow(); $shadow->setVisible(true); - $shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'blurRad'))); - $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'dist'))); - $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem($outerShdw->attributes(), 'dir'))); - $shadow->setAlignment((string) self::getArrayItem($outerShdw->attributes(), 'algn')); - $clr = isset($outerShdw->srgbClr) ? $outerShdw->srgbClr : $outerShdw->prstClr; - $shadow->getColor()->setRGB(self::getArrayItem($clr->attributes(), 'val')); - $shadow->setAlpha(self::getArrayItem($clr->alpha->attributes(), 'val') / 1000); + $shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'blurRad'))); + $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'dist'))); + $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($outerShdw), 'dir'))); + $shadow->setAlignment((string) self::getArrayItem(self::getAttributes($outerShdw), 'algn')); + $clr = $outerShdw->srgbClr ?? $outerShdw->prstClr; + $shadow->getColor()->setRGB(self::getArrayItem(self::getAttributes($clr), 'val')); + $shadow->setAlpha(self::getArrayItem(self::getAttributes($clr->alpha), 'val') / 1000); } $this->readHyperLinkDrawing($objDrawing, $oneCellAnchor, $hyperlinks); $objDrawing->setWorksheet($docSheet); - } else { - // ? Can charts be positioned with a oneCellAnchor ? - $coordinates = Coordinate::stringFromColumnIndex(((string) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1); + } elseif ($this->includeCharts && $oneCellAnchor->graphicFrame) { + // Exported XLSX from Google Sheets positions charts with a oneCellAnchor + $coordinates = Coordinate::stringFromColumnIndex(((int) $oneCellAnchor->from->col) + 1) . ($oneCellAnchor->from->row + 1); $offsetX = Drawing::EMUToPixels($oneCellAnchor->from->colOff); $offsetY = Drawing::EMUToPixels($oneCellAnchor->from->rowOff); - $width = Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cx')); - $height = Drawing::EMUToPixels(self::getArrayItem($oneCellAnchor->ext->attributes(), 'cy')); + $width = Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($oneCellAnchor->ext), 'cx')); + $height = Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($oneCellAnchor->ext), 'cy')); + + $graphic = $oneCellAnchor->graphicFrame->children(Namespaces::DRAWINGML)->graphic; + /** @var SimpleXMLElement $chartRef */ + $chartRef = $graphic->graphicData->children(Namespaces::CHART)->chart; + $thisChart = (string) self::getAttributes($chartRef, $xmlNamespaceBase); + + $chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [ + 'fromCoordinate' => $coordinates, + 'fromOffsetX' => $offsetX, + 'fromOffsetY' => $offsetY, + 'width' => $width, + 'height' => $height, + 'worksheetTitle' => $docSheet->getTitle(), + 'oneCellAnchor' => true, + ]; } } } if ($xmlDrawingChildren->twoCellAnchor) { foreach ($xmlDrawingChildren->twoCellAnchor as $twoCellAnchor) { + $twoCellAnchor = self::testSimpleXml($twoCellAnchor); if ($twoCellAnchor->pic->blipFill) { - $blip = $twoCellAnchor->pic->blipFill->children('http://schemas.openxmlformats.org/drawingml/2006/main')->blip; - $xfrm = $twoCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->xfrm; - $outerShdw = $twoCellAnchor->pic->spPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->effectLst->outerShdw; - $hlinkClick = $twoCellAnchor->pic->nvPicPr->cNvPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->hlinkClick; + $blip = $twoCellAnchor->pic->blipFill->children(Namespaces::DRAWINGML)->blip; + $xfrm = $twoCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->xfrm; + $outerShdw = $twoCellAnchor->pic->spPr->children(Namespaces::DRAWINGML)->effectLst->outerShdw; $objDrawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing(); - $objDrawing->setName((string) self::getArrayItem($twoCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'name')); - $objDrawing->setDescription((string) self::getArrayItem($twoCellAnchor->pic->nvPicPr->cNvPr->attributes(), 'descr')); - $objDrawing->setPath( - 'zip://' . File::realpath($pFilename) . '#' . - $images[(string) self::getArrayItem( - $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), - 'embed' - )], - false + /** @scrutinizer ignore-call */ + $editAs = $twoCellAnchor->attributes(); + if (isset($editAs, $editAs['editAs'])) { + $objDrawing->setEditAs($editAs['editAs']); + } + $objDrawing->setName((string) self::getArrayItem(self::getAttributes($twoCellAnchor->pic->nvPicPr->cNvPr), 'name')); + $objDrawing->setDescription((string) self::getArrayItem(self::getAttributes($twoCellAnchor->pic->nvPicPr->cNvPr), 'descr')); + $embedImageKey = (string) self::getArrayItem( + self::getAttributes($blip, $xmlNamespaceBase), + 'embed' ); - $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((string) $twoCellAnchor->from->col) + 1) . ($twoCellAnchor->from->row + 1)); + if (isset($images[$embedImageKey])) { + $objDrawing->setPath( + 'zip://' . File::realpath($filename) . '#' . + $images[$embedImageKey], + false + ); + } else { + $linkImageKey = (string) self::getArrayItem( + $blip->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'), + 'link' + ); + if (isset($images[$linkImageKey])) { + $url = str_replace('xl/drawings/', '', $images[$linkImageKey]); + $objDrawing->setPath($url); + } + } + $objDrawing->setCoordinates(Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->from->col) + 1) . ($twoCellAnchor->from->row + 1)); + $objDrawing->setOffsetX(Drawing::EMUToPixels($twoCellAnchor->from->colOff)); $objDrawing->setOffsetY(Drawing::EMUToPixels($twoCellAnchor->from->rowOff)); + + $objDrawing->setCoordinates2(Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->to->col) + 1) . ($twoCellAnchor->to->row + 1)); + + $objDrawing->setOffsetX2(Drawing::EMUToPixels($twoCellAnchor->to->colOff)); + $objDrawing->setOffsetY2(Drawing::EMUToPixels($twoCellAnchor->to->rowOff)); + $objDrawing->setResizeProportional(false); if ($xfrm) { - $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem($xfrm->ext->attributes(), 'cx'))); - $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem($xfrm->ext->attributes(), 'cy'))); - $objDrawing->setRotation(Drawing::angleToDegrees(self::getArrayItem($xfrm->attributes(), 'rot'))); + $objDrawing->setWidth(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($xfrm->ext), 'cx'))); + $objDrawing->setHeight(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($xfrm->ext), 'cy'))); + $objDrawing->setRotation(Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($xfrm), 'rot'))); } if ($outerShdw) { $shadow = $objDrawing->getShadow(); $shadow->setVisible(true); - $shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'blurRad'))); - $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem($outerShdw->attributes(), 'dist'))); - $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem($outerShdw->attributes(), 'dir'))); - $shadow->setAlignment((string) self::getArrayItem($outerShdw->attributes(), 'algn')); - $clr = isset($outerShdw->srgbClr) ? $outerShdw->srgbClr : $outerShdw->prstClr; - $shadow->getColor()->setRGB(self::getArrayItem($clr->attributes(), 'val')); - $shadow->setAlpha(self::getArrayItem($clr->alpha->attributes(), 'val') / 1000); + $shadow->setBlurRadius(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'blurRad'))); + $shadow->setDistance(Drawing::EMUToPixels(self::getArrayItem(self::getAttributes($outerShdw), 'dist'))); + $shadow->setDirection(Drawing::angleToDegrees(self::getArrayItem(self::getAttributes($outerShdw), 'dir'))); + $shadow->setAlignment((string) self::getArrayItem(self::getAttributes($outerShdw), 'algn')); + $clr = $outerShdw->srgbClr ?? $outerShdw->prstClr; + $shadow->getColor()->setRGB(self::getArrayItem(self::getAttributes($clr), 'val')); + $shadow->setAlpha(self::getArrayItem(self::getAttributes($clr->alpha), 'val') / 1000); } $this->readHyperLinkDrawing($objDrawing, $twoCellAnchor, $hyperlinks); $objDrawing->setWorksheet($docSheet); } elseif (($this->includeCharts) && ($twoCellAnchor->graphicFrame)) { - $fromCoordinate = Coordinate::stringFromColumnIndex(((string) $twoCellAnchor->from->col) + 1) . ($twoCellAnchor->from->row + 1); + $fromCoordinate = Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->from->col) + 1) . ($twoCellAnchor->from->row + 1); $fromOffsetX = Drawing::EMUToPixels($twoCellAnchor->from->colOff); $fromOffsetY = Drawing::EMUToPixels($twoCellAnchor->from->rowOff); - $toCoordinate = Coordinate::stringFromColumnIndex(((string) $twoCellAnchor->to->col) + 1) . ($twoCellAnchor->to->row + 1); + $toCoordinate = Coordinate::stringFromColumnIndex(((int) $twoCellAnchor->to->col) + 1) . ($twoCellAnchor->to->row + 1); $toOffsetX = Drawing::EMUToPixels($twoCellAnchor->to->colOff); $toOffsetY = Drawing::EMUToPixels($twoCellAnchor->to->rowOff); - $graphic = $twoCellAnchor->graphicFrame->children('http://schemas.openxmlformats.org/drawingml/2006/main')->graphic; + $graphic = $twoCellAnchor->graphicFrame->children(Namespaces::DRAWINGML)->graphic; /** @var SimpleXMLElement $chartRef */ - $chartRef = $graphic->graphicData->children('http://schemas.openxmlformats.org/drawingml/2006/chart')->chart; - $thisChart = (string) $chartRef->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $chartRef = $graphic->graphicData->children(Namespaces::CHART)->chart; + $thisChart = (string) self::getAttributes($chartRef, $xmlNamespaceBase); $chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [ 'fromCoordinate' => $fromCoordinate, @@ -1261,7 +1486,28 @@ class Xlsx extends BaseReader } } } - if ($relsDrawing === false && $xmlDrawing->count() == 0) { + if ($xmlDrawingChildren->absoluteAnchor) { + foreach ($xmlDrawingChildren->absoluteAnchor as $absoluteAnchor) { + if (($this->includeCharts) && ($absoluteAnchor->graphicFrame)) { + $graphic = $absoluteAnchor->graphicFrame->children(Namespaces::DRAWINGML)->graphic; + /** @var SimpleXMLElement $chartRef */ + $chartRef = $graphic->graphicData->children(Namespaces::CHART)->chart; + $thisChart = (string) self::getAttributes($chartRef, $xmlNamespaceBase); + $width = Drawing::EMUToPixels((int) self::getArrayItem(self::getAttributes($absoluteAnchor->ext), 'cx')[0]); + $height = Drawing::EMUToPixels((int) self::getArrayItem(self::getAttributes($absoluteAnchor->ext), 'cy')[0]); + + $chartDetails[$docSheet->getTitle() . '!' . $thisChart] = [ + 'fromCoordinate' => 'A1', + 'fromOffsetX' => 0, + 'fromOffsetY' => 0, + 'width' => $width, + 'height' => $height, + 'worksheetTitle' => $docSheet->getTitle(), + ]; + } + } + } + if (empty($relsDrawing) && $xmlDrawing->count() == 0) { // Save Drawing without rels and children as unparsed $unparsedDrawings[$drawingRelId] = $xmlDrawing->asXML(); } @@ -1270,7 +1516,7 @@ class Xlsx extends BaseReader // store original rId of drawing files $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'] = []; foreach ($relsWorksheet->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing') { + if ((string) $ele['Type'] === "$xmlNamespaceBase/drawing") { $drawingRelId = (string) $ele['Id']; $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingOriginalIds'][(string) $ele['Target']] = $drawingRelId; if (isset($unparsedDrawings[$drawingRelId])) { @@ -1278,24 +1524,29 @@ class Xlsx extends BaseReader } } } + if ($xmlSheet->legacyDrawing && !$this->readDataOnly) { + foreach ($xmlSheet->legacyDrawing as $drawing) { + $drawingRelId = (string) self::getArrayItem(self::getAttributes($drawing, $xmlNamespaceBase), 'id'); + if (isset($vmlDrawingContents[$drawingRelId])) { + $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['legacyDrawing'] = $vmlDrawingContents[$drawingRelId]; + } + } + } // unparsed drawing AlternateContent - $xmlAltDrawing = simplexml_load_string( - $this->securityScanner->scan($this->getFromZipArchive($zip, $fileDrawing)), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - )->children('http://schemas.openxmlformats.org/markup-compatibility/2006'); + $xmlAltDrawing = $this->loadZip((string) $fileDrawing, Namespaces::COMPATIBILITY); if ($xmlAltDrawing->AlternateContent) { foreach ($xmlAltDrawing->AlternateContent as $alternateContent) { + $alternateContent = self::testSimpleXml($alternateContent); $unparsedLoadedData['sheets'][$docSheet->getCodeName()]['drawingAlternateContents'][] = $alternateContent->asXML(); } } } } - $this->readFormControlProperties($excel, $zip, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData); - $this->readPrinterSettings($excel, $zip, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData); + $this->readFormControlProperties($excel, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData); + $this->readPrinterSettings($excel, $dir, $fileWorksheet, $docSheet, $unparsedLoadedData); // Loop through definedNames if ($xmlWorkbook->definedNames) { @@ -1309,7 +1560,7 @@ class Xlsx extends BaseReader } // Valid range? - if (stripos((string) $definedName, '#REF!') !== false || $extractedRange == '') { + if ($extractedRange == '') { continue; } @@ -1349,16 +1600,21 @@ class Xlsx extends BaseReader break; case '_xlnm.Print_Area': - $rangeSets = preg_split("/('?(?:.*?)'?(?:![A-Z0-9]+:[A-Z0-9]+)),?/", $extractedRange, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); + $rangeSets = preg_split("/('?(?:.*?)'?(?:![A-Z0-9]+:[A-Z0-9]+)),?/", $extractedRange, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) ?: []; $newRangeSets = []; foreach ($rangeSets as $rangeSet) { - [$sheetName, $rangeSet] = Worksheet::extractSheetTitle($rangeSet, true); + [, $rangeSet] = Worksheet::extractSheetTitle($rangeSet, true); + if (empty($rangeSet)) { + continue; + } if (strpos($rangeSet, ':') === false) { $rangeSet = $rangeSet . ':' . $rangeSet; } $newRangeSets[] = str_replace('$', '', $rangeSet); } - $docSheet->getPageSetup()->setPrintArea(implode(',', $newRangeSets)); + if (count($newRangeSets) > 0) { + $docSheet->getPageSetup()->setPrintArea(implode(',', $newRangeSets)); + } break; default: @@ -1377,14 +1633,9 @@ class Xlsx extends BaseReader foreach ($xmlWorkbook->definedNames->definedName as $definedName) { // Extract range $extractedRange = (string) $definedName; - if (($spos = strpos($extractedRange, '!')) !== false) { - $extractedRange = substr($extractedRange, 0, $spos) . str_replace('$', '', substr($extractedRange, $spos)); - } else { - $extractedRange = str_replace('$', '', $extractedRange); - } // Valid range? - if (stripos((string) $definedName, '#REF!') !== false || $extractedRange == '') { + if ($extractedRange == '') { continue; } @@ -1399,113 +1650,56 @@ class Xlsx extends BaseReader break; default: if ($mapSheetId[(int) $definedName['localSheetId']] !== null) { + $range = Worksheet::extractSheetTitle((string) $definedName, true); + $scope = $excel->getSheet($mapSheetId[(int) $definedName['localSheetId']]); if (strpos((string) $definedName, '!') !== false) { - $range = Worksheet::extractSheetTitle((string) $definedName, true); $range[0] = str_replace("''", "'", $range[0]); $range[0] = str_replace("'", '', $range[0]); - if ($worksheet = $docSheet->getParent()->getSheetByName($range[0])) { - $extractedRange = str_replace('$', '', $range[1]); - $scope = $docSheet->getParent()->getSheet($mapSheetId[(int) $definedName['localSheetId']]); - $excel->addNamedRange(new NamedRange((string) $definedName['name'], $worksheet, $extractedRange, true, $scope)); + if ($worksheet = $excel->getSheetByName($range[0])) { // @phpstan-ignore-line + $excel->addDefinedName(DefinedName::createInstance((string) $definedName['name'], $worksheet, $extractedRange, true, $scope)); + } else { + $excel->addDefinedName(DefinedName::createInstance((string) $definedName['name'], $scope, $extractedRange, true, $scope)); } + } else { + $excel->addDefinedName(DefinedName::createInstance((string) $definedName['name'], $scope, $extractedRange, true)); } } break; } } elseif (!isset($definedName['localSheetId'])) { + $definedRange = (string) $definedName; // "Global" definedNames $locatedSheet = null; - $extractedSheetName = ''; if (strpos((string) $definedName, '!') !== false) { + // Modify range, and extract the first worksheet reference + // Need to split on a comma or a space if not in quotes, and extract the first part. + $definedNameValueParts = preg_split("/[ ,](?=([^']*'[^']*')*[^']*$)/miuU", $definedRange); // Extract sheet name - $extractedSheetName = Worksheet::extractSheetTitle((string) $definedName, true); - $extractedSheetName = trim($extractedSheetName[0], "'"); + [$extractedSheetName] = Worksheet::extractSheetTitle((string) $definedNameValueParts[0], true); // @phpstan-ignore-line + $extractedSheetName = trim($extractedSheetName, "'"); // Locate sheet $locatedSheet = $excel->getSheetByName($extractedSheetName); - - // Modify range - [$worksheetName, $extractedRange] = Worksheet::extractSheetTitle($extractedRange, true); } - if ($locatedSheet !== null) { - $excel->addNamedRange(new NamedRange((string) $definedName['name'], $locatedSheet, $extractedRange, false)); + if ($locatedSheet === null && !DefinedName::testIfFormula($definedRange)) { + $definedRange = '#REF!'; } + $excel->addDefinedName(DefinedName::createInstance((string) $definedName['name'], $locatedSheet, $definedRange, false)); } } } } - if ((!$this->readDataOnly) || (!empty($this->loadSheetsOnly))) { - $workbookView = $xmlWorkbook->bookViews->workbookView; - - // active sheet index - $activeTab = (int) ($workbookView['activeTab']); // refers to old sheet index - - // keep active sheet index if sheet is still loaded, else first sheet is set as the active - if (isset($mapSheetId[$activeTab]) && $mapSheetId[$activeTab] !== null) { - $excel->setActiveSheetIndex($mapSheetId[$activeTab]); - } else { - if ($excel->getSheetCount() == 0) { - $excel->createSheet(); - } - $excel->setActiveSheetIndex(0); - } - - if (isset($workbookView['showHorizontalScroll'])) { - $showHorizontalScroll = (string) $workbookView['showHorizontalScroll']; - $excel->setShowHorizontalScroll($this->castXsdBooleanToBool($showHorizontalScroll)); - } - - if (isset($workbookView['showVerticalScroll'])) { - $showVerticalScroll = (string) $workbookView['showVerticalScroll']; - $excel->setShowVerticalScroll($this->castXsdBooleanToBool($showVerticalScroll)); - } - - if (isset($workbookView['showSheetTabs'])) { - $showSheetTabs = (string) $workbookView['showSheetTabs']; - $excel->setShowSheetTabs($this->castXsdBooleanToBool($showSheetTabs)); - } - - if (isset($workbookView['minimized'])) { - $minimized = (string) $workbookView['minimized']; - $excel->setMinimized($this->castXsdBooleanToBool($minimized)); - } - - if (isset($workbookView['autoFilterDateGrouping'])) { - $autoFilterDateGrouping = (string) $workbookView['autoFilterDateGrouping']; - $excel->setAutoFilterDateGrouping($this->castXsdBooleanToBool($autoFilterDateGrouping)); - } - - if (isset($workbookView['firstSheet'])) { - $firstSheet = (string) $workbookView['firstSheet']; - $excel->setFirstSheetIndex((int) $firstSheet); - } - - if (isset($workbookView['visibility'])) { - $visibility = (string) $workbookView['visibility']; - $excel->setVisibility($visibility); - } - - if (isset($workbookView['tabRatio'])) { - $tabRatio = (string) $workbookView['tabRatio']; - $excel->setTabRatio((int) $tabRatio); - } - } + (new WorkbookView($excel))->viewSettings($xmlWorkbook, $mainNS, $mapSheetId, $this->readDataOnly); break; } } if (!$this->readDataOnly) { - $contentTypes = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, '[Content_Types].xml') - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $contentTypes = $this->loadZip('[Content_Types].xml'); // Default content types foreach ($contentTypes->Default as $contentType) { @@ -1522,30 +1716,36 @@ class Xlsx extends BaseReader switch ($contentType['ContentType']) { case 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml': if ($this->includeCharts) { - $chartEntryRef = ltrim($contentType['PartName'], '/'); - $chartElements = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, $chartEntryRef) - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); - $objChart = Chart::readChart($chartElements, basename($chartEntryRef, '.xml')); - + $chartEntryRef = ltrim((string) $contentType['PartName'], '/'); + $chartElements = $this->loadZip($chartEntryRef); + $chartReader = new Chart($chartNS, $drawingNS); + $objChart = $chartReader->readChart($chartElements, basename($chartEntryRef, '.xml')); if (isset($charts[$chartEntryRef])) { $chartPositionRef = $charts[$chartEntryRef]['sheet'] . '!' . $charts[$chartEntryRef]['id']; if (isset($chartDetails[$chartPositionRef])) { - $excel->getSheetByName($charts[$chartEntryRef]['sheet'])->addChart($objChart); + $excel->getSheetByName($charts[$chartEntryRef]['sheet'])->addChart($objChart); // @phpstan-ignore-line $objChart->setWorksheet($excel->getSheetByName($charts[$chartEntryRef]['sheet'])); - $objChart->setTopLeftPosition($chartDetails[$chartPositionRef]['fromCoordinate'], $chartDetails[$chartPositionRef]['fromOffsetX'], $chartDetails[$chartPositionRef]['fromOffsetY']); - $objChart->setBottomRightPosition($chartDetails[$chartPositionRef]['toCoordinate'], $chartDetails[$chartPositionRef]['toOffsetX'], $chartDetails[$chartPositionRef]['toOffsetY']); + // For oneCellAnchor or absoluteAnchor positioned charts, + // toCoordinate is not in the data. Does it need to be calculated? + if (array_key_exists('toCoordinate', $chartDetails[$chartPositionRef])) { + // twoCellAnchor + $objChart->setTopLeftPosition($chartDetails[$chartPositionRef]['fromCoordinate'], $chartDetails[$chartPositionRef]['fromOffsetX'], $chartDetails[$chartPositionRef]['fromOffsetY']); + $objChart->setBottomRightPosition($chartDetails[$chartPositionRef]['toCoordinate'], $chartDetails[$chartPositionRef]['toOffsetX'], $chartDetails[$chartPositionRef]['toOffsetY']); + } else { + // oneCellAnchor or absoluteAnchor (e.g. Chart sheet) + $objChart->setTopLeftPosition($chartDetails[$chartPositionRef]['fromCoordinate'], $chartDetails[$chartPositionRef]['fromOffsetX'], $chartDetails[$chartPositionRef]['fromOffsetY']); + $objChart->setBottomRightPosition('', $chartDetails[$chartPositionRef]['width'], $chartDetails[$chartPositionRef]['height']); + if (array_key_exists('oneCellAnchor', $chartDetails[$chartPositionRef])) { + $objChart->setOneCellAnchor($chartDetails[$chartPositionRef]['oneCellAnchor']); + } + } } } } break; - // unparsed + // unparsed case 'application/vnd.ms-excel.controlproperties+xml': $unparsedLoadedData['override_content_types'][(string) $contentType['PartName']] = (string) $contentType['ContentType']; @@ -1561,228 +1761,86 @@ class Xlsx extends BaseReader return $excel; } - private static function readColor($color, $background = false) - { - if (isset($color['rgb'])) { - return (string) $color['rgb']; - } elseif (isset($color['indexed'])) { - return Color::indexedColor($color['indexed'] - 7, $background)->getARGB(); - } elseif (isset($color['theme'])) { - if (self::$theme !== null) { - $returnColour = self::$theme->getColourByIndex((int) $color['theme']); - if (isset($color['tint'])) { - $tintAdjust = (float) $color['tint']; - $returnColour = Color::changeBrightness($returnColour, $tintAdjust); - } - - return 'FF' . $returnColour; - } - } - - if ($background) { - return 'FFFFFFFF'; - } - - return 'FF000000'; - } - /** - * @param Style $docStyle - * @param SimpleXMLElement|\stdClass $style - */ - private static function readStyle(Style $docStyle, $style) - { - $docStyle->getNumberFormat()->setFormatCode($style->numFmt); - - // font - if (isset($style->font)) { - $docStyle->getFont()->setName((string) $style->font->name['val']); - $docStyle->getFont()->setSize((string) $style->font->sz['val']); - if (isset($style->font->b)) { - $docStyle->getFont()->setBold(!isset($style->font->b['val']) || self::boolean((string) $style->font->b['val'])); - } - if (isset($style->font->i)) { - $docStyle->getFont()->setItalic(!isset($style->font->i['val']) || self::boolean((string) $style->font->i['val'])); - } - if (isset($style->font->strike)) { - $docStyle->getFont()->setStrikethrough(!isset($style->font->strike['val']) || self::boolean((string) $style->font->strike['val'])); - } - $docStyle->getFont()->getColor()->setARGB(self::readColor($style->font->color)); - - if (isset($style->font->u) && !isset($style->font->u['val'])) { - $docStyle->getFont()->setUnderline(\PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLE); - } elseif (isset($style->font->u, $style->font->u['val'])) { - $docStyle->getFont()->setUnderline((string) $style->font->u['val']); - } - - if (isset($style->font->vertAlign, $style->font->vertAlign['val'])) { - $vertAlign = strtolower((string) $style->font->vertAlign['val']); - if ($vertAlign == 'superscript') { - $docStyle->getFont()->setSuperscript(true); - } - if ($vertAlign == 'subscript') { - $docStyle->getFont()->setSubscript(true); - } - } - } - - // fill - if (isset($style->fill)) { - if ($style->fill->gradientFill) { - /** @var SimpleXMLElement $gradientFill */ - $gradientFill = $style->fill->gradientFill[0]; - if (!empty($gradientFill['type'])) { - $docStyle->getFill()->setFillType((string) $gradientFill['type']); - } - $docStyle->getFill()->setRotation((float) ($gradientFill['degree'])); - $gradientFill->registerXPathNamespace('sml', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'); - $docStyle->getFill()->getStartColor()->setARGB(self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=0]'))->color)); - $docStyle->getFill()->getEndColor()->setARGB(self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=1]'))->color)); - } elseif ($style->fill->patternFill) { - $patternType = (string) $style->fill->patternFill['patternType'] != '' ? (string) $style->fill->patternFill['patternType'] : 'solid'; - $docStyle->getFill()->setFillType($patternType); - if ($style->fill->patternFill->fgColor) { - $docStyle->getFill()->getStartColor()->setARGB(self::readColor($style->fill->patternFill->fgColor, true)); - } else { - $docStyle->getFill()->getStartColor()->setARGB('FF000000'); - } - if ($style->fill->patternFill->bgColor) { - $docStyle->getFill()->getEndColor()->setARGB(self::readColor($style->fill->patternFill->bgColor, true)); - } - } - } - - // border - if (isset($style->border)) { - $diagonalUp = self::boolean((string) $style->border['diagonalUp']); - $diagonalDown = self::boolean((string) $style->border['diagonalDown']); - if (!$diagonalUp && !$diagonalDown) { - $docStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_NONE); - } elseif ($diagonalUp && !$diagonalDown) { - $docStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_UP); - } elseif (!$diagonalUp && $diagonalDown) { - $docStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_DOWN); - } else { - $docStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_BOTH); - } - self::readBorder($docStyle->getBorders()->getLeft(), $style->border->left); - self::readBorder($docStyle->getBorders()->getRight(), $style->border->right); - self::readBorder($docStyle->getBorders()->getTop(), $style->border->top); - self::readBorder($docStyle->getBorders()->getBottom(), $style->border->bottom); - self::readBorder($docStyle->getBorders()->getDiagonal(), $style->border->diagonal); - } - - // alignment - if (isset($style->alignment)) { - $docStyle->getAlignment()->setHorizontal((string) $style->alignment['horizontal']); - $docStyle->getAlignment()->setVertical((string) $style->alignment['vertical']); - - $textRotation = 0; - if ((int) $style->alignment['textRotation'] <= 90) { - $textRotation = (int) $style->alignment['textRotation']; - } elseif ((int) $style->alignment['textRotation'] > 90) { - $textRotation = 90 - (int) $style->alignment['textRotation']; - } - - $docStyle->getAlignment()->setTextRotation((int) $textRotation); - $docStyle->getAlignment()->setWrapText(self::boolean((string) $style->alignment['wrapText'])); - $docStyle->getAlignment()->setShrinkToFit(self::boolean((string) $style->alignment['shrinkToFit'])); - $docStyle->getAlignment()->setIndent((int) ((string) $style->alignment['indent']) > 0 ? (int) ((string) $style->alignment['indent']) : 0); - $docStyle->getAlignment()->setReadOrder((int) ((string) $style->alignment['readingOrder']) > 0 ? (int) ((string) $style->alignment['readingOrder']) : 0); - } - - // protection - if (isset($style->protection)) { - if (isset($style->protection['locked'])) { - if (self::boolean((string) $style->protection['locked'])) { - $docStyle->getProtection()->setLocked(Protection::PROTECTION_PROTECTED); - } else { - $docStyle->getProtection()->setLocked(Protection::PROTECTION_UNPROTECTED); - } - } - - if (isset($style->protection['hidden'])) { - if (self::boolean((string) $style->protection['hidden'])) { - $docStyle->getProtection()->setHidden(Protection::PROTECTION_PROTECTED); - } else { - $docStyle->getProtection()->setHidden(Protection::PROTECTION_UNPROTECTED); - } - } - } - - // top-level style settings - if (isset($style->quotePrefix)) { - $docStyle->setQuotePrefix($style->quotePrefix); - } - } - - /** - * @param Border $docBorder - * @param SimpleXMLElement $eleBorder - */ - private static function readBorder(Border $docBorder, $eleBorder) - { - if (isset($eleBorder['style'])) { - $docBorder->setBorderStyle((string) $eleBorder['style']); - } - if (isset($eleBorder->color)) { - $docBorder->getColor()->setARGB(self::readColor($eleBorder->color)); - } - } - - /** - * @param SimpleXMLElement | null $is - * * @return RichText */ - private function parseRichText($is) + private function parseRichText(?SimpleXMLElement $is) { $value = new RichText(); if (isset($is->t)) { $value->createText(StringHelper::controlCharacterOOXML2PHP((string) $is->t)); - } else { + } elseif ($is !== null) { if (is_object($is->r)) { + /** @var SimpleXMLElement $run */ foreach ($is->r as $run) { if (!isset($run->rPr)) { $value->createText(StringHelper::controlCharacterOOXML2PHP((string) $run->t)); } else { $objText = $value->createTextRun(StringHelper::controlCharacterOOXML2PHP((string) $run->t)); + $objFont = $objText->getFont() ?? new StyleFont(); - if (isset($run->rPr->rFont['val'])) { - $objText->getFont()->setName((string) $run->rPr->rFont['val']); + if (isset($run->rPr->rFont)) { + $attr = $run->rPr->rFont->attributes(); + if (isset($attr['val'])) { + $objFont->setName((string) $attr['val']); + } } - if (isset($run->rPr->sz['val'])) { - $objText->getFont()->setSize((float) $run->rPr->sz['val']); + if (isset($run->rPr->sz)) { + $attr = $run->rPr->sz->attributes(); + if (isset($attr['val'])) { + $objFont->setSize((float) $attr['val']); + } } if (isset($run->rPr->color)) { - $objText->getFont()->setColor(new Color(self::readColor($run->rPr->color))); + $objFont->setColor(new Color($this->styleReader->readColor($run->rPr->color))); } - if ((isset($run->rPr->b['val']) && self::boolean((string) $run->rPr->b['val'])) || - (isset($run->rPr->b) && !isset($run->rPr->b['val']))) { - $objText->getFont()->setBold(true); - } - if ((isset($run->rPr->i['val']) && self::boolean((string) $run->rPr->i['val'])) || - (isset($run->rPr->i) && !isset($run->rPr->i['val']))) { - $objText->getFont()->setItalic(true); - } - if (isset($run->rPr->vertAlign, $run->rPr->vertAlign['val'])) { - $vertAlign = strtolower((string) $run->rPr->vertAlign['val']); - if ($vertAlign == 'superscript') { - $objText->getFont()->setSuperscript(true); - } - if ($vertAlign == 'subscript') { - $objText->getFont()->setSubscript(true); + if (isset($run->rPr->b)) { + $attr = $run->rPr->b->attributes(); + if ( + (isset($attr['val']) && self::boolean((string) $attr['val'])) || + (!isset($attr['val'])) + ) { + $objFont->setBold(true); } } - if (isset($run->rPr->u) && !isset($run->rPr->u['val'])) { - $objText->getFont()->setUnderline(\PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLE); - } elseif (isset($run->rPr->u, $run->rPr->u['val'])) { - $objText->getFont()->setUnderline((string) $run->rPr->u['val']); + if (isset($run->rPr->i)) { + $attr = $run->rPr->i->attributes(); + if ( + (isset($attr['val']) && self::boolean((string) $attr['val'])) || + (!isset($attr['val'])) + ) { + $objFont->setItalic(true); + } } - if ((isset($run->rPr->strike['val']) && self::boolean((string) $run->rPr->strike['val'])) || - (isset($run->rPr->strike) && !isset($run->rPr->strike['val']))) { - $objText->getFont()->setStrikethrough(true); + if (isset($run->rPr->vertAlign)) { + $attr = $run->rPr->vertAlign->attributes(); + if (isset($attr['val'])) { + $vertAlign = strtolower((string) $attr['val']); + if ($vertAlign == 'superscript') { + $objFont->setSuperscript(true); + } + if ($vertAlign == 'subscript') { + $objFont->setSubscript(true); + } + } + } + if (isset($run->rPr->u)) { + $attr = $run->rPr->u->attributes(); + if (!isset($attr['val'])) { + $objFont->setUnderline(\PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLE); + } else { + $objFont->setUnderline((string) $attr['val']); + } + } + if (isset($run->rPr->strike)) { + $attr = $run->rPr->strike->attributes(); + if ( + (isset($attr['val']) && self::boolean((string) $attr['val'])) || + (!isset($attr['val'])) + ) { + $objFont->setStrikethrough(true); + } } } } @@ -1792,12 +1850,7 @@ class Xlsx extends BaseReader return $value; } - /** - * @param Spreadsheet $excel - * @param mixed $customUITarget - * @param mixed $zip - */ - private function readRibbon(Spreadsheet $excel, $customUITarget, $zip) + private function readRibbon(Spreadsheet $excel, string $customUITarget, ZipArchive $zip): void { $baseDir = dirname($customUITarget); $nameCustomUI = basename($customUITarget); @@ -1818,7 +1871,7 @@ class Xlsx extends BaseReader if (false !== $UIRels) { // we need to save id and target to avoid parsing customUI.xml and "guess" if it's a pseudo callback who load the image foreach ($UIRels->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image') { + if ((string) $ele['Type'] === Namespaces::SCHEMA_OFFICE_DOCUMENT . '/image') { // an image ? $customUIImagesNames[(string) $ele['Id']] = (string) $ele['Target']; $customUIImagesBinaries[(string) $ele['Target']] = $this->getFromZipArchive($zip, $baseDir . '/' . (string) $ele['Target']); @@ -1839,19 +1892,32 @@ class Xlsx extends BaseReader } } + /** + * @param null|array|bool|SimpleXMLElement $array + * @param int|string $key + * + * @return mixed + */ private static function getArrayItem($array, $key = 0) { - return $array[$key] ?? null; + return ($array === null || is_bool($array)) ? null : ($array[$key] ?? null); } - private static function dirAdd($base, $add) + /** + * @param null|SimpleXMLElement|string $base + * @param null|SimpleXMLElement|string $add + */ + private static function dirAdd($base, $add): string { - return preg_replace('~[^/]+/\.\./~', '', dirname($base) . "/$add"); + $base = (string) $base; + $add = (string) $add; + + return (string) preg_replace('~[^/]+/\.\./~', '', dirname($base) . "/$add"); } - private static function toCSSArray($style) + private static function toCSSArray(string $style): array { - $style = trim(str_replace(["\r", "\n"], '', $style), ';'); + $style = self::stripWhiteSpaceFromStyleString($style); $temp = explode(';', $style); $style = []; @@ -1863,15 +1929,15 @@ class Xlsx extends BaseReader } if (strpos($item[1], 'pt') !== false) { $item[1] = str_replace('pt', '', $item[1]); - $item[1] = Font::fontSizeToPixels($item[1]); + $item[1] = (string) Font::fontSizeToPixels((int) $item[1]); } if (strpos($item[1], 'in') !== false) { $item[1] = str_replace('in', '', $item[1]); - $item[1] = Font::inchSizeToPixels($item[1]); + $item[1] = (string) Font::inchSizeToPixels((int) $item[1]); } if (strpos($item[1], 'cm') !== false) { $item[1] = str_replace('cm', '', $item[1]); - $item[1] = Font::centimeterSizeToPixels($item[1]); + $item[1] = (string) Font::centimeterSizeToPixels((int) $item[1]); } $style[$item[0]] = $item[1]; @@ -1880,11 +1946,13 @@ class Xlsx extends BaseReader return $style; } - private static function boolean($value) + public static function stripWhiteSpaceFromStyleString(string $string): string + { + return trim(str_replace(["\r", "\n", ' '], '', $string), ';'); + } + + private static function boolean(string $value): bool { - if (is_object($value)) { - $value = (string) $value; - } if (is_numeric($value)) { return (bool) $value; } @@ -1893,70 +1961,73 @@ class Xlsx extends BaseReader } /** - * @param \PhpOffice\PhpSpreadsheet\Worksheet\Drawing $objDrawing - * @param \SimpleXMLElement $cellAnchor * @param array $hyperlinks */ - private function readHyperLinkDrawing($objDrawing, $cellAnchor, $hyperlinks) + private function readHyperLinkDrawing(\PhpOffice\PhpSpreadsheet\Worksheet\Drawing $objDrawing, SimpleXMLElement $cellAnchor, $hyperlinks): void { - $hlinkClick = $cellAnchor->pic->nvPicPr->cNvPr->children('http://schemas.openxmlformats.org/drawingml/2006/main')->hlinkClick; + $hlinkClick = $cellAnchor->pic->nvPicPr->cNvPr->children(Namespaces::DRAWINGML)->hlinkClick; if ($hlinkClick->count() === 0) { return; } - $hlinkId = (string) $hlinkClick->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships')['id']; + $hlinkId = (string) self::getAttributes($hlinkClick, Namespaces::SCHEMA_OFFICE_DOCUMENT)['id']; $hyperlink = new Hyperlink( $hyperlinks[$hlinkId], - (string) self::getArrayItem($cellAnchor->pic->nvPicPr->cNvPr->attributes(), 'name') + (string) self::getArrayItem(self::getAttributes($cellAnchor->pic->nvPicPr->cNvPr), 'name') ); $objDrawing->setHyperlink($hyperlink); } - private function readProtection(Spreadsheet $excel, SimpleXMLElement $xmlWorkbook) + private function readProtection(Spreadsheet $excel, SimpleXMLElement $xmlWorkbook): void { if (!$xmlWorkbook->workbookProtection) { return; } - if ($xmlWorkbook->workbookProtection['lockRevision']) { - $excel->getSecurity()->setLockRevision((bool) $xmlWorkbook->workbookProtection['lockRevision']); - } - - if ($xmlWorkbook->workbookProtection['lockStructure']) { - $excel->getSecurity()->setLockStructure((bool) $xmlWorkbook->workbookProtection['lockStructure']); - } - - if ($xmlWorkbook->workbookProtection['lockWindows']) { - $excel->getSecurity()->setLockWindows((bool) $xmlWorkbook->workbookProtection['lockWindows']); - } + $excel->getSecurity()->setLockRevision(self::getLockValue($xmlWorkbook->workbookProtection, 'lockRevision')); + $excel->getSecurity()->setLockStructure(self::getLockValue($xmlWorkbook->workbookProtection, 'lockStructure')); + $excel->getSecurity()->setLockWindows(self::getLockValue($xmlWorkbook->workbookProtection, 'lockWindows')); if ($xmlWorkbook->workbookProtection['revisionsPassword']) { - $excel->getSecurity()->setRevisionsPassword((string) $xmlWorkbook->workbookProtection['revisionsPassword'], true); + $excel->getSecurity()->setRevisionsPassword( + (string) $xmlWorkbook->workbookProtection['revisionsPassword'], + true + ); } if ($xmlWorkbook->workbookProtection['workbookPassword']) { - $excel->getSecurity()->setWorkbookPassword((string) $xmlWorkbook->workbookProtection['workbookPassword'], true); + $excel->getSecurity()->setWorkbookPassword( + (string) $xmlWorkbook->workbookProtection['workbookPassword'], + true + ); } } - private function readFormControlProperties(Spreadsheet $excel, ZipArchive $zip, $dir, $fileWorksheet, $docSheet, array &$unparsedLoadedData) + private static function getLockValue(SimpleXmlElement $protection, string $key): ?bool { + $returnValue = null; + $protectKey = $protection[$key]; + if (!empty($protectKey)) { + $protectKey = (string) $protectKey; + $returnValue = $protectKey !== 'false' && (bool) $protectKey; + } + + return $returnValue; + } + + private function readFormControlProperties(Spreadsheet $excel, string $dir, string $fileWorksheet, Worksheet $docSheet, array &$unparsedLoadedData): void + { + $zip = $this->zip; if (!$zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { return; } - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $relsWorksheet = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels') - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $filename = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'; + $relsWorksheet = $this->loadZipNoNamespace($filename, Namespaces::RELATIONSHIPS); $ctrlProps = []; foreach ($relsWorksheet->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp') { + if ((string) $ele['Type'] === Namespaces::SCHEMA_OFFICE_DOCUMENT . '/ctrlProp') { $ctrlProps[(string) $ele['Id']] = $ele; } } @@ -1972,23 +2043,18 @@ class Xlsx extends BaseReader unset($unparsedCtrlProps); } - private function readPrinterSettings(Spreadsheet $excel, ZipArchive $zip, $dir, $fileWorksheet, $docSheet, array &$unparsedLoadedData) + private function readPrinterSettings(Spreadsheet $excel, string $dir, string $fileWorksheet, Worksheet $docSheet, array &$unparsedLoadedData): void { + $zip = $this->zip; if (!$zip->locateName(dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels')) { return; } - //~ http://schemas.openxmlformats.org/package/2006/relationships" - $relsWorksheet = simplexml_load_string( - $this->securityScanner->scan( - $this->getFromZipArchive($zip, dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels') - ), - 'SimpleXMLElement', - Settings::getLibXmlLoaderOptions() - ); + $filename = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'; + $relsWorksheet = $this->loadZipNoNamespace($filename, Namespaces::RELATIONSHIPS); $sheetPrinterSettings = []; foreach ($relsWorksheet->Relationship as $ele) { - if ($ele['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerSettings') { + if ((string) $ele['Type'] === Namespaces::SCHEMA_OFFICE_DOCUMENT . '/printerSettings') { $sheetPrinterSettings[(string) $ele['Id']] = $ele; } } @@ -1996,6 +2062,9 @@ class Xlsx extends BaseReader $unparsedPrinterSettings = &$unparsedLoadedData['sheets'][$docSheet->getCodeName()]['printerSettings']; foreach ($sheetPrinterSettings as $rId => $printerSettings) { $rId = substr($rId, 3); // rIdXXX + if (substr($rId, -2) !== 'ps') { + $rId = $rId . 'ps'; // rIdXXX, add 'ps' suffix to avoid identical resource identifier collision with unparsed vmlDrawing + } $unparsedPrinterSettings[$rId] = []; $unparsedPrinterSettings[$rId]['filePath'] = self::dirAdd("$dir/$fileWorksheet", $printerSettings['Target']); $unparsedPrinterSettings[$rId]['relFilePath'] = (string) $printerSettings['Target']; @@ -2004,26 +2073,136 @@ class Xlsx extends BaseReader unset($unparsedPrinterSettings); } - /** - * Convert an 'xsd:boolean' XML value to a PHP boolean value. - * A valid 'xsd:boolean' XML value can be one of the following - * four values: 'true', 'false', '1', '0'. It is case sensitive. - * - * Note that just doing '(bool) $xsdBoolean' is not safe, - * since '(bool) "false"' returns true. - * - * @see https://www.w3.org/TR/xmlschema11-2/#boolean - * - * @param string $xsdBoolean An XML string value of type 'xsd:boolean' - * - * @return bool Boolean value - */ - private function castXsdBooleanToBool($xsdBoolean) + private function getWorkbookBaseName(): array { - if ($xsdBoolean === 'false') { - return false; + $workbookBasename = ''; + $xmlNamespaceBase = ''; + + // check if it is an OOXML archive + $rels = $this->loadZip(self::INITIAL_FILE); + foreach ($rels->children(Namespaces::RELATIONSHIPS)->Relationship as $rel) { + $rel = self::getAttributes($rel); + $type = (string) $rel['Type']; + switch ($type) { + case Namespaces::OFFICE_DOCUMENT: + case Namespaces::PURL_OFFICE_DOCUMENT: + $basename = basename((string) $rel['Target']); + $xmlNamespaceBase = dirname($type); + if (preg_match('/workbook.*\.xml/', $basename)) { + $workbookBasename = $basename; + } + + break; + } } - return (bool) $xsdBoolean; + return [$workbookBasename, $xmlNamespaceBase]; + } + + private function readSheetProtection(Worksheet $docSheet, SimpleXMLElement $xmlSheet): void + { + if ($this->readDataOnly || !$xmlSheet->sheetProtection) { + return; + } + + $algorithmName = (string) $xmlSheet->sheetProtection['algorithmName']; + $protection = $docSheet->getProtection(); + $protection->setAlgorithm($algorithmName); + + if ($algorithmName) { + $protection->setPassword((string) $xmlSheet->sheetProtection['hashValue'], true); + $protection->setSalt((string) $xmlSheet->sheetProtection['saltValue']); + $protection->setSpinCount((int) $xmlSheet->sheetProtection['spinCount']); + } else { + $protection->setPassword((string) $xmlSheet->sheetProtection['password'], true); + } + + if ($xmlSheet->protectedRanges->protectedRange) { + foreach ($xmlSheet->protectedRanges->protectedRange as $protectedRange) { + $docSheet->protectCells((string) $protectedRange['sqref'], (string) $protectedRange['password'], true); + } + } + } + + private function readAutoFilter( + SimpleXMLElement $xmlSheet, + Worksheet $docSheet + ): void { + if ($xmlSheet && $xmlSheet->autoFilter) { + (new AutoFilter($docSheet, $xmlSheet))->load(); + } + } + + private function readTables( + SimpleXMLElement $xmlSheet, + Worksheet $docSheet, + string $dir, + string $fileWorksheet, + ZipArchive $zip + ): void { + if ($xmlSheet && $xmlSheet->tableParts && (int) $xmlSheet->tableParts['count'] > 0) { + $this->readTablesInTablesFile($xmlSheet, $dir, $fileWorksheet, $zip, $docSheet); + } + } + + private function readTablesInTablesFile( + SimpleXMLElement $xmlSheet, + string $dir, + string $fileWorksheet, + ZipArchive $zip, + Worksheet $docSheet + ): void { + foreach ($xmlSheet->tableParts->tablePart as $tablePart) { + $relation = self::getAttributes($tablePart, Namespaces::SCHEMA_OFFICE_DOCUMENT); + $tablePartRel = (string) $relation['id']; + $relationsFileName = dirname("$dir/$fileWorksheet") . '/_rels/' . basename($fileWorksheet) . '.rels'; + + if ($zip->locateName($relationsFileName)) { + $relsTableReferences = $this->loadZip($relationsFileName, Namespaces::RELATIONSHIPS); + foreach ($relsTableReferences->Relationship as $relationship) { + $relationshipAttributes = self::getAttributes($relationship, ''); + + if ((string) $relationshipAttributes['Id'] === $tablePartRel) { + $relationshipFileName = (string) $relationshipAttributes['Target']; + $relationshipFilePath = dirname("$dir/$fileWorksheet") . '/' . $relationshipFileName; + $relationshipFilePath = File::realpath($relationshipFilePath); + + if ($this->fileExistsInArchive($this->zip, $relationshipFilePath)) { + $tableXml = $this->loadZip($relationshipFilePath); + (new TableReader($docSheet, $tableXml))->load(); + } + } + } + } + } + } + + private static function extractStyles(?SimpleXMLElement $sxml, string $node1, string $node2): array + { + $array = []; + if ($sxml && $sxml->{$node1}->{$node2}) { + foreach ($sxml->{$node1}->{$node2} as $node) { + $array[] = $node; + } + } + + return $array; + } + + private static function extractPalette(?SimpleXMLElement $sxml): array + { + $array = []; + if ($sxml && $sxml->colors->indexedColors) { + foreach ($sxml->colors->indexedColors->rgbColor as $node) { + if ($node !== null) { + $attr = $node->attributes(); + if (isset($attr['rgb'])) { + $array[] = (string) $attr['rgb']; + } + } + } + } + + return $array; } } diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php b/PhpOffice/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php old mode 100755 new mode 100644 index 6929758..a6ab4d8 --- a/PhpOffice/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Xlsx/AutoFilter.php @@ -4,31 +4,43 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx; use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column; use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule; +use PhpOffice\PhpSpreadsheet\Worksheet\Table; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use SimpleXMLElement; class AutoFilter { - private $worksheet; + /** + * @var Table|Worksheet + */ + private $parent; + /** + * @var SimpleXMLElement + */ private $worksheetXml; - public function __construct(Worksheet $workSheet, \SimpleXMLElement $worksheetXml) + /** + * @param Table|Worksheet $parent + */ + public function __construct($parent, SimpleXMLElement $worksheetXml) { - $this->worksheet = $workSheet; + $this->parent = $parent; $this->worksheetXml = $worksheetXml; } - public function load() + public function load(): void { - $autoFilterRange = (string) $this->worksheetXml->autoFilter['ref']; + // Remove all "$" in the auto filter range + $autoFilterRange = (string) preg_replace('/\$/', '', $this->worksheetXml->autoFilter['ref'] ?? ''); if (strpos($autoFilterRange, ':') !== false) { $this->readAutoFilter($autoFilterRange, $this->worksheetXml); } } - private function readAutoFilter($autoFilterRange, $xmlSheet) + private function readAutoFilter(string $autoFilterRange, SimpleXMLElement $xmlSheet): void { - $autoFilter = $this->worksheet->getAutoFilter(); + $autoFilter = $this->parent->getAutoFilter(); $autoFilter->setRange($autoFilterRange); foreach ($xmlSheet->autoFilter->filterColumn as $filterColumn) { @@ -37,15 +49,15 @@ class AutoFilter if ($filterColumn->filters) { $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_FILTER); $filters = $filterColumn->filters; - if ((isset($filters['blank'])) && ($filters['blank'] == 1)) { + if ((isset($filters['blank'])) && ((int) $filters['blank'] == 1)) { // Operator is undefined, but always treated as EQUAL - $column->createRule()->setRule(null, '')->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER); + $column->createRule()->setRule('', '')->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER); } // Standard filters are always an OR join, so no join rule needs to be set // Entries can be either filter elements foreach ($filters->filter as $filterRule) { // Operator is undefined, but always treated as EQUAL - $column->createRule()->setRule(null, (string) $filterRule['val'])->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER); + $column->createRule()->setRule('', (string) $filterRule['val'])->setRuleType(Rule::AUTOFILTER_RULETYPE_FILTER); } // Or Date Group elements @@ -59,14 +71,15 @@ class AutoFilter // Check for dynamic filters $this->readTopTenAutoFilter($filterColumn, $column); } + $autoFilter->setEvaluated(true); } - private function readDateRangeAutoFilter(\SimpleXMLElement $filters, Column $column) + private function readDateRangeAutoFilter(SimpleXMLElement $filters, Column $column): void { foreach ($filters->dateGroupItem as $dateGroupItem) { // Operator is undefined, but always treated as EQUAL $column->createRule()->setRule( - null, + '', [ 'year' => (string) $dateGroupItem['year'], 'month' => (string) $dateGroupItem['month'], @@ -80,14 +93,14 @@ class AutoFilter } } - private function readCustomAutoFilter(\SimpleXMLElement $filterColumn, Column $column) + private function readCustomAutoFilter(?SimpleXMLElement $filterColumn, Column $column): void { - if ($filterColumn->customFilters) { + if (isset($filterColumn, $filterColumn->customFilters)) { $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_CUSTOMFILTER); $customFilters = $filterColumn->customFilters; // Custom filters can an AND or an OR join; // and there should only ever be one or two entries - if ((isset($customFilters['and'])) && ($customFilters['and'] == 1)) { + if ((isset($customFilters['and'])) && ((string) $customFilters['and'] === '1')) { $column->setJoin(Column::AUTOFILTER_COLUMN_JOIN_AND); } foreach ($customFilters->customFilter as $filterRule) { @@ -99,15 +112,15 @@ class AutoFilter } } - private function readDynamicAutoFilter(\SimpleXMLElement $filterColumn, Column $column) + private function readDynamicAutoFilter(?SimpleXMLElement $filterColumn, Column $column): void { - if ($filterColumn->dynamicFilter) { + if (isset($filterColumn, $filterColumn->dynamicFilter)) { $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER); // We should only ever have one dynamic filter foreach ($filterColumn->dynamicFilter as $filterRule) { // Operator is undefined, but always treated as EQUAL $column->createRule()->setRule( - null, + '', (string) $filterRule['val'], (string) $filterRule['type'] )->setRuleType(Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER); @@ -121,19 +134,21 @@ class AutoFilter } } - private function readTopTenAutoFilter(\SimpleXMLElement $filterColumn, Column $column) + private function readTopTenAutoFilter(?SimpleXMLElement $filterColumn, Column $column): void { - if ($filterColumn->top10) { + if (isset($filterColumn, $filterColumn->top10)) { $column->setFilterType(Column::AUTOFILTER_FILTERTYPE_TOPTENFILTER); // We should only ever have one top10 filter foreach ($filterColumn->top10 as $filterRule) { $column->createRule()->setRule( - (((isset($filterRule['percent'])) && ($filterRule['percent'] == 1)) + ( + ((isset($filterRule['percent'])) && ((string) $filterRule['percent'] === '1')) ? Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT : Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_BY_VALUE ), (string) $filterRule['val'], - (((isset($filterRule['top'])) && ($filterRule['top'] == 1)) + ( + ((isset($filterRule['top'])) && ((string) $filterRule['top'] === '1')) ? Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP : Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_BOTTOM ) diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php b/PhpOffice/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php old mode 100755 new mode 100644 index 1679f01..6b99877 --- a/PhpOffice/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Xlsx/BaseParserClass.php @@ -4,16 +4,19 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx; class BaseParserClass { - protected static function boolean($value) + /** + * @param mixed $value + */ + protected static function boolean($value): bool { if (is_object($value)) { - $value = (string) $value; + $value = (string) $value; // @phpstan-ignore-line } if (is_numeric($value)) { return (bool) $value; } - return $value === strtolower('true'); + return $value === 'true' || $value === 'TRUE'; } } diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xlsx/Chart.php b/PhpOffice/PhpSpreadsheet/Reader/Xlsx/Chart.php old mode 100755 new mode 100644 index 2b920d7..d42df9a --- a/PhpOffice/PhpSpreadsheet/Reader/Xlsx/Chart.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Xlsx/Chart.php @@ -2,22 +2,38 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx; -use PhpOffice\PhpSpreadsheet\Calculation\Functions; +use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError; +use PhpOffice\PhpSpreadsheet\Chart\Axis; +use PhpOffice\PhpSpreadsheet\Chart\ChartColor; use PhpOffice\PhpSpreadsheet\Chart\DataSeries; use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues; +use PhpOffice\PhpSpreadsheet\Chart\GridLines; use PhpOffice\PhpSpreadsheet\Chart\Layout; use PhpOffice\PhpSpreadsheet\Chart\Legend; use PhpOffice\PhpSpreadsheet\Chart\PlotArea; +use PhpOffice\PhpSpreadsheet\Chart\Properties as ChartProperties; use PhpOffice\PhpSpreadsheet\Chart\Title; +use PhpOffice\PhpSpreadsheet\Chart\TrendLine; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx; use PhpOffice\PhpSpreadsheet\RichText\RichText; -use PhpOffice\PhpSpreadsheet\Style\Color; use PhpOffice\PhpSpreadsheet\Style\Font; use SimpleXMLElement; class Chart { + /** @var string */ + private $cNamespace; + + /** @var string */ + private $aNamespace; + + public function __construct(string $cNamespace = Namespaces::CHART, string $aNamespace = Namespaces::DRAWINGML) + { + $this->cNamespace = $cNamespace; + $this->aNamespace = $aNamespace; + } + /** - * @param SimpleXMLElement $component * @param string $name * @param string $format * @@ -26,13 +42,15 @@ class Chart private static function getAttribute(SimpleXMLElement $component, $name, $format) { $attributes = $component->attributes(); - if (isset($attributes[$name])) { + if (@isset($attributes[$name])) { if ($format == 'string') { return (string) $attributes[$name]; } elseif ($format == 'integer') { return (int) $attributes[$name]; } elseif ($format == 'boolean') { - return (bool) ($attributes[$name] === '0' || $attributes[$name] !== 'true') ? false : true; + $value = (string) $attributes[$name]; + + return $value === 'true' || $value === '1'; } return (float) $attributes[$name]; @@ -41,129 +59,264 @@ class Chart return null; } - private static function readColor($color, $background = false) - { - if (isset($color['rgb'])) { - return (string) $color['rgb']; - } elseif (isset($color['indexed'])) { - return Color::indexedColor($color['indexed'] - 7, $background)->getARGB(); - } - } - /** - * @param SimpleXMLElement $chartElements * @param string $chartName * * @return \PhpOffice\PhpSpreadsheet\Chart\Chart */ - public static function readChart(SimpleXMLElement $chartElements, $chartName) + public function readChart(SimpleXMLElement $chartElements, $chartName) { - $namespacesChartMeta = $chartElements->getNamespaces(true); - $chartElementsC = $chartElements->children($namespacesChartMeta['c']); + $chartElementsC = $chartElements->children($this->cNamespace); $XaxisLabel = $YaxisLabel = $legend = $title = null; $dispBlanksAs = $plotVisOnly = null; - + $plotArea = null; + $rotX = $rotY = $rAngAx = $perspective = null; + $xAxis = new Axis(); + $yAxis = new Axis(); + $autoTitleDeleted = null; + $chartNoFill = false; + $gradientArray = []; + $gradientLin = null; + $roundedCorners = false; foreach ($chartElementsC as $chartElementKey => $chartElement) { switch ($chartElementKey) { + case 'spPr': + $possibleNoFill = $chartElementsC->spPr->children($this->aNamespace); + if (isset($possibleNoFill->noFill)) { + $chartNoFill = true; + } + + break; + case 'roundedCorners': + /** @var bool */ + $roundedCorners = self::getAttribute($chartElementsC->roundedCorners, 'val', 'boolean'); + + break; case 'chart': foreach ($chartElement as $chartDetailsKey => $chartDetails) { - $chartDetailsC = $chartDetails->children($namespacesChartMeta['c']); + $chartDetails = Xlsx::testSimpleXml($chartDetails); switch ($chartDetailsKey) { + case 'autoTitleDeleted': + /** @var bool */ + $autoTitleDeleted = self::getAttribute($chartElementsC->chart->autoTitleDeleted, 'val', 'boolean'); + + break; + case 'view3D': + $rotX = self::getAttribute($chartDetails->rotX, 'val', 'integer'); + $rotY = self::getAttribute($chartDetails->rotY, 'val', 'integer'); + $rAngAx = self::getAttribute($chartDetails->rAngAx, 'val', 'integer'); + $perspective = self::getAttribute($chartDetails->perspective, 'val', 'integer'); + + break; case 'plotArea': - $plotAreaLayout = $XaxisLable = $YaxisLable = null; + $plotAreaLayout = $XaxisLabel = $YaxisLabel = null; $plotSeries = $plotAttributes = []; + $catAxRead = false; + $plotNoFill = false; foreach ($chartDetails as $chartDetailKey => $chartDetail) { + $chartDetail = Xlsx::testSimpleXml($chartDetail); switch ($chartDetailKey) { + case 'spPr': + $possibleNoFill = $chartDetails->spPr->children($this->aNamespace); + if (isset($possibleNoFill->noFill)) { + $plotNoFill = true; + } + if (isset($possibleNoFill->gradFill->gsLst)) { + foreach ($possibleNoFill->gradFill->gsLst->gs as $gradient) { + $gradient = Xlsx::testSimpleXml($gradient); + /** @var float */ + $pos = self::getAttribute($gradient, 'pos', 'float'); + $gradientArray[] = [ + $pos / ChartProperties::PERCENTAGE_MULTIPLIER, + new ChartColor($this->readColor($gradient)), + ]; + } + } + if (isset($possibleNoFill->gradFill->lin)) { + $gradientLin = ChartProperties::XmlToAngle((string) self::getAttribute($possibleNoFill->gradFill->lin, 'ang', 'string')); + } + + break; case 'layout': - $plotAreaLayout = self::chartLayoutDetails($chartDetail, $namespacesChartMeta); + $plotAreaLayout = $this->chartLayoutDetails($chartDetail); break; - case 'catAx': + case Axis::AXIS_TYPE_CATEGORY: + case Axis::AXIS_TYPE_DATE: + $catAxRead = true; if (isset($chartDetail->title)) { - $XaxisLabel = self::chartTitle($chartDetail->title->children($namespacesChartMeta['c']), $namespacesChartMeta); + $XaxisLabel = $this->chartTitle($chartDetail->title->children($this->cNamespace)); } + $xAxis->setAxisType($chartDetailKey); + $this->readEffects($chartDetail, $xAxis); + $this->readLineStyle($chartDetail, $xAxis); + if (isset($chartDetail->spPr)) { + $sppr = $chartDetail->spPr->children($this->aNamespace); + if (isset($sppr->solidFill)) { + $axisColorArray = $this->readColor($sppr->solidFill); + $xAxis->setFillParameters($axisColorArray['value'], $axisColorArray['alpha'], $axisColorArray['type']); + } + } + if (isset($chartDetail->majorGridlines)) { + $majorGridlines = new GridLines(); + if (isset($chartDetail->majorGridlines->spPr)) { + $this->readEffects($chartDetail->majorGridlines, $majorGridlines); + $this->readLineStyle($chartDetail->majorGridlines, $majorGridlines); + } + $xAxis->setMajorGridlines($majorGridlines); + } + if (isset($chartDetail->minorGridlines)) { + $minorGridlines = new GridLines(); + $minorGridlines->activateObject(); + if (isset($chartDetail->minorGridlines->spPr)) { + $this->readEffects($chartDetail->minorGridlines, $minorGridlines); + $this->readLineStyle($chartDetail->minorGridlines, $minorGridlines); + } + $xAxis->setMinorGridlines($minorGridlines); + } + $this->setAxisProperties($chartDetail, $xAxis); break; - case 'dateAx': - if (isset($chartDetail->title)) { - $XaxisLabel = self::chartTitle($chartDetail->title->children($namespacesChartMeta['c']), $namespacesChartMeta); + case Axis::AXIS_TYPE_VALUE: + $whichAxis = null; + $axPos = null; + if (isset($chartDetail->axPos)) { + $axPos = self::getAttribute($chartDetail->axPos, 'val', 'string'); } + if ($catAxRead) { + $whichAxis = $yAxis; + $yAxis->setAxisType($chartDetailKey); + } elseif (!empty($axPos)) { + switch ($axPos) { + case 't': + case 'b': + $whichAxis = $xAxis; + $xAxis->setAxisType($chartDetailKey); - break; - case 'valAx': - if (isset($chartDetail->title)) { - $YaxisLabel = self::chartTitle($chartDetail->title->children($namespacesChartMeta['c']), $namespacesChartMeta); + break; + case 'r': + case 'l': + $whichAxis = $yAxis; + $yAxis->setAxisType($chartDetailKey); + + break; + } } + if (isset($chartDetail->title)) { + $axisLabel = $this->chartTitle($chartDetail->title->children($this->cNamespace)); + + switch ($axPos) { + case 't': + case 'b': + $XaxisLabel = $axisLabel; + + break; + case 'r': + case 'l': + $YaxisLabel = $axisLabel; + + break; + } + } + $this->readEffects($chartDetail, $whichAxis); + $this->readLineStyle($chartDetail, $whichAxis); + if ($whichAxis !== null && isset($chartDetail->spPr)) { + $sppr = $chartDetail->spPr->children($this->aNamespace); + if (isset($sppr->solidFill)) { + $axisColorArray = $this->readColor($sppr->solidFill); + $whichAxis->setFillParameters($axisColorArray['value'], $axisColorArray['alpha'], $axisColorArray['type']); + } + } + if ($whichAxis !== null && isset($chartDetail->majorGridlines)) { + $majorGridlines = new GridLines(); + if (isset($chartDetail->majorGridlines->spPr)) { + $this->readEffects($chartDetail->majorGridlines, $majorGridlines); + $this->readLineStyle($chartDetail->majorGridlines, $majorGridlines); + } + $whichAxis->setMajorGridlines($majorGridlines); + } + if ($whichAxis !== null && isset($chartDetail->minorGridlines)) { + $minorGridlines = new GridLines(); + $minorGridlines->activateObject(); + if (isset($chartDetail->minorGridlines->spPr)) { + $this->readEffects($chartDetail->minorGridlines, $minorGridlines); + $this->readLineStyle($chartDetail->minorGridlines, $minorGridlines); + } + $whichAxis->setMinorGridlines($minorGridlines); + } + $this->setAxisProperties($chartDetail, $whichAxis); break; case 'barChart': case 'bar3DChart': $barDirection = self::getAttribute($chartDetail->barDir, 'val', 'string'); - $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey); - $plotSer->setPlotDirection($barDirection); + $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey); + $plotSer->setPlotDirection("$barDirection"); $plotSeries[] = $plotSer; - $plotAttributes = self::readChartAttributes($chartDetail); + $plotAttributes = $this->readChartAttributes($chartDetail); break; case 'lineChart': case 'line3DChart': - $plotSeries[] = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey); - $plotAttributes = self::readChartAttributes($chartDetail); + $plotSeries[] = $this->chartDataSeries($chartDetail, $chartDetailKey); + $plotAttributes = $this->readChartAttributes($chartDetail); break; case 'areaChart': case 'area3DChart': - $plotSeries[] = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey); - $plotAttributes = self::readChartAttributes($chartDetail); + $plotSeries[] = $this->chartDataSeries($chartDetail, $chartDetailKey); + $plotAttributes = $this->readChartAttributes($chartDetail); break; case 'doughnutChart': case 'pieChart': case 'pie3DChart': - $explosion = isset($chartDetail->ser->explosion); - $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey); - $plotSer->setPlotStyle($explosion); + $explosion = self::getAttribute($chartDetail->ser->explosion, 'val', 'string'); + $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey); + $plotSer->setPlotStyle("$explosion"); $plotSeries[] = $plotSer; - $plotAttributes = self::readChartAttributes($chartDetail); + $plotAttributes = $this->readChartAttributes($chartDetail); break; case 'scatterChart': + /** @var string */ $scatterStyle = self::getAttribute($chartDetail->scatterStyle, 'val', 'string'); - $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey); + $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey); $plotSer->setPlotStyle($scatterStyle); $plotSeries[] = $plotSer; - $plotAttributes = self::readChartAttributes($chartDetail); + $plotAttributes = $this->readChartAttributes($chartDetail); break; case 'bubbleChart': $bubbleScale = self::getAttribute($chartDetail->bubbleScale, 'val', 'integer'); - $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey); - $plotSer->setPlotStyle($bubbleScale); + $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey); + $plotSer->setPlotStyle("$bubbleScale"); $plotSeries[] = $plotSer; - $plotAttributes = self::readChartAttributes($chartDetail); + $plotAttributes = $this->readChartAttributes($chartDetail); break; case 'radarChart': + /** @var string */ $radarStyle = self::getAttribute($chartDetail->radarStyle, 'val', 'string'); - $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey); + $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey); $plotSer->setPlotStyle($radarStyle); $plotSeries[] = $plotSer; - $plotAttributes = self::readChartAttributes($chartDetail); + $plotAttributes = $this->readChartAttributes($chartDetail); break; case 'surfaceChart': case 'surface3DChart': $wireFrame = self::getAttribute($chartDetail->wireframe, 'val', 'boolean'); - $plotSer = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey); - $plotSer->setPlotStyle($wireFrame); + $plotSer = $this->chartDataSeries($chartDetail, $chartDetailKey); + $plotSer->setPlotStyle("$wireFrame"); $plotSeries[] = $plotSer; - $plotAttributes = self::readChartAttributes($chartDetail); + $plotAttributes = $this->readChartAttributes($chartDetail); break; case 'stockChart': - $plotSeries[] = self::chartDataSeries($chartDetail, $namespacesChartMeta, $chartDetailKey); - $plotAttributes = self::readChartAttributes($plotAreaLayout); + $plotSeries[] = $this->chartDataSeries($chartDetail, $chartDetailKey); + $plotAttributes = $this->readChartAttributes($chartDetail); break; } @@ -172,7 +325,13 @@ class Chart $plotAreaLayout = new Layout(); } $plotArea = new PlotArea($plotAreaLayout, $plotSeries); - self::setChartAttributes($plotAreaLayout, $plotAttributes); + $this->setChartAttributes($plotAreaLayout, $plotAttributes); + if ($plotNoFill) { + $plotArea->setNoFill(true); + } + if (!empty($gradientArray)) { + $plotArea->setGradientFillProperties($gradientArray, $gradientLin); + } break; case 'plotVisOnly': @@ -184,7 +343,7 @@ class Chart break; case 'title': - $title = self::chartTitle($chartDetails, $namespacesChartMeta); + $title = $this->chartTitle($chartDetails); break; case 'legend': @@ -192,6 +351,7 @@ class Chart $legendLayout = null; $legendOverlay = false; foreach ($chartDetails as $chartDetailKey => $chartDetail) { + $chartDetail = Xlsx::testSimpleXml($chartDetail); switch ($chartDetailKey) { case 'legendPos': $legendPos = self::getAttribute($chartDetail, 'val', 'string'); @@ -202,42 +362,71 @@ class Chart break; case 'layout': - $legendLayout = self::chartLayoutDetails($chartDetail, $namespacesChartMeta); + $legendLayout = $this->chartLayoutDetails($chartDetail); break; } } - $legend = new Legend($legendPos, $legendLayout, $legendOverlay); + $legend = new Legend("$legendPos", $legendLayout, (bool) $legendOverlay); break; } } } } - $chart = new \PhpOffice\PhpSpreadsheet\Chart\Chart($chartName, $title, $legend, $plotArea, $plotVisOnly, $dispBlanksAs, $XaxisLabel, $YaxisLabel); + $chart = new \PhpOffice\PhpSpreadsheet\Chart\Chart($chartName, $title, $legend, $plotArea, $plotVisOnly, (string) $dispBlanksAs, $XaxisLabel, $YaxisLabel, $xAxis, $yAxis); + if ($chartNoFill) { + $chart->setNoFill(true); + } + $chart->setRoundedCorners($roundedCorners); + if (is_bool($autoTitleDeleted)) { + $chart->setAutoTitleDeleted($autoTitleDeleted); + } + if (is_int($rotX)) { + $chart->setRotX($rotX); + } + if (is_int($rotY)) { + $chart->setRotY($rotY); + } + if (is_int($rAngAx)) { + $chart->setRAngAx($rAngAx); + } + if (is_int($perspective)) { + $chart->setPerspective($perspective); + } return $chart; } - private static function chartTitle(SimpleXMLElement $titleDetails, array $namespacesChartMeta) + private function chartTitle(SimpleXMLElement $titleDetails): Title { $caption = []; $titleLayout = null; foreach ($titleDetails as $titleDetailKey => $chartDetail) { + $chartDetail = Xlsx::testSimpleXml($chartDetail); switch ($titleDetailKey) { case 'tx': - $titleDetails = $chartDetail->rich->children($namespacesChartMeta['a']); - foreach ($titleDetails as $titleKey => $titleDetail) { - switch ($titleKey) { - case 'p': - $titleDetailPart = $titleDetail->children($namespacesChartMeta['a']); - $caption[] = self::parseRichText($titleDetailPart); + if (isset($chartDetail->rich)) { + $titleDetails = $chartDetail->rich->children($this->aNamespace); + foreach ($titleDetails as $titleKey => $titleDetail) { + $titleDetail = Xlsx::testSimpleXml($titleDetail); + switch ($titleKey) { + case 'p': + $titleDetailPart = $titleDetail->children($this->aNamespace); + $caption[] = $this->parseRichText($titleDetailPart); + } + } + } elseif (isset($chartDetail->strRef->strCache)) { + foreach ($chartDetail->strRef->strCache->pt as $pt) { + if (isset($pt->v)) { + $caption[] = (string) $pt->v; + } } } break; case 'layout': - $titleLayout = self::chartLayoutDetails($chartDetail, $namespacesChartMeta); + $titleLayout = $this->chartLayoutDetails($chartDetail); break; } @@ -246,30 +435,31 @@ class Chart return new Title($caption, $titleLayout); } - private static function chartLayoutDetails($chartDetail, $namespacesChartMeta) + private function chartLayoutDetails(SimpleXMLElement $chartDetail): ?Layout { if (!isset($chartDetail->manualLayout)) { return null; } - $details = $chartDetail->manualLayout->children($namespacesChartMeta['c']); + $details = $chartDetail->manualLayout->children($this->cNamespace); if ($details === null) { return null; } $layout = []; foreach ($details as $detailKey => $detail) { + $detail = Xlsx::testSimpleXml($detail); $layout[$detailKey] = self::getAttribute($detail, 'val', 'string'); } return new Layout($layout); } - private static function chartDataSeries($chartDetail, $namespacesChartMeta, $plotType) + private function chartDataSeries(SimpleXMLElement $chartDetail, string $plotType): DataSeries { $multiSeriesType = null; $smoothLine = false; - $seriesLabel = $seriesCategory = $seriesValues = $plotOrder = []; + $seriesLabel = $seriesCategory = $seriesValues = $plotOrder = $seriesBubbles = []; - $seriesDetailSet = $chartDetail->children($namespacesChartMeta['c']); + $seriesDetailSet = $chartDetail->children($this->cNamespace); foreach ($seriesDetailSet as $seriesDetailKey => $seriesDetails) { switch ($seriesDetailKey) { case 'grouping': @@ -279,7 +469,18 @@ class Chart case 'ser': $marker = null; $seriesIndex = ''; + $fillColor = null; + $pointSize = null; + $noFill = false; + $bubble3D = false; + $dptColors = []; + $markerFillColor = null; + $markerBorderColor = null; + $lineStyle = null; + $labelLayout = null; + $trendLines = []; foreach ($seriesDetails as $seriesKey => $seriesDetail) { + $seriesDetail = Xlsx::testSimpleXml($seriesDetail); switch ($seriesKey) { case 'idx': $seriesIndex = self::getAttribute($seriesDetail, 'val', 'integer'); @@ -291,11 +492,89 @@ class Chart break; case 'tx': - $seriesLabel[$seriesIndex] = self::chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta); + $seriesLabel[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail); + + break; + case 'spPr': + $children = $seriesDetail->children($this->aNamespace); + if (isset($children->ln)) { + $ln = $children->ln; + if (is_countable($ln->noFill) && count($ln->noFill) === 1) { + $noFill = true; + } + $lineStyle = new GridLines(); + $this->readLineStyle($seriesDetails, $lineStyle); + } + if (isset($children->effectLst)) { + if ($lineStyle === null) { + $lineStyle = new GridLines(); + } + $this->readEffects($seriesDetails, $lineStyle); + } + if (isset($children->solidFill)) { + $fillColor = new ChartColor($this->readColor($children->solidFill)); + } + + break; + case 'dPt': + $dptIdx = (int) self::getAttribute($seriesDetail->idx, 'val', 'string'); + if (isset($seriesDetail->spPr)) { + $children = $seriesDetail->spPr->children($this->aNamespace); + if (isset($children->solidFill)) { + $arrayColors = $this->readColor($children->solidFill); + $dptColors[$dptIdx] = new ChartColor($arrayColors); + } + } + + break; + case 'trendline': + $trendLine = new TrendLine(); + $this->readLineStyle($seriesDetail, $trendLine); + /** @var ?string */ + $trendLineType = self::getAttribute($seriesDetail->trendlineType, 'val', 'string'); + /** @var ?bool */ + $dispRSqr = self::getAttribute($seriesDetail->dispRSqr, 'val', 'boolean'); + /** @var ?bool */ + $dispEq = self::getAttribute($seriesDetail->dispEq, 'val', 'boolean'); + /** @var ?int */ + $order = self::getAttribute($seriesDetail->order, 'val', 'integer'); + /** @var ?int */ + $period = self::getAttribute($seriesDetail->period, 'val', 'integer'); + /** @var ?float */ + $forward = self::getAttribute($seriesDetail->forward, 'val', 'float'); + /** @var ?float */ + $backward = self::getAttribute($seriesDetail->backward, 'val', 'float'); + /** @var ?float */ + $intercept = self::getAttribute($seriesDetail->intercept, 'val', 'float'); + /** @var ?string */ + $name = (string) $seriesDetail->name; + $trendLine->setTrendLineProperties( + $trendLineType, + $order, + $period, + $dispRSqr, + $dispEq, + $backward, + $forward, + $intercept, + $name + ); + $trendLines[] = $trendLine; break; case 'marker': $marker = self::getAttribute($seriesDetail->symbol, 'val', 'string'); + $pointSize = self::getAttribute($seriesDetail->size, 'val', 'string'); + $pointSize = is_numeric($pointSize) ? ((int) $pointSize) : null; + if (isset($seriesDetail->spPr)) { + $children = $seriesDetail->spPr->children($this->aNamespace); + if (isset($children->solidFill)) { + $markerFillColor = $this->readColor($children->solidFill); + } + if (isset($children->ln->solidFill)) { + $markerBorderColor = $this->readColor($children->ln->solidFill); + } + } break; case 'smooth': @@ -303,65 +582,218 @@ class Chart break; case 'cat': - $seriesCategory[$seriesIndex] = self::chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta); + $seriesCategory[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail); break; case 'val': - $seriesValues[$seriesIndex] = self::chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta, $marker); + $seriesValues[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize"); break; case 'xVal': - $seriesCategory[$seriesIndex] = self::chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta, $marker); + $seriesCategory[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize"); break; case 'yVal': - $seriesValues[$seriesIndex] = self::chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta, $marker); + $seriesValues[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize"); + + break; + case 'bubbleSize': + $seriesBubbles[$seriesIndex] = $this->chartDataSeriesValueSet($seriesDetail, "$marker", $fillColor, "$pointSize"); + + break; + case 'bubble3D': + $bubble3D = self::getAttribute($seriesDetail, 'val', 'boolean'); + + break; + case 'dLbls': + $labelLayout = new Layout($this->readChartAttributes($seriesDetails)); break; } } + if ($labelLayout) { + if (isset($seriesLabel[$seriesIndex])) { + $seriesLabel[$seriesIndex]->setLabelLayout($labelLayout); + } + if (isset($seriesCategory[$seriesIndex])) { + $seriesCategory[$seriesIndex]->setLabelLayout($labelLayout); + } + if (isset($seriesValues[$seriesIndex])) { + $seriesValues[$seriesIndex]->setLabelLayout($labelLayout); + } + } + if ($noFill) { + if (isset($seriesLabel[$seriesIndex])) { + $seriesLabel[$seriesIndex]->setScatterLines(false); + } + if (isset($seriesCategory[$seriesIndex])) { + $seriesCategory[$seriesIndex]->setScatterLines(false); + } + if (isset($seriesValues[$seriesIndex])) { + $seriesValues[$seriesIndex]->setScatterLines(false); + } + } + if ($lineStyle !== null) { + if (isset($seriesLabel[$seriesIndex])) { + $seriesLabel[$seriesIndex]->copyLineStyles($lineStyle); + } + if (isset($seriesCategory[$seriesIndex])) { + $seriesCategory[$seriesIndex]->copyLineStyles($lineStyle); + } + if (isset($seriesValues[$seriesIndex])) { + $seriesValues[$seriesIndex]->copyLineStyles($lineStyle); + } + } + if ($bubble3D) { + if (isset($seriesLabel[$seriesIndex])) { + $seriesLabel[$seriesIndex]->setBubble3D($bubble3D); + } + if (isset($seriesCategory[$seriesIndex])) { + $seriesCategory[$seriesIndex]->setBubble3D($bubble3D); + } + if (isset($seriesValues[$seriesIndex])) { + $seriesValues[$seriesIndex]->setBubble3D($bubble3D); + } + } + if (!empty($dptColors)) { + if (isset($seriesLabel[$seriesIndex])) { + $seriesLabel[$seriesIndex]->setFillColor($dptColors); + } + if (isset($seriesCategory[$seriesIndex])) { + $seriesCategory[$seriesIndex]->setFillColor($dptColors); + } + if (isset($seriesValues[$seriesIndex])) { + $seriesValues[$seriesIndex]->setFillColor($dptColors); + } + } + if ($markerFillColor !== null) { + if (isset($seriesLabel[$seriesIndex])) { + $seriesLabel[$seriesIndex]->getMarkerFillColor()->setColorPropertiesArray($markerFillColor); + } + if (isset($seriesCategory[$seriesIndex])) { + $seriesCategory[$seriesIndex]->getMarkerFillColor()->setColorPropertiesArray($markerFillColor); + } + if (isset($seriesValues[$seriesIndex])) { + $seriesValues[$seriesIndex]->getMarkerFillColor()->setColorPropertiesArray($markerFillColor); + } + } + if ($markerBorderColor !== null) { + if (isset($seriesLabel[$seriesIndex])) { + $seriesLabel[$seriesIndex]->getMarkerBorderColor()->setColorPropertiesArray($markerBorderColor); + } + if (isset($seriesCategory[$seriesIndex])) { + $seriesCategory[$seriesIndex]->getMarkerBorderColor()->setColorPropertiesArray($markerBorderColor); + } + if (isset($seriesValues[$seriesIndex])) { + $seriesValues[$seriesIndex]->getMarkerBorderColor()->setColorPropertiesArray($markerBorderColor); + } + } + if ($smoothLine) { + if (isset($seriesLabel[$seriesIndex])) { + $seriesLabel[$seriesIndex]->setSmoothLine(true); + } + if (isset($seriesCategory[$seriesIndex])) { + $seriesCategory[$seriesIndex]->setSmoothLine(true); + } + if (isset($seriesValues[$seriesIndex])) { + $seriesValues[$seriesIndex]->setSmoothLine(true); + } + } + if (!empty($trendLines)) { + if (isset($seriesLabel[$seriesIndex])) { + $seriesLabel[$seriesIndex]->setTrendLines($trendLines); + } + if (isset($seriesCategory[$seriesIndex])) { + $seriesCategory[$seriesIndex]->setTrendLines($trendLines); + } + if (isset($seriesValues[$seriesIndex])) { + $seriesValues[$seriesIndex]->setTrendLines($trendLines); + } + } } } + /** @phpstan-ignore-next-line */ + $series = new DataSeries($plotType, $multiSeriesType, $plotOrder, $seriesLabel, $seriesCategory, $seriesValues, $smoothLine); + $series->setPlotBubbleSizes($seriesBubbles); - return new DataSeries($plotType, $multiSeriesType, $plotOrder, $seriesLabel, $seriesCategory, $seriesValues, $smoothLine); + return $series; } - private static function chartDataSeriesValueSet($seriesDetail, $namespacesChartMeta, $marker = null) + /** + * @return mixed + */ + private function chartDataSeriesValueSet(SimpleXMLElement $seriesDetail, ?string $marker = null, ?ChartColor $fillColor = null, ?string $pointSize = null) { if (isset($seriesDetail->strRef)) { $seriesSource = (string) $seriesDetail->strRef->f; - $seriesData = self::chartDataSeriesValues($seriesDetail->strRef->strCache->children($namespacesChartMeta['c']), 's'); + $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize"); - return new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, $seriesData['formatCode'], $seriesData['pointCount'], $seriesData['dataValues'], $marker); + if (isset($seriesDetail->strRef->strCache)) { + $seriesData = $this->chartDataSeriesValues($seriesDetail->strRef->strCache->children($this->cNamespace), 's'); + $seriesValues + ->setFormatCode($seriesData['formatCode']) + ->setDataValues($seriesData['dataValues']); + } + + return $seriesValues; } elseif (isset($seriesDetail->numRef)) { $seriesSource = (string) $seriesDetail->numRef->f; - $seriesData = self::chartDataSeriesValues($seriesDetail->numRef->numCache->children($namespacesChartMeta['c'])); + $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize"); + if (isset($seriesDetail->numRef->numCache)) { + $seriesData = $this->chartDataSeriesValues($seriesDetail->numRef->numCache->children($this->cNamespace)); + $seriesValues + ->setFormatCode($seriesData['formatCode']) + ->setDataValues($seriesData['dataValues']); + } - return new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_NUMBER, $seriesSource, $seriesData['formatCode'], $seriesData['pointCount'], $seriesData['dataValues'], $marker); + return $seriesValues; } elseif (isset($seriesDetail->multiLvlStrRef)) { $seriesSource = (string) $seriesDetail->multiLvlStrRef->f; - $seriesData = self::chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlStrRef->multiLvlStrCache->children($namespacesChartMeta['c']), 's'); - $seriesData['pointCount'] = count($seriesData['dataValues']); + $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize"); - return new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, $seriesData['formatCode'], $seriesData['pointCount'], $seriesData['dataValues'], $marker); + if (isset($seriesDetail->multiLvlStrRef->multiLvlStrCache)) { + $seriesData = $this->chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlStrRef->multiLvlStrCache->children($this->cNamespace), 's'); + $seriesValues + ->setFormatCode($seriesData['formatCode']) + ->setDataValues($seriesData['dataValues']); + } + + return $seriesValues; } elseif (isset($seriesDetail->multiLvlNumRef)) { $seriesSource = (string) $seriesDetail->multiLvlNumRef->f; - $seriesData = self::chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlNumRef->multiLvlNumCache->children($namespacesChartMeta['c']), 's'); - $seriesData['pointCount'] = count($seriesData['dataValues']); + $seriesValues = new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, null, 0, null, $marker, $fillColor, "$pointSize"); - return new DataSeriesValues(DataSeriesValues::DATASERIES_TYPE_STRING, $seriesSource, $seriesData['formatCode'], $seriesData['pointCount'], $seriesData['dataValues'], $marker); + if (isset($seriesDetail->multiLvlNumRef->multiLvlNumCache)) { + $seriesData = $this->chartDataSeriesValuesMultiLevel($seriesDetail->multiLvlNumRef->multiLvlNumCache->children($this->cNamespace), 's'); + $seriesValues + ->setFormatCode($seriesData['formatCode']) + ->setDataValues($seriesData['dataValues']); + } + + return $seriesValues; + } + + if (isset($seriesDetail->v)) { + return new DataSeriesValues( + DataSeriesValues::DATASERIES_TYPE_STRING, + null, + null, + 1, + [(string) $seriesDetail->v] + ); } return null; } - private static function chartDataSeriesValues($seriesValueSet, $dataType = 'n') + private function chartDataSeriesValues(SimpleXMLElement $seriesValueSet, string $dataType = 'n'): array { $seriesVal = []; $formatCode = ''; $pointCount = 0; foreach ($seriesValueSet as $seriesValueIdx => $seriesValue) { + $seriesValue = Xlsx::testSimpleXml($seriesValue); switch ($seriesValueIdx) { case 'ptCount': $pointCount = self::getAttribute($seriesValue, 'val', 'integer'); @@ -375,7 +807,7 @@ class Chart $pointVal = self::getAttribute($seriesValue, 'idx', 'integer'); if ($dataType == 's') { $seriesVal[$pointVal] = (string) $seriesValue->v; - } elseif ($seriesValue->v === Functions::NA()) { + } elseif ((string) $seriesValue->v === ExcelError::NA()) { $seriesVal[$pointVal] = null; } else { $seriesVal[$pointVal] = (float) $seriesValue->v; @@ -392,7 +824,7 @@ class Chart ]; } - private static function chartDataSeriesValuesMultiLevel($seriesValueSet, $dataType = 'n') + private function chartDataSeriesValuesMultiLevel(SimpleXMLElement $seriesValueSet, string $dataType = 'n'): array { $seriesVal = []; $formatCode = ''; @@ -400,6 +832,7 @@ class Chart foreach ($seriesValueSet->lvl as $seriesLevelIdx => $seriesLevel) { foreach ($seriesLevel as $seriesValueIdx => $seriesValue) { + $seriesValue = Xlsx::testSimpleXml($seriesValue); switch ($seriesValueIdx) { case 'ptCount': $pointCount = self::getAttribute($seriesValue, 'val', 'integer'); @@ -413,7 +846,7 @@ class Chart $pointVal = self::getAttribute($seriesValue, 'idx', 'integer'); if ($dataType == 's') { $seriesVal[$pointVal][] = (string) $seriesValue->v; - } elseif ($seriesValue->v === Functions::NA()) { + } elseif ((string) $seriesValue->v === ExcelError::NA()) { $seriesVal[$pointVal] = null; } else { $seriesVal[$pointVal][] = (float) $seriesValue->v; @@ -431,78 +864,234 @@ class Chart ]; } - private static function parseRichText(SimpleXMLElement $titleDetailPart) + private function parseRichText(SimpleXMLElement $titleDetailPart): RichText { $value = new RichText(); - $objText = null; - foreach ($titleDetailPart as $titleDetailElementKey => $titleDetailElement) { - if (isset($titleDetailElement->t)) { - $objText = $value->createTextRun((string) $titleDetailElement->t); + $defaultFontSize = null; + $defaultBold = null; + $defaultItalic = null; + $defaultUnderscore = null; + $defaultStrikethrough = null; + $defaultBaseline = null; + $defaultFontName = null; + $defaultLatin = null; + $defaultEastAsian = null; + $defaultComplexScript = null; + $defaultFontColor = null; + if (isset($titleDetailPart->pPr->defRPr)) { + /** @var ?int */ + $defaultFontSize = self::getAttribute($titleDetailPart->pPr->defRPr, 'sz', 'integer'); + /** @var ?bool */ + $defaultBold = self::getAttribute($titleDetailPart->pPr->defRPr, 'b', 'boolean'); + /** @var ?bool */ + $defaultItalic = self::getAttribute($titleDetailPart->pPr->defRPr, 'i', 'boolean'); + /** @var ?string */ + $defaultUnderscore = self::getAttribute($titleDetailPart->pPr->defRPr, 'u', 'string'); + /** @var ?string */ + $defaultStrikethrough = self::getAttribute($titleDetailPart->pPr->defRPr, 'strike', 'string'); + /** @var ?int */ + $defaultBaseline = self::getAttribute($titleDetailPart->pPr->defRPr, 'baseline', 'integer'); + if (isset($titleDetailPart->defRPr->rFont['val'])) { + $defaultFontName = (string) $titleDetailPart->defRPr->rFont['val']; } + if (isset($titleDetailPart->pPr->defRPr->latin)) { + /** @var ?string */ + $defaultLatin = self::getAttribute($titleDetailPart->pPr->defRPr->latin, 'typeface', 'string'); + } + if (isset($titleDetailPart->pPr->defRPr->ea)) { + /** @var ?string */ + $defaultEastAsian = self::getAttribute($titleDetailPart->pPr->defRPr->ea, 'typeface', 'string'); + } + if (isset($titleDetailPart->pPr->defRPr->cs)) { + /** @var ?string */ + $defaultComplexScript = self::getAttribute($titleDetailPart->pPr->defRPr->cs, 'typeface', 'string'); + } + if (isset($titleDetailPart->pPr->defRPr->solidFill)) { + $defaultFontColor = $this->readColor($titleDetailPart->pPr->defRPr->solidFill); + } + } + foreach ($titleDetailPart as $titleDetailElementKey => $titleDetailElement) { + if ( + (string) $titleDetailElementKey !== 'r' + || !isset($titleDetailElement->t) + ) { + continue; + } + $objText = $value->createTextRun((string) $titleDetailElement->t); + if ($objText->getFont() === null) { + // @codeCoverageIgnoreStart + continue; + // @codeCoverageIgnoreEnd + } + $fontSize = null; + $bold = null; + $italic = null; + $underscore = null; + $strikethrough = null; + $baseline = null; + $fontName = null; + $latinName = null; + $eastAsian = null; + $complexScript = null; + $fontColor = null; + $underlineColor = null; if (isset($titleDetailElement->rPr)) { + // not used now, not sure it ever was, grandfathering if (isset($titleDetailElement->rPr->rFont['val'])) { - $objText->getFont()->setName((string) $titleDetailElement->rPr->rFont['val']); - } - - $fontSize = (self::getAttribute($titleDetailElement->rPr, 'sz', 'integer')); - if ($fontSize !== null) { - $objText->getFont()->setSize(floor($fontSize / 100)); - } - - $fontColor = (self::getAttribute($titleDetailElement->rPr, 'color', 'string')); - if ($fontColor !== null) { - $objText->getFont()->setColor(new Color(self::readColor($fontColor))); + // @codeCoverageIgnoreStart + $fontName = (string) $titleDetailElement->rPr->rFont['val']; + // @codeCoverageIgnoreEnd + } + if (isset($titleDetailElement->rPr->latin)) { + /** @var ?string */ + $latinName = self::getAttribute($titleDetailElement->rPr->latin, 'typeface', 'string'); + } + if (isset($titleDetailElement->rPr->ea)) { + /** @var ?string */ + $eastAsian = self::getAttribute($titleDetailElement->rPr->ea, 'typeface', 'string'); + } + if (isset($titleDetailElement->rPr->cs)) { + /** @var ?string */ + $complexScript = self::getAttribute($titleDetailElement->rPr->cs, 'typeface', 'string'); + } + /** @var ?int */ + $fontSize = self::getAttribute($titleDetailElement->rPr, 'sz', 'integer'); + + // not used now, not sure it ever was, grandfathering + if (isset($titleDetailElement->rPr->solidFill)) { + $fontColor = $this->readColor($titleDetailElement->rPr->solidFill); } + /** @var ?bool */ $bold = self::getAttribute($titleDetailElement->rPr, 'b', 'boolean'); - if ($bold !== null) { - $objText->getFont()->setBold($bold); - } + /** @var ?bool */ $italic = self::getAttribute($titleDetailElement->rPr, 'i', 'boolean'); - if ($italic !== null) { - $objText->getFont()->setItalic($italic); - } + /** @var ?int */ $baseline = self::getAttribute($titleDetailElement->rPr, 'baseline', 'integer'); - if ($baseline !== null) { - if ($baseline > 0) { - $objText->getFont()->setSuperscript(true); - } elseif ($baseline < 0) { - $objText->getFont()->setSubscript(true); - } + + /** @var ?string */ + $underscore = self::getAttribute($titleDetailElement->rPr, 'u', 'string'); + if (isset($titleDetailElement->rPr->uFill->solidFill)) { + $underlineColor = $this->readColor($titleDetailElement->rPr->uFill->solidFill); } - $underscore = (self::getAttribute($titleDetailElement->rPr, 'u', 'string')); - if ($underscore !== null) { - if ($underscore == 'sng') { - $objText->getFont()->setUnderline(Font::UNDERLINE_SINGLE); - } elseif ($underscore == 'dbl') { - $objText->getFont()->setUnderline(Font::UNDERLINE_DOUBLE); - } else { - $objText->getFont()->setUnderline(Font::UNDERLINE_NONE); - } - } + /** @var ?string */ + $strikethrough = self::getAttribute($titleDetailElement->rPr, 'strike', 'string'); + } - $strikethrough = (self::getAttribute($titleDetailElement->rPr, 's', 'string')); - if ($strikethrough !== null) { - if ($strikethrough == 'noStrike') { - $objText->getFont()->setStrikethrough(false); - } else { - $objText->getFont()->setStrikethrough(true); - } + $fontFound = false; + $latinName = $latinName ?? $defaultLatin; + if ($latinName !== null) { + $objText->getFont()->setLatin($latinName); + $fontFound = true; + } + $eastAsian = $eastAsian ?? $defaultEastAsian; + if ($eastAsian !== null) { + $objText->getFont()->setEastAsian($eastAsian); + $fontFound = true; + } + $complexScript = $complexScript ?? $defaultComplexScript; + if ($complexScript !== null) { + $objText->getFont()->setComplexScript($complexScript); + $fontFound = true; + } + $fontName = $fontName ?? $defaultFontName; + if ($fontName !== null) { + // @codeCoverageIgnoreStart + $objText->getFont()->setName($fontName); + $fontFound = true; + // @codeCoverageIgnoreEnd + } + + $fontSize = $fontSize ?? $defaultFontSize; + if (is_int($fontSize)) { + $objText->getFont()->setSize(floor($fontSize / 100)); + $fontFound = true; + } else { + $objText->getFont()->setSize(null, true); + } + + $fontColor = $fontColor ?? $defaultFontColor; + if (!empty($fontColor)) { + $objText->getFont()->setChartColor($fontColor); + $fontFound = true; + } + + $bold = $bold ?? $defaultBold; + if ($bold !== null) { + $objText->getFont()->setBold($bold); + $fontFound = true; + } + + $italic = $italic ?? $defaultItalic; + if ($italic !== null) { + $objText->getFont()->setItalic($italic); + $fontFound = true; + } + + $baseline = $baseline ?? $defaultBaseline; + if ($baseline !== null) { + $objText->getFont()->setBaseLine($baseline); + if ($baseline > 0) { + $objText->getFont()->setSuperscript(true); + } elseif ($baseline < 0) { + $objText->getFont()->setSubscript(true); } + $fontFound = true; + } + + $underscore = $underscore ?? $defaultUnderscore; + if ($underscore !== null) { + if ($underscore == 'sng') { + $objText->getFont()->setUnderline(Font::UNDERLINE_SINGLE); + } elseif ($underscore == 'dbl') { + $objText->getFont()->setUnderline(Font::UNDERLINE_DOUBLE); + } elseif ($underscore !== '') { + $objText->getFont()->setUnderline($underscore); + } else { + $objText->getFont()->setUnderline(Font::UNDERLINE_NONE); + } + $fontFound = true; + if ($underlineColor) { + $objText->getFont()->setUnderlineColor($underlineColor); + } + } + + $strikethrough = $strikethrough ?? $defaultStrikethrough; + if ($strikethrough !== null) { + $objText->getFont()->setStrikeType($strikethrough); + if ($strikethrough == 'noStrike') { + $objText->getFont()->setStrikethrough(false); + } else { + $objText->getFont()->setStrikethrough(true); + } + $fontFound = true; + } + if ($fontFound === false) { + $objText->setFont(null); } } return $value; } - private static function readChartAttributes($chartDetail) + /** + * @param ?SimpleXMLElement $chartDetail + */ + private function readChartAttributes($chartDetail): array { $plotAttributes = []; if (isset($chartDetail->dLbls)) { - if (isset($chartDetail->dLbls->howLegendKey)) { + if (isset($chartDetail->dLbls->dLblPos)) { + $plotAttributes['dLblPos'] = self::getAttribute($chartDetail->dLbls->dLblPos, 'val', 'string'); + } + if (isset($chartDetail->dLbls->numFmt)) { + $plotAttributes['numFmtCode'] = self::getAttribute($chartDetail->dLbls->numFmt, 'formatCode', 'string'); + $plotAttributes['numFmtLinked'] = self::getAttribute($chartDetail->dLbls->numFmt, 'sourceLinked', 'boolean'); + } + if (isset($chartDetail->dLbls->showLegendKey)) { $plotAttributes['showLegendKey'] = self::getAttribute($chartDetail->dLbls->showLegendKey, 'val', 'string'); } if (isset($chartDetail->dLbls->showVal)) { @@ -523,16 +1112,30 @@ class Chart if (isset($chartDetail->dLbls->showLeaderLines)) { $plotAttributes['showLeaderLines'] = self::getAttribute($chartDetail->dLbls->showLeaderLines, 'val', 'string'); } + if (isset($chartDetail->dLbls->spPr)) { + $sppr = $chartDetail->dLbls->spPr->children($this->aNamespace); + if (isset($sppr->solidFill)) { + $plotAttributes['labelFillColor'] = new ChartColor($this->readColor($sppr->solidFill)); + } + if (isset($sppr->ln->solidFill)) { + $plotAttributes['labelBorderColor'] = new ChartColor($this->readColor($sppr->ln->solidFill)); + } + } + if (isset($chartDetail->dLbls->txPr)) { + $txpr = $chartDetail->dLbls->txPr->children($this->aNamespace); + if (isset($txpr->p->pPr->defRPr->solidFill)) { + $plotAttributes['labelFontColor'] = new ChartColor($this->readColor($txpr->p->pPr->defRPr->solidFill)); + } + } } return $plotAttributes; } /** - * @param Layout $plotArea * @param mixed $plotAttributes */ - private static function setChartAttributes(Layout $plotArea, $plotAttributes) + private function setChartAttributes(Layout $plotArea, $plotAttributes): void { foreach ($plotAttributes as $plotAttributeKey => $plotAttributeValue) { switch ($plotAttributeKey) { @@ -567,4 +1170,250 @@ class Chart } } } + + private function readEffects(SimpleXMLElement $chartDetail, ?ChartProperties $chartObject): void + { + if (!isset($chartObject, $chartDetail->spPr)) { + return; + } + $sppr = $chartDetail->spPr->children($this->aNamespace); + + if (isset($sppr->effectLst->glow)) { + $axisGlowSize = (float) self::getAttribute($sppr->effectLst->glow, 'rad', 'integer') / ChartProperties::POINTS_WIDTH_MULTIPLIER; + if ($axisGlowSize != 0.0) { + $colorArray = $this->readColor($sppr->effectLst->glow); + $chartObject->setGlowProperties($axisGlowSize, $colorArray['value'], $colorArray['alpha'], $colorArray['type']); + } + } + + if (isset($sppr->effectLst->softEdge)) { + /** @var string */ + $softEdgeSize = self::getAttribute($sppr->effectLst->softEdge, 'rad', 'string'); + if (is_numeric($softEdgeSize)) { + $chartObject->setSoftEdges((float) ChartProperties::xmlToPoints($softEdgeSize)); + } + } + + $type = ''; + foreach (self::SHADOW_TYPES as $shadowType) { + if (isset($sppr->effectLst->$shadowType)) { + $type = $shadowType; + + break; + } + } + if ($type !== '') { + /** @var string */ + $blur = self::getAttribute($sppr->effectLst->$type, 'blurRad', 'string'); + $blur = is_numeric($blur) ? ChartProperties::xmlToPoints($blur) : null; + /** @var string */ + $dist = self::getAttribute($sppr->effectLst->$type, 'dist', 'string'); + $dist = is_numeric($dist) ? ChartProperties::xmlToPoints($dist) : null; + /** @var string */ + $direction = self::getAttribute($sppr->effectLst->$type, 'dir', 'string'); + $direction = is_numeric($direction) ? ChartProperties::xmlToAngle($direction) : null; + $algn = self::getAttribute($sppr->effectLst->$type, 'algn', 'string'); + $rot = self::getAttribute($sppr->effectLst->$type, 'rotWithShape', 'string'); + $size = []; + foreach (['sx', 'sy'] as $sizeType) { + $sizeValue = self::getAttribute($sppr->effectLst->$type, $sizeType, 'string'); + if (is_numeric($sizeValue)) { + $size[$sizeType] = ChartProperties::xmlToTenthOfPercent((string) $sizeValue); + } else { + $size[$sizeType] = null; + } + } + foreach (['kx', 'ky'] as $sizeType) { + $sizeValue = self::getAttribute($sppr->effectLst->$type, $sizeType, 'string'); + if (is_numeric($sizeValue)) { + $size[$sizeType] = ChartProperties::xmlToAngle((string) $sizeValue); + } else { + $size[$sizeType] = null; + } + } + $colorArray = $this->readColor($sppr->effectLst->$type); + $chartObject + ->setShadowProperty('effect', $type) + ->setShadowProperty('blur', $blur) + ->setShadowProperty('direction', $direction) + ->setShadowProperty('distance', $dist) + ->setShadowProperty('algn', $algn) + ->setShadowProperty('rotWithShape', $rot) + ->setShadowProperty('size', $size) + ->setShadowProperty('color', $colorArray); + } + } + + private const SHADOW_TYPES = [ + 'outerShdw', + 'innerShdw', + ]; + + private function readColor(SimpleXMLElement $colorXml): array + { + $result = [ + 'type' => null, + 'value' => null, + 'alpha' => null, + 'brightness' => null, + ]; + foreach (ChartColor::EXCEL_COLOR_TYPES as $type) { + if (isset($colorXml->$type)) { + $result['type'] = $type; + $result['value'] = self::getAttribute($colorXml->$type, 'val', 'string'); + if (isset($colorXml->$type->alpha)) { + /** @var string */ + $alpha = self::getAttribute($colorXml->$type->alpha, 'val', 'string'); + if (is_numeric($alpha)) { + $result['alpha'] = ChartColor::alphaFromXml($alpha); + } + } + if (isset($colorXml->$type->lumMod)) { + /** @var string */ + $brightness = self::getAttribute($colorXml->$type->lumMod, 'val', 'string'); + if (is_numeric($brightness)) { + $result['brightness'] = ChartColor::alphaFromXml($brightness); + } + } + + break; + } + } + + return $result; + } + + private function readLineStyle(SimpleXMLElement $chartDetail, ?ChartProperties $chartObject): void + { + if (!isset($chartObject, $chartDetail->spPr)) { + return; + } + $sppr = $chartDetail->spPr->children($this->aNamespace); + + if (!isset($sppr->ln)) { + return; + } + $lineWidth = null; + /** @var string */ + $lineWidthTemp = self::getAttribute($sppr->ln, 'w', 'string'); + if (is_numeric($lineWidthTemp)) { + $lineWidth = ChartProperties::xmlToPoints($lineWidthTemp); + } + /** @var string */ + $compoundType = self::getAttribute($sppr->ln, 'cmpd', 'string'); + /** @var string */ + $dashType = self::getAttribute($sppr->ln->prstDash, 'val', 'string'); + /** @var string */ + $capType = self::getAttribute($sppr->ln, 'cap', 'string'); + if (isset($sppr->ln->miter)) { + $joinType = ChartProperties::LINE_STYLE_JOIN_MITER; + } elseif (isset($sppr->ln->bevel)) { + $joinType = ChartProperties::LINE_STYLE_JOIN_BEVEL; + } else { + $joinType = ''; + } + $headArrowSize = ''; + $endArrowSize = ''; + /** @var string */ + $headArrowType = self::getAttribute($sppr->ln->headEnd, 'type', 'string'); + /** @var string */ + $headArrowWidth = self::getAttribute($sppr->ln->headEnd, 'w', 'string'); + /** @var string */ + $headArrowLength = self::getAttribute($sppr->ln->headEnd, 'len', 'string'); + /** @var string */ + $endArrowType = self::getAttribute($sppr->ln->tailEnd, 'type', 'string'); + /** @var string */ + $endArrowWidth = self::getAttribute($sppr->ln->tailEnd, 'w', 'string'); + /** @var string */ + $endArrowLength = self::getAttribute($sppr->ln->tailEnd, 'len', 'string'); + $chartObject->setLineStyleProperties( + $lineWidth, + $compoundType, + $dashType, + $capType, + $joinType, + $headArrowType, + $headArrowSize, + $endArrowType, + $endArrowSize, + $headArrowWidth, + $headArrowLength, + $endArrowWidth, + $endArrowLength + ); + $colorArray = $this->readColor($sppr->ln->solidFill); + $chartObject->getLineColor()->setColorPropertiesArray($colorArray); + } + + private function setAxisProperties(SimpleXMLElement $chartDetail, ?Axis $whichAxis): void + { + if (!isset($whichAxis)) { + return; + } + if (isset($chartDetail->delete)) { + $whichAxis->setAxisOption('hidden', (string) self::getAttribute($chartDetail->delete, 'val', 'string')); + } + if (isset($chartDetail->numFmt)) { + $whichAxis->setAxisNumberProperties( + (string) self::getAttribute($chartDetail->numFmt, 'formatCode', 'string'), + null, + (int) self::getAttribute($chartDetail->numFmt, 'sourceLinked', 'int') + ); + } + if (isset($chartDetail->crossBetween)) { + $whichAxis->setCrossBetween((string) self::getAttribute($chartDetail->crossBetween, 'val', 'string')); + } + if (isset($chartDetail->majorTickMark)) { + $whichAxis->setAxisOption('major_tick_mark', (string) self::getAttribute($chartDetail->majorTickMark, 'val', 'string')); + } + if (isset($chartDetail->minorTickMark)) { + $whichAxis->setAxisOption('minor_tick_mark', (string) self::getAttribute($chartDetail->minorTickMark, 'val', 'string')); + } + if (isset($chartDetail->tickLblPos)) { + $whichAxis->setAxisOption('axis_labels', (string) self::getAttribute($chartDetail->tickLblPos, 'val', 'string')); + } + if (isset($chartDetail->crosses)) { + $whichAxis->setAxisOption('horizontal_crosses', (string) self::getAttribute($chartDetail->crosses, 'val', 'string')); + } + if (isset($chartDetail->crossesAt)) { + $whichAxis->setAxisOption('horizontal_crosses_value', (string) self::getAttribute($chartDetail->crossesAt, 'val', 'string')); + } + if (isset($chartDetail->scaling->orientation)) { + $whichAxis->setAxisOption('orientation', (string) self::getAttribute($chartDetail->scaling->orientation, 'val', 'string')); + } + if (isset($chartDetail->scaling->max)) { + $whichAxis->setAxisOption('maximum', (string) self::getAttribute($chartDetail->scaling->max, 'val', 'string')); + } + if (isset($chartDetail->scaling->min)) { + $whichAxis->setAxisOption('minimum', (string) self::getAttribute($chartDetail->scaling->min, 'val', 'string')); + } + if (isset($chartDetail->scaling->min)) { + $whichAxis->setAxisOption('minimum', (string) self::getAttribute($chartDetail->scaling->min, 'val', 'string')); + } + if (isset($chartDetail->majorUnit)) { + $whichAxis->setAxisOption('major_unit', (string) self::getAttribute($chartDetail->majorUnit, 'val', 'string')); + } + if (isset($chartDetail->minorUnit)) { + $whichAxis->setAxisOption('minor_unit', (string) self::getAttribute($chartDetail->minorUnit, 'val', 'string')); + } + if (isset($chartDetail->baseTimeUnit)) { + $whichAxis->setAxisOption('baseTimeUnit', (string) self::getAttribute($chartDetail->baseTimeUnit, 'val', 'string')); + } + if (isset($chartDetail->majorTimeUnit)) { + $whichAxis->setAxisOption('majorTimeUnit', (string) self::getAttribute($chartDetail->majorTimeUnit, 'val', 'string')); + } + if (isset($chartDetail->minorTimeUnit)) { + $whichAxis->setAxisOption('minorTimeUnit', (string) self::getAttribute($chartDetail->minorTimeUnit, 'val', 'string')); + } + if (isset($chartDetail->txPr)) { + $children = $chartDetail->txPr->children($this->aNamespace); + if (isset($children->bodyPr)) { + /** @var string */ + $textRotation = self::getAttribute($children->bodyPr, 'rot', 'string'); + if (is_numeric($textRotation)) { + $whichAxis->setAxisOption('textRotation', (string) ChartProperties::xmlToAngle($textRotation)); + } + } + } + } } diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php b/PhpOffice/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php old mode 100755 new mode 100644 index e901d99..2b14eab --- a/PhpOffice/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php @@ -3,16 +3,20 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; +use PhpOffice\PhpSpreadsheet\Reader\DefaultReadFilter; use PhpOffice\PhpSpreadsheet\Reader\IReadFilter; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use SimpleXMLElement; class ColumnAndRowAttributes extends BaseParserClass { + /** @var Worksheet */ private $worksheet; + /** @var ?SimpleXMLElement */ private $worksheetXml; - public function __construct(Worksheet $workSheet, \SimpleXMLElement $worksheetXml = null) + public function __construct(Worksheet $workSheet, ?SimpleXMLElement $worksheetXml = null) { $this->worksheet = $workSheet; $this->worksheetXml = $worksheetXml; @@ -25,7 +29,7 @@ class ColumnAndRowAttributes extends BaseParserClass * @param array $columnAttributes array of attributes (indexes are attribute name, values are value) * 'xfIndex', 'visible', 'collapsed', 'outlineLevel', 'width', ... ? */ - private function setColumnAttributes($columnAddress, array $columnAttributes) + private function setColumnAttributes($columnAddress, array $columnAttributes): void { if (isset($columnAttributes['xfIndex'])) { $this->worksheet->getColumnDimension($columnAddress)->setXfIndex($columnAttributes['xfIndex']); @@ -51,7 +55,7 @@ class ColumnAndRowAttributes extends BaseParserClass * @param array $rowAttributes array of attributes (indexes are attribute name, values are value) * 'xfIndex', 'visible', 'collapsed', 'outlineLevel', 'rowHeight', ... ? */ - private function setRowAttributes($rowNumber, array $rowAttributes) + private function setRowAttributes($rowNumber, array $rowAttributes): void { if (isset($rowAttributes['xfIndex'])) { $this->worksheet->getRowDimension($rowNumber)->setXfIndex($rowAttributes['xfIndex']); @@ -70,11 +74,7 @@ class ColumnAndRowAttributes extends BaseParserClass } } - /** - * @param IReadFilter $readFilter - * @param bool $readDataOnly - */ - public function load(IReadFilter $readFilter = null, $readDataOnly = false) + public function load(?IReadFilter $readFilter = null, bool $readDataOnly = false): void { if ($this->worksheetXml === null) { return; @@ -90,11 +90,17 @@ class ColumnAndRowAttributes extends BaseParserClass $rowsAttributes = $this->readRowAttributes($this->worksheetXml->sheetData->row, $readDataOnly); } + if ($readFilter !== null && get_class($readFilter) === DefaultReadFilter::class) { + $readFilter = null; + } + // set columns/rows attributes $columnsAttributesAreSet = []; foreach ($columnsAttributes as $columnCoordinate => $columnAttributes) { - if ($readFilter === null || - !$this->isFilteredColumn($readFilter, $columnCoordinate, $rowsAttributes)) { + if ( + $readFilter === null || + !$this->isFilteredColumn($readFilter, $columnCoordinate, $rowsAttributes) + ) { if (!isset($columnsAttributesAreSet[$columnCoordinate])) { $this->setColumnAttributes($columnCoordinate, $columnAttributes); $columnsAttributesAreSet[$columnCoordinate] = true; @@ -104,8 +110,10 @@ class ColumnAndRowAttributes extends BaseParserClass $rowsAttributesAreSet = []; foreach ($rowsAttributes as $rowCoordinate => $rowAttributes) { - if ($readFilter === null || - !$this->isFilteredRow($readFilter, $rowCoordinate, $columnsAttributes)) { + if ( + $readFilter === null || + !$this->isFilteredRow($readFilter, $rowCoordinate, $columnsAttributes) + ) { if (!isset($rowsAttributesAreSet[$rowCoordinate])) { $this->setRowAttributes($rowCoordinate, $rowAttributes); $rowsAttributesAreSet[$rowCoordinate] = true; @@ -114,7 +122,7 @@ class ColumnAndRowAttributes extends BaseParserClass } } - private function isFilteredColumn(IReadFilter $readFilter, $columnCoordinate, array $rowsAttributes) + private function isFilteredColumn(IReadFilter $readFilter, string $columnCoordinate, array $rowsAttributes): bool { foreach ($rowsAttributes as $rowCoordinate => $rowAttributes) { if (!$readFilter->readCell($columnCoordinate, $rowCoordinate, $this->worksheet->getTitle())) { @@ -125,19 +133,23 @@ class ColumnAndRowAttributes extends BaseParserClass return false; } - private function readColumnAttributes(\SimpleXMLElement $worksheetCols, $readDataOnly) + private function readColumnAttributes(SimpleXMLElement $worksheetCols, bool $readDataOnly): array { $columnAttributes = []; - foreach ($worksheetCols->col as $column) { - $startColumn = Coordinate::stringFromColumnIndex((int) $column['min']); - $endColumn = Coordinate::stringFromColumnIndex((int) $column['max']); - ++$endColumn; - for ($columnAddress = $startColumn; $columnAddress !== $endColumn; ++$columnAddress) { - $columnAttributes[$columnAddress] = $this->readColumnRangeAttributes($column, $readDataOnly); + foreach ($worksheetCols->col as $columnx) { + /** @scrutinizer ignore-call */ + $column = $columnx->attributes(); + if ($column !== null) { + $startColumn = Coordinate::stringFromColumnIndex((int) $column['min']); + $endColumn = Coordinate::stringFromColumnIndex((int) $column['max']); + ++$endColumn; + for ($columnAddress = $startColumn; $columnAddress !== $endColumn; ++$columnAddress) { + $columnAttributes[$columnAddress] = $this->readColumnRangeAttributes($column, $readDataOnly); - if ((int) ($column['max']) == 16384) { - break; + if ((int) ($column['max']) == 16384) { + break; + } } } } @@ -145,28 +157,31 @@ class ColumnAndRowAttributes extends BaseParserClass return $columnAttributes; } - private function readColumnRangeAttributes(\SimpleXMLElement $column, $readDataOnly) + private function readColumnRangeAttributes(?SimpleXMLElement $column, bool $readDataOnly): array { $columnAttributes = []; - - if ($column['style'] && !$readDataOnly) { - $columnAttributes['xfIndex'] = (int) $column['style']; + if ($column !== null) { + if (isset($column['style']) && !$readDataOnly) { + $columnAttributes['xfIndex'] = (int) $column['style']; + } + if (isset($column['hidden']) && self::boolean($column['hidden'])) { + $columnAttributes['visible'] = false; + } + if (isset($column['collapsed']) && self::boolean($column['collapsed'])) { + $columnAttributes['collapsed'] = true; + } + if (isset($column['outlineLevel']) && ((int) $column['outlineLevel']) > 0) { + $columnAttributes['outlineLevel'] = (int) $column['outlineLevel']; + } + if (isset($column['width'])) { + $columnAttributes['width'] = (float) $column['width']; + } } - if (self::boolean($column['hidden'])) { - $columnAttributes['visible'] = false; - } - if (self::boolean($column['collapsed'])) { - $columnAttributes['collapsed'] = true; - } - if (((int) $column['outlineLevel']) > 0) { - $columnAttributes['outlineLevel'] = (int) $column['outlineLevel']; - } - $columnAttributes['width'] = (float) $column['width']; return $columnAttributes; } - private function isFilteredRow(IReadFilter $readFilter, $rowCoordinate, array $columnsAttributes) + private function isFilteredRow(IReadFilter $readFilter, int $rowCoordinate, array $columnsAttributes): bool { foreach ($columnsAttributes as $columnCoordinate => $columnAttributes) { if (!$readFilter->readCell($columnCoordinate, $rowCoordinate, $this->worksheet->getTitle())) { @@ -177,25 +192,29 @@ class ColumnAndRowAttributes extends BaseParserClass return false; } - private function readRowAttributes(\SimpleXMLElement $worksheetRow, $readDataOnly) + private function readRowAttributes(SimpleXMLElement $worksheetRow, bool $readDataOnly): array { $rowAttributes = []; - foreach ($worksheetRow as $row) { - if ($row['ht'] && !$readDataOnly) { - $rowAttributes[(int) $row['r']]['rowHeight'] = (float) $row['ht']; - } - if (self::boolean($row['hidden'])) { - $rowAttributes[(int) $row['r']]['visible'] = false; - } - if (self::boolean($row['collapsed'])) { - $rowAttributes[(int) $row['r']]['collapsed'] = true; - } - if ((int) $row['outlineLevel'] > 0) { - $rowAttributes[(int) $row['r']]['outlineLevel'] = (int) $row['outlineLevel']; - } - if ($row['s'] && !$readDataOnly) { - $rowAttributes[(int) $row['r']]['xfIndex'] = (int) $row['s']; + foreach ($worksheetRow as $rowx) { + /** @scrutinizer ignore-call */ + $row = $rowx->attributes(); + if ($row !== null) { + if (isset($row['ht']) && !$readDataOnly) { + $rowAttributes[(int) $row['r']]['rowHeight'] = (float) $row['ht']; + } + if (isset($row['hidden']) && self::boolean($row['hidden'])) { + $rowAttributes[(int) $row['r']]['visible'] = false; + } + if (isset($row['collapsed']) && self::boolean($row['collapsed'])) { + $rowAttributes[(int) $row['r']]['collapsed'] = true; + } + if (isset($row['outlineLevel']) && (int) $row['outlineLevel'] > 0) { + $rowAttributes[(int) $row['r']]['outlineLevel'] = (int) $row['outlineLevel']; + } + if (isset($row['s']) && !$readDataOnly) { + $rowAttributes[(int) $row['r']]['xfIndex'] = (int) $row['s']; + } } } diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php b/PhpOffice/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php old mode 100755 new mode 100644 index b3de5d1..aa6b62b --- a/PhpOffice/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Xlsx/ConditionalStyles.php @@ -2,42 +2,170 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Styles as StyleReader; use PhpOffice\PhpSpreadsheet\Style\Conditional; +use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalDataBar; +use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormattingRuleExtension; +use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormatValueObject; +use PhpOffice\PhpSpreadsheet\Style\Style as Style; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use SimpleXMLElement; +use stdClass; class ConditionalStyles { + /** @var Worksheet */ private $worksheet; + /** @var SimpleXMLElement */ private $worksheetXml; + /** + * @var array + */ + private $ns; + + /** @var array */ private $dxfs; - public function __construct(Worksheet $workSheet, \SimpleXMLElement $worksheetXml, array $dxfs = []) + public function __construct(Worksheet $workSheet, SimpleXMLElement $worksheetXml, array $dxfs = []) { $this->worksheet = $workSheet; $this->worksheetXml = $worksheetXml; $this->dxfs = $dxfs; } - public function load() + public function load(): void { + $selectedCells = $this->worksheet->getSelectedCells(); + $this->setConditionalStyles( $this->worksheet, - $this->readConditionalStyles($this->worksheetXml) + $this->readConditionalStyles($this->worksheetXml), + $this->worksheetXml->extLst ); + + $this->worksheet->setSelectedCells($selectedCells); } - private function readConditionalStyles($xmlSheet) + public function loadFromExt(StyleReader $styleReader): void + { + $selectedCells = $this->worksheet->getSelectedCells(); + + $this->ns = $this->worksheetXml->getNamespaces(true); + $this->setConditionalsFromExt( + $this->readConditionalsFromExt($this->worksheetXml->extLst, $styleReader) + ); + + $this->worksheet->setSelectedCells($selectedCells); + } + + private function setConditionalsFromExt(array $conditionals): void + { + foreach ($conditionals as $conditionalRange => $cfRules) { + ksort($cfRules); + // Priority is used as the key for sorting; but may not start at 0, + // so we use array_values to reset the index after sorting. + $this->worksheet->getStyle($conditionalRange) + ->setConditionalStyles(array_values($cfRules)); + } + } + + private function readConditionalsFromExt(SimpleXMLElement $extLst, StyleReader $styleReader): array + { + $conditionals = []; + + if (isset($extLst->ext['uri']) && (string) $extLst->ext['uri'] === '{78C0D931-6437-407d-A8EE-F0AAD7539E65}') { + $conditionalFormattingRuleXml = $extLst->ext->children($this->ns['x14']); + if (!$conditionalFormattingRuleXml->conditionalFormattings) { + return []; + } + + foreach ($conditionalFormattingRuleXml->children($this->ns['x14']) as $extFormattingXml) { + $extFormattingRangeXml = $extFormattingXml->children($this->ns['xm']); + if (!$extFormattingRangeXml->sqref) { + continue; + } + + $sqref = (string) $extFormattingRangeXml->sqref; + $extCfRuleXml = $extFormattingXml->cfRule; + + $attributes = $extCfRuleXml->attributes(); + if (!$attributes) { + continue; + } + $conditionType = (string) $attributes->type; + if ( + !Conditional::isValidConditionType($conditionType) || + $conditionType === Conditional::CONDITION_DATABAR + ) { + continue; + } + + $priority = (int) $attributes->priority; + + $conditional = $this->readConditionalRuleFromExt($extCfRuleXml, $attributes); + $cfStyle = $this->readStyleFromExt($extCfRuleXml, $styleReader); + $conditional->setStyle($cfStyle); + $conditionals[$sqref][$priority] = $conditional; + } + } + + return $conditionals; + } + + private function readConditionalRuleFromExt(SimpleXMLElement $cfRuleXml, SimpleXMLElement $attributes): Conditional + { + $conditionType = (string) $attributes->type; + $operatorType = (string) $attributes->operator; + + $operands = []; + foreach ($cfRuleXml->children($this->ns['xm']) as $cfRuleOperandsXml) { + $operands[] = (string) $cfRuleOperandsXml; + } + + $conditional = new Conditional(); + $conditional->setConditionType($conditionType); + $conditional->setOperatorType($operatorType); + if ( + $conditionType === Conditional::CONDITION_CONTAINSTEXT || + $conditionType === Conditional::CONDITION_NOTCONTAINSTEXT || + $conditionType === Conditional::CONDITION_BEGINSWITH || + $conditionType === Conditional::CONDITION_ENDSWITH || + $conditionType === Conditional::CONDITION_TIMEPERIOD + ) { + $conditional->setText(array_pop($operands) ?? ''); + } + $conditional->setConditions($operands); + + return $conditional; + } + + private function readStyleFromExt(SimpleXMLElement $extCfRuleXml, StyleReader $styleReader): Style + { + $cfStyle = new Style(false, true); + if ($extCfRuleXml->dxf) { + $styleXML = $extCfRuleXml->dxf->children(); + + if ($styleXML->borders) { + $styleReader->readBorderStyle($cfStyle->getBorders(), $styleXML->borders); + } + if ($styleXML->fill) { + $styleReader->readFillStyle($cfStyle->getFill(), $styleXML->fill); + } + } + + return $cfStyle; + } + + private function readConditionalStyles(SimpleXMLElement $xmlSheet): array { $conditionals = []; foreach ($xmlSheet->conditionalFormatting as $conditional) { foreach ($conditional->cfRule as $cfRule) { - if (((string) $cfRule['type'] == Conditional::CONDITION_NONE - || (string) $cfRule['type'] == Conditional::CONDITION_CELLIS - || (string) $cfRule['type'] == Conditional::CONDITION_CONTAINSTEXT - || (string) $cfRule['type'] == Conditional::CONDITION_EXPRESSION) - && isset($this->dxfs[(int) ($cfRule['dxfId'])])) { + if (Conditional::isValidConditionType((string) $cfRule['type']) && isset($this->dxfs[(int) ($cfRule['dxfId'])])) { + $conditionals[(string) $conditional['sqref']][(int) ($cfRule['priority'])] = $cfRule; + } elseif ((string) $cfRule['type'] == Conditional::CONDITION_DATABAR) { $conditionals[(string) $conditional['sqref']][(int) ($cfRule['priority'])] = $cfRule; } } @@ -46,23 +174,25 @@ class ConditionalStyles return $conditionals; } - private function setConditionalStyles(Worksheet $worksheet, array $conditionals) + private function setConditionalStyles(Worksheet $worksheet, array $conditionals, SimpleXMLElement $xmlExtLst): void { - foreach ($conditionals as $ref => $cfRules) { + foreach ($conditionals as $cellRangeReference => $cfRules) { ksort($cfRules); - $conditionalStyles = $this->readStyleRules($cfRules); + $conditionalStyles = $this->readStyleRules($cfRules, $xmlExtLst); - // Extract all cell references in $ref - $cellBlocks = explode(' ', str_replace('$', '', strtoupper($ref))); + // Extract all cell references in $cellRangeReference + $cellBlocks = explode(' ', str_replace('$', '', strtoupper($cellRangeReference))); foreach ($cellBlocks as $cellBlock) { $worksheet->getStyle($cellBlock)->setConditionalStyles($conditionalStyles); } } } - private function readStyleRules($cfRules) + private function readStyleRules(array $cfRules, SimpleXMLElement $extLst): array { + $conditionalFormattingRuleExtensions = ConditionalFormattingRuleExtension::parseExtLstXml($extLst); $conditionalStyles = []; + foreach ($cfRules as $cfRule) { $objConditional = new Conditional(); $objConditional->setConditionType((string) $cfRule['type']); @@ -70,23 +200,91 @@ class ConditionalStyles if ((string) $cfRule['text'] != '') { $objConditional->setText((string) $cfRule['text']); + } elseif ((string) $cfRule['timePeriod'] != '') { + $objConditional->setText((string) $cfRule['timePeriod']); } if (isset($cfRule['stopIfTrue']) && (int) $cfRule['stopIfTrue'] === 1) { $objConditional->setStopIfTrue(true); } - if (count($cfRule->formula) > 1) { - foreach ($cfRule->formula as $formula) { - $objConditional->addCondition((string) $formula); + if (count($cfRule->formula) >= 1) { + foreach ($cfRule->formula as $formulax) { + $formula = (string) $formulax; + if ($formula === 'TRUE') { + $objConditional->addCondition(true); + } elseif ($formula === 'FALSE') { + $objConditional->addCondition(false); + } else { + $objConditional->addCondition($formula); + } } } else { - $objConditional->addCondition((string) $cfRule->formula); + $objConditional->addCondition(''); } - $objConditional->setStyle(clone $this->dxfs[(int) ($cfRule['dxfId'])]); + + if (isset($cfRule->dataBar)) { + $objConditional->setDataBar( + $this->readDataBarOfConditionalRule($cfRule, $conditionalFormattingRuleExtensions) // @phpstan-ignore-line + ); + } else { + $objConditional->setStyle(clone $this->dxfs[(int) ($cfRule['dxfId'])]); + } + $conditionalStyles[] = $objConditional; } return $conditionalStyles; } + + /** + * @param SimpleXMLElement|stdClass $cfRule + */ + private function readDataBarOfConditionalRule($cfRule, array $conditionalFormattingRuleExtensions): ConditionalDataBar + { + $dataBar = new ConditionalDataBar(); + //dataBar attribute + if (isset($cfRule->dataBar['showValue'])) { + $dataBar->setShowValue((bool) $cfRule->dataBar['showValue']); + } + + //dataBar children + //conditionalFormatValueObjects + $cfvoXml = $cfRule->dataBar->cfvo; + $cfvoIndex = 0; + foreach ((count($cfvoXml) > 1 ? $cfvoXml : [$cfvoXml]) as $cfvo) { + if ($cfvoIndex === 0) { + $dataBar->setMinimumConditionalFormatValueObject(new ConditionalFormatValueObject((string) $cfvo['type'], (string) $cfvo['val'])); + } + if ($cfvoIndex === 1) { + $dataBar->setMaximumConditionalFormatValueObject(new ConditionalFormatValueObject((string) $cfvo['type'], (string) $cfvo['val'])); + } + ++$cfvoIndex; + } + + //color + if (isset($cfRule->dataBar->color)) { + $dataBar->setColor((string) $cfRule->dataBar->color['rgb']); + } + //extLst + $this->readDataBarExtLstOfConditionalRule($dataBar, $cfRule, $conditionalFormattingRuleExtensions); + + return $dataBar; + } + + /** + * @param SimpleXMLElement|stdClass $cfRule + */ + private function readDataBarExtLstOfConditionalRule(ConditionalDataBar $dataBar, $cfRule, array $conditionalFormattingRuleExtensions): void + { + if (isset($cfRule->extLst)) { + $ns = $cfRule->extLst->getNamespaces(true); + foreach ((count($cfRule->extLst) > 0 ? $cfRule->extLst->ext : [$cfRule->extLst->ext]) as $ext) { + $extId = (string) $ext->children($ns['x14'])->id; + if (isset($conditionalFormattingRuleExtensions[$extId]) && (string) $ext['uri'] === '{B025F937-C7B1-47D3-B67F-A62EFF666E3E}') { + $dataBar->setConditionalFormattingRuleExt($conditionalFormattingRuleExtensions[$extId]); + } + } + } + } } diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xlsx/DataValidations.php b/PhpOffice/PhpSpreadsheet/Reader/Xlsx/DataValidations.php old mode 100755 new mode 100644 index 4bb4412..dac7623 --- a/PhpOffice/PhpSpreadsheet/Reader/Xlsx/DataValidations.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Xlsx/DataValidations.php @@ -4,24 +4,27 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use SimpleXMLElement; class DataValidations { + /** @var Worksheet */ private $worksheet; + /** @var SimpleXMLElement */ private $worksheetXml; - public function __construct(Worksheet $workSheet, \SimpleXMLElement $worksheetXml) + public function __construct(Worksheet $workSheet, SimpleXMLElement $worksheetXml) { $this->worksheet = $workSheet; $this->worksheetXml = $worksheetXml; } - public function load() + public function load(): void { foreach ($this->worksheetXml->dataValidations->dataValidation as $dataValidation) { // Uppercase coordinate - $range = strtoupper($dataValidation['sqref']); + $range = strtoupper((string) $dataValidation['sqref']); $rangeSet = explode(' ', $range); foreach ($rangeSet as $range) { $stRange = $this->worksheet->shrinkRangeToFit($range); @@ -33,16 +36,18 @@ class DataValidations $docValidation->setType((string) $dataValidation['type']); $docValidation->setErrorStyle((string) $dataValidation['errorStyle']); $docValidation->setOperator((string) $dataValidation['operator']); - $docValidation->setAllowBlank($dataValidation['allowBlank'] != 0); - $docValidation->setShowDropDown($dataValidation['showDropDown'] == 0); - $docValidation->setShowInputMessage($dataValidation['showInputMessage'] != 0); - $docValidation->setShowErrorMessage($dataValidation['showErrorMessage'] != 0); + $docValidation->setAllowBlank(filter_var($dataValidation['allowBlank'], FILTER_VALIDATE_BOOLEAN)); + // showDropDown is inverted (works as hideDropDown if true) + $docValidation->setShowDropDown(!filter_var($dataValidation['showDropDown'], FILTER_VALIDATE_BOOLEAN)); + $docValidation->setShowInputMessage(filter_var($dataValidation['showInputMessage'], FILTER_VALIDATE_BOOLEAN)); + $docValidation->setShowErrorMessage(filter_var($dataValidation['showErrorMessage'], FILTER_VALIDATE_BOOLEAN)); $docValidation->setErrorTitle((string) $dataValidation['errorTitle']); $docValidation->setError((string) $dataValidation['error']); $docValidation->setPromptTitle((string) $dataValidation['promptTitle']); $docValidation->setPrompt((string) $dataValidation['prompt']); $docValidation->setFormula1((string) $dataValidation->formula1); $docValidation->setFormula2((string) $dataValidation->formula2); + $docValidation->setSqref($range); } } } diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php b/PhpOffice/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php old mode 100755 new mode 100644 index 400b272..7d48c79 --- a/PhpOffice/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php @@ -3,12 +3,16 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use SimpleXMLElement; class Hyperlinks { + /** @var Worksheet */ private $worksheet; + /** @var array */ private $hyperlinks = []; public function __construct(Worksheet $workSheet) @@ -16,42 +20,46 @@ class Hyperlinks $this->worksheet = $workSheet; } - public function readHyperlinks(\SimpleXMLElement $relsWorksheet) + public function readHyperlinks(SimpleXMLElement $relsWorksheet): void { - foreach ($relsWorksheet->Relationship as $element) { - if ($element['Type'] == 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink') { - $this->hyperlinks[(string) $element['Id']] = (string) $element['Target']; + foreach ($relsWorksheet->children(Namespaces::RELATIONSHIPS)->Relationship as $elementx) { + $element = Xlsx::getAttributes($elementx); + if ($element->Type == Namespaces::HYPERLINK) { + $this->hyperlinks[(string) $element->Id] = (string) $element->Target; } } } - public function setHyperlinks(\SimpleXMLElement $worksheetXml) + public function setHyperlinks(SimpleXMLElement $worksheetXml): void { - foreach ($worksheetXml->hyperlink as $hyperlink) { - $this->setHyperlink($hyperlink, $this->worksheet); + foreach ($worksheetXml->children(Namespaces::MAIN)->hyperlink as $hyperlink) { + if ($hyperlink !== null) { + $this->setHyperlink($hyperlink, $this->worksheet); + } } } - private function setHyperlink(\SimpleXMLElement $hyperlink, Worksheet $worksheet) + private function setHyperlink(SimpleXMLElement $hyperlink, Worksheet $worksheet): void { // Link url - $linkRel = $hyperlink->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $linkRel = Xlsx::getAttributes($hyperlink, Namespaces::SCHEMA_OFFICE_DOCUMENT); - foreach (Coordinate::extractAllCellReferencesInRange($hyperlink['ref']) as $cellReference) { + $attributes = Xlsx::getAttributes($hyperlink); + foreach (Coordinate::extractAllCellReferencesInRange($attributes->ref) as $cellReference) { $cell = $worksheet->getCell($cellReference); if (isset($linkRel['id'])) { - $hyperlinkUrl = $this->hyperlinks[(string) $linkRel['id']]; - if (isset($hyperlink['location'])) { - $hyperlinkUrl .= '#' . (string) $hyperlink['location']; + $hyperlinkUrl = $this->hyperlinks[(string) $linkRel['id']] ?? null; + if (isset($attributes['location'])) { + $hyperlinkUrl .= '#' . (string) $attributes['location']; } $cell->getHyperlink()->setUrl($hyperlinkUrl); - } elseif (isset($hyperlink['location'])) { - $cell->getHyperlink()->setUrl('sheet://' . (string) $hyperlink['location']); + } elseif (isset($attributes['location'])) { + $cell->getHyperlink()->setUrl('sheet://' . (string) $attributes['location']); } // Tooltip - if (isset($hyperlink['tooltip'])) { - $cell->getHyperlink()->setTooltip((string) $hyperlink['tooltip']); + if (isset($attributes['tooltip'])) { + $cell->getHyperlink()->setTooltip((string) $attributes['tooltip']); } } } diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xlsx/Namespaces.php b/PhpOffice/PhpSpreadsheet/Reader/Xlsx/Namespaces.php new file mode 100644 index 0000000..fa3e57e --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Reader/Xlsx/Namespaces.php @@ -0,0 +1,118 @@ +worksheet = $workSheet; $this->worksheetXml = $worksheetXml; } - public function load(array $unparsedLoadedData) + public function load(array $unparsedLoadedData): array { - if (!$this->worksheetXml) { + $worksheetXml = $this->worksheetXml; + if ($worksheetXml === null) { return $unparsedLoadedData; } - $this->margins($this->worksheetXml, $this->worksheet); - $unparsedLoadedData = $this->pageSetup($this->worksheetXml, $this->worksheet, $unparsedLoadedData); - $this->headerFooter($this->worksheetXml, $this->worksheet); - $this->pageBreaks($this->worksheetXml, $this->worksheet); + $this->margins($worksheetXml, $this->worksheet); + $unparsedLoadedData = $this->pageSetup($worksheetXml, $this->worksheet, $unparsedLoadedData); + $this->headerFooter($worksheetXml, $this->worksheet); + $this->pageBreaks($worksheetXml, $this->worksheet); return $unparsedLoadedData; } - private function margins(\SimpleXMLElement $xmlSheet, Worksheet $worksheet) + private function margins(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void { if ($xmlSheet->pageMargins) { $docPageMargins = $worksheet->getPageMargins(); @@ -44,7 +48,7 @@ class PageSetup extends BaseParserClass } } - private function pageSetup(\SimpleXMLElement $xmlSheet, Worksheet $worksheet, array $unparsedLoadedData) + private function pageSetup(SimpleXMLElement $xmlSheet, Worksheet $worksheet, array $unparsedLoadedData): array { if ($xmlSheet->pageSetup) { $docPageSetup = $worksheet->getPageSetup(); @@ -64,45 +68,62 @@ class PageSetup extends BaseParserClass if (isset($xmlSheet->pageSetup['fitToWidth']) && (int) ($xmlSheet->pageSetup['fitToWidth']) >= 0) { $docPageSetup->setFitToWidth((int) ($xmlSheet->pageSetup['fitToWidth']), false); } - if (isset($xmlSheet->pageSetup['firstPageNumber'], $xmlSheet->pageSetup['useFirstPageNumber']) && - self::boolean((string) $xmlSheet->pageSetup['useFirstPageNumber'])) { + if ( + isset($xmlSheet->pageSetup['firstPageNumber'], $xmlSheet->pageSetup['useFirstPageNumber']) && + self::boolean((string) $xmlSheet->pageSetup['useFirstPageNumber']) + ) { $docPageSetup->setFirstPageNumber((int) ($xmlSheet->pageSetup['firstPageNumber'])); } + if (isset($xmlSheet->pageSetup['pageOrder'])) { + $docPageSetup->setPageOrder((string) $xmlSheet->pageSetup['pageOrder']); + } - $relAttributes = $xmlSheet->pageSetup->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $relAttributes = $xmlSheet->pageSetup->attributes(Namespaces::SCHEMA_OFFICE_DOCUMENT); if (isset($relAttributes['id'])) { - $unparsedLoadedData['sheets'][$worksheet->getCodeName()]['pageSetupRelId'] = (string) $relAttributes['id']; + $relid = (string) $relAttributes['id']; + if (substr($relid, -2) !== 'ps') { + $relid .= 'ps'; + } + $unparsedLoadedData['sheets'][$worksheet->getCodeName()]['pageSetupRelId'] = $relid; } } return $unparsedLoadedData; } - private function headerFooter(\SimpleXMLElement $xmlSheet, Worksheet $worksheet) + private function headerFooter(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void { if ($xmlSheet->headerFooter) { $docHeaderFooter = $worksheet->getHeaderFooter(); - if (isset($xmlSheet->headerFooter['differentOddEven']) && - self::boolean((string) $xmlSheet->headerFooter['differentOddEven'])) { + if ( + isset($xmlSheet->headerFooter['differentOddEven']) && + self::boolean((string) $xmlSheet->headerFooter['differentOddEven']) + ) { $docHeaderFooter->setDifferentOddEven(true); } else { $docHeaderFooter->setDifferentOddEven(false); } - if (isset($xmlSheet->headerFooter['differentFirst']) && - self::boolean((string) $xmlSheet->headerFooter['differentFirst'])) { + if ( + isset($xmlSheet->headerFooter['differentFirst']) && + self::boolean((string) $xmlSheet->headerFooter['differentFirst']) + ) { $docHeaderFooter->setDifferentFirst(true); } else { $docHeaderFooter->setDifferentFirst(false); } - if (isset($xmlSheet->headerFooter['scaleWithDoc']) && - !self::boolean((string) $xmlSheet->headerFooter['scaleWithDoc'])) { + if ( + isset($xmlSheet->headerFooter['scaleWithDoc']) && + !self::boolean((string) $xmlSheet->headerFooter['scaleWithDoc']) + ) { $docHeaderFooter->setScaleWithDocument(false); } else { $docHeaderFooter->setScaleWithDocument(true); } - if (isset($xmlSheet->headerFooter['alignWithMargins']) && - !self::boolean((string) $xmlSheet->headerFooter['alignWithMargins'])) { + if ( + isset($xmlSheet->headerFooter['alignWithMargins']) && + !self::boolean((string) $xmlSheet->headerFooter['alignWithMargins']) + ) { $docHeaderFooter->setAlignWithMargins(false); } else { $docHeaderFooter->setAlignWithMargins(true); @@ -117,7 +138,7 @@ class PageSetup extends BaseParserClass } } - private function pageBreaks(\SimpleXMLElement $xmlSheet, Worksheet $worksheet) + private function pageBreaks(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void { if ($xmlSheet->rowBreaks && $xmlSheet->rowBreaks->brk) { $this->rowBreaks($xmlSheet, $worksheet); @@ -127,7 +148,7 @@ class PageSetup extends BaseParserClass } } - private function rowBreaks(\SimpleXMLElement $xmlSheet, Worksheet $worksheet) + private function rowBreaks(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void { foreach ($xmlSheet->rowBreaks->brk as $brk) { if ($brk['man']) { @@ -136,7 +157,7 @@ class PageSetup extends BaseParserClass } } - private function columnBreaks(\SimpleXMLElement $xmlSheet, Worksheet $worksheet) + private function columnBreaks(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void { foreach ($xmlSheet->colBreaks->brk as $brk) { if ($brk['man']) { diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xlsx/Properties.php b/PhpOffice/PhpSpreadsheet/Reader/Xlsx/Properties.php old mode 100755 new mode 100644 index bf8e57d..72addff --- a/PhpOffice/PhpSpreadsheet/Reader/Xlsx/Properties.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Xlsx/Properties.php @@ -5,11 +5,14 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx; use PhpOffice\PhpSpreadsheet\Document\Properties as DocumentProperties; use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner; use PhpOffice\PhpSpreadsheet\Settings; +use SimpleXMLElement; class Properties { + /** @var XmlScanner */ private $securityScanner; + /** @var DocumentProperties */ private $docProps; public function __construct(XmlScanner $securityScanner, DocumentProperties $docProps) @@ -18,28 +21,39 @@ class Properties $this->docProps = $docProps; } - private function extractPropertyData($propertyData) + /** + * @param mixed $obj + */ + private static function nullOrSimple($obj): ?SimpleXMLElement { - return simplexml_load_string( + return ($obj instanceof SimpleXMLElement) ? $obj : null; + } + + private function extractPropertyData(string $propertyData): ?SimpleXMLElement + { + // okay to omit namespace because everything will be processed by xpath + $obj = simplexml_load_string( $this->securityScanner->scan($propertyData), 'SimpleXMLElement', Settings::getLibXmlLoaderOptions() ); + + return self::nullOrSimple($obj); } - public function readCoreProperties($propertyData) + public function readCoreProperties(string $propertyData): void { $xmlCore = $this->extractPropertyData($propertyData); if (is_object($xmlCore)) { - $xmlCore->registerXPathNamespace('dc', 'http://purl.org/dc/elements/1.1/'); - $xmlCore->registerXPathNamespace('dcterms', 'http://purl.org/dc/terms/'); - $xmlCore->registerXPathNamespace('cp', 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties'); + $xmlCore->registerXPathNamespace('dc', Namespaces::DC_ELEMENTS); + $xmlCore->registerXPathNamespace('dcterms', Namespaces::DC_TERMS); + $xmlCore->registerXPathNamespace('cp', Namespaces::CORE_PROPERTIES2); $this->docProps->setCreator((string) self::getArrayItem($xmlCore->xpath('dc:creator'))); $this->docProps->setLastModifiedBy((string) self::getArrayItem($xmlCore->xpath('cp:lastModifiedBy'))); - $this->docProps->setCreated(strtotime(self::getArrayItem($xmlCore->xpath('dcterms:created')))); //! respect xsi:type - $this->docProps->setModified(strtotime(self::getArrayItem($xmlCore->xpath('dcterms:modified')))); //! respect xsi:type + $this->docProps->setCreated((string) self::getArrayItem($xmlCore->xpath('dcterms:created'))); //! respect xsi:type + $this->docProps->setModified((string) self::getArrayItem($xmlCore->xpath('dcterms:modified'))); //! respect xsi:type $this->docProps->setTitle((string) self::getArrayItem($xmlCore->xpath('dc:title'))); $this->docProps->setDescription((string) self::getArrayItem($xmlCore->xpath('dc:description'))); $this->docProps->setSubject((string) self::getArrayItem($xmlCore->xpath('dc:subject'))); @@ -48,7 +62,7 @@ class Properties } } - public function readExtendedProperties($propertyData) + public function readExtendedProperties(string $propertyData): void { $xmlCore = $this->extractPropertyData($propertyData); @@ -62,13 +76,13 @@ class Properties } } - public function readCustomProperties($propertyData) + public function readCustomProperties(string $propertyData): void { $xmlCore = $this->extractPropertyData($propertyData); if (is_object($xmlCore)) { foreach ($xmlCore as $xmlProperty) { - /** @var \SimpleXMLElement $xmlProperty */ + /** @var SimpleXMLElement $xmlProperty */ $cellDataOfficeAttributes = $xmlProperty->attributes(); if (isset($cellDataOfficeAttributes['name'])) { $propertyName = (string) $cellDataOfficeAttributes['name']; @@ -84,8 +98,12 @@ class Properties } } - private static function getArrayItem(array $array, $key = 0) + /** + * @param null|array|false $array + * @param mixed $key + */ + private static function getArrayItem($array, $key = 0): ?SimpleXMLElement { - return $array[$key] ?? null; + return is_array($array) ? ($array[$key] ?? null) : null; } } diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php b/PhpOffice/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php old mode 100755 new mode 100644 index eb61a5d..5a49644 --- a/PhpOffice/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Xlsx/SheetViewOptions.php @@ -3,33 +3,34 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use SimpleXMLElement; class SheetViewOptions extends BaseParserClass { + /** @var Worksheet */ private $worksheet; + /** @var ?SimpleXMLElement */ private $worksheetXml; - public function __construct(Worksheet $workSheet, \SimpleXMLElement $worksheetXml = null) + public function __construct(Worksheet $workSheet, ?SimpleXMLElement $worksheetXml = null) { $this->worksheet = $workSheet; $this->worksheetXml = $worksheetXml; } - /** - * @param bool $readDataOnly - */ - public function load($readDataOnly = false) + public function load(bool $readDataOnly, Styles $styleReader): void { if ($this->worksheetXml === null) { return; } if (isset($this->worksheetXml->sheetPr)) { - $this->tabColor($this->worksheetXml->sheetPr); - $this->codeName($this->worksheetXml->sheetPr); - $this->outlines($this->worksheetXml->sheetPr); - $this->pageSetup($this->worksheetXml->sheetPr); + $sheetPr = $this->worksheetXml->sheetPr; + $this->tabColor($sheetPr, $styleReader); + $this->codeName($sheetPr); + $this->outlines($sheetPr); + $this->pageSetup($sheetPr); } if (isset($this->worksheetXml->sheetFormatPr)) { @@ -41,32 +42,38 @@ class SheetViewOptions extends BaseParserClass } } - private function tabColor(\SimpleXMLElement $sheetPr) + private function tabColor(SimpleXMLElement $sheetPr, Styles $styleReader): void { - if (isset($sheetPr->tabColor, $sheetPr->tabColor['rgb'])) { - $this->worksheet->getTabColor()->setARGB((string) $sheetPr->tabColor['rgb']); + if (isset($sheetPr->tabColor)) { + $this->worksheet->getTabColor()->setARGB($styleReader->readColor($sheetPr->tabColor)); } } - private function codeName(\SimpleXMLElement $sheetPr) + private function codeName(SimpleXMLElement $sheetPrx): void { + $sheetPr = $sheetPrx->attributes() ?? []; if (isset($sheetPr['codeName'])) { $this->worksheet->setCodeName((string) $sheetPr['codeName'], false); } } - private function outlines(\SimpleXMLElement $sheetPr) + private function outlines(SimpleXMLElement $sheetPr): void { if (isset($sheetPr->outlinePr)) { - if (isset($sheetPr->outlinePr['summaryRight']) && - !self::boolean((string) $sheetPr->outlinePr['summaryRight'])) { + $attr = $sheetPr->outlinePr->attributes() ?? []; + if ( + isset($attr['summaryRight']) && + !self::boolean((string) $attr['summaryRight']) + ) { $this->worksheet->setShowSummaryRight(false); } else { $this->worksheet->setShowSummaryRight(true); } - if (isset($sheetPr->outlinePr['summaryBelow']) && - !self::boolean((string) $sheetPr->outlinePr['summaryBelow'])) { + if ( + isset($attr['summaryBelow']) && + !self::boolean((string) $attr['summaryBelow']) + ) { $this->worksheet->setShowSummaryBelow(false); } else { $this->worksheet->setShowSummaryBelow(true); @@ -74,11 +81,14 @@ class SheetViewOptions extends BaseParserClass } } - private function pageSetup(\SimpleXMLElement $sheetPr) + private function pageSetup(SimpleXMLElement $sheetPr): void { if (isset($sheetPr->pageSetUpPr)) { - if (isset($sheetPr->pageSetUpPr['fitToPage']) && - !self::boolean((string) $sheetPr->pageSetUpPr['fitToPage'])) { + $attr = $sheetPr->pageSetUpPr->attributes() ?? []; + if ( + isset($attr['fitToPage']) && + !self::boolean((string) $attr['fitToPage']) + ) { $this->worksheet->getPageSetup()->setFitToPage(false); } else { $this->worksheet->getPageSetup()->setFitToPage(true); @@ -86,11 +96,14 @@ class SheetViewOptions extends BaseParserClass } } - private function sheetFormat(\SimpleXMLElement $sheetFormatPr) + private function sheetFormat(SimpleXMLElement $sheetFormatPrx): void { - if (isset($sheetFormatPr['customHeight']) && + $sheetFormatPr = $sheetFormatPrx->attributes() ?? []; + if ( + isset($sheetFormatPr['customHeight']) && self::boolean((string) $sheetFormatPr['customHeight']) && - isset($sheetFormatPr['defaultRowHeight'])) { + isset($sheetFormatPr['defaultRowHeight']) + ) { $this->worksheet->getDefaultRowDimension() ->setRowHeight((float) $sheetFormatPr['defaultRowHeight']); } @@ -100,24 +113,27 @@ class SheetViewOptions extends BaseParserClass ->setWidth((float) $sheetFormatPr['defaultColWidth']); } - if (isset($sheetFormatPr['zeroHeight']) && - ((string) $sheetFormatPr['zeroHeight'] === '1')) { + if ( + isset($sheetFormatPr['zeroHeight']) && + ((string) $sheetFormatPr['zeroHeight'] === '1') + ) { $this->worksheet->getDefaultRowDimension()->setZeroHeight(true); } } - private function printOptions(\SimpleXMLElement $printOptions) + private function printOptions(SimpleXMLElement $printOptionsx): void { - if (self::boolean((string) $printOptions['gridLinesSet'])) { + $printOptions = $printOptionsx->attributes() ?? []; + if (isset($printOptions['gridLinesSet']) && self::boolean((string) $printOptions['gridLinesSet'])) { $this->worksheet->setShowGridlines(true); } - if (self::boolean((string) $printOptions['gridLines'])) { + if (isset($printOptions['gridLines']) && self::boolean((string) $printOptions['gridLines'])) { $this->worksheet->setPrintGridlines(true); } - if (self::boolean((string) $printOptions['horizontalCentered'])) { + if (isset($printOptions['horizontalCentered']) && self::boolean((string) $printOptions['horizontalCentered'])) { $this->worksheet->getPageSetup()->setHorizontalCentered(true); } - if (self::boolean((string) $printOptions['verticalCentered'])) { + if (isset($printOptions['verticalCentered']) && self::boolean((string) $printOptions['verticalCentered'])) { $this->worksheet->getPageSetup()->setVerticalCentered(true); } } diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xlsx/SheetViews.php b/PhpOffice/PhpSpreadsheet/Reader/Xlsx/SheetViews.php old mode 100755 new mode 100644 index 2caaec3..b2bc99f --- a/PhpOffice/PhpSpreadsheet/Reader/Xlsx/SheetViews.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Xlsx/SheetViews.php @@ -3,40 +3,50 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use SimpleXMLElement; class SheetViews extends BaseParserClass { + /** @var SimpleXMLElement */ private $sheetViewXml; + /** @var SimpleXMLElement */ + private $sheetViewAttributes; + + /** @var Worksheet */ private $worksheet; - public function __construct(\SimpleXMLElement $sheetViewXml, Worksheet $workSheet) + public function __construct(SimpleXMLElement $sheetViewXml, Worksheet $workSheet) { $this->sheetViewXml = $sheetViewXml; + $this->sheetViewAttributes = Xlsx::testSimpleXml($sheetViewXml->attributes()); $this->worksheet = $workSheet; } - public function load() + public function load(): void { + $this->topLeft(); $this->zoomScale(); $this->view(); $this->gridLines(); $this->headers(); $this->direction(); + $this->showZeros(); if (isset($this->sheetViewXml->pane)) { $this->pane(); } - if (isset($this->sheetViewXml->selection, $this->sheetViewXml->selection['sqref'])) { + if (isset($this->sheetViewXml->selection, $this->sheetViewXml->selection->attributes()->sqref)) { $this->selection(); } } - private function zoomScale() + private function zoomScale(): void { - if (isset($this->sheetViewXml['zoomScale'])) { - $zoomScale = (int) ($this->sheetViewXml['zoomScale']); + if (isset($this->sheetViewAttributes->zoomScale)) { + $zoomScale = (int) ($this->sheetViewAttributes->zoomScale); if ($zoomScale <= 0) { // setZoomScale will throw an Exception if the scale is less than or equals 0 // that is OK when manually creating documents, but we should be able to read all documents @@ -46,8 +56,8 @@ class SheetViews extends BaseParserClass $this->worksheet->getSheetView()->setZoomScale($zoomScale); } - if (isset($this->sheetViewXml['zoomScaleNormal'])) { - $zoomScaleNormal = (int) ($this->sheetViewXml['zoomScaleNormal']); + if (isset($this->sheetViewAttributes->zoomScaleNormal)) { + $zoomScaleNormal = (int) ($this->sheetViewAttributes->zoomScaleNormal); if ($zoomScaleNormal <= 0) { // setZoomScaleNormal will throw an Exception if the scale is less than or equals 0 // that is OK when manually creating documents, but we should be able to read all documents @@ -58,56 +68,73 @@ class SheetViews extends BaseParserClass } } - private function view() + private function view(): void { - if (isset($this->sheetViewXml['view'])) { - $this->worksheet->getSheetView()->setView((string) $this->sheetViewXml['view']); + if (isset($this->sheetViewAttributes->view)) { + $this->worksheet->getSheetView()->setView((string) $this->sheetViewAttributes->view); } } - private function gridLines() + private function topLeft(): void { - if (isset($this->sheetViewXml['showGridLines'])) { + if (isset($this->sheetViewAttributes->topLeftCell)) { + $this->worksheet->setTopLeftCell($this->sheetViewAttributes->topLeftCell); + } + } + + private function gridLines(): void + { + if (isset($this->sheetViewAttributes->showGridLines)) { $this->worksheet->setShowGridLines( - self::boolean((string) $this->sheetViewXml['showGridLines']) + self::boolean((string) $this->sheetViewAttributes->showGridLines) ); } } - private function headers() + private function headers(): void { - if (isset($this->sheetViewXml['showRowColHeaders'])) { + if (isset($this->sheetViewAttributes->showRowColHeaders)) { $this->worksheet->setShowRowColHeaders( - self::boolean((string) $this->sheetViewXml['showRowColHeaders']) + self::boolean((string) $this->sheetViewAttributes->showRowColHeaders) ); } } - private function direction() + private function direction(): void { - if (isset($this->sheetViewXml['rightToLeft'])) { + if (isset($this->sheetViewAttributes->rightToLeft)) { $this->worksheet->setRightToLeft( - self::boolean((string) $this->sheetViewXml['rightToLeft']) + self::boolean((string) $this->sheetViewAttributes->rightToLeft) ); } } - private function pane() + private function showZeros(): void + { + if (isset($this->sheetViewAttributes->showZeros)) { + $this->worksheet->getSheetView()->setShowZeros( + self::boolean((string) $this->sheetViewAttributes->showZeros) + ); + } + } + + private function pane(): void { $xSplit = 0; $ySplit = 0; $topLeftCell = null; + $paneAttributes = $this->sheetViewXml->pane->attributes(); - if (isset($this->sheetViewXml->pane['xSplit'])) { - $xSplit = (int) ($this->sheetViewXml->pane['xSplit']); + if (isset($paneAttributes->xSplit)) { + $xSplit = (int) ($paneAttributes->xSplit); } - if (isset($this->sheetViewXml->pane['ySplit'])) { - $ySplit = (int) ($this->sheetViewXml->pane['ySplit']); + if (isset($paneAttributes->ySplit)) { + $ySplit = (int) ($paneAttributes->ySplit); } - if (isset($this->sheetViewXml->pane['topLeftCell'])) { - $topLeftCell = (string) $this->sheetViewXml->pane['topLeftCell']; + if (isset($paneAttributes->topLeftCell)) { + $topLeftCell = (string) $paneAttributes->topLeftCell; } $this->worksheet->freezePane( @@ -116,12 +143,14 @@ class SheetViews extends BaseParserClass ); } - private function selection() + private function selection(): void { - $sqref = (string) $this->sheetViewXml->selection['sqref']; - $sqref = explode(' ', $sqref); - $sqref = $sqref[0]; - - $this->worksheet->setSelectedCells($sqref); + $attributes = $this->sheetViewXml->selection->attributes(); + if ($attributes !== null) { + $sqref = (string) $attributes->sqref; + $sqref = explode(' ', $sqref); + $sqref = $sqref[0]; + $this->worksheet->setSelectedCells($sqref); + } } } diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xlsx/Styles.php b/PhpOffice/PhpSpreadsheet/Reader/Xlsx/Styles.php old mode 100755 new mode 100644 index af52a6b..5b089fa --- a/PhpOffice/PhpSpreadsheet/Reader/Xlsx/Styles.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Xlsx/Styles.php @@ -2,187 +2,332 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx; use PhpOffice\PhpSpreadsheet\Style\Alignment; use PhpOffice\PhpSpreadsheet\Style\Border; use PhpOffice\PhpSpreadsheet\Style\Borders; use PhpOffice\PhpSpreadsheet\Style\Color; use PhpOffice\PhpSpreadsheet\Style\Fill; use PhpOffice\PhpSpreadsheet\Style\Font; +use PhpOffice\PhpSpreadsheet\Style\NumberFormat; use PhpOffice\PhpSpreadsheet\Style\Protection; use PhpOffice\PhpSpreadsheet\Style\Style; +use SimpleXMLElement; +use stdClass; class Styles extends BaseParserClass { /** * Theme instance. * - * @var Theme + * @var ?Theme */ - private static $theme = null; + private $theme; + /** @var array */ + private $workbookPalette = []; + + /** @var array */ private $styles = []; + /** @var array */ private $cellStyles = []; + /** @var SimpleXMLElement */ private $styleXml; - public function __construct(\SimpleXMLElement $styleXml) + /** @var string */ + private $namespace = ''; + + public function setNamespace(string $namespace): void + { + $this->namespace = $namespace; + } + + public function setWorkbookPalette(array $palette): void + { + $this->workbookPalette = $palette; + } + + /** + * Cast SimpleXMLElement to bool to overcome Scrutinizer problem. + * + * @param mixed $value + */ + private static function castBool($value): bool + { + return (bool) $value; + } + + private function getStyleAttributes(SimpleXMLElement $value): SimpleXMLElement + { + $attr = null; + if (self::castBool($value)) { + $attr = $value->attributes(''); + if ($attr === null || count($attr) === 0) { + $attr = $value->attributes($this->namespace); + } + } + + return Xlsx::testSimpleXml($attr); + } + + public function setStyleXml(SimpleXmlElement $styleXml): void { $this->styleXml = $styleXml; } - public function setStyleBaseData(Theme $theme = null, $styles = [], $cellStyles = []) + public function setTheme(Theme $theme): void { - self::$theme = $theme; + $this->theme = $theme; + } + + public function setStyleBaseData(?Theme $theme = null, array $styles = [], array $cellStyles = []): void + { + $this->theme = $theme; $this->styles = $styles; $this->cellStyles = $cellStyles; } - private static function readFontStyle(Font $fontStyle, \SimpleXMLElement $fontStyleXml) + public function readFontStyle(Font $fontStyle, SimpleXMLElement $fontStyleXml): void { - $fontStyle->setName((string) $fontStyleXml->name['val']); - $fontStyle->setSize((float) $fontStyleXml->sz['val']); - + if (isset($fontStyleXml->name)) { + $attr = $this->getStyleAttributes($fontStyleXml->name); + if (isset($attr['val'])) { + $fontStyle->setName((string) $attr['val']); + } + } + if (isset($fontStyleXml->sz)) { + $attr = $this->getStyleAttributes($fontStyleXml->sz); + if (isset($attr['val'])) { + $fontStyle->setSize((float) $attr['val']); + } + } if (isset($fontStyleXml->b)) { - $fontStyle->setBold(!isset($fontStyleXml->b['val']) || self::boolean((string) $fontStyleXml->b['val'])); + $attr = $this->getStyleAttributes($fontStyleXml->b); + $fontStyle->setBold(!isset($attr['val']) || self::boolean((string) $attr['val'])); } if (isset($fontStyleXml->i)) { - $fontStyle->setItalic(!isset($fontStyleXml->i['val']) || self::boolean((string) $fontStyleXml->i['val'])); + $attr = $this->getStyleAttributes($fontStyleXml->i); + $fontStyle->setItalic(!isset($attr['val']) || self::boolean((string) $attr['val'])); } if (isset($fontStyleXml->strike)) { - $fontStyle->setStrikethrough(!isset($fontStyleXml->strike['val']) || self::boolean((string) $fontStyleXml->strike['val'])); + $attr = $this->getStyleAttributes($fontStyleXml->strike); + $fontStyle->setStrikethrough(!isset($attr['val']) || self::boolean((string) $attr['val'])); } - $fontStyle->getColor()->setARGB(self::readColor($fontStyleXml->color)); + $fontStyle->getColor()->setARGB($this->readColor($fontStyleXml->color)); - if (isset($fontStyleXml->u) && !isset($fontStyleXml->u['val'])) { - $fontStyle->setUnderline(Font::UNDERLINE_SINGLE); - } elseif (isset($fontStyleXml->u, $fontStyleXml->u['val'])) { - $fontStyle->setUnderline((string) $fontStyleXml->u['val']); - } - - if (isset($fontStyleXml->vertAlign, $fontStyleXml->vertAlign['val'])) { - $verticalAlign = strtolower((string) $fontStyleXml->vertAlign['val']); - if ($verticalAlign === 'superscript') { - $fontStyle->setSuperscript(true); + if (isset($fontStyleXml->u)) { + $attr = $this->getStyleAttributes($fontStyleXml->u); + if (!isset($attr['val'])) { + $fontStyle->setUnderline(Font::UNDERLINE_SINGLE); + } else { + $fontStyle->setUnderline((string) $attr['val']); } - if ($verticalAlign === 'subscript') { - $fontStyle->setSubscript(true); + } + if (isset($fontStyleXml->vertAlign)) { + $attr = $this->getStyleAttributes($fontStyleXml->vertAlign); + if (isset($attr['val'])) { + $verticalAlign = strtolower((string) $attr['val']); + if ($verticalAlign === 'superscript') { + $fontStyle->setSuperscript(true); + } elseif ($verticalAlign === 'subscript') { + $fontStyle->setSubscript(true); + } } } } - private static function readFillStyle(Fill $fillStyle, \SimpleXMLElement $fillStyleXml) + private function readNumberFormat(NumberFormat $numfmtStyle, SimpleXMLElement $numfmtStyleXml): void + { + if ((string) $numfmtStyleXml['formatCode'] !== '') { + $numfmtStyle->setFormatCode(self::formatGeneral((string) $numfmtStyleXml['formatCode'])); + + return; + } + $numfmt = $this->getStyleAttributes($numfmtStyleXml); + if (isset($numfmt['formatCode'])) { + $numfmtStyle->setFormatCode(self::formatGeneral((string) $numfmt['formatCode'])); + } + } + + public function readFillStyle(Fill $fillStyle, SimpleXMLElement $fillStyleXml): void { if ($fillStyleXml->gradientFill) { - /** @var \SimpleXMLElement $gradientFill */ + /** @var SimpleXMLElement $gradientFill */ $gradientFill = $fillStyleXml->gradientFill[0]; - if (!empty($gradientFill['type'])) { - $fillStyle->setFillType((string) $gradientFill['type']); + $attr = $this->getStyleAttributes($gradientFill); + if (!empty($attr['type'])) { + $fillStyle->setFillType((string) $attr['type']); } - $fillStyle->setRotation((float) ($gradientFill['degree'])); - $gradientFill->registerXPathNamespace('sml', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'); - $fillStyle->getStartColor()->setARGB(self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=0]'))->color)); - $fillStyle->getEndColor()->setARGB(self::readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=1]'))->color)); + $fillStyle->setRotation((float) ($attr['degree'])); + $gradientFill->registerXPathNamespace('sml', Namespaces::MAIN); + $fillStyle->getStartColor()->setARGB($this->readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=0]'))->color)); + $fillStyle->getEndColor()->setARGB($this->readColor(self::getArrayItem($gradientFill->xpath('sml:stop[@position=1]'))->color)); } elseif ($fillStyleXml->patternFill) { - $patternType = (string) $fillStyleXml->patternFill['patternType'] != '' ? (string) $fillStyleXml->patternFill['patternType'] : 'solid'; - $fillStyle->setFillType($patternType); + $defaultFillStyle = Fill::FILL_NONE; if ($fillStyleXml->patternFill->fgColor) { - $fillStyle->getStartColor()->setARGB(self::readColor($fillStyleXml->patternFill->fgColor, true)); - } else { - $fillStyle->getStartColor()->setARGB('FF000000'); + $fillStyle->getStartColor()->setARGB($this->readColor($fillStyleXml->patternFill->fgColor, true)); + $defaultFillStyle = Fill::FILL_SOLID; } if ($fillStyleXml->patternFill->bgColor) { - $fillStyle->getEndColor()->setARGB(self::readColor($fillStyleXml->patternFill->bgColor, true)); + $fillStyle->getEndColor()->setARGB($this->readColor($fillStyleXml->patternFill->bgColor, true)); + $defaultFillStyle = Fill::FILL_SOLID; } + + $type = ''; + if ((string) $fillStyleXml->patternFill['patternType'] !== '') { + $type = (string) $fillStyleXml->patternFill['patternType']; + } else { + $attr = $this->getStyleAttributes($fillStyleXml->patternFill); + $type = (string) $attr['patternType']; + } + $patternType = ($type === '') ? $defaultFillStyle : $type; + + $fillStyle->setFillType($patternType); } } - private static function readBorderStyle(Borders $borderStyle, \SimpleXMLElement $borderStyleXml) + public function readBorderStyle(Borders $borderStyle, SimpleXMLElement $borderStyleXml): void { - $diagonalUp = self::boolean((string) $borderStyleXml['diagonalUp']); - $diagonalDown = self::boolean((string) $borderStyleXml['diagonalDown']); - if (!$diagonalUp && !$diagonalDown) { - $borderStyle->setDiagonalDirection(Borders::DIAGONAL_NONE); - } elseif ($diagonalUp && !$diagonalDown) { + $diagonalUp = $this->getAttribute($borderStyleXml, 'diagonalUp'); + $diagonalUp = self::boolean($diagonalUp); + $diagonalDown = $this->getAttribute($borderStyleXml, 'diagonalDown'); + $diagonalDown = self::boolean($diagonalDown); + if ($diagonalUp === false) { + if ($diagonalDown === false) { + $borderStyle->setDiagonalDirection(Borders::DIAGONAL_NONE); + } else { + $borderStyle->setDiagonalDirection(Borders::DIAGONAL_DOWN); + } + } elseif ($diagonalDown === false) { $borderStyle->setDiagonalDirection(Borders::DIAGONAL_UP); - } elseif (!$diagonalUp && $diagonalDown) { - $borderStyle->setDiagonalDirection(Borders::DIAGONAL_DOWN); } else { $borderStyle->setDiagonalDirection(Borders::DIAGONAL_BOTH); } - self::readBorder($borderStyle->getLeft(), $borderStyleXml->left); - self::readBorder($borderStyle->getRight(), $borderStyleXml->right); - self::readBorder($borderStyle->getTop(), $borderStyleXml->top); - self::readBorder($borderStyle->getBottom(), $borderStyleXml->bottom); - self::readBorder($borderStyle->getDiagonal(), $borderStyleXml->diagonal); + $this->readBorder($borderStyle->getLeft(), $borderStyleXml->left); + $this->readBorder($borderStyle->getRight(), $borderStyleXml->right); + $this->readBorder($borderStyle->getTop(), $borderStyleXml->top); + $this->readBorder($borderStyle->getBottom(), $borderStyleXml->bottom); + $this->readBorder($borderStyle->getDiagonal(), $borderStyleXml->diagonal); } - private static function readBorder(Border $border, \SimpleXMLElement $borderXml) + private function getAttribute(SimpleXMLElement $xml, string $attribute): string { - if (isset($borderXml['style'])) { - $border->setBorderStyle((string) $borderXml['style']); + $style = ''; + if ((string) $xml[$attribute] !== '') { + $style = (string) $xml[$attribute]; + } else { + $attr = $this->getStyleAttributes($xml); + if (isset($attr[$attribute])) { + $style = (string) $attr[$attribute]; + } + } + + return $style; + } + + private function readBorder(Border $border, SimpleXMLElement $borderXml): void + { + $style = $this->getAttribute($borderXml, 'style'); + if ($style !== '') { + $border->setBorderStyle((string) $style); } if (isset($borderXml->color)) { - $border->getColor()->setARGB(self::readColor($borderXml->color)); + $border->getColor()->setARGB($this->readColor($borderXml->color)); } } - private static function readAlignmentStyle(Alignment $alignment, \SimpleXMLElement $alignmentXml) + public function readAlignmentStyle(Alignment $alignment, SimpleXMLElement $alignmentXml): void { - $alignment->setHorizontal((string) $alignmentXml->alignment['horizontal']); - $alignment->setVertical((string) $alignmentXml->alignment['vertical']); + $horizontal = $this->getAttribute($alignmentXml, 'horizontal'); + $alignment->setHorizontal($horizontal); + $vertical = $this->getAttribute($alignmentXml, 'vertical'); + $alignment->setVertical((string) $vertical); - $textRotation = 0; - if ((int) $alignmentXml->alignment['textRotation'] <= 90) { - $textRotation = (int) $alignmentXml->alignment['textRotation']; - } elseif ((int) $alignmentXml->alignment['textRotation'] > 90) { - $textRotation = 90 - (int) $alignmentXml->alignment['textRotation']; + $textRotation = (int) $this->getAttribute($alignmentXml, 'textRotation'); + if ($textRotation > 90) { + $textRotation = 90 - $textRotation; } + $alignment->setTextRotation($textRotation); - $alignment->setTextRotation((int) $textRotation); - $alignment->setWrapText(self::boolean((string) $alignmentXml->alignment['wrapText'])); - $alignment->setShrinkToFit(self::boolean((string) $alignmentXml->alignment['shrinkToFit'])); - $alignment->setIndent((int) ((string) $alignmentXml->alignment['indent']) > 0 ? (int) ((string) $alignmentXml->alignment['indent']) : 0); - $alignment->setReadOrder((int) ((string) $alignmentXml->alignment['readingOrder']) > 0 ? (int) ((string) $alignmentXml->alignment['readingOrder']) : 0); + $wrapText = $this->getAttribute($alignmentXml, 'wrapText'); + $alignment->setWrapText(self::boolean((string) $wrapText)); + $shrinkToFit = $this->getAttribute($alignmentXml, 'shrinkToFit'); + $alignment->setShrinkToFit(self::boolean((string) $shrinkToFit)); + $indent = (int) $this->getAttribute($alignmentXml, 'indent'); + $alignment->setIndent(max($indent, 0)); + $readingOrder = (int) $this->getAttribute($alignmentXml, 'readingOrder'); + $alignment->setReadOrder(max($readingOrder, 0)); } - private function readStyle(Style $docStyle, $style) + private static function formatGeneral(string $formatString): string { - $docStyle->getNumberFormat()->setFormatCode($style->numFmt); + if ($formatString === 'GENERAL') { + $formatString = NumberFormat::FORMAT_GENERAL; + } + + return $formatString; + } + + /** + * Read style. + * + * @param SimpleXMLElement|stdClass $style + */ + public function readStyle(Style $docStyle, $style): void + { + if ($style instanceof SimpleXMLElement) { + $this->readNumberFormat($docStyle->getNumberFormat(), $style->numFmt); + } else { + $docStyle->getNumberFormat()->setFormatCode(self::formatGeneral((string) $style->numFmt)); + } if (isset($style->font)) { - self::readFontStyle($docStyle->getFont(), $style->font); + $this->readFontStyle($docStyle->getFont(), $style->font); } if (isset($style->fill)) { - self::readFillStyle($docStyle->getFill(), $style->fill); + $this->readFillStyle($docStyle->getFill(), $style->fill); } if (isset($style->border)) { - self::readBorderStyle($docStyle->getBorders(), $style->border); + $this->readBorderStyle($docStyle->getBorders(), $style->border); } if (isset($style->alignment)) { - self::readAlignmentStyle($docStyle->getAlignment(), $style->alignment); + $this->readAlignmentStyle($docStyle->getAlignment(), $style->alignment); } // protection if (isset($style->protection)) { - $this->readProtectionLocked($docStyle, $style); - $this->readProtectionHidden($docStyle, $style); + $this->readProtectionLocked($docStyle, $style->protection); + $this->readProtectionHidden($docStyle, $style->protection); } // top-level style settings if (isset($style->quotePrefix)) { - $docStyle->setQuotePrefix(true); + $docStyle->setQuotePrefix((bool) $style->quotePrefix); } } - private function readProtectionLocked(Style $docStyle, $style) + /** + * Read protection locked attribute. + */ + public function readProtectionLocked(Style $docStyle, SimpleXMLElement $style): void { - if (isset($style->protection['locked'])) { - if (self::boolean((string) $style->protection['locked'])) { + $locked = ''; + if ((string) $style['locked'] !== '') { + $locked = (string) $style['locked']; + } else { + $attr = $this->getStyleAttributes($style); + if (isset($attr['locked'])) { + $locked = (string) $attr['locked']; + } + } + if ($locked !== '') { + if (self::boolean($locked)) { $docStyle->getProtection()->setLocked(Protection::PROTECTION_PROTECTED); } else { $docStyle->getProtection()->setLocked(Protection::PROTECTION_UNPROTECTED); @@ -190,10 +335,22 @@ class Styles extends BaseParserClass } } - private function readProtectionHidden(Style $docStyle, $style) + /** + * Read protection hidden attribute. + */ + public function readProtectionHidden(Style $docStyle, SimpleXMLElement $style): void { - if (isset($style->protection['hidden'])) { - if (self::boolean((string) $style->protection['hidden'])) { + $hidden = ''; + if ((string) $style['hidden'] !== '') { + $hidden = (string) $style['hidden']; + } else { + $attr = $this->getStyleAttributes($style); + if (isset($attr['hidden'])) { + $hidden = (string) $attr['hidden']; + } + } + if ($hidden !== '') { + if (self::boolean((string) $hidden)) { $docStyle->getProtection()->setHidden(Protection::PROTECTION_PROTECTED); } else { $docStyle->getProtection()->setHidden(Protection::PROTECTION_UNPROTECTED); @@ -201,18 +358,26 @@ class Styles extends BaseParserClass } } - private static function readColor($color, $background = false) + public function readColor(SimpleXMLElement $color, bool $background = false): string { - if (isset($color['rgb'])) { - return (string) $color['rgb']; - } elseif (isset($color['indexed'])) { - return Color::indexedColor($color['indexed'] - 7, $background)->getARGB(); - } elseif (isset($color['theme'])) { - if (self::$theme !== null) { - $returnColour = self::$theme->getColourByIndex((int) $color['theme']); - if (isset($color['tint'])) { - $tintAdjust = (float) $color['tint']; - $returnColour = Color::changeBrightness($returnColour, $tintAdjust); + $attr = $this->getStyleAttributes($color); + if (isset($attr['rgb'])) { + return (string) $attr['rgb']; + } + if (isset($attr['indexed'])) { + $indexedColor = (int) $attr['indexed']; + if ($indexedColor >= count($this->workbookPalette)) { + return Color::indexedColor($indexedColor - 7, $background)->getARGB() ?? ''; + } + + return Color::indexedColor($indexedColor, $background, $this->workbookPalette)->getARGB() ?? ''; + } + if (isset($attr['theme'])) { + if ($this->theme !== null) { + $returnColour = $this->theme->getColourByIndex((int) $attr['theme']); + if (isset($attr['tint'])) { + $tintAdjust = (float) $attr['tint']; + $returnColour = Color::changeBrightness($returnColour ?? '', $tintAdjust); } return 'FF' . $returnColour; @@ -222,7 +387,7 @@ class Styles extends BaseParserClass return ($background) ? 'FFFFFFFF' : 'FF000000'; } - public function dxfs($readDataOnly = false) + public function dxfs(bool $readDataOnly = false): array { $dxfs = []; if (!$readDataOnly && $this->styleXml) { @@ -236,7 +401,8 @@ class Styles extends BaseParserClass } // Cell Styles if ($this->styleXml->cellStyles) { - foreach ($this->styleXml->cellStyles->cellStyle as $cellStyle) { + foreach ($this->styleXml->cellStyles->cellStyle as $cellStylex) { + $cellStyle = Xlsx::getAttributes($cellStylex); if ((int) ($cellStyle['builtinId']) == 0) { if (isset($this->cellStyles[(int) ($cellStyle['xfId'])])) { // Set default style @@ -253,13 +419,20 @@ class Styles extends BaseParserClass return $dxfs; } - public function styles() + public function styles(): array { return $this->styles; } - private static function getArrayItem($array, $key = 0) + /** + * Get array item. + * + * @param mixed $array (usually array, in theory can be false) + * + * @return stdClass + */ + private static function getArrayItem($array, int $key = 0) { - return $array[$key] ?? null; + return is_array($array) ? ($array[$key] ?? null) : null; } } diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xlsx/TableReader.php b/PhpOffice/PhpSpreadsheet/Reader/Xlsx/TableReader.php new file mode 100644 index 0000000..cf89e99 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Reader/Xlsx/TableReader.php @@ -0,0 +1,113 @@ +worksheet = $workSheet; + $this->tableXml = $tableXml; + } + + /** + * Loads Table into the Worksheet. + */ + public function load(): void + { + // Remove all "$" in the table range + $tableRange = (string) preg_replace('/\$/', '', $this->tableXml['ref'] ?? ''); + if (strpos($tableRange, ':') !== false) { + $this->readTable($tableRange, $this->tableXml); + } + } + + /** + * Read Table from xml. + */ + private function readTable(string $tableRange, SimpleXMLElement $tableXml): void + { + $table = new Table($tableRange); + $table->setName((string) $tableXml['displayName']); + $table->setShowHeaderRow((string) $tableXml['headerRowCount'] !== '0'); + $table->setShowTotalsRow((string) $tableXml['totalsRowCount'] === '1'); + + $this->readTableAutoFilter($table, $tableXml->autoFilter); + $this->readTableColumns($table, $tableXml->tableColumns); + $this->readTableStyle($table, $tableXml->tableStyleInfo); + + (new AutoFilter($table, $tableXml))->load(); + $this->worksheet->addTable($table); + } + + /** + * Reads TableAutoFilter from xml. + */ + private function readTableAutoFilter(Table $table, SimpleXMLElement $autoFilterXml): void + { + if ($autoFilterXml->filterColumn === null) { + $table->setAllowFilter(false); + + return; + } + + foreach ($autoFilterXml->filterColumn as $filterColumn) { + $column = $table->getColumnByOffset((int) $filterColumn['colId']); + $column->setShowFilterButton((string) $filterColumn['hiddenButton'] !== '1'); + } + } + + /** + * Reads TableColumns from xml. + */ + private function readTableColumns(Table $table, SimpleXMLElement $tableColumnsXml): void + { + $offset = 0; + foreach ($tableColumnsXml->tableColumn as $tableColumn) { + $column = $table->getColumnByOffset($offset++); + + if ($table->getShowTotalsRow()) { + if ($tableColumn['totalsRowLabel']) { + $column->setTotalsRowLabel((string) $tableColumn['totalsRowLabel']); + } + + if ($tableColumn['totalsRowFunction']) { + $column->setTotalsRowFunction((string) $tableColumn['totalsRowFunction']); + } + } + + if ($tableColumn->calculatedColumnFormula) { + $column->setColumnFormula((string) $tableColumn->calculatedColumnFormula); + } + } + } + + /** + * Reads TableStyle from xml. + */ + private function readTableStyle(Table $table, SimpleXMLElement $tableStyleInfoXml): void + { + $tableStyle = new TableStyle(); + $tableStyle->setTheme((string) $tableStyleInfoXml['name']); + $tableStyle->setShowRowStripes((string) $tableStyleInfoXml['showRowStripes'] === '1'); + $tableStyle->setShowColumnStripes((string) $tableStyleInfoXml['showColumnStripes'] === '1'); + $tableStyle->setShowFirstColumn((string) $tableStyleInfoXml['showFirstColumn'] === '1'); + $tableStyle->setShowLastColumn((string) $tableStyleInfoXml['showLastColumn'] === '1'); + $table->setStyle($tableStyle); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xlsx/Theme.php b/PhpOffice/PhpSpreadsheet/Reader/Xlsx/Theme.php old mode 100755 new mode 100644 index c105f3c..706c4d1 --- a/PhpOffice/PhpSpreadsheet/Reader/Xlsx/Theme.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Xlsx/Theme.php @@ -21,16 +21,16 @@ class Theme /** * Colour Map. * - * @var array of string + * @var string[] */ private $colourMap; /** * Create a new Theme. * - * @param mixed $themeName - * @param mixed $colourSchemeName - * @param mixed $colourMap + * @param string $themeName + * @param string $colourSchemeName + * @param string[] $colourMap */ public function __construct($themeName, $colourSchemeName, $colourMap) { @@ -41,9 +41,11 @@ class Theme } /** - * Get Theme Name. + * Not called by Reader, never accessible any other time. * * @return string + * + * @codeCoverageIgnore */ public function getThemeName() { @@ -51,9 +53,11 @@ class Theme } /** - * Get colour Scheme Name. + * Not called by Reader, never accessible any other time. * * @return string + * + * @codeCoverageIgnore */ public function getColourSchemeName() { @@ -63,31 +67,12 @@ class Theme /** * Get colour Map Value by Position. * - * @param mixed $index + * @param int $index * - * @return string + * @return null|string */ public function getColourByIndex($index) { - if (isset($this->colourMap[$index])) { - return $this->colourMap[$index]; - } - - return null; - } - - /** - * Implement PHP __clone to create a deep clone, not just a shallow copy. - */ - public function __clone() - { - $vars = get_object_vars($this); - foreach ($vars as $key => $value) { - if ((is_object($value)) && ($key != '_parent')) { - $this->$key = clone $value; - } else { - $this->$key = $value; - } - } + return $this->colourMap[$index] ?? null; } } diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xlsx/WorkbookView.php b/PhpOffice/PhpSpreadsheet/Reader/Xlsx/WorkbookView.php new file mode 100644 index 0000000..4743afb --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Reader/Xlsx/WorkbookView.php @@ -0,0 +1,153 @@ +spreadsheet = $spreadsheet; + } + + /** + * @param mixed $mainNS + */ + public function viewSettings(SimpleXMLElement $xmlWorkbook, $mainNS, array $mapSheetId, bool $readDataOnly): void + { + if ($this->spreadsheet->getSheetCount() == 0) { + $this->spreadsheet->createSheet(); + } + // Default active sheet index to the first loaded worksheet from the file + $this->spreadsheet->setActiveSheetIndex(0); + + $workbookView = $xmlWorkbook->children($mainNS)->bookViews->workbookView; + if ($readDataOnly !== true && !empty($workbookView)) { + $workbookViewAttributes = self::testSimpleXml(self::getAttributes($workbookView)); + // active sheet index + $activeTab = (int) $workbookViewAttributes->activeTab; // refers to old sheet index + // keep active sheet index if sheet is still loaded, else first sheet is set as the active worksheet + if (isset($mapSheetId[$activeTab]) && $mapSheetId[$activeTab] !== null) { + $this->spreadsheet->setActiveSheetIndex($mapSheetId[$activeTab]); + } + + $this->horizontalScroll($workbookViewAttributes); + $this->verticalScroll($workbookViewAttributes); + $this->sheetTabs($workbookViewAttributes); + $this->minimized($workbookViewAttributes); + $this->autoFilterDateGrouping($workbookViewAttributes); + $this->firstSheet($workbookViewAttributes); + $this->visibility($workbookViewAttributes); + $this->tabRatio($workbookViewAttributes); + } + } + + /** + * @param mixed $value + */ + public static function testSimpleXml($value): SimpleXMLElement + { + return ($value instanceof SimpleXMLElement) + ? $value + : new SimpleXMLElement(''); + } + + public static function getAttributes(?SimpleXMLElement $value, string $ns = ''): SimpleXMLElement + { + return self::testSimpleXml($value === null ? $value : $value->attributes($ns)); + } + + /** + * Convert an 'xsd:boolean' XML value to a PHP boolean value. + * A valid 'xsd:boolean' XML value can be one of the following + * four values: 'true', 'false', '1', '0'. It is case sensitive. + * + * Note that just doing '(bool) $xsdBoolean' is not safe, + * since '(bool) "false"' returns true. + * + * @see https://www.w3.org/TR/xmlschema11-2/#boolean + * + * @param string $xsdBoolean An XML string value of type 'xsd:boolean' + * + * @return bool Boolean value + */ + private function castXsdBooleanToBool(string $xsdBoolean): bool + { + if ($xsdBoolean === 'false') { + return false; + } + + return (bool) $xsdBoolean; + } + + private function horizontalScroll(SimpleXMLElement $workbookViewAttributes): void + { + if (isset($workbookViewAttributes->showHorizontalScroll)) { + $showHorizontalScroll = (string) $workbookViewAttributes->showHorizontalScroll; + $this->spreadsheet->setShowHorizontalScroll($this->castXsdBooleanToBool($showHorizontalScroll)); + } + } + + private function verticalScroll(SimpleXMLElement $workbookViewAttributes): void + { + if (isset($workbookViewAttributes->showVerticalScroll)) { + $showVerticalScroll = (string) $workbookViewAttributes->showVerticalScroll; + $this->spreadsheet->setShowVerticalScroll($this->castXsdBooleanToBool($showVerticalScroll)); + } + } + + private function sheetTabs(SimpleXMLElement $workbookViewAttributes): void + { + if (isset($workbookViewAttributes->showSheetTabs)) { + $showSheetTabs = (string) $workbookViewAttributes->showSheetTabs; + $this->spreadsheet->setShowSheetTabs($this->castXsdBooleanToBool($showSheetTabs)); + } + } + + private function minimized(SimpleXMLElement $workbookViewAttributes): void + { + if (isset($workbookViewAttributes->minimized)) { + $minimized = (string) $workbookViewAttributes->minimized; + $this->spreadsheet->setMinimized($this->castXsdBooleanToBool($minimized)); + } + } + + private function autoFilterDateGrouping(SimpleXMLElement $workbookViewAttributes): void + { + if (isset($workbookViewAttributes->autoFilterDateGrouping)) { + $autoFilterDateGrouping = (string) $workbookViewAttributes->autoFilterDateGrouping; + $this->spreadsheet->setAutoFilterDateGrouping($this->castXsdBooleanToBool($autoFilterDateGrouping)); + } + } + + private function firstSheet(SimpleXMLElement $workbookViewAttributes): void + { + if (isset($workbookViewAttributes->firstSheet)) { + $firstSheet = (string) $workbookViewAttributes->firstSheet; + $this->spreadsheet->setFirstSheetIndex((int) $firstSheet); + } + } + + private function visibility(SimpleXMLElement $workbookViewAttributes): void + { + if (isset($workbookViewAttributes->visibility)) { + $visibility = (string) $workbookViewAttributes->visibility; + $this->spreadsheet->setVisibility($visibility); + } + } + + private function tabRatio(SimpleXMLElement $workbookViewAttributes): void + { + if (isset($workbookViewAttributes->tabRatio)) { + $tabRatio = (string) $workbookViewAttributes->tabRatio; + $this->spreadsheet->setTabRatio((int) $tabRatio); + } + } +} diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xml.php b/PhpOffice/PhpSpreadsheet/Reader/Xml.php old mode 100755 new mode 100644 index 8ab7a9c..565a5af --- a/PhpOffice/PhpSpreadsheet/Reader/Xml.php +++ b/PhpOffice/PhpSpreadsheet/Reader/Xml.php @@ -2,19 +2,23 @@ namespace PhpOffice\PhpSpreadsheet\Reader; +use DateTime; +use DateTimeZone; +use PhpOffice\PhpSpreadsheet\Cell\AddressHelper; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Cell\DataType; -use PhpOffice\PhpSpreadsheet\Document\Properties; +use PhpOffice\PhpSpreadsheet\DefinedName; use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner; +use PhpOffice\PhpSpreadsheet\Reader\Xml\PageSettings; +use PhpOffice\PhpSpreadsheet\Reader\Xml\Properties; +use PhpOffice\PhpSpreadsheet\Reader\Xml\Style; use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\Settings; use PhpOffice\PhpSpreadsheet\Shared\Date; use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Spreadsheet; -use PhpOffice\PhpSpreadsheet\Style\Alignment; -use PhpOffice\PhpSpreadsheet\Style\Border; -use PhpOffice\PhpSpreadsheet\Style\Font; +use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use SimpleXMLElement; /** @@ -29,13 +33,6 @@ class Xml extends BaseReader */ protected $styles = []; - /** - * Character set used in the file. - * - * @var string - */ - protected $charSet = 'UTF-8'; - /** * Create a new Excel2003XML Reader instance. */ @@ -45,16 +42,20 @@ class Xml extends BaseReader $this->securityScanner = XmlScanner::getInstance($this); } + private $fileContents = ''; + + public static function xmlMappings(): array + { + return array_merge( + Style\Fill::FILL_MAPPINGS, + Style\Border::BORDER_MAPPINGS + ); + } + /** * Can the current IReader read the file? - * - * @param string $pFilename - * - * @throws Exception - * - * @return bool */ - public function canRead($pFilename) + public function canRead(string $filename): bool { // Office xmlns:o="urn:schemas-microsoft-com:office:office" // Excel xmlns:x="urn:schemas-microsoft-com:office:excel" @@ -68,17 +69,14 @@ class Xml extends BaseReader $signature = [ '', + 'xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet', ]; // Open file - $this->openFile($pFilename); - $fileHandle = $this->fileHandle; + $data = file_get_contents($filename); - // Read sample data (first 2 KB will do) - $data = fread($fileHandle, 2048); - fclose($fileHandle); - $data = str_replace("'", '"', $data); // fix headers with single quote + // Why? + //$data = str_replace("'", '"', $data); // fix headers with single quote $valid = true; foreach ($signature as $match) { @@ -91,9 +89,14 @@ class Xml extends BaseReader } // Retrieve charset encoding - if (preg_match('//um', $data, $matches)) { - $this->charSet = strtoupper($matches[1]); + if (preg_match('//m', $data, $matches)) { + $charSet = strtoupper($matches[1]); + if (preg_match('/^ISO-8859-\d[\dL]?$/i', $charSet) === 1) { + $data = StringHelper::convertEncoding($data, 'UTF-8', $charSet); + $data = (string) preg_replace('/()/um', '$1' . 'UTF-8' . '$2', $data, 1); + } } + $this->fileContents = $data; return $valid; } @@ -101,23 +104,22 @@ class Xml extends BaseReader /** * Check if the file is a valid SimpleXML. * - * @param string $pFilename + * @param string $filename * - * @throws Exception - * - * @return false|\SimpleXMLElement + * @return false|SimpleXMLElement */ - public function trySimpleXMLLoadString($pFilename) + public function trySimpleXMLLoadString($filename) { try { $xml = simplexml_load_string( - $this->securityScanner->scan(file_get_contents($pFilename)), + $this->securityScanner->scan($this->fileContents ?: file_get_contents($filename)), 'SimpleXMLElement', Settings::getLibXmlLoaderOptions() ); } catch (\Exception $e) { - throw new Exception('Cannot load invalid XML file: ' . $pFilename, 0, $e); + throw new Exception('Cannot load invalid XML file: ' . $filename, 0, $e); } + $this->fileContents = ''; return $xml; } @@ -125,29 +127,30 @@ class Xml extends BaseReader /** * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object. * - * @param string $pFilename - * - * @throws Exception + * @param string $filename * * @return array */ - public function listWorksheetNames($pFilename) + public function listWorksheetNames($filename) { - File::assertFile($pFilename); - if (!$this->canRead($pFilename)) { - throw new Exception($pFilename . ' is an Invalid Spreadsheet file.'); + File::assertFile($filename); + if (!$this->canRead($filename)) { + throw new Exception($filename . ' is an Invalid Spreadsheet file.'); } $worksheetNames = []; - $xml = $this->trySimpleXMLLoadString($pFilename); + $xml = $this->trySimpleXMLLoadString($filename); + if ($xml === false) { + throw new Exception("Problem reading {$filename}"); + } $namespaces = $xml->getNamespaces(true); $xml_ss = $xml->children($namespaces['ss']); foreach ($xml_ss->Worksheet as $worksheet) { - $worksheet_ss = $worksheet->attributes($namespaces['ss']); - $worksheetNames[] = self::convertStringEncoding((string) $worksheet_ss['Name'], $this->charSet); + $worksheet_ss = self::getAttributes($worksheet, $namespaces['ss']); + $worksheetNames[] = (string) $worksheet_ss['Name']; } return $worksheetNames; @@ -156,26 +159,30 @@ class Xml extends BaseReader /** * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns). * - * @param string $pFilename - * - * @throws Exception + * @param string $filename * * @return array */ - public function listWorksheetInfo($pFilename) + public function listWorksheetInfo($filename) { - File::assertFile($pFilename); + File::assertFile($filename); + if (!$this->canRead($filename)) { + throw new Exception($filename . ' is an Invalid Spreadsheet file.'); + } $worksheetInfo = []; - $xml = $this->trySimpleXMLLoadString($pFilename); + $xml = $this->trySimpleXMLLoadString($filename); + if ($xml === false) { + throw new Exception("Problem reading {$filename}"); + } $namespaces = $xml->getNamespaces(true); $worksheetID = 1; $xml_ss = $xml->children($namespaces['ss']); foreach ($xml_ss->Worksheet as $worksheet) { - $worksheet_ss = $worksheet->attributes($namespaces['ss']); + $worksheet_ss = self::getAttributes($worksheet, $namespaces['ss']); $tmpInfo = []; $tmpInfo['worksheetName'] = ''; @@ -184,10 +191,9 @@ class Xml extends BaseReader $tmpInfo['totalRows'] = 0; $tmpInfo['totalColumns'] = 0; + $tmpInfo['worksheetName'] = "Worksheet_{$worksheetID}"; if (isset($worksheet_ss['Name'])) { $tmpInfo['worksheetName'] = (string) $worksheet_ss['Name']; - } else { - $tmpInfo['worksheetName'] = "Worksheet_{$worksheetID}"; } if (isset($worksheet->Table->Row)) { @@ -226,213 +232,87 @@ class Xml extends BaseReader /** * Loads Spreadsheet from file. - * - * @param string $pFilename - * - * @throws Exception - * - * @return Spreadsheet */ - public function load($pFilename) + protected function loadSpreadsheetFromFile(string $filename): Spreadsheet { // Create new Spreadsheet $spreadsheet = new Spreadsheet(); $spreadsheet->removeSheetByIndex(0); // Load into this instance - return $this->loadIntoExisting($pFilename, $spreadsheet); - } - - private static function identifyFixedStyleValue($styleList, &$styleAttributeValue) - { - $styleAttributeValue = strtolower($styleAttributeValue); - foreach ($styleList as $style) { - if ($styleAttributeValue == strtolower($style)) { - $styleAttributeValue = $style; - - return true; - } - } - - return false; - } - - /** - * pixel units to excel width units(units of 1/256th of a character width). - * - * @param float $pxs - * - * @return float - */ - protected static function pixel2WidthUnits($pxs) - { - $UNIT_OFFSET_MAP = [0, 36, 73, 109, 146, 182, 219]; - - $widthUnits = 256 * ($pxs / 7); - $widthUnits += $UNIT_OFFSET_MAP[($pxs % 7)]; - - return $widthUnits; - } - - /** - * excel width units(units of 1/256th of a character width) to pixel units. - * - * @param float $widthUnits - * - * @return float - */ - protected static function widthUnits2Pixel($widthUnits) - { - $pixels = ($widthUnits / 256) * 7; - $offsetWidthUnits = $widthUnits % 256; - $pixels += round($offsetWidthUnits / (256 / 7)); - - return $pixels; - } - - protected static function hex2str($hex) - { - return chr(hexdec($hex[1])); + return $this->loadIntoExisting($filename, $spreadsheet); } /** * Loads from file into Spreadsheet instance. * - * @param string $pFilename - * @param Spreadsheet $spreadsheet - * - * @throws Exception + * @param string $filename * * @return Spreadsheet */ - public function loadIntoExisting($pFilename, Spreadsheet $spreadsheet) + public function loadIntoExisting($filename, Spreadsheet $spreadsheet) { - File::assertFile($pFilename); - if (!$this->canRead($pFilename)) { - throw new Exception($pFilename . ' is an Invalid Spreadsheet file.'); + File::assertFile($filename); + if (!$this->canRead($filename)) { + throw new Exception($filename . ' is an Invalid Spreadsheet file.'); } - $xml = $this->trySimpleXMLLoadString($pFilename); + $xml = $this->trySimpleXMLLoadString($filename); + if ($xml === false) { + throw new Exception("Problem reading {$filename}"); + } $namespaces = $xml->getNamespaces(true); - $docProps = $spreadsheet->getProperties(); - if (isset($xml->DocumentProperties[0])) { - foreach ($xml->DocumentProperties[0] as $propertyName => $propertyValue) { - switch ($propertyName) { - case 'Title': - $docProps->setTitle(self::convertStringEncoding($propertyValue, $this->charSet)); + (new Properties($spreadsheet))->readProperties($xml, $namespaces); - break; - case 'Subject': - $docProps->setSubject(self::convertStringEncoding($propertyValue, $this->charSet)); - - break; - case 'Author': - $docProps->setCreator(self::convertStringEncoding($propertyValue, $this->charSet)); - - break; - case 'Created': - $creationDate = strtotime($propertyValue); - $docProps->setCreated($creationDate); - - break; - case 'LastAuthor': - $docProps->setLastModifiedBy(self::convertStringEncoding($propertyValue, $this->charSet)); - - break; - case 'LastSaved': - $lastSaveDate = strtotime($propertyValue); - $docProps->setModified($lastSaveDate); - - break; - case 'Company': - $docProps->setCompany(self::convertStringEncoding($propertyValue, $this->charSet)); - - break; - case 'Category': - $docProps->setCategory(self::convertStringEncoding($propertyValue, $this->charSet)); - - break; - case 'Manager': - $docProps->setManager(self::convertStringEncoding($propertyValue, $this->charSet)); - - break; - case 'Keywords': - $docProps->setKeywords(self::convertStringEncoding($propertyValue, $this->charSet)); - - break; - case 'Description': - $docProps->setDescription(self::convertStringEncoding($propertyValue, $this->charSet)); - - break; - } - } - } - if (isset($xml->CustomDocumentProperties)) { - foreach ($xml->CustomDocumentProperties[0] as $propertyName => $propertyValue) { - $propertyAttributes = $propertyValue->attributes($namespaces['dt']); - $propertyName = preg_replace_callback('/_x([0-9a-z]{4})_/', ['self', 'hex2str'], $propertyName); - $propertyType = Properties::PROPERTY_TYPE_UNKNOWN; - switch ((string) $propertyAttributes) { - case 'string': - $propertyType = Properties::PROPERTY_TYPE_STRING; - $propertyValue = trim($propertyValue); - - break; - case 'boolean': - $propertyType = Properties::PROPERTY_TYPE_BOOLEAN; - $propertyValue = (bool) $propertyValue; - - break; - case 'integer': - $propertyType = Properties::PROPERTY_TYPE_INTEGER; - $propertyValue = (int) $propertyValue; - - break; - case 'float': - $propertyType = Properties::PROPERTY_TYPE_FLOAT; - $propertyValue = (float) $propertyValue; - - break; - case 'dateTime.tz': - $propertyType = Properties::PROPERTY_TYPE_DATE; - $propertyValue = strtotime(trim($propertyValue)); - - break; - } - $docProps->setCustomProperty($propertyName, $propertyValue, $propertyType); - } - } - - $this->parseStyles($xml, $namespaces); + $this->styles = (new Style())->parseStyles($xml, $namespaces); $worksheetID = 0; $xml_ss = $xml->children($namespaces['ss']); - foreach ($xml_ss->Worksheet as $worksheet) { - $worksheet_ss = $worksheet->attributes($namespaces['ss']); + /** @var null|SimpleXMLElement $worksheetx */ + foreach ($xml_ss->Worksheet as $worksheetx) { + $worksheet = $worksheetx ?? new SimpleXMLElement(''); + $worksheet_ss = self::getAttributes($worksheet, $namespaces['ss']); - if ((isset($this->loadSheetsOnly)) && (isset($worksheet_ss['Name'])) && - (!in_array($worksheet_ss['Name'], $this->loadSheetsOnly))) { + if ( + isset($this->loadSheetsOnly, $worksheet_ss['Name']) && + (!in_array($worksheet_ss['Name'], /** @scrutinizer ignore-type */ $this->loadSheetsOnly)) + ) { continue; } // Create new Worksheet $spreadsheet->createSheet(); $spreadsheet->setActiveSheetIndex($worksheetID); + $worksheetName = ''; if (isset($worksheet_ss['Name'])) { - $worksheetName = self::convertStringEncoding((string) $worksheet_ss['Name'], $this->charSet); + $worksheetName = (string) $worksheet_ss['Name']; // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in // formula cells... during the load, all formulae should be correct, and we're simply bringing // the worksheet name in line with the formula, not the reverse $spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false); } + // locally scoped defined names + if (isset($worksheet->Names[0])) { + foreach ($worksheet->Names[0] as $definedName) { + $definedName_ss = self::getAttributes($definedName, $namespaces['ss']); + $name = (string) $definedName_ss['Name']; + $definedValue = (string) $definedName_ss['RefersTo']; + $convertedValue = AddressHelper::convertFormulaToA1($definedValue); + if ($convertedValue[0] === '=') { + $convertedValue = substr($convertedValue, 1); + } + $spreadsheet->addDefinedName(DefinedName::createInstance($name, $spreadsheet->getActiveSheet(), $convertedValue, true)); + } + } + $columnID = 'A'; if (isset($worksheet->Table->Column)) { foreach ($worksheet->Table->Column as $columnData) { - $columnData_ss = $columnData->attributes($namespaces['ss']); + $columnData_ss = self::getAttributes($columnData, $namespaces['ss']); if (isset($columnData_ss['Index'])) { $columnID = Coordinate::stringFromColumnIndex((int) $columnData_ss['Index']); } @@ -449,14 +329,14 @@ class Xml extends BaseReader $additionalMergedCells = 0; foreach ($worksheet->Table->Row as $rowData) { $rowHasData = false; - $row_ss = $rowData->attributes($namespaces['ss']); + $row_ss = self::getAttributes($rowData, $namespaces['ss']); if (isset($row_ss['Index'])) { $rowID = (int) $row_ss['Index']; } $columnID = 'A'; foreach ($rowData->Cell as $cell) { - $cell_ss = $cell->attributes($namespaces['ss']); + $cell_ss = self::getAttributes($cell, $namespaces['ss']); if (isset($cell_ss['Index'])) { $columnID = Coordinate::stringFromColumnIndex((int) $cell_ss['Index']); } @@ -471,33 +351,34 @@ class Xml extends BaseReader } if (isset($cell_ss['HRef'])) { - $spreadsheet->getActiveSheet()->getCell($cellRange)->getHyperlink()->setUrl($cell_ss['HRef']); + $spreadsheet->getActiveSheet()->getCell($cellRange)->getHyperlink()->setUrl((string) $cell_ss['HRef']); } if ((isset($cell_ss['MergeAcross'])) || (isset($cell_ss['MergeDown']))) { $columnTo = $columnID; if (isset($cell_ss['MergeAcross'])) { $additionalMergedCells += (int) $cell_ss['MergeAcross']; - $columnTo = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($columnID) + $cell_ss['MergeAcross']); + $columnTo = Coordinate::stringFromColumnIndex((int) (Coordinate::columnIndexFromString($columnID) + $cell_ss['MergeAcross'])); } $rowTo = $rowID; if (isset($cell_ss['MergeDown'])) { $rowTo = $rowTo + $cell_ss['MergeDown']; } $cellRange .= ':' . $columnTo . $rowTo; - $spreadsheet->getActiveSheet()->mergeCells($cellRange); + $spreadsheet->getActiveSheet()->mergeCells($cellRange, Worksheet::MERGE_CELL_CONTENT_HIDE); } - $cellIsSet = $hasCalculatedValue = false; + $hasCalculatedValue = false; $cellDataFormula = ''; if (isset($cell_ss['Formula'])) { $cellDataFormula = $cell_ss['Formula']; $hasCalculatedValue = true; } if (isset($cell->Data)) { - $cellValue = $cellData = $cell->Data; + $cellData = $cell->Data; + $cellValue = (string) $cellData; $type = DataType::TYPE_NULL; - $cellData_ss = $cellData->attributes($namespaces['ss']); + $cellData_ss = self::getAttributes($cellData, $namespaces['ss']); if (isset($cellData_ss['Type'])) { $cellDataType = $cellData_ss['Type']; switch ($cellDataType) { @@ -511,7 +392,6 @@ class Xml extends BaseReader const TYPE_ERROR = 'e'; */ case 'String': - $cellValue = self::convertStringEncoding($cellValue, $this->charSet); $type = DataType::TYPE_STRING; break; @@ -530,11 +410,13 @@ class Xml extends BaseReader break; case 'DateTime': $type = DataType::TYPE_NUMERIC; - $cellValue = Date::PHPToExcel(strtotime($cellValue)); + $dateTime = new DateTime($cellValue, new DateTimeZone('UTC')); + $cellValue = Date::PHPToExcel($dateTime); break; case 'Error': $type = DataType::TYPE_ERROR; + $hasCalculatedValue = false; break; } @@ -543,85 +425,28 @@ class Xml extends BaseReader if ($hasCalculatedValue) { $type = DataType::TYPE_FORMULA; $columnNumber = Coordinate::columnIndexFromString($columnID); - if (substr($cellDataFormula, 0, 3) == 'of:') { - $cellDataFormula = substr($cellDataFormula, 3); - $temp = explode('"', $cellDataFormula); - $key = false; - foreach ($temp as &$value) { - // Only replace in alternate array entries (i.e. non-quoted blocks) - if ($key = !$key) { - $value = str_replace(['[.', '.', ']'], '', $value); - } - } - } else { - // Convert R1C1 style references to A1 style references (but only when not quoted) - $temp = explode('"', $cellDataFormula); - $key = false; - foreach ($temp as &$value) { - // Only replace in alternate array entries (i.e. non-quoted blocks) - if ($key = !$key) { - preg_match_all('/(R(\[?-?\d*\]?))(C(\[?-?\d*\]?))/', $value, $cellReferences, PREG_SET_ORDER + PREG_OFFSET_CAPTURE); - // Reverse the matches array, otherwise all our offsets will become incorrect if we modify our way - // through the formula from left to right. Reversing means that we work right to left.through - // the formula - $cellReferences = array_reverse($cellReferences); - // Loop through each R1C1 style reference in turn, converting it to its A1 style equivalent, - // then modify the formula to use that new reference - foreach ($cellReferences as $cellReference) { - $rowReference = $cellReference[2][0]; - // Empty R reference is the current row - if ($rowReference == '') { - $rowReference = $rowID; - } - // Bracketed R references are relative to the current row - if ($rowReference[0] == '[') { - $rowReference = $rowID + trim($rowReference, '[]'); - } - $columnReference = $cellReference[4][0]; - // Empty C reference is the current column - if ($columnReference == '') { - $columnReference = $columnNumber; - } - // Bracketed C references are relative to the current column - if ($columnReference[0] == '[') { - $columnReference = $columnNumber + trim($columnReference, '[]'); - } - $A1CellReference = Coordinate::stringFromColumnIndex($columnReference) . $rowReference; - $value = substr_replace($value, $A1CellReference, $cellReference[0][1], strlen($cellReference[0][0])); - } - } - } - } - unset($value); - // Then rebuild the formula string - $cellDataFormula = implode('"', $temp); + $cellDataFormula = AddressHelper::convertFormulaToA1($cellDataFormula, $rowID, $columnNumber); } $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setValueExplicit((($hasCalculatedValue) ? $cellDataFormula : $cellValue), $type); if ($hasCalculatedValue) { $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setCalculatedValue($cellValue); } - $cellIsSet = $rowHasData = true; + $rowHasData = true; } if (isset($cell->Comment)) { - $commentAttributes = $cell->Comment->attributes($namespaces['ss']); - $author = 'unknown'; - if (isset($commentAttributes->Author)) { - $author = (string) $commentAttributes->Author; - } - $node = $cell->Comment->Data->asXML(); - $annotation = strip_tags($node); - $spreadsheet->getActiveSheet()->getComment($columnID . $rowID)->setAuthor(self::convertStringEncoding($author, $this->charSet))->setText($this->parseRichText($annotation)); + $this->parseCellComment($cell->Comment, $namespaces, $spreadsheet, $columnID, $rowID); } - if (($cellIsSet) && (isset($cell_ss['StyleID']))) { + if (isset($cell_ss['StyleID'])) { $style = (string) $cell_ss['StyleID']; if ((isset($this->styles[$style])) && (!empty($this->styles[$style]))) { - if (!$spreadsheet->getActiveSheet()->cellExists($columnID . $rowID)) { - $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setValue(null); - } - $spreadsheet->getActiveSheet()->getStyle($cellRange)->applyFromArray($this->styles[$style]); + //if (!$spreadsheet->getActiveSheet()->cellExists($columnID . $rowID)) { + // $spreadsheet->getActiveSheet()->getCell($columnID . $rowID)->setValue(null); + //} + $spreadsheet->getActiveSheet()->getStyle($cellRange) + ->applyFromArray($this->styles[$style]); } } ++$columnID; @@ -634,249 +459,75 @@ class Xml extends BaseReader if ($rowHasData) { if (isset($row_ss['Height'])) { $rowHeight = $row_ss['Height']; - $spreadsheet->getActiveSheet()->getRowDimension($rowID)->setRowHeight($rowHeight); + $spreadsheet->getActiveSheet()->getRowDimension($rowID)->setRowHeight((float) $rowHeight); } } ++$rowID; } + + if (isset($namespaces['x'])) { + $xmlX = $worksheet->children($namespaces['x']); + if (isset($xmlX->WorksheetOptions)) { + (new PageSettings($xmlX, $namespaces))->loadPageSettings($spreadsheet); + } + } } ++$worksheetID; } + // Globally scoped defined names + $activeWorksheet = $spreadsheet->setActiveSheetIndex(0); + if (isset($xml->Names[0])) { + foreach ($xml->Names[0] as $definedName) { + $definedName_ss = self::getAttributes($definedName, $namespaces['ss']); + $name = (string) $definedName_ss['Name']; + $definedValue = (string) $definedName_ss['RefersTo']; + $convertedValue = AddressHelper::convertFormulaToA1($definedValue); + if ($convertedValue[0] === '=') { + $convertedValue = substr($convertedValue, 1); + } + $spreadsheet->addDefinedName(DefinedName::createInstance($name, $activeWorksheet, $convertedValue)); + } + } + // Return return $spreadsheet; } - protected static function convertStringEncoding($string, $charset) - { - if ($charset != 'UTF-8') { - return StringHelper::convertEncoding($string, 'UTF-8', $charset); + protected function parseCellComment( + SimpleXMLElement $comment, + array $namespaces, + Spreadsheet $spreadsheet, + string $columnID, + int $rowID + ): void { + $commentAttributes = $comment->attributes($namespaces['ss']); + $author = 'unknown'; + if (isset($commentAttributes->Author)) { + $author = (string) $commentAttributes->Author; } - return $string; + $node = $comment->Data->asXML(); + $annotation = strip_tags((string) $node); + $spreadsheet->getActiveSheet()->getComment($columnID . $rowID) + ->setAuthor($author) + ->setText($this->parseRichText($annotation)); } - protected function parseRichText($is) + protected function parseRichText(string $annotation): RichText { $value = new RichText(); - $value->createText(self::convertStringEncoding($is, $this->charSet)); + $value->createText($annotation); return $value; } - /** - * @param SimpleXMLElement $xml - * @param array $namespaces - */ - private function parseStyles(SimpleXMLElement $xml, array $namespaces) + private static function getAttributes(?SimpleXMLElement $simple, string $node): SimpleXMLElement { - if (!isset($xml->Styles)) { - return; - } - - foreach ($xml->Styles[0] as $style) { - $style_ss = $style->attributes($namespaces['ss']); - $styleID = (string) $style_ss['ID']; - $this->styles[$styleID] = (isset($this->styles['Default'])) ? $this->styles['Default'] : []; - foreach ($style as $styleType => $styleData) { - $styleAttributes = $styleData->attributes($namespaces['ss']); - switch ($styleType) { - case 'Alignment': - $this->parseStyleAlignment($styleID, $styleAttributes); - - break; - case 'Borders': - $this->parseStyleBorders($styleID, $styleData, $namespaces); - - break; - case 'Font': - $this->parseStyleFont($styleID, $styleAttributes); - - break; - case 'Interior': - $this->parseStyleInterior($styleID, $styleAttributes); - - break; - case 'NumberFormat': - $this->parseStyleNumberFormat($styleID, $styleAttributes); - - break; - } - } - } - } - - /** - * @param string $styleID - * @param SimpleXMLElement $styleAttributes - */ - private function parseStyleAlignment($styleID, SimpleXMLElement $styleAttributes) - { - $verticalAlignmentStyles = [ - Alignment::VERTICAL_BOTTOM, - Alignment::VERTICAL_TOP, - Alignment::VERTICAL_CENTER, - Alignment::VERTICAL_JUSTIFY, - ]; - $horizontalAlignmentStyles = [ - Alignment::HORIZONTAL_GENERAL, - Alignment::HORIZONTAL_LEFT, - Alignment::HORIZONTAL_RIGHT, - Alignment::HORIZONTAL_CENTER, - Alignment::HORIZONTAL_CENTER_CONTINUOUS, - Alignment::HORIZONTAL_JUSTIFY, - ]; - - foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) { - $styleAttributeValue = (string) $styleAttributeValue; - switch ($styleAttributeKey) { - case 'Vertical': - if (self::identifyFixedStyleValue($verticalAlignmentStyles, $styleAttributeValue)) { - $this->styles[$styleID]['alignment']['vertical'] = $styleAttributeValue; - } - - break; - case 'Horizontal': - if (self::identifyFixedStyleValue($horizontalAlignmentStyles, $styleAttributeValue)) { - $this->styles[$styleID]['alignment']['horizontal'] = $styleAttributeValue; - } - - break; - case 'WrapText': - $this->styles[$styleID]['alignment']['wrapText'] = true; - - break; - } - } - } - - /** - * @param $styleID - * @param SimpleXMLElement $styleData - * @param array $namespaces - */ - private function parseStyleBorders($styleID, SimpleXMLElement $styleData, array $namespaces) - { - foreach ($styleData->Border as $borderStyle) { - $borderAttributes = $borderStyle->attributes($namespaces['ss']); - $thisBorder = []; - foreach ($borderAttributes as $borderStyleKey => $borderStyleValue) { - switch ($borderStyleKey) { - case 'LineStyle': - $thisBorder['borderStyle'] = Border::BORDER_MEDIUM; - - break; - case 'Weight': - break; - case 'Position': - $borderPosition = strtolower($borderStyleValue); - - break; - case 'Color': - $borderColour = substr($borderStyleValue, 1); - $thisBorder['color']['rgb'] = $borderColour; - - break; - } - } - if (!empty($thisBorder)) { - if (($borderPosition == 'left') || ($borderPosition == 'right') || ($borderPosition == 'top') || ($borderPosition == 'bottom')) { - $this->styles[$styleID]['borders'][$borderPosition] = $thisBorder; - } - } - } - } - - /** - * @param $styleID - * @param SimpleXMLElement $styleAttributes - */ - private function parseStyleFont($styleID, SimpleXMLElement $styleAttributes) - { - $underlineStyles = [ - Font::UNDERLINE_NONE, - Font::UNDERLINE_DOUBLE, - Font::UNDERLINE_DOUBLEACCOUNTING, - Font::UNDERLINE_SINGLE, - Font::UNDERLINE_SINGLEACCOUNTING, - ]; - - foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) { - $styleAttributeValue = (string) $styleAttributeValue; - switch ($styleAttributeKey) { - case 'FontName': - $this->styles[$styleID]['font']['name'] = $styleAttributeValue; - - break; - case 'Size': - $this->styles[$styleID]['font']['size'] = $styleAttributeValue; - - break; - case 'Color': - $this->styles[$styleID]['font']['color']['rgb'] = substr($styleAttributeValue, 1); - - break; - case 'Bold': - $this->styles[$styleID]['font']['bold'] = true; - - break; - case 'Italic': - $this->styles[$styleID]['font']['italic'] = true; - - break; - case 'Underline': - if (self::identifyFixedStyleValue($underlineStyles, $styleAttributeValue)) { - $this->styles[$styleID]['font']['underline'] = $styleAttributeValue; - } - - break; - } - } - } - - /** - * @param $styleID - * @param SimpleXMLElement $styleAttributes - */ - private function parseStyleInterior($styleID, SimpleXMLElement $styleAttributes) - { - foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) { - switch ($styleAttributeKey) { - case 'Color': - $this->styles[$styleID]['fill']['color']['rgb'] = substr($styleAttributeValue, 1); - - break; - case 'Pattern': - $this->styles[$styleID]['fill']['fillType'] = strtolower($styleAttributeValue); - - break; - } - } - } - - /** - * @param $styleID - * @param SimpleXMLElement $styleAttributes - */ - private function parseStyleNumberFormat($styleID, SimpleXMLElement $styleAttributes) - { - $fromFormats = ['\-', '\ ']; - $toFormats = ['-', ' ']; - - foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValue) { - $styleAttributeValue = str_replace($fromFormats, $toFormats, $styleAttributeValue); - switch ($styleAttributeValue) { - case 'Short Date': - $styleAttributeValue = 'dd/mm/yyyy'; - - break; - } - - if ($styleAttributeValue > '') { - $this->styles[$styleID]['numberFormat']['formatCode'] = $styleAttributeValue; - } - } + return ($simple === null) + ? new SimpleXMLElement('') + : ($simple->attributes($node) ?? new SimpleXMLElement('')); } } diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xml/PageSettings.php b/PhpOffice/PhpSpreadsheet/Reader/Xml/PageSettings.php new file mode 100644 index 0000000..a12986c --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Reader/Xml/PageSettings.php @@ -0,0 +1,135 @@ +pageSetup($xmlX, $namespaces, $this->getPrintDefaults()); + $this->printSettings = $this->printSetup($xmlX, $printSettings); + } + + public function loadPageSettings(Spreadsheet $spreadsheet): void + { + $spreadsheet->getActiveSheet()->getPageSetup() + ->setPaperSize($this->printSettings->paperSize) + ->setOrientation($this->printSettings->orientation) + ->setScale($this->printSettings->scale) + ->setVerticalCentered($this->printSettings->verticalCentered) + ->setHorizontalCentered($this->printSettings->horizontalCentered) + ->setPageOrder($this->printSettings->printOrder); + $spreadsheet->getActiveSheet()->getPageMargins() + ->setTop($this->printSettings->topMargin) + ->setHeader($this->printSettings->headerMargin) + ->setLeft($this->printSettings->leftMargin) + ->setRight($this->printSettings->rightMargin) + ->setBottom($this->printSettings->bottomMargin) + ->setFooter($this->printSettings->footerMargin); + } + + private function getPrintDefaults(): stdClass + { + return (object) [ + 'paperSize' => 9, + 'orientation' => PageSetup::ORIENTATION_DEFAULT, + 'scale' => 100, + 'horizontalCentered' => false, + 'verticalCentered' => false, + 'printOrder' => PageSetup::PAGEORDER_DOWN_THEN_OVER, + 'topMargin' => 0.75, + 'headerMargin' => 0.3, + 'leftMargin' => 0.7, + 'rightMargin' => 0.7, + 'bottomMargin' => 0.75, + 'footerMargin' => 0.3, + ]; + } + + private function pageSetup(SimpleXMLElement $xmlX, array $namespaces, stdClass $printDefaults): stdClass + { + if (isset($xmlX->WorksheetOptions->PageSetup)) { + foreach ($xmlX->WorksheetOptions->PageSetup as $pageSetupData) { + foreach ($pageSetupData as $pageSetupKey => $pageSetupValue) { + /** @scrutinizer ignore-call */ + $pageSetupAttributes = $pageSetupValue->attributes($namespaces['x']); + if (!$pageSetupAttributes) { + continue; + } + + switch ($pageSetupKey) { + case 'Layout': + $this->setLayout($printDefaults, $pageSetupAttributes); + + break; + case 'Header': + $printDefaults->headerMargin = (float) $pageSetupAttributes->Margin ?: 1.0; + + break; + case 'Footer': + $printDefaults->footerMargin = (float) $pageSetupAttributes->Margin ?: 1.0; + + break; + case 'PageMargins': + $this->setMargins($printDefaults, $pageSetupAttributes); + + break; + } + } + } + } + + return $printDefaults; + } + + private function printSetup(SimpleXMLElement $xmlX, stdClass $printDefaults): stdClass + { + if (isset($xmlX->WorksheetOptions->Print)) { + foreach ($xmlX->WorksheetOptions->Print as $printData) { + foreach ($printData as $printKey => $printValue) { + switch ($printKey) { + case 'LeftToRight': + $printDefaults->printOrder = PageSetup::PAGEORDER_OVER_THEN_DOWN; + + break; + case 'PaperSizeIndex': + $printDefaults->paperSize = (int) $printValue ?: 9; + + break; + case 'Scale': + $printDefaults->scale = (int) $printValue ?: 100; + + break; + } + } + } + } + + return $printDefaults; + } + + private function setLayout(stdClass $printDefaults, SimpleXMLElement $pageSetupAttributes): void + { + $printDefaults->orientation = (string) strtolower($pageSetupAttributes->Orientation ?? '') ?: PageSetup::ORIENTATION_PORTRAIT; + $printDefaults->horizontalCentered = (bool) $pageSetupAttributes->CenterHorizontal ?: false; + $printDefaults->verticalCentered = (bool) $pageSetupAttributes->CenterVertical ?: false; + } + + private function setMargins(stdClass $printDefaults, SimpleXMLElement $pageSetupAttributes): void + { + $printDefaults->leftMargin = (float) $pageSetupAttributes->Left ?: 1.0; + $printDefaults->rightMargin = (float) $pageSetupAttributes->Right ?: 1.0; + $printDefaults->topMargin = (float) $pageSetupAttributes->Top ?: 1.0; + $printDefaults->bottomMargin = (float) $pageSetupAttributes->Bottom ?: 1.0; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xml/Properties.php b/PhpOffice/PhpSpreadsheet/Reader/Xml/Properties.php new file mode 100644 index 0000000..9e10526 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Reader/Xml/Properties.php @@ -0,0 +1,157 @@ +spreadsheet = $spreadsheet; + } + + public function readProperties(SimpleXMLElement $xml, array $namespaces): void + { + $this->readStandardProperties($xml); + $this->readCustomProperties($xml, $namespaces); + } + + protected function readStandardProperties(SimpleXMLElement $xml): void + { + if (isset($xml->DocumentProperties[0])) { + $docProps = $this->spreadsheet->getProperties(); + + foreach ($xml->DocumentProperties[0] as $propertyName => $propertyValue) { + $propertyValue = (string) $propertyValue; + + $this->processStandardProperty($docProps, $propertyName, $propertyValue); + } + } + } + + protected function readCustomProperties(SimpleXMLElement $xml, array $namespaces): void + { + if (isset($xml->CustomDocumentProperties)) { + $docProps = $this->spreadsheet->getProperties(); + + foreach ($xml->CustomDocumentProperties[0] as $propertyName => $propertyValue) { + $propertyAttributes = self::getAttributes($propertyValue, $namespaces['dt']); + $propertyName = (string) preg_replace_callback('/_x([0-9a-f]{4})_/i', [$this, 'hex2str'], $propertyName); + + $this->processCustomProperty($docProps, $propertyName, $propertyValue, $propertyAttributes); + } + } + } + + protected function processStandardProperty( + DocumentProperties $docProps, + string $propertyName, + string $stringValue + ): void { + switch ($propertyName) { + case 'Title': + $docProps->setTitle($stringValue); + + break; + case 'Subject': + $docProps->setSubject($stringValue); + + break; + case 'Author': + $docProps->setCreator($stringValue); + + break; + case 'Created': + $docProps->setCreated($stringValue); + + break; + case 'LastAuthor': + $docProps->setLastModifiedBy($stringValue); + + break; + case 'LastSaved': + $docProps->setModified($stringValue); + + break; + case 'Company': + $docProps->setCompany($stringValue); + + break; + case 'Category': + $docProps->setCategory($stringValue); + + break; + case 'Manager': + $docProps->setManager($stringValue); + + break; + case 'Keywords': + $docProps->setKeywords($stringValue); + + break; + case 'Description': + $docProps->setDescription($stringValue); + + break; + } + } + + protected function processCustomProperty( + DocumentProperties $docProps, + string $propertyName, + ?SimpleXMLElement $propertyValue, + SimpleXMLElement $propertyAttributes + ): void { + $propertyType = DocumentProperties::PROPERTY_TYPE_UNKNOWN; + + switch ((string) $propertyAttributes) { + case 'string': + $propertyType = DocumentProperties::PROPERTY_TYPE_STRING; + $propertyValue = trim((string) $propertyValue); + + break; + case 'boolean': + $propertyType = DocumentProperties::PROPERTY_TYPE_BOOLEAN; + $propertyValue = (bool) $propertyValue; + + break; + case 'integer': + $propertyType = DocumentProperties::PROPERTY_TYPE_INTEGER; + $propertyValue = (int) $propertyValue; + + break; + case 'float': + $propertyType = DocumentProperties::PROPERTY_TYPE_FLOAT; + $propertyValue = (float) $propertyValue; + + break; + case 'dateTime.tz': + $propertyType = DocumentProperties::PROPERTY_TYPE_DATE; + $propertyValue = trim((string) $propertyValue); + + break; + } + + $docProps->setCustomProperty($propertyName, $propertyValue, $propertyType); + } + + protected function hex2str(array $hex): string + { + return mb_chr((int) hexdec($hex[1]), 'UTF-8'); + } + + private static function getAttributes(?SimpleXMLElement $simple, string $node): SimpleXMLElement + { + return ($simple === null) + ? new SimpleXMLElement('') + : ($simple->attributes($node) ?? new SimpleXMLElement('')); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xml/Style.php b/PhpOffice/PhpSpreadsheet/Reader/Xml/Style.php new file mode 100644 index 0000000..0e3cd16 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Reader/Xml/Style.php @@ -0,0 +1,83 @@ +Styles)) { + return []; + } + + $alignmentStyleParser = new Style\Alignment(); + $borderStyleParser = new Style\Border(); + $fontStyleParser = new Style\Font(); + $fillStyleParser = new Style\Fill(); + $numberFormatStyleParser = new Style\NumberFormat(); + + foreach ($xml->Styles[0] as $style) { + $style_ss = self::getAttributes($style, $namespaces['ss']); + $styleID = (string) $style_ss['ID']; + $this->styles[$styleID] = $this->styles['Default'] ?? []; + + $alignment = $border = $font = $fill = $numberFormat = []; + + foreach ($style as $styleType => $styleDatax) { + $styleData = $styleDatax ?? new SimpleXMLElement(''); + $styleAttributes = $styleData->attributes($namespaces['ss']); + + switch ($styleType) { + case 'Alignment': + if ($styleAttributes) { + $alignment = $alignmentStyleParser->parseStyle($styleAttributes); + } + + break; + case 'Borders': + $border = $borderStyleParser->parseStyle($styleData, $namespaces); + + break; + case 'Font': + if ($styleAttributes) { + $font = $fontStyleParser->parseStyle($styleAttributes); + } + + break; + case 'Interior': + if ($styleAttributes) { + $fill = $fillStyleParser->parseStyle($styleAttributes); + } + + break; + case 'NumberFormat': + if ($styleAttributes) { + $numberFormat = $numberFormatStyleParser->parseStyle($styleAttributes); + } + + break; + } + } + + $this->styles[$styleID] = array_merge($alignment, $border, $font, $fill, $numberFormat); + } + + return $this->styles; + } + + protected static function getAttributes(?SimpleXMLElement $simple, string $node): SimpleXMLElement + { + return ($simple === null) + ? new SimpleXMLElement('') + : ($simple->attributes($node) ?? new SimpleXMLElement('')); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xml/Style/Alignment.php b/PhpOffice/PhpSpreadsheet/Reader/Xml/Style/Alignment.php new file mode 100644 index 0000000..d136354 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Reader/Xml/Style/Alignment.php @@ -0,0 +1,58 @@ + $styleAttributeValue) { + $styleAttributeValue = (string) $styleAttributeValue; + switch ($styleAttributeKey) { + case 'Vertical': + if (self::identifyFixedStyleValue(self::VERTICAL_ALIGNMENT_STYLES, $styleAttributeValue)) { + $style['alignment']['vertical'] = $styleAttributeValue; + } + + break; + case 'Horizontal': + if (self::identifyFixedStyleValue(self::HORIZONTAL_ALIGNMENT_STYLES, $styleAttributeValue)) { + $style['alignment']['horizontal'] = $styleAttributeValue; + } + + break; + case 'WrapText': + $style['alignment']['wrapText'] = true; + + break; + case 'Rotate': + $style['alignment']['textRotation'] = $styleAttributeValue; + + break; + } + } + + return $style; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xml/Style/Border.php b/PhpOffice/PhpSpreadsheet/Reader/Xml/Style/Border.php new file mode 100644 index 0000000..8aefd9c --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Reader/Xml/Style/Border.php @@ -0,0 +1,98 @@ + [ + '1continuous' => BorderStyle::BORDER_THIN, + '1dash' => BorderStyle::BORDER_DASHED, + '1dashdot' => BorderStyle::BORDER_DASHDOT, + '1dashdotdot' => BorderStyle::BORDER_DASHDOTDOT, + '1dot' => BorderStyle::BORDER_DOTTED, + '1double' => BorderStyle::BORDER_DOUBLE, + '2continuous' => BorderStyle::BORDER_MEDIUM, + '2dash' => BorderStyle::BORDER_MEDIUMDASHED, + '2dashdot' => BorderStyle::BORDER_MEDIUMDASHDOT, + '2dashdotdot' => BorderStyle::BORDER_MEDIUMDASHDOTDOT, + '2dot' => BorderStyle::BORDER_DOTTED, + '2double' => BorderStyle::BORDER_DOUBLE, + '3continuous' => BorderStyle::BORDER_THICK, + '3dash' => BorderStyle::BORDER_MEDIUMDASHED, + '3dashdot' => BorderStyle::BORDER_MEDIUMDASHDOT, + '3dashdotdot' => BorderStyle::BORDER_MEDIUMDASHDOTDOT, + '3dot' => BorderStyle::BORDER_DOTTED, + '3double' => BorderStyle::BORDER_DOUBLE, + ], + ]; + + public function parseStyle(SimpleXMLElement $styleData, array $namespaces): array + { + $style = []; + + $diagonalDirection = ''; + $borderPosition = ''; + foreach ($styleData->Border as $borderStyle) { + $borderAttributes = self::getAttributes($borderStyle, $namespaces['ss']); + $thisBorder = []; + $styleType = (string) $borderAttributes->Weight; + $styleType .= strtolower((string) $borderAttributes->LineStyle); + $thisBorder['borderStyle'] = self::BORDER_MAPPINGS['borderStyle'][$styleType] ?? BorderStyle::BORDER_NONE; + + foreach ($borderAttributes as $borderStyleKey => $borderStyleValuex) { + $borderStyleValue = (string) $borderStyleValuex; + switch ($borderStyleKey) { + case 'Position': + [$borderPosition, $diagonalDirection] = + $this->parsePosition($borderStyleValue, $diagonalDirection); + + break; + case 'Color': + $borderColour = substr($borderStyleValue, 1); + $thisBorder['color']['rgb'] = $borderColour; + + break; + } + } + + if ($borderPosition) { + $style['borders'][$borderPosition] = $thisBorder; + } elseif ($diagonalDirection) { + $style['borders']['diagonalDirection'] = $diagonalDirection; + $style['borders']['diagonal'] = $thisBorder; + } + } + + return $style; + } + + protected function parsePosition(string $borderStyleValue, string $diagonalDirection): array + { + $borderStyleValue = strtolower($borderStyleValue); + + if (in_array($borderStyleValue, self::BORDER_POSITIONS)) { + $borderPosition = $borderStyleValue; + } elseif ($borderStyleValue === 'diagonalleft') { + $diagonalDirection = $diagonalDirection ? Borders::DIAGONAL_BOTH : Borders::DIAGONAL_DOWN; + } elseif ($borderStyleValue === 'diagonalright') { + $diagonalDirection = $diagonalDirection ? Borders::DIAGONAL_BOTH : Borders::DIAGONAL_UP; + } + + return [$borderPosition ?? null, $diagonalDirection]; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xml/Style/Fill.php b/PhpOffice/PhpSpreadsheet/Reader/Xml/Style/Fill.php new file mode 100644 index 0000000..9a61215 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Reader/Xml/Style/Fill.php @@ -0,0 +1,63 @@ + [ + 'solid' => FillStyles::FILL_SOLID, + 'gray75' => FillStyles::FILL_PATTERN_DARKGRAY, + 'gray50' => FillStyles::FILL_PATTERN_MEDIUMGRAY, + 'gray25' => FillStyles::FILL_PATTERN_LIGHTGRAY, + 'gray125' => FillStyles::FILL_PATTERN_GRAY125, + 'gray0625' => FillStyles::FILL_PATTERN_GRAY0625, + 'horzstripe' => FillStyles::FILL_PATTERN_DARKHORIZONTAL, // horizontal stripe + 'vertstripe' => FillStyles::FILL_PATTERN_DARKVERTICAL, // vertical stripe + 'reversediagstripe' => FillStyles::FILL_PATTERN_DARKUP, // reverse diagonal stripe + 'diagstripe' => FillStyles::FILL_PATTERN_DARKDOWN, // diagonal stripe + 'diagcross' => FillStyles::FILL_PATTERN_DARKGRID, // diagoanl crosshatch + 'thickdiagcross' => FillStyles::FILL_PATTERN_DARKTRELLIS, // thick diagonal crosshatch + 'thinhorzstripe' => FillStyles::FILL_PATTERN_LIGHTHORIZONTAL, + 'thinvertstripe' => FillStyles::FILL_PATTERN_LIGHTVERTICAL, + 'thinreversediagstripe' => FillStyles::FILL_PATTERN_LIGHTUP, + 'thindiagstripe' => FillStyles::FILL_PATTERN_LIGHTDOWN, + 'thinhorzcross' => FillStyles::FILL_PATTERN_LIGHTGRID, // thin horizontal crosshatch + 'thindiagcross' => FillStyles::FILL_PATTERN_LIGHTTRELLIS, // thin diagonal crosshatch + ], + ]; + + public function parseStyle(SimpleXMLElement $styleAttributes): array + { + $style = []; + + foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValuex) { + $styleAttributeValue = (string) $styleAttributeValuex; + switch ($styleAttributeKey) { + case 'Color': + $style['fill']['endColor']['rgb'] = substr($styleAttributeValue, 1); + $style['fill']['startColor']['rgb'] = substr($styleAttributeValue, 1); + + break; + case 'PatternColor': + $style['fill']['startColor']['rgb'] = substr($styleAttributeValue, 1); + + break; + case 'Pattern': + $lcStyleAttributeValue = strtolower((string) $styleAttributeValue); + $style['fill']['fillType'] + = self::FILL_MAPPINGS['fillType'][$lcStyleAttributeValue] ?? FillStyles::FILL_NONE; + + break; + } + } + + return $style; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xml/Style/Font.php b/PhpOffice/PhpSpreadsheet/Reader/Xml/Style/Font.php new file mode 100644 index 0000000..16ab44d --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Reader/Xml/Style/Font.php @@ -0,0 +1,79 @@ + $styleAttributeValue) { + $styleAttributeValue = (string) $styleAttributeValue; + switch ($styleAttributeKey) { + case 'FontName': + $style['font']['name'] = $styleAttributeValue; + + break; + case 'Size': + $style['font']['size'] = $styleAttributeValue; + + break; + case 'Color': + $style['font']['color']['rgb'] = substr($styleAttributeValue, 1); + + break; + case 'Bold': + $style['font']['bold'] = true; + + break; + case 'Italic': + $style['font']['italic'] = true; + + break; + case 'Underline': + $style = $this->parseUnderline($style, $styleAttributeValue); + + break; + case 'VerticalAlign': + $style = $this->parseVerticalAlign($style, $styleAttributeValue); + + break; + } + } + + return $style; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xml/Style/NumberFormat.php b/PhpOffice/PhpSpreadsheet/Reader/Xml/Style/NumberFormat.php new file mode 100644 index 0000000..a31aa9e --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Reader/Xml/Style/NumberFormat.php @@ -0,0 +1,33 @@ + $styleAttributeValue) { + $styleAttributeValue = str_replace($fromFormats, $toFormats, $styleAttributeValue); + + switch ($styleAttributeValue) { + case 'Short Date': + $styleAttributeValue = 'dd/mm/yyyy'; + + break; + } + + if ($styleAttributeValue > '') { + $style['numberFormat']['formatCode'] = $styleAttributeValue; + } + } + + return $style; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Reader/Xml/Style/StyleBase.php b/PhpOffice/PhpSpreadsheet/Reader/Xml/Style/StyleBase.php new file mode 100644 index 0000000..fc9ace8 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Reader/Xml/Style/StyleBase.php @@ -0,0 +1,32 @@ +') + : ($simple->attributes($node) ?? new SimpleXMLElement('')); + } +} diff --git a/PhpOffice/PhpSpreadsheet/ReferenceHelper.php b/PhpOffice/PhpSpreadsheet/ReferenceHelper.php old mode 100755 new mode 100644 index 143e80d..b88199f --- a/PhpOffice/PhpSpreadsheet/ReferenceHelper.php +++ b/PhpOffice/PhpSpreadsheet/ReferenceHelper.php @@ -2,8 +2,12 @@ namespace PhpOffice\PhpSpreadsheet; +use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Cell\DataType; +use PhpOffice\PhpSpreadsheet\Style\Conditional; +use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter; +use PhpOffice\PhpSpreadsheet\Worksheet\Table; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; class ReferenceHelper @@ -18,10 +22,15 @@ class ReferenceHelper /** * Instance of this class. * - * @var ReferenceHelper + * @var ?ReferenceHelper */ private static $instance; + /** + * @var CellReferenceHelper + */ + private $cellReferenceHelper; + /** * Get an instance of this class. * @@ -29,7 +38,7 @@ class ReferenceHelper */ public static function getInstance() { - if (!isset(self::$instance) || (self::$instance === null)) { + if (self::$instance === null) { self::$instance = new self(); } @@ -68,9 +77,12 @@ class ReferenceHelper */ public static function columnReverseSort($a, $b) { - return 1 - strcasecmp(strlen($a) . $a, strlen($b) . $b); + return -strcasecmp(strlen($a) . $a, strlen($b) . $b); } + /** @var int */ + private static $scrutinizer0 = 0; + /** * Compare two cell addresses * Intended for use as a Callback function for sorting cell addresses by column and row. @@ -82,9 +94,14 @@ class ReferenceHelper */ public static function cellSort($a, $b) { - [$ac, $ar] = sscanf($a, '%[A-Z]%d'); - [$bc, $br] = sscanf($b, '%[A-Z]%d'); + $ac = $bc = ''; + $ar = self::$scrutinizer0; + $br = 0; + sscanf($a, '%[A-Z]%d', $ac, $ar); + sscanf($b, '%[A-Z]%d', $bc, $br); + $ac = (string) $ac; + $bc = (string) $bc; if ($ar === $br) { return strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc); } @@ -103,73 +120,47 @@ class ReferenceHelper */ public static function cellReverseSort($a, $b) { - [$ac, $ar] = sscanf($a, '%[A-Z]%d'); - [$bc, $br] = sscanf($b, '%[A-Z]%d'); + $ac = $bc = ''; + $ar = self::$scrutinizer0; + $br = 0; + sscanf($a, '%[A-Z]%d', $ac, $ar); + sscanf($b, '%[A-Z]%d', $bc, $br); + $ac = (string) $ac; + $bc = (string) $bc; if ($ar === $br) { - return 1 - strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc); + return -strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc); } return ($ar < $br) ? 1 : -1; } - /** - * Test whether a cell address falls within a defined range of cells. - * - * @param string $cellAddress Address of the cell we're testing - * @param int $beforeRow Number of the row we're inserting/deleting before - * @param int $pNumRows Number of rows to insert/delete (negative values indicate deletion) - * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before - * @param int $pNumCols Number of columns to insert/delete (negative values indicate deletion) - * - * @return bool - */ - private static function cellAddressInDeleteRange($cellAddress, $beforeRow, $pNumRows, $beforeColumnIndex, $pNumCols) - { - [$cellColumn, $cellRow] = Coordinate::coordinateFromString($cellAddress); - $cellColumnIndex = Coordinate::columnIndexFromString($cellColumn); - // Is cell within the range of rows/columns if we're deleting - if ($pNumRows < 0 && - ($cellRow >= ($beforeRow + $pNumRows)) && - ($cellRow < $beforeRow)) { - return true; - } elseif ($pNumCols < 0 && - ($cellColumnIndex >= ($beforeColumnIndex + $pNumCols)) && - ($cellColumnIndex < $beforeColumnIndex)) { - return true; - } - - return false; - } - /** * Update page breaks when inserting/deleting rows/columns. * - * @param Worksheet $pSheet The worksheet that we're editing - * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1') - * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before - * @param int $pNumCols Number of columns to insert/delete (negative values indicate deletion) - * @param int $beforeRow Number of the row we're inserting/deleting before - * @param int $pNumRows Number of rows to insert/delete (negative values indicate deletion) + * @param Worksheet $worksheet The worksheet that we're editing + * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) + * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) */ - protected function adjustPageBreaks(Worksheet $pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows) + protected function adjustPageBreaks(Worksheet $worksheet, $numberOfColumns, $numberOfRows): void { - $aBreaks = $pSheet->getBreaks(); - ($pNumCols > 0 || $pNumRows > 0) ? - uksort($aBreaks, ['self', 'cellReverseSort']) : uksort($aBreaks, ['self', 'cellSort']); + $aBreaks = $worksheet->getBreaks(); + ($numberOfColumns > 0 || $numberOfRows > 0) + ? uksort($aBreaks, [self::class, 'cellReverseSort']) + : uksort($aBreaks, [self::class, 'cellSort']); - foreach ($aBreaks as $key => $value) { - if (self::cellAddressInDeleteRange($key, $beforeRow, $pNumRows, $beforeColumnIndex, $pNumCols)) { + foreach ($aBreaks as $cellAddress => $value) { + if ($this->cellReferenceHelper->cellAddressInDeleteRange($cellAddress) === true) { // If we're deleting, then clear any defined breaks that are within the range // of rows/columns that we're deleting - $pSheet->setBreak($key, Worksheet::BREAK_NONE); + $worksheet->setBreak($cellAddress, Worksheet::BREAK_NONE); } else { // Otherwise update any affected breaks by inserting a new break at the appropriate point // and removing the old affected break - $newReference = $this->updateCellReference($key, $pBefore, $pNumCols, $pNumRows); - if ($key != $newReference) { - $pSheet->setBreak($newReference, $value) - ->setBreak($key, Worksheet::BREAK_NONE); + $newReference = $this->updateCellReference($cellAddress); + if ($cellAddress !== $newReference) { + $worksheet->setBreak($newReference, $value) + ->setBreak($cellAddress, Worksheet::BREAK_NONE); } } } @@ -178,76 +169,109 @@ class ReferenceHelper /** * Update cell comments when inserting/deleting rows/columns. * - * @param Worksheet $pSheet The worksheet that we're editing - * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1') - * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before - * @param int $pNumCols Number of columns to insert/delete (negative values indicate deletion) - * @param int $beforeRow Number of the row we're inserting/deleting before - * @param int $pNumRows Number of rows to insert/delete (negative values indicate deletion) + * @param Worksheet $worksheet The worksheet that we're editing */ - protected function adjustComments($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows) + protected function adjustComments($worksheet): void { - $aComments = $pSheet->getComments(); + $aComments = $worksheet->getComments(); $aNewComments = []; // the new array of all comments - foreach ($aComments as $key => &$value) { + foreach ($aComments as $cellAddress => &$value) { // Any comments inside a deleted range will be ignored - if (!self::cellAddressInDeleteRange($key, $beforeRow, $pNumRows, $beforeColumnIndex, $pNumCols)) { + if ($this->cellReferenceHelper->cellAddressInDeleteRange($cellAddress) === false) { // Otherwise build a new array of comments indexed by the adjusted cell reference - $newReference = $this->updateCellReference($key, $pBefore, $pNumCols, $pNumRows); + $newReference = $this->updateCellReference($cellAddress); $aNewComments[$newReference] = $value; } } // Replace the comments array with the new set of comments - $pSheet->setComments($aNewComments); + $worksheet->setComments($aNewComments); } /** * Update hyperlinks when inserting/deleting rows/columns. * - * @param Worksheet $pSheet The worksheet that we're editing - * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1') - * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before - * @param int $pNumCols Number of columns to insert/delete (negative values indicate deletion) - * @param int $beforeRow Number of the row we're inserting/deleting before - * @param int $pNumRows Number of rows to insert/delete (negative values indicate deletion) + * @param Worksheet $worksheet The worksheet that we're editing + * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) + * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) */ - protected function adjustHyperlinks($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows) + protected function adjustHyperlinks($worksheet, $numberOfColumns, $numberOfRows): void { - $aHyperlinkCollection = $pSheet->getHyperlinkCollection(); - ($pNumCols > 0 || $pNumRows > 0) ? - uksort($aHyperlinkCollection, ['self', 'cellReverseSort']) : uksort($aHyperlinkCollection, ['self', 'cellSort']); + $aHyperlinkCollection = $worksheet->getHyperlinkCollection(); + ($numberOfColumns > 0 || $numberOfRows > 0) + ? uksort($aHyperlinkCollection, [self::class, 'cellReverseSort']) + : uksort($aHyperlinkCollection, [self::class, 'cellSort']); - foreach ($aHyperlinkCollection as $key => $value) { - $newReference = $this->updateCellReference($key, $pBefore, $pNumCols, $pNumRows); - if ($key != $newReference) { - $pSheet->setHyperlink($newReference, $value); - $pSheet->setHyperlink($key, null); + foreach ($aHyperlinkCollection as $cellAddress => $value) { + $newReference = $this->updateCellReference($cellAddress); + if ($this->cellReferenceHelper->cellAddressInDeleteRange($cellAddress) === true) { + $worksheet->setHyperlink($cellAddress, null); + } elseif ($cellAddress !== $newReference) { + $worksheet->setHyperlink($newReference, $value); + $worksheet->setHyperlink($cellAddress, null); } } } + /** + * Update conditional formatting styles when inserting/deleting rows/columns. + * + * @param Worksheet $worksheet The worksheet that we're editing + * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) + * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) + */ + protected function adjustConditionalFormatting($worksheet, $numberOfColumns, $numberOfRows): void + { + $aStyles = $worksheet->getConditionalStylesCollection(); + ($numberOfColumns > 0 || $numberOfRows > 0) + ? uksort($aStyles, [self::class, 'cellReverseSort']) + : uksort($aStyles, [self::class, 'cellSort']); + + foreach ($aStyles as $cellAddress => $cfRules) { + $worksheet->removeConditionalStyles($cellAddress); + $newReference = $this->updateCellReference($cellAddress); + + foreach ($cfRules as &$cfRule) { + /** @var Conditional $cfRule */ + $conditions = $cfRule->getConditions(); + foreach ($conditions as &$condition) { + if (is_string($condition)) { + $condition = $this->updateFormulaReferences( + $condition, + $this->cellReferenceHelper->beforeCellAddress(), + $numberOfColumns, + $numberOfRows, + $worksheet->getTitle(), + true + ); + } + } + $cfRule->setConditions($conditions); + } + $worksheet->setConditionalStyles($newReference, $cfRules); + } + } + /** * Update data validations when inserting/deleting rows/columns. * - * @param Worksheet $pSheet The worksheet that we're editing - * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1') - * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before - * @param int $pNumCols Number of columns to insert/delete (negative values indicate deletion) - * @param int $beforeRow Number of the row we're inserting/deleting before - * @param int $pNumRows Number of rows to insert/delete (negative values indicate deletion) + * @param Worksheet $worksheet The worksheet that we're editing + * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) + * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) */ - protected function adjustDataValidations($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows) + protected function adjustDataValidations(Worksheet $worksheet, $numberOfColumns, $numberOfRows): void { - $aDataValidationCollection = $pSheet->getDataValidationCollection(); - ($pNumCols > 0 || $pNumRows > 0) ? - uksort($aDataValidationCollection, ['self', 'cellReverseSort']) : uksort($aDataValidationCollection, ['self', 'cellSort']); + $aDataValidationCollection = $worksheet->getDataValidationCollection(); + ($numberOfColumns > 0 || $numberOfRows > 0) + ? uksort($aDataValidationCollection, [self::class, 'cellReverseSort']) + : uksort($aDataValidationCollection, [self::class, 'cellSort']); - foreach ($aDataValidationCollection as $key => $value) { - $newReference = $this->updateCellReference($key, $pBefore, $pNumCols, $pNumRows); - if ($key != $newReference) { - $pSheet->setDataValidation($newReference, $value); - $pSheet->setDataValidation($key, null); + foreach ($aDataValidationCollection as $cellAddress => $dataValidation) { + $newReference = $this->updateCellReference($cellAddress); + if ($cellAddress !== $newReference) { + $dataValidation->setSqref($newReference); + $worksheet->setDataValidation($newReference, $dataValidation); + $worksheet->setDataValidation($cellAddress, null); } } } @@ -255,44 +279,37 @@ class ReferenceHelper /** * Update merged cells when inserting/deleting rows/columns. * - * @param Worksheet $pSheet The worksheet that we're editing - * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1') - * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before - * @param int $pNumCols Number of columns to insert/delete (negative values indicate deletion) - * @param int $beforeRow Number of the row we're inserting/deleting before - * @param int $pNumRows Number of rows to insert/delete (negative values indicate deletion) + * @param Worksheet $worksheet The worksheet that we're editing */ - protected function adjustMergeCells($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows) + protected function adjustMergeCells(Worksheet $worksheet): void { - $aMergeCells = $pSheet->getMergeCells(); + $aMergeCells = $worksheet->getMergeCells(); $aNewMergeCells = []; // the new array of all merge cells - foreach ($aMergeCells as $key => &$value) { - $newReference = $this->updateCellReference($key, $pBefore, $pNumCols, $pNumRows); + foreach ($aMergeCells as $cellAddress => &$value) { + $newReference = $this->updateCellReference($cellAddress); $aNewMergeCells[$newReference] = $newReference; } - $pSheet->setMergeCells($aNewMergeCells); // replace the merge cells array + $worksheet->setMergeCells($aNewMergeCells); // replace the merge cells array } /** * Update protected cells when inserting/deleting rows/columns. * - * @param Worksheet $pSheet The worksheet that we're editing - * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1') - * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before - * @param int $pNumCols Number of columns to insert/delete (negative values indicate deletion) - * @param int $beforeRow Number of the row we're inserting/deleting before - * @param int $pNumRows Number of rows to insert/delete (negative values indicate deletion) + * @param Worksheet $worksheet The worksheet that we're editing + * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) + * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) */ - protected function adjustProtectedCells($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows) + protected function adjustProtectedCells(Worksheet $worksheet, $numberOfColumns, $numberOfRows): void { - $aProtectedCells = $pSheet->getProtectedCells(); - ($pNumCols > 0 || $pNumRows > 0) ? - uksort($aProtectedCells, ['self', 'cellReverseSort']) : uksort($aProtectedCells, ['self', 'cellSort']); - foreach ($aProtectedCells as $key => $value) { - $newReference = $this->updateCellReference($key, $pBefore, $pNumCols, $pNumRows); - if ($key != $newReference) { - $pSheet->protectCells($newReference, $value, true); - $pSheet->unprotectCells($key); + $aProtectedCells = $worksheet->getProtectedCells(); + ($numberOfColumns > 0 || $numberOfRows > 0) + ? uksort($aProtectedCells, [self::class, 'cellReverseSort']) + : uksort($aProtectedCells, [self::class, 'cellSort']); + foreach ($aProtectedCells as $cellAddress => $value) { + $newReference = $this->updateCellReference($cellAddress); + if ($cellAddress !== $newReference) { + $worksheet->protectCells($newReference, $value, true); + $worksheet->unprotectCells($cellAddress); } } } @@ -300,54 +317,49 @@ class ReferenceHelper /** * Update column dimensions when inserting/deleting rows/columns. * - * @param Worksheet $pSheet The worksheet that we're editing - * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1') - * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before - * @param int $pNumCols Number of columns to insert/delete (negative values indicate deletion) - * @param int $beforeRow Number of the row we're inserting/deleting before - * @param int $pNumRows Number of rows to insert/delete (negative values indicate deletion) + * @param Worksheet $worksheet The worksheet that we're editing */ - protected function adjustColumnDimensions($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows) + protected function adjustColumnDimensions(Worksheet $worksheet): void { - $aColumnDimensions = array_reverse($pSheet->getColumnDimensions(), true); + $aColumnDimensions = array_reverse($worksheet->getColumnDimensions(), true); if (!empty($aColumnDimensions)) { foreach ($aColumnDimensions as $objColumnDimension) { - $newReference = $this->updateCellReference($objColumnDimension->getColumnIndex() . '1', $pBefore, $pNumCols, $pNumRows); + $newReference = $this->updateCellReference($objColumnDimension->getColumnIndex() . '1'); [$newReference] = Coordinate::coordinateFromString($newReference); - if ($objColumnDimension->getColumnIndex() != $newReference) { + if ($objColumnDimension->getColumnIndex() !== $newReference) { $objColumnDimension->setColumnIndex($newReference); } } - $pSheet->refreshColumnDimensions(); + + $worksheet->refreshColumnDimensions(); } } /** * Update row dimensions when inserting/deleting rows/columns. * - * @param Worksheet $pSheet The worksheet that we're editing - * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1') - * @param int $beforeColumnIndex Index number of the column we're inserting/deleting before - * @param int $pNumCols Number of columns to insert/delete (negative values indicate deletion) + * @param Worksheet $worksheet The worksheet that we're editing * @param int $beforeRow Number of the row we're inserting/deleting before - * @param int $pNumRows Number of rows to insert/delete (negative values indicate deletion) + * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) */ - protected function adjustRowDimensions($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows) + protected function adjustRowDimensions(Worksheet $worksheet, $beforeRow, $numberOfRows): void { - $aRowDimensions = array_reverse($pSheet->getRowDimensions(), true); + $aRowDimensions = array_reverse($worksheet->getRowDimensions(), true); if (!empty($aRowDimensions)) { foreach ($aRowDimensions as $objRowDimension) { - $newReference = $this->updateCellReference('A' . $objRowDimension->getRowIndex(), $pBefore, $pNumCols, $pNumRows); + $newReference = $this->updateCellReference('A' . $objRowDimension->getRowIndex()); [, $newReference] = Coordinate::coordinateFromString($newReference); - if ($objRowDimension->getRowIndex() != $newReference) { - $objRowDimension->setRowIndex($newReference); + $newRoweference = (int) $newReference; + if ($objRowDimension->getRowIndex() !== $newRoweference) { + $objRowDimension->setRowIndex($newRoweference); } } - $pSheet->refreshRowDimensions(); - $copyDimension = $pSheet->getRowDimension($beforeRow - 1); - for ($i = $beforeRow; $i <= $beforeRow - 1 + $pNumRows; ++$i) { - $newDimension = $pSheet->getRowDimension($i); + $worksheet->refreshRowDimensions(); + + $copyDimension = $worksheet->getRowDimension($beforeRow - 1); + for ($i = $beforeRow; $i <= $beforeRow - 1 + $numberOfRows; ++$i) { + $newDimension = $worksheet->getRowDimension($i); $newDimension->setRowHeight($copyDimension->getRowHeight()); $newDimension->setVisible($copyDimension->getVisible()); $newDimension->setOutlineLevel($copyDimension->getOutlineLevel()); @@ -359,301 +371,244 @@ class ReferenceHelper /** * Insert a new column or row, updating all possible related data. * - * @param string $pBefore Insert before this cell address (e.g. 'A1') - * @param int $pNumCols Number of columns to insert/delete (negative values indicate deletion) - * @param int $pNumRows Number of rows to insert/delete (negative values indicate deletion) - * @param Worksheet $pSheet The worksheet that we're editing - * - * @throws Exception + * @param string $beforeCellAddress Insert before this cell address (e.g. 'A1') + * @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion) + * @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion) + * @param Worksheet $worksheet The worksheet that we're editing */ - public function insertNewBefore($pBefore, $pNumCols, $pNumRows, Worksheet $pSheet) - { - $remove = ($pNumCols < 0 || $pNumRows < 0); - $allCoordinates = $pSheet->getCoordinates(); + public function insertNewBefore( + string $beforeCellAddress, + int $numberOfColumns, + int $numberOfRows, + Worksheet $worksheet + ): void { + $remove = ($numberOfColumns < 0 || $numberOfRows < 0); - // Get coordinate of $pBefore - [$beforeColumn, $beforeRow] = Coordinate::coordinateFromString($pBefore); - $beforeColumnIndex = Coordinate::columnIndexFromString($beforeColumn); + if ( + $this->cellReferenceHelper === null || + $this->cellReferenceHelper->refreshRequired($beforeCellAddress, $numberOfColumns, $numberOfRows) + ) { + $this->cellReferenceHelper = new CellReferenceHelper($beforeCellAddress, $numberOfColumns, $numberOfRows); + } + + // Get coordinate of $beforeCellAddress + [$beforeColumn, $beforeRow] = Coordinate::indexesFromString($beforeCellAddress); // Clear cells if we are removing columns or rows - $highestColumn = $pSheet->getHighestColumn(); - $highestRow = $pSheet->getHighestRow(); + $highestColumn = $worksheet->getHighestColumn(); + $highestRow = $worksheet->getHighestRow(); // 1. Clear column strips if we are removing columns - if ($pNumCols < 0 && $beforeColumnIndex - 2 + $pNumCols > 0) { - for ($i = 1; $i <= $highestRow - 1; ++$i) { - for ($j = $beforeColumnIndex - 1 + $pNumCols; $j <= $beforeColumnIndex - 2; ++$j) { - $coordinate = Coordinate::stringFromColumnIndex($j + 1) . $i; - $pSheet->removeConditionalStyles($coordinate); - if ($pSheet->cellExists($coordinate)) { - $pSheet->getCell($coordinate)->setValueExplicit('', DataType::TYPE_NULL); - $pSheet->getCell($coordinate)->setXfIndex(0); - } - } - } + if ($numberOfColumns < 0 && $beforeColumn - 2 + $numberOfColumns > 0) { + $this->clearColumnStrips($highestRow, $beforeColumn, $numberOfColumns, $worksheet); } // 2. Clear row strips if we are removing rows - if ($pNumRows < 0 && $beforeRow - 1 + $pNumRows > 0) { - for ($i = $beforeColumnIndex - 1; $i <= Coordinate::columnIndexFromString($highestColumn) - 1; ++$i) { - for ($j = $beforeRow + $pNumRows; $j <= $beforeRow - 1; ++$j) { - $coordinate = Coordinate::stringFromColumnIndex($i + 1) . $j; - $pSheet->removeConditionalStyles($coordinate); - if ($pSheet->cellExists($coordinate)) { - $pSheet->getCell($coordinate)->setValueExplicit('', DataType::TYPE_NULL); - $pSheet->getCell($coordinate)->setXfIndex(0); - } - } + if ($numberOfRows < 0 && $beforeRow - 1 + $numberOfRows > 0) { + $this->clearRowStrips($highestColumn, $beforeColumn, $beforeRow, $numberOfRows, $worksheet); + } + + // Find missing coordinates. This is important when inserting column before the last column + $cellCollection = $worksheet->getCellCollection(); + $missingCoordinates = array_filter( + array_map(function ($row) use ($highestColumn) { + return $highestColumn . $row; + }, range(1, $highestRow)), + function ($coordinate) use ($cellCollection) { + return $cellCollection->has($coordinate) === false; + } + ); + + // Create missing cells with null values + if (!empty($missingCoordinates)) { + foreach ($missingCoordinates as $coordinate) { + $worksheet->createNewCell($coordinate); } } - // Loop through cells, bottom-up, and change cell coordinate + $allCoordinates = $worksheet->getCoordinates(); if ($remove) { // It's faster to reverse and pop than to use unshift, especially with large cell collections $allCoordinates = array_reverse($allCoordinates); } + + // Loop through cells, bottom-up, and change cell coordinate while ($coordinate = array_pop($allCoordinates)) { - $cell = $pSheet->getCell($coordinate); + $cell = $worksheet->getCell($coordinate); $cellIndex = Coordinate::columnIndexFromString($cell->getColumn()); - if ($cellIndex - 1 + $pNumCols < 0) { + if ($cellIndex - 1 + $numberOfColumns < 0) { continue; } // New coordinate - $newCoordinate = Coordinate::stringFromColumnIndex($cellIndex + $pNumCols) . ($cell->getRow() + $pNumRows); + $newCoordinate = Coordinate::stringFromColumnIndex($cellIndex + $numberOfColumns) . ($cell->getRow() + $numberOfRows); // Should the cell be updated? Move value and cellXf index from one cell to another. - if (($cellIndex >= $beforeColumnIndex) && ($cell->getRow() >= $beforeRow)) { + if (($cellIndex >= $beforeColumn) && ($cell->getRow() >= $beforeRow)) { // Update cell styles - $pSheet->getCell($newCoordinate)->setXfIndex($cell->getXfIndex()); + $worksheet->getCell($newCoordinate)->setXfIndex($cell->getXfIndex()); // Insert this cell at its new location - if ($cell->getDataType() == DataType::TYPE_FORMULA) { + if ($cell->getDataType() === DataType::TYPE_FORMULA) { // Formula should be adjusted - $pSheet->getCell($newCoordinate) - ->setValue($this->updateFormulaReferences($cell->getValue(), $pBefore, $pNumCols, $pNumRows, $pSheet->getTitle())); + $worksheet->getCell($newCoordinate) + ->setValue($this->updateFormulaReferences($cell->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle())); } else { // Formula should not be adjusted - $pSheet->getCell($newCoordinate)->setValue($cell->getValue()); + $worksheet->getCell($newCoordinate)->setValueExplicit($cell->getValue(), $cell->getDataType()); } // Clear the original cell - $pSheet->getCellCollection()->delete($coordinate); + $worksheet->getCellCollection()->delete($coordinate); } else { /* We don't need to update styles for rows/columns before our insertion position, but we do still need to adjust any formulae in those cells */ - if ($cell->getDataType() == DataType::TYPE_FORMULA) { + if ($cell->getDataType() === DataType::TYPE_FORMULA) { // Formula should be adjusted - $cell->setValue($this->updateFormulaReferences($cell->getValue(), $pBefore, $pNumCols, $pNumRows, $pSheet->getTitle())); + $cell->setValue($this->updateFormulaReferences($cell->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle())); } } } // Duplicate styles for the newly inserted cells - $highestColumn = $pSheet->getHighestColumn(); - $highestRow = $pSheet->getHighestRow(); + $highestColumn = $worksheet->getHighestColumn(); + $highestRow = $worksheet->getHighestRow(); - if ($pNumCols > 0 && $beforeColumnIndex - 2 > 0) { - for ($i = $beforeRow; $i <= $highestRow - 1; ++$i) { - // Style - $coordinate = Coordinate::stringFromColumnIndex($beforeColumnIndex - 1) . $i; - if ($pSheet->cellExists($coordinate)) { - $xfIndex = $pSheet->getCell($coordinate)->getXfIndex(); - $conditionalStyles = $pSheet->conditionalStylesExists($coordinate) ? - $pSheet->getConditionalStyles($coordinate) : false; - for ($j = $beforeColumnIndex; $j <= $beforeColumnIndex - 1 + $pNumCols; ++$j) { - $pSheet->getCellByColumnAndRow($j, $i)->setXfIndex($xfIndex); - if ($conditionalStyles) { - $cloned = []; - foreach ($conditionalStyles as $conditionalStyle) { - $cloned[] = clone $conditionalStyle; - } - $pSheet->setConditionalStyles(Coordinate::stringFromColumnIndex($j) . $i, $cloned); - } - } - } - } + if ($numberOfColumns > 0 && $beforeColumn - 2 > 0) { + $this->duplicateStylesByColumn($worksheet, $beforeColumn, $beforeRow, $highestRow, $numberOfColumns); } - if ($pNumRows > 0 && $beforeRow - 1 > 0) { - for ($i = $beforeColumnIndex; $i <= Coordinate::columnIndexFromString($highestColumn); ++$i) { - // Style - $coordinate = Coordinate::stringFromColumnIndex($i) . ($beforeRow - 1); - if ($pSheet->cellExists($coordinate)) { - $xfIndex = $pSheet->getCell($coordinate)->getXfIndex(); - $conditionalStyles = $pSheet->conditionalStylesExists($coordinate) ? - $pSheet->getConditionalStyles($coordinate) : false; - for ($j = $beforeRow; $j <= $beforeRow - 1 + $pNumRows; ++$j) { - $pSheet->getCell(Coordinate::stringFromColumnIndex($i) . $j)->setXfIndex($xfIndex); - if ($conditionalStyles) { - $cloned = []; - foreach ($conditionalStyles as $conditionalStyle) { - $cloned[] = clone $conditionalStyle; - } - $pSheet->setConditionalStyles(Coordinate::stringFromColumnIndex($i) . $j, $cloned); - } - } - } - } + if ($numberOfRows > 0 && $beforeRow - 1 > 0) { + $this->duplicateStylesByRow($worksheet, $beforeColumn, $beforeRow, $highestColumn, $numberOfRows); } // Update worksheet: column dimensions - $this->adjustColumnDimensions($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows); + $this->adjustColumnDimensions($worksheet); // Update worksheet: row dimensions - $this->adjustRowDimensions($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows); + $this->adjustRowDimensions($worksheet, $beforeRow, $numberOfRows); // Update worksheet: page breaks - $this->adjustPageBreaks($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows); + $this->adjustPageBreaks($worksheet, $numberOfColumns, $numberOfRows); // Update worksheet: comments - $this->adjustComments($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows); + $this->adjustComments($worksheet); // Update worksheet: hyperlinks - $this->adjustHyperlinks($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows); + $this->adjustHyperlinks($worksheet, $numberOfColumns, $numberOfRows); + + // Update worksheet: conditional formatting styles + $this->adjustConditionalFormatting($worksheet, $numberOfColumns, $numberOfRows); // Update worksheet: data validations - $this->adjustDataValidations($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows); + $this->adjustDataValidations($worksheet, $numberOfColumns, $numberOfRows); // Update worksheet: merge cells - $this->adjustMergeCells($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows); + $this->adjustMergeCells($worksheet); // Update worksheet: protected cells - $this->adjustProtectedCells($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows); + $this->adjustProtectedCells($worksheet, $numberOfColumns, $numberOfRows); // Update worksheet: autofilter - $autoFilter = $pSheet->getAutoFilter(); - $autoFilterRange = $autoFilter->getRange(); - if (!empty($autoFilterRange)) { - if ($pNumCols != 0) { - $autoFilterColumns = $autoFilter->getColumns(); - if (count($autoFilterColumns) > 0) { - $column = ''; - $row = 0; - sscanf($pBefore, '%[A-Z]%d', $column, $row); - $columnIndex = Coordinate::columnIndexFromString($column); - [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($autoFilterRange); - if ($columnIndex <= $rangeEnd[0]) { - if ($pNumCols < 0) { - // If we're actually deleting any columns that fall within the autofilter range, - // then we delete any rules for those columns - $deleteColumn = $columnIndex + $pNumCols - 1; - $deleteCount = abs($pNumCols); - for ($i = 1; $i <= $deleteCount; ++$i) { - if (isset($autoFilterColumns[Coordinate::stringFromColumnIndex($deleteColumn + 1)])) { - $autoFilter->clearColumn(Coordinate::stringFromColumnIndex($deleteColumn + 1)); - } - ++$deleteColumn; - } - } - $startCol = ($columnIndex > $rangeStart[0]) ? $columnIndex : $rangeStart[0]; + $this->adjustAutoFilter($worksheet, $beforeCellAddress, $numberOfColumns); - // Shuffle columns in autofilter range - if ($pNumCols > 0) { - $startColRef = $startCol; - $endColRef = $rangeEnd[0]; - $toColRef = $rangeEnd[0] + $pNumCols; - - do { - $autoFilter->shiftColumn(Coordinate::stringFromColumnIndex($endColRef), Coordinate::stringFromColumnIndex($toColRef)); - --$endColRef; - --$toColRef; - } while ($startColRef <= $endColRef); - } else { - // For delete, we shuffle from beginning to end to avoid overwriting - $startColID = Coordinate::stringFromColumnIndex($startCol); - $toColID = Coordinate::stringFromColumnIndex($startCol + $pNumCols); - $endColID = Coordinate::stringFromColumnIndex($rangeEnd[0] + 1); - do { - $autoFilter->shiftColumn($startColID, $toColID); - ++$startColID; - ++$toColID; - } while ($startColID != $endColID); - } - } - } - } - $pSheet->setAutoFilter($this->updateCellReference($autoFilterRange, $pBefore, $pNumCols, $pNumRows)); - } + // Update worksheet: table + $this->adjustTable($worksheet, $beforeCellAddress, $numberOfColumns); // Update worksheet: freeze pane - if ($pSheet->getFreezePane()) { - $splitCell = $pSheet->getFreezePane(); - $topLeftCell = $pSheet->getTopLeftCell(); + if ($worksheet->getFreezePane()) { + $splitCell = $worksheet->getFreezePane() ?? ''; + $topLeftCell = $worksheet->getTopLeftCell() ?? ''; - $splitCell = $this->updateCellReference($splitCell, $pBefore, $pNumCols, $pNumRows); - $topLeftCell = $this->updateCellReference($topLeftCell, $pBefore, $pNumCols, $pNumRows); + $splitCell = $this->updateCellReference($splitCell); + $topLeftCell = $this->updateCellReference($topLeftCell); - $pSheet->freezePane($splitCell, $topLeftCell); + $worksheet->freezePane($splitCell, $topLeftCell); } // Page setup - if ($pSheet->getPageSetup()->isPrintAreaSet()) { - $pSheet->getPageSetup()->setPrintArea($this->updateCellReference($pSheet->getPageSetup()->getPrintArea(), $pBefore, $pNumCols, $pNumRows)); + if ($worksheet->getPageSetup()->isPrintAreaSet()) { + $worksheet->getPageSetup()->setPrintArea( + $this->updateCellReference($worksheet->getPageSetup()->getPrintArea()) + ); } // Update worksheet: drawings - $aDrawings = $pSheet->getDrawingCollection(); + $aDrawings = $worksheet->getDrawingCollection(); foreach ($aDrawings as $objDrawing) { - $newReference = $this->updateCellReference($objDrawing->getCoordinates(), $pBefore, $pNumCols, $pNumRows); + $newReference = $this->updateCellReference($objDrawing->getCoordinates()); if ($objDrawing->getCoordinates() != $newReference) { $objDrawing->setCoordinates($newReference); } - } - - // Update workbook: named ranges - if (count($pSheet->getParent()->getNamedRanges()) > 0) { - foreach ($pSheet->getParent()->getNamedRanges() as $namedRange) { - if ($namedRange->getWorksheet()->getHashCode() == $pSheet->getHashCode()) { - $namedRange->setRange($this->updateCellReference($namedRange->getRange(), $pBefore, $pNumCols, $pNumRows)); + if ($objDrawing->getCoordinates2() !== '') { + $newReference = $this->updateCellReference($objDrawing->getCoordinates2()); + if ($objDrawing->getCoordinates2() != $newReference) { + $objDrawing->setCoordinates2($newReference); } } } + // Update workbook: define names + if (count($worksheet->getParent()->getDefinedNames()) > 0) { + $this->updateDefinedNames($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows); + } + // Garbage collect - $pSheet->garbageCollect(); + $worksheet->garbageCollect(); } /** * Update references within formulas. * - * @param string $pFormula Formula to update - * @param string $pBefore Insert before this one - * @param int $pNumCols Number of columns to insert - * @param int $pNumRows Number of rows to insert - * @param string $sheetName Worksheet name/title - * - * @throws Exception + * @param string $formula Formula to update + * @param string $beforeCellAddress Insert before this one + * @param int $numberOfColumns Number of columns to insert + * @param int $numberOfRows Number of rows to insert + * @param string $worksheetName Worksheet name/title * * @return string Updated formula */ - public function updateFormulaReferences($pFormula = '', $pBefore = 'A1', $pNumCols = 0, $pNumRows = 0, $sheetName = '') - { + public function updateFormulaReferences( + $formula = '', + $beforeCellAddress = 'A1', + $numberOfColumns = 0, + $numberOfRows = 0, + $worksheetName = '', + bool $includeAbsoluteReferences = false + ) { + if ( + $this->cellReferenceHelper === null || + $this->cellReferenceHelper->refreshRequired($beforeCellAddress, $numberOfColumns, $numberOfRows) + ) { + $this->cellReferenceHelper = new CellReferenceHelper($beforeCellAddress, $numberOfColumns, $numberOfRows); + } + // Update cell references in the formula - $formulaBlocks = explode('"', $pFormula); + $formulaBlocks = explode('"', $formula); $i = false; foreach ($formulaBlocks as &$formulaBlock) { // Ignore blocks that were enclosed in quotes (alternating entries in the $formulaBlocks array after the explode) - if ($i = !$i) { + $i = $i === false; + if ($i) { $adjustCount = 0; $newCellTokens = $cellTokens = []; // Search for row ranges (e.g. 'Sheet1'!3:5 or 3:5) with or without $ absolutes (e.g. $3:5) - $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_ROWRANGE . '/i', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER); + $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_ROWRANGE . '/mui', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER); if ($matchCount > 0) { foreach ($matches as $match) { $fromString = ($match[2] > '') ? $match[2] . '!' : ''; $fromString .= $match[3] . ':' . $match[4]; - $modified3 = substr($this->updateCellReference('$A' . $match[3], $pBefore, $pNumCols, $pNumRows), 2); - $modified4 = substr($this->updateCellReference('$A' . $match[4], $pBefore, $pNumCols, $pNumRows), 2); + $modified3 = substr($this->updateCellReference('$A' . $match[3], $includeAbsoluteReferences), 2); + $modified4 = substr($this->updateCellReference('$A' . $match[4], $includeAbsoluteReferences), 2); if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) { - if (($match[2] == '') || (trim($match[2], "'") == $sheetName)) { + if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) { $toString = ($match[2] > '') ? $match[2] . '!' : ''; $toString .= $modified3 . ':' . $modified4; // Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more $column = 100000; - $row = 10000000 + trim($match[3], '$'); + $row = 10000000 + (int) trim($match[3], '$'); $cellIndex = $column . $row; $newCellTokens[$cellIndex] = preg_quote($toString, '/'); @@ -664,16 +619,16 @@ class ReferenceHelper } } // Search for column ranges (e.g. 'Sheet1'!C:E or C:E) with or without $ absolutes (e.g. $C:E) - $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_COLRANGE . '/i', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER); + $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_COLRANGE . '/mui', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER); if ($matchCount > 0) { foreach ($matches as $match) { $fromString = ($match[2] > '') ? $match[2] . '!' : ''; $fromString .= $match[3] . ':' . $match[4]; - $modified3 = substr($this->updateCellReference($match[3] . '$1', $pBefore, $pNumCols, $pNumRows), 0, -2); - $modified4 = substr($this->updateCellReference($match[4] . '$1', $pBefore, $pNumCols, $pNumRows), 0, -2); + $modified3 = substr($this->updateCellReference($match[3] . '$1', $includeAbsoluteReferences), 0, -2); + $modified4 = substr($this->updateCellReference($match[4] . '$1', $includeAbsoluteReferences), 0, -2); if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) { - if (($match[2] == '') || (trim($match[2], "'") == $sheetName)) { + if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) { $toString = ($match[2] > '') ? $match[2] . '!' : ''; $toString .= $modified3 . ':' . $modified4; // Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more @@ -689,22 +644,22 @@ class ReferenceHelper } } // Search for cell ranges (e.g. 'Sheet1'!A3:C5 or A3:C5) with or without $ absolutes (e.g. $A1:C$5) - $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_CELLRANGE . '/i', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER); + $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_CELLRANGE . '/mui', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER); if ($matchCount > 0) { foreach ($matches as $match) { $fromString = ($match[2] > '') ? $match[2] . '!' : ''; $fromString .= $match[3] . ':' . $match[4]; - $modified3 = $this->updateCellReference($match[3], $pBefore, $pNumCols, $pNumRows); - $modified4 = $this->updateCellReference($match[4], $pBefore, $pNumCols, $pNumRows); + $modified3 = $this->updateCellReference($match[3], $includeAbsoluteReferences); + $modified4 = $this->updateCellReference($match[4], $includeAbsoluteReferences); if ($match[3] . $match[4] !== $modified3 . $modified4) { - if (($match[2] == '') || (trim($match[2], "'") == $sheetName)) { + if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) { $toString = ($match[2] > '') ? $match[2] . '!' : ''; $toString .= $modified3 . ':' . $modified4; [$column, $row] = Coordinate::coordinateFromString($match[3]); // Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more $column = Coordinate::columnIndexFromString(trim($column, '$')) + 100000; - $row = trim($row, '$') + 10000000; + $row = (int) trim($row, '$') + 10000000; $cellIndex = $column . $row; $newCellTokens[$cellIndex] = preg_quote($toString, '/'); @@ -715,23 +670,25 @@ class ReferenceHelper } } // Search for cell references (e.g. 'Sheet1'!A3 or C5) with or without $ absolutes (e.g. $A1 or C$5) - $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_CELLREF . '/i', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER); + $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_CELLREF . '/mui', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER); if ($matchCount > 0) { foreach ($matches as $match) { $fromString = ($match[2] > '') ? $match[2] . '!' : ''; $fromString .= $match[3]; - $modified3 = $this->updateCellReference($match[3], $pBefore, $pNumCols, $pNumRows); + $modified3 = $this->updateCellReference($match[3], $includeAbsoluteReferences); if ($match[3] !== $modified3) { - if (($match[2] == '') || (trim($match[2], "'") == $sheetName)) { + if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) { $toString = ($match[2] > '') ? $match[2] . '!' : ''; $toString .= $modified3; [$column, $row] = Coordinate::coordinateFromString($match[3]); + $columnAdditionalIndex = $column[0] === '$' ? 1 : 0; + $rowAdditionalIndex = $row[0] === '$' ? 1 : 0; // Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more $column = Coordinate::columnIndexFromString(trim($column, '$')) + 100000; - $row = trim($row, '$') + 10000000; - $cellIndex = $row . $column; + $row = (int) trim($row, '$') + 10000000; + $cellIndex = $row . $rowAdditionalIndex . $column . $columnAdditionalIndex; $newCellTokens[$cellIndex] = preg_quote($toString, '/'); $cellTokens[$cellIndex] = '/(? 0) { - if ($pNumCols > 0 || $pNumRows > 0) { + if ($numberOfColumns > 0 || $numberOfRows > 0) { krsort($cellTokens); krsort($newCellTokens); } else { ksort($cellTokens); ksort($newCellTokens); } // Update cell references in the formula - $formulaBlock = str_replace('\\', '', preg_replace($cellTokens, $newCellTokens, $formulaBlock)); + $formulaBlock = str_replace('\\', '', (string) preg_replace($cellTokens, $newCellTokens, $formulaBlock)); } } } @@ -759,43 +716,173 @@ class ReferenceHelper } /** - * Update cell reference. - * - * @param string $pCellRange Cell range - * @param string $pBefore Insert before this one - * @param int $pNumCols Number of columns to increment - * @param int $pNumRows Number of rows to increment - * - * @throws Exception - * - * @return string Updated cell range + * Update all cell references within a formula, irrespective of worksheet. */ - public function updateCellReference($pCellRange = 'A1', $pBefore = 'A1', $pNumCols = 0, $pNumRows = 0) + public function updateFormulaReferencesAnyWorksheet(string $formula = '', int $numberOfColumns = 0, int $numberOfRows = 0): string { - // Is it in another worksheet? Will not have to update anything. - if (strpos($pCellRange, '!') !== false) { - return $pCellRange; - // Is it a range or a single cell? - } elseif (!Coordinate::coordinateIsRange($pCellRange)) { - // Single cell - return $this->updateSingleCellReference($pCellRange, $pBefore, $pNumCols, $pNumRows); - } elseif (Coordinate::coordinateIsRange($pCellRange)) { - // Range - return $this->updateCellRange($pCellRange, $pBefore, $pNumCols, $pNumRows); + $formula = $this->updateCellReferencesAllWorksheets($formula, $numberOfColumns, $numberOfRows); + + if ($numberOfColumns !== 0) { + $formula = $this->updateColumnRangesAllWorksheets($formula, $numberOfColumns); } - // Return original - return $pCellRange; + if ($numberOfRows !== 0) { + $formula = $this->updateRowRangesAllWorksheets($formula, $numberOfRows); + } + + return $formula; + } + + private function updateCellReferencesAllWorksheets(string $formula, int $numberOfColumns, int $numberOfRows): string + { + $splitCount = preg_match_all( + '/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/mui', + $formula, + $splitRanges, + PREG_OFFSET_CAPTURE + ); + + $columnLengths = array_map('strlen', array_column($splitRanges[6], 0)); + $rowLengths = array_map('strlen', array_column($splitRanges[7], 0)); + $columnOffsets = array_column($splitRanges[6], 1); + $rowOffsets = array_column($splitRanges[7], 1); + + $columns = $splitRanges[6]; + $rows = $splitRanges[7]; + + while ($splitCount > 0) { + --$splitCount; + $columnLength = $columnLengths[$splitCount]; + $rowLength = $rowLengths[$splitCount]; + $columnOffset = $columnOffsets[$splitCount]; + $rowOffset = $rowOffsets[$splitCount]; + $column = $columns[$splitCount][0]; + $row = $rows[$splitCount][0]; + + if (!empty($column) && $column[0] !== '$') { + $column = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($column) + $numberOfColumns); + $formula = substr($formula, 0, $columnOffset) . $column . substr($formula, $columnOffset + $columnLength); + } + if (!empty($row) && $row[0] !== '$') { + $row = (int) $row + $numberOfRows; + $formula = substr($formula, 0, $rowOffset) . $row . substr($formula, $rowOffset + $rowLength); + } + } + + return $formula; + } + + private function updateColumnRangesAllWorksheets(string $formula, int $numberOfColumns): string + { + $splitCount = preg_match_all( + '/' . Calculation::CALCULATION_REGEXP_COLUMNRANGE_RELATIVE . '/mui', + $formula, + $splitRanges, + PREG_OFFSET_CAPTURE + ); + + $fromColumnLengths = array_map('strlen', array_column($splitRanges[1], 0)); + $fromColumnOffsets = array_column($splitRanges[1], 1); + $toColumnLengths = array_map('strlen', array_column($splitRanges[2], 0)); + $toColumnOffsets = array_column($splitRanges[2], 1); + + $fromColumns = $splitRanges[1]; + $toColumns = $splitRanges[2]; + + while ($splitCount > 0) { + --$splitCount; + $fromColumnLength = $fromColumnLengths[$splitCount]; + $toColumnLength = $toColumnLengths[$splitCount]; + $fromColumnOffset = $fromColumnOffsets[$splitCount]; + $toColumnOffset = $toColumnOffsets[$splitCount]; + $fromColumn = $fromColumns[$splitCount][0]; + $toColumn = $toColumns[$splitCount][0]; + + if (!empty($fromColumn) && $fromColumn[0] !== '$') { + $fromColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($fromColumn) + $numberOfColumns); + $formula = substr($formula, 0, $fromColumnOffset) . $fromColumn . substr($formula, $fromColumnOffset + $fromColumnLength); + } + if (!empty($toColumn) && $toColumn[0] !== '$') { + $toColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($toColumn) + $numberOfColumns); + $formula = substr($formula, 0, $toColumnOffset) . $toColumn . substr($formula, $toColumnOffset + $toColumnLength); + } + } + + return $formula; + } + + private function updateRowRangesAllWorksheets(string $formula, int $numberOfRows): string + { + $splitCount = preg_match_all( + '/' . Calculation::CALCULATION_REGEXP_ROWRANGE_RELATIVE . '/mui', + $formula, + $splitRanges, + PREG_OFFSET_CAPTURE + ); + + $fromRowLengths = array_map('strlen', array_column($splitRanges[1], 0)); + $fromRowOffsets = array_column($splitRanges[1], 1); + $toRowLengths = array_map('strlen', array_column($splitRanges[2], 0)); + $toRowOffsets = array_column($splitRanges[2], 1); + + $fromRows = $splitRanges[1]; + $toRows = $splitRanges[2]; + + while ($splitCount > 0) { + --$splitCount; + $fromRowLength = $fromRowLengths[$splitCount]; + $toRowLength = $toRowLengths[$splitCount]; + $fromRowOffset = $fromRowOffsets[$splitCount]; + $toRowOffset = $toRowOffsets[$splitCount]; + $fromRow = $fromRows[$splitCount][0]; + $toRow = $toRows[$splitCount][0]; + + if (!empty($fromRow) && $fromRow[0] !== '$') { + $fromRow = (int) $fromRow + $numberOfRows; + $formula = substr($formula, 0, $fromRowOffset) . $fromRow . substr($formula, $fromRowOffset + $fromRowLength); + } + if (!empty($toRow) && $toRow[0] !== '$') { + $toRow = (int) $toRow + $numberOfRows; + $formula = substr($formula, 0, $toRowOffset) . $toRow . substr($formula, $toRowOffset + $toRowLength); + } + } + + return $formula; } /** - * Update named formulas (i.e. containing worksheet references / named ranges). + * Update cell reference. + * + * @param string $cellReference Cell address or range of addresses + * + * @return string Updated cell range + */ + private function updateCellReference($cellReference = 'A1', bool $includeAbsoluteReferences = false) + { + // Is it in another worksheet? Will not have to update anything. + if (strpos($cellReference, '!') !== false) { + return $cellReference; + // Is it a range or a single cell? + } elseif (!Coordinate::coordinateIsRange($cellReference)) { + // Single cell + return $this->cellReferenceHelper->updateCellReference($cellReference, $includeAbsoluteReferences); + } elseif (Coordinate::coordinateIsRange($cellReference)) { + // Range + return $this->updateCellRange($cellReference, $includeAbsoluteReferences); + } + + // Return original + return $cellReference; + } + + /** + * Update named formulae (i.e. containing worksheet references / named ranges). * * @param Spreadsheet $spreadsheet Object to update * @param string $oldName Old name (name to replace) * @param string $newName New name */ - public function updateNamedFormulas(Spreadsheet $spreadsheet, $oldName = '', $newName = '') + public function updateNamedFormulae(Spreadsheet $spreadsheet, $oldName = '', $newName = ''): void { if ($oldName == '') { return; @@ -804,7 +891,7 @@ class ReferenceHelper foreach ($spreadsheet->getWorksheetIterator() as $sheet) { foreach ($sheet->getCoordinates(false) as $coordinate) { $cell = $sheet->getCell($coordinate); - if (($cell !== null) && ($cell->getDataType() == DataType::TYPE_FORMULA)) { + if ($cell->getDataType() === DataType::TYPE_FORMULA) { $formula = $cell->getValue(); if (strpos($formula, $oldName) !== false) { $formula = str_replace("'" . $oldName . "'!", "'" . $newName . "'!", $formula); @@ -816,38 +903,69 @@ class ReferenceHelper } } + private function updateDefinedNames(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void + { + foreach ($worksheet->getParent()->getDefinedNames() as $definedName) { + if ($definedName->isFormula() === false) { + $this->updateNamedRange($definedName, $worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows); + } else { + $this->updateNamedFormula($definedName, $worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows); + } + } + } + + private function updateNamedRange(DefinedName $definedName, Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void + { + $cellAddress = $definedName->getValue(); + $asFormula = ($cellAddress[0] === '='); + if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashCode() === $worksheet->getHashCode()) { + if ($asFormula === true) { + $formula = $this->updateFormulaReferences($cellAddress, $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle()); + $definedName->setValue($formula); + } else { + $definedName->setValue($this->updateCellReference(ltrim($cellAddress, '='))); + } + } + } + + private function updateNamedFormula(DefinedName $definedName, Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void + { + if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashCode() === $worksheet->getHashCode()) { + $formula = $definedName->getValue(); + $formula = $this->updateFormulaReferences($formula, $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle()); + $definedName->setValue($formula); + } + } + /** * Update cell range. * - * @param string $pCellRange Cell range (e.g. 'B2:D4', 'B:C' or '2:3') - * @param string $pBefore Insert before this one - * @param int $pNumCols Number of columns to increment - * @param int $pNumRows Number of rows to increment - * - * @throws Exception + * @param string $cellRange Cell range (e.g. 'B2:D4', 'B:C' or '2:3') * * @return string Updated cell range */ - private function updateCellRange($pCellRange = 'A1:A1', $pBefore = 'A1', $pNumCols = 0, $pNumRows = 0) + private function updateCellRange(string $cellRange = 'A1:A1', bool $includeAbsoluteReferences = false): string { - if (!Coordinate::coordinateIsRange($pCellRange)) { + if (!Coordinate::coordinateIsRange($cellRange)) { throw new Exception('Only cell ranges may be passed to this method.'); } // Update range - $range = Coordinate::splitRange($pCellRange); + $range = Coordinate::splitRange($cellRange); $ic = count($range); for ($i = 0; $i < $ic; ++$i) { $jc = count($range[$i]); for ($j = 0; $j < $jc; ++$j) { if (ctype_alpha($range[$i][$j])) { - $r = Coordinate::coordinateFromString($this->updateSingleCellReference($range[$i][$j] . '1', $pBefore, $pNumCols, $pNumRows)); - $range[$i][$j] = $r[0]; + $range[$i][$j] = Coordinate::coordinateFromString( + $this->cellReferenceHelper->updateCellReference($range[$i][$j] . '1', $includeAbsoluteReferences) + )[0]; } elseif (ctype_digit($range[$i][$j])) { - $r = Coordinate::coordinateFromString($this->updateSingleCellReference('A' . $range[$i][$j], $pBefore, $pNumCols, $pNumRows)); - $range[$i][$j] = $r[1]; + $range[$i][$j] = Coordinate::coordinateFromString( + $this->cellReferenceHelper->updateCellReference('A' . $range[$i][$j], $includeAbsoluteReferences) + )[1]; } else { - $range[$i][$j] = $this->updateSingleCellReference($range[$i][$j], $pBefore, $pNumCols, $pNumRows); + $range[$i][$j] = $this->cellReferenceHelper->updateCellReference($range[$i][$j], $includeAbsoluteReferences); } } } @@ -856,52 +974,234 @@ class ReferenceHelper return Coordinate::buildRange($range); } - /** - * Update single cell reference. - * - * @param string $pCellReference Single cell reference - * @param string $pBefore Insert before this one - * @param int $pNumCols Number of columns to increment - * @param int $pNumRows Number of rows to increment - * - * @throws Exception - * - * @return string Updated cell reference - */ - private function updateSingleCellReference($pCellReference = 'A1', $pBefore = 'A1', $pNumCols = 0, $pNumRows = 0) + private function clearColumnStrips(int $highestRow, int $beforeColumn, int $numberOfColumns, Worksheet $worksheet): void { - if (Coordinate::coordinateIsRange($pCellReference)) { - throw new Exception('Only single cell references may be passed to this method.'); + $startColumnId = Coordinate::stringFromColumnIndex($beforeColumn + $numberOfColumns); + $endColumnId = Coordinate::stringFromColumnIndex($beforeColumn); + + for ($row = 1; $row <= $highestRow - 1; ++$row) { + for ($column = $startColumnId; $column !== $endColumnId; ++$column) { + $coordinate = $column . $row; + $this->clearStripCell($worksheet, $coordinate); + } } + } - // Get coordinate of $pBefore - [$beforeColumn, $beforeRow] = Coordinate::coordinateFromString($pBefore); + private function clearRowStrips(string $highestColumn, int $beforeColumn, int $beforeRow, int $numberOfRows, Worksheet $worksheet): void + { + $startColumnId = Coordinate::stringFromColumnIndex($beforeColumn); + ++$highestColumn; - // Get coordinate of $pCellReference - [$newColumn, $newRow] = Coordinate::coordinateFromString($pCellReference); - - // Verify which parts should be updated - $updateColumn = (($newColumn[0] != '$') && ($beforeColumn[0] != '$') && (Coordinate::columnIndexFromString($newColumn) >= Coordinate::columnIndexFromString($beforeColumn))); - $updateRow = (($newRow[0] != '$') && ($beforeRow[0] != '$') && $newRow >= $beforeRow); - - // Create new column reference - if ($updateColumn) { - $newColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($newColumn) + $pNumCols); + for ($column = $startColumnId; $column !== $highestColumn; ++$column) { + for ($row = $beforeRow + $numberOfRows; $row <= $beforeRow - 1; ++$row) { + $coordinate = $column . $row; + $this->clearStripCell($worksheet, $coordinate); + } } + } - // Create new row reference - if ($updateRow) { - $newRow = $newRow + $pNumRows; + private function clearStripCell(Worksheet $worksheet, string $coordinate): void + { + $worksheet->removeConditionalStyles($coordinate); + $worksheet->setHyperlink($coordinate); + $worksheet->setDataValidation($coordinate); + $worksheet->removeComment($coordinate); + + if ($worksheet->cellExists($coordinate)) { + $worksheet->getCell($coordinate)->setValueExplicit(null, DataType::TYPE_NULL); + $worksheet->getCell($coordinate)->setXfIndex(0); } + } - // Return new reference - return $newColumn . $newRow; + private function adjustAutoFilter(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns): void + { + $autoFilter = $worksheet->getAutoFilter(); + $autoFilterRange = $autoFilter->getRange(); + if (!empty($autoFilterRange)) { + if ($numberOfColumns !== 0) { + $autoFilterColumns = $autoFilter->getColumns(); + if (count($autoFilterColumns) > 0) { + $column = ''; + $row = 0; + sscanf($beforeCellAddress, '%[A-Z]%d', $column, $row); + $columnIndex = Coordinate::columnIndexFromString((string) $column); + [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($autoFilterRange); + if ($columnIndex <= $rangeEnd[0]) { + if ($numberOfColumns < 0) { + $this->adjustAutoFilterDeleteRules($columnIndex, $numberOfColumns, $autoFilterColumns, $autoFilter); + } + $startCol = ($columnIndex > $rangeStart[0]) ? $columnIndex : $rangeStart[0]; + + // Shuffle columns in autofilter range + if ($numberOfColumns > 0) { + $this->adjustAutoFilterInsert($startCol, $numberOfColumns, $rangeEnd[0], $autoFilter); + } else { + $this->adjustAutoFilterDelete($startCol, $numberOfColumns, $rangeEnd[0], $autoFilter); + } + } + } + } + + $worksheet->setAutoFilter( + $this->updateCellReference($autoFilterRange) + ); + } + } + + private function adjustAutoFilterDeleteRules(int $columnIndex, int $numberOfColumns, array $autoFilterColumns, AutoFilter $autoFilter): void + { + // If we're actually deleting any columns that fall within the autofilter range, + // then we delete any rules for those columns + $deleteColumn = $columnIndex + $numberOfColumns - 1; + $deleteCount = abs($numberOfColumns); + + for ($i = 1; $i <= $deleteCount; ++$i) { + $columnName = Coordinate::stringFromColumnIndex($deleteColumn + 1); + if (isset($autoFilterColumns[$columnName])) { + $autoFilter->clearColumn($columnName); + } + ++$deleteColumn; + } + } + + private function adjustAutoFilterInsert(int $startCol, int $numberOfColumns, int $rangeEnd, AutoFilter $autoFilter): void + { + $startColRef = $startCol; + $endColRef = $rangeEnd; + $toColRef = $rangeEnd + $numberOfColumns; + + do { + $autoFilter->shiftColumn(Coordinate::stringFromColumnIndex($endColRef), Coordinate::stringFromColumnIndex($toColRef)); + --$endColRef; + --$toColRef; + } while ($startColRef <= $endColRef); + } + + private function adjustAutoFilterDelete(int $startCol, int $numberOfColumns, int $rangeEnd, AutoFilter $autoFilter): void + { + // For delete, we shuffle from beginning to end to avoid overwriting + $startColID = Coordinate::stringFromColumnIndex($startCol); + $toColID = Coordinate::stringFromColumnIndex($startCol + $numberOfColumns); + $endColID = Coordinate::stringFromColumnIndex($rangeEnd + 1); + + do { + $autoFilter->shiftColumn($startColID, $toColID); + ++$startColID; + ++$toColID; + } while ($startColID !== $endColID); + } + + private function adjustTable(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns): void + { + $tableCollection = $worksheet->getTableCollection(); + + foreach ($tableCollection as $table) { + $tableRange = $table->getRange(); + if (!empty($tableRange)) { + if ($numberOfColumns !== 0) { + $tableColumns = $table->getColumns(); + if (count($tableColumns) > 0) { + $column = ''; + $row = 0; + sscanf($beforeCellAddress, '%[A-Z]%d', $column, $row); + $columnIndex = Coordinate::columnIndexFromString((string) $column); + [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($tableRange); + if ($columnIndex <= $rangeEnd[0]) { + if ($numberOfColumns < 0) { + $this->adjustTableDeleteRules($columnIndex, $numberOfColumns, $tableColumns, $table); + } + $startCol = ($columnIndex > $rangeStart[0]) ? $columnIndex : $rangeStart[0]; + + // Shuffle columns in table range + if ($numberOfColumns > 0) { + $this->adjustTableInsert($startCol, $numberOfColumns, $rangeEnd[0], $table); + } else { + $this->adjustTableDelete($startCol, $numberOfColumns, $rangeEnd[0], $table); + } + } + } + } + + $table->setRange($this->updateCellReference($tableRange)); + } + } + } + + private function adjustTableDeleteRules(int $columnIndex, int $numberOfColumns, array $tableColumns, Table $table): void + { + // If we're actually deleting any columns that fall within the table range, + // then we delete any rules for those columns + $deleteColumn = $columnIndex + $numberOfColumns - 1; + $deleteCount = abs($numberOfColumns); + + for ($i = 1; $i <= $deleteCount; ++$i) { + $columnName = Coordinate::stringFromColumnIndex($deleteColumn + 1); + if (isset($tableColumns[$columnName])) { + $table->clearColumn($columnName); + } + ++$deleteColumn; + } + } + + private function adjustTableInsert(int $startCol, int $numberOfColumns, int $rangeEnd, Table $table): void + { + $startColRef = $startCol; + $endColRef = $rangeEnd; + $toColRef = $rangeEnd + $numberOfColumns; + + do { + $table->shiftColumn(Coordinate::stringFromColumnIndex($endColRef), Coordinate::stringFromColumnIndex($toColRef)); + --$endColRef; + --$toColRef; + } while ($startColRef <= $endColRef); + } + + private function adjustTableDelete(int $startCol, int $numberOfColumns, int $rangeEnd, Table $table): void + { + // For delete, we shuffle from beginning to end to avoid overwriting + $startColID = Coordinate::stringFromColumnIndex($startCol); + $toColID = Coordinate::stringFromColumnIndex($startCol + $numberOfColumns); + $endColID = Coordinate::stringFromColumnIndex($rangeEnd + 1); + + do { + $table->shiftColumn($startColID, $toColID); + ++$startColID; + ++$toColID; + } while ($startColID !== $endColID); + } + + private function duplicateStylesByColumn(Worksheet $worksheet, int $beforeColumn, int $beforeRow, int $highestRow, int $numberOfColumns): void + { + $beforeColumnName = Coordinate::stringFromColumnIndex($beforeColumn - 1); + for ($i = $beforeRow; $i <= $highestRow - 1; ++$i) { + // Style + $coordinate = $beforeColumnName . $i; + if ($worksheet->cellExists($coordinate)) { + $xfIndex = $worksheet->getCell($coordinate)->getXfIndex(); + for ($j = $beforeColumn; $j <= $beforeColumn - 1 + $numberOfColumns; ++$j) { + $worksheet->getCell([$j, $i])->setXfIndex($xfIndex); + } + } + } + } + + private function duplicateStylesByRow(Worksheet $worksheet, int $beforeColumn, int $beforeRow, string $highestColumn, int $numberOfRows): void + { + $highestColumnIndex = Coordinate::columnIndexFromString($highestColumn); + for ($i = $beforeColumn; $i <= $highestColumnIndex; ++$i) { + // Style + $coordinate = Coordinate::stringFromColumnIndex($i) . ($beforeRow - 1); + if ($worksheet->cellExists($coordinate)) { + $xfIndex = $worksheet->getCell($coordinate)->getXfIndex(); + for ($j = $beforeRow; $j <= $beforeRow - 1 + $numberOfRows; ++$j) { + $worksheet->getCell(Coordinate::stringFromColumnIndex($i) . $j)->setXfIndex($xfIndex); + } + } + } } /** * __clone implementation. Cloning should not be allowed in a Singleton! - * - * @throws Exception */ final public function __clone() { diff --git a/PhpOffice/PhpSpreadsheet/RichText/ITextElement.php b/PhpOffice/PhpSpreadsheet/RichText/ITextElement.php old mode 100755 new mode 100644 index 6995467..39b70c8 --- a/PhpOffice/PhpSpreadsheet/RichText/ITextElement.php +++ b/PhpOffice/PhpSpreadsheet/RichText/ITextElement.php @@ -14,7 +14,7 @@ interface ITextElement /** * Set text. * - * @param $text string Text + * @param string $text Text * * @return ITextElement */ diff --git a/PhpOffice/PhpSpreadsheet/RichText/RichText.php b/PhpOffice/PhpSpreadsheet/RichText/RichText.php old mode 100755 new mode 100644 index 76a04d5..88e7c79 --- a/PhpOffice/PhpSpreadsheet/RichText/RichText.php +++ b/PhpOffice/PhpSpreadsheet/RichText/RichText.php @@ -4,7 +4,6 @@ namespace PhpOffice\PhpSpreadsheet\RichText; use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Cell\DataType; -use PhpOffice\PhpSpreadsheet\Exception; use PhpOffice\PhpSpreadsheet\IComparable; class RichText implements IComparable @@ -18,40 +17,36 @@ class RichText implements IComparable /** * Create a new RichText instance. - * - * @param Cell $pCell - * - * @throws Exception */ - public function __construct(Cell $pCell = null) + public function __construct(?Cell $cell = null) { // Initialise variables $this->richTextElements = []; // Rich-Text string attached to cell? - if ($pCell !== null) { + if ($cell !== null) { // Add cell text and style - if ($pCell->getValue() != '') { - $objRun = new Run($pCell->getValue()); - $objRun->setFont(clone $pCell->getWorksheet()->getStyle($pCell->getCoordinate())->getFont()); + if ($cell->getValue() != '') { + $objRun = new Run($cell->getValue()); + $objRun->setFont(clone $cell->getWorksheet()->getStyle($cell->getCoordinate())->getFont()); $this->addText($objRun); } // Set parent value - $pCell->setValueExplicit($this, DataType::TYPE_STRING); + $cell->setValueExplicit($this, DataType::TYPE_STRING); } } /** * Add text. * - * @param ITextElement $pText Rich text element + * @param ITextElement $text Rich text element * - * @return RichText + * @return $this */ - public function addText(ITextElement $pText) + public function addText(ITextElement $text) { - $this->richTextElements[] = $pText; + $this->richTextElements[] = $text; return $this; } @@ -59,15 +54,13 @@ class RichText implements IComparable /** * Create text. * - * @param string $pText Text - * - * @throws Exception + * @param string $text Text * * @return TextElement */ - public function createText($pText) + public function createText($text) { - $objText = new TextElement($pText); + $objText = new TextElement($text); $this->addText($objText); return $objText; @@ -76,15 +69,13 @@ class RichText implements IComparable /** * Create text run. * - * @param string $pText Text - * - * @throws Exception + * @param string $text Text * * @return Run */ - public function createTextRun($pText) + public function createTextRun($text) { - $objText = new Run($pText); + $objText = new Run($text); $this->addText($objText); return $objText; @@ -133,7 +124,7 @@ class RichText implements IComparable * * @param ITextElement[] $textElements Array of elements * - * @return RichText + * @return $this */ public function setRichTextElements(array $textElements) { @@ -167,11 +158,14 @@ class RichText implements IComparable { $vars = get_object_vars($this); foreach ($vars as $key => $value) { - if (is_object($value)) { - $this->$key = clone $value; - } else { - $this->$key = $value; + $newValue = is_object($value) ? (clone $value) : $value; + if (is_array($value)) { + $newValue = []; + foreach ($value as $key2 => $value2) { + $newValue[$key2] = is_object($value2) ? (clone $value2) : $value2; + } } + $this->$key = $newValue; } } } diff --git a/PhpOffice/PhpSpreadsheet/RichText/Run.php b/PhpOffice/PhpSpreadsheet/RichText/Run.php old mode 100755 new mode 100644 index b499623..9c9f807 --- a/PhpOffice/PhpSpreadsheet/RichText/Run.php +++ b/PhpOffice/PhpSpreadsheet/RichText/Run.php @@ -16,11 +16,11 @@ class Run extends TextElement implements ITextElement /** * Create a new Run instance. * - * @param string $pText Text + * @param string $text Text */ - public function __construct($pText = '') + public function __construct($text = '') { - parent::__construct($pText); + parent::__construct($text); // Initialise variables $this->font = new Font(); } @@ -38,13 +38,13 @@ class Run extends TextElement implements ITextElement /** * Set font. * - * @param Font $pFont Font + * @param Font $font Font * - * @return ITextElement + * @return $this */ - public function setFont(Font $pFont = null) + public function setFont(?Font $font = null) { - $this->font = $pFont; + $this->font = $font; return $this; } diff --git a/PhpOffice/PhpSpreadsheet/RichText/TextElement.php b/PhpOffice/PhpSpreadsheet/RichText/TextElement.php old mode 100755 new mode 100644 index d9ad0d7..2373343 --- a/PhpOffice/PhpSpreadsheet/RichText/TextElement.php +++ b/PhpOffice/PhpSpreadsheet/RichText/TextElement.php @@ -14,12 +14,12 @@ class TextElement implements ITextElement /** * Create a new TextElement instance. * - * @param string $pText Text + * @param string $text Text */ - public function __construct($pText = '') + public function __construct($text = '') { // Initialise variables - $this->text = $pText; + $this->text = $text; } /** @@ -35,9 +35,9 @@ class TextElement implements ITextElement /** * Set text. * - * @param $text string Text + * @param string $text Text * - * @return ITextElement + * @return $this */ public function setText($text) { @@ -68,19 +68,4 @@ class TextElement implements ITextElement __CLASS__ ); } - - /** - * Implement PHP __clone to create a deep clone, not just a shallow copy. - */ - public function __clone() - { - $vars = get_object_vars($this); - foreach ($vars as $key => $value) { - if (is_object($value)) { - $this->$key = clone $value; - } else { - $this->$key = $value; - } - } - } } diff --git a/PhpOffice/PhpSpreadsheet/Settings.php b/PhpOffice/PhpSpreadsheet/Settings.php old mode 100755 new mode 100644 index 6361b5f..0baf446 --- a/PhpOffice/PhpSpreadsheet/Settings.php +++ b/PhpOffice/PhpSpreadsheet/Settings.php @@ -5,6 +5,10 @@ namespace PhpOffice\PhpSpreadsheet; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Chart\Renderer\IRenderer; use PhpOffice\PhpSpreadsheet\Collection\Memory; +use Psr\Http\Client\ClientInterface; +use Psr\Http\Message\RequestFactoryInterface; +use Psr\SimpleCache\CacheInterface; +use ReflectionClass; class Settings { @@ -12,37 +16,36 @@ class Settings * Class name of the chart renderer used for rendering charts * eg: PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraph. * - * @var string + * @var ?string */ private static $chartRenderer; /** * Default options for libxml loader. * - * @var int + * @var ?int */ - private static $libXmlLoaderOptions = null; - - /** - * Allow/disallow libxml_disable_entity_loader() call when not thread safe. - * Default behaviour is to do the check, but if you're running PHP versions - * 7.2 < 7.2.1 - * 7.1 < 7.1.13 - * 7.0 < 7.0.27 - * then you may need to disable this check to prevent unwanted behaviour in other threads - * SECURITY WARNING: Changing this flag is not recommended. - * - * @var bool - */ - private static $libXmlDisableEntityLoader = true; + private static $libXmlLoaderOptions; /** * The cache implementation to be used for cell collection. * - * @var CacheInterface + * @var ?CacheInterface */ private static $cache; + /** + * The HTTP client implementation to be used for network request. + * + * @var null|ClientInterface + */ + private static $httpClient; + + /** + * @var null|RequestFactoryInterface + */ + private static $requestFactory; + /** * Set the locale code to use for formula translations and any special formatting. * @@ -50,26 +53,29 @@ class Settings * * @return bool Success or failure */ - public static function setLocale($locale) + public static function setLocale(string $locale) { return Calculation::getInstance()->setLocale($locale); } + public static function getLocale(): string + { + return Calculation::getInstance()->getLocale(); + } + /** * Identify to PhpSpreadsheet the external library to use for rendering charts. * - * @param string $rendererClass Class name of the chart renderer + * @param string $rendererClassName Class name of the chart renderer * eg: PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraph - * - * @throws Exception */ - public static function setChartRenderer($rendererClass) + public static function setChartRenderer(string $rendererClassName): void { - if (!is_a($rendererClass, IRenderer::class, true)) { + if (!is_a($rendererClassName, IRenderer::class, true)) { throw new Exception('Chart renderer must implement ' . IRenderer::class); } - self::$chartRenderer = $rendererClass; + self::$chartRenderer = $rendererClassName; } /** @@ -78,22 +84,29 @@ class Settings * @return null|string Class name of the chart renderer * eg: PhpOffice\PhpSpreadsheet\Chart\Renderer\JpGraph */ - public static function getChartRenderer() + public static function getChartRenderer(): ?string { return self::$chartRenderer; } + public static function htmlEntityFlags(): int + { + return \ENT_COMPAT; + } + /** * Set default options for libxml loader. * - * @param int $options Default options for libxml loader + * @param ?int $options Default options for libxml loader */ - public static function setLibXmlLoaderOptions($options) + public static function setLibXmlLoaderOptions($options): int { - if ($options === null && defined('LIBXML_DTDLOAD')) { - $options = LIBXML_DTDLOAD | LIBXML_DTDATTR; + if ($options === null) { + $options = defined('LIBXML_DTDLOAD') ? (LIBXML_DTDLOAD | LIBXML_DTDATTR) : 0; } self::$libXmlLoaderOptions = $options; + + return $options; } /** @@ -102,65 +115,109 @@ class Settings * * @return int Default options for libxml loader */ - public static function getLibXmlLoaderOptions() + public static function getLibXmlLoaderOptions(): int { - if (self::$libXmlLoaderOptions === null && defined('LIBXML_DTDLOAD')) { - self::setLibXmlLoaderOptions(LIBXML_DTDLOAD | LIBXML_DTDATTR); - } elseif (self::$libXmlLoaderOptions === null) { - self::$libXmlLoaderOptions = true; + if (self::$libXmlLoaderOptions === null) { + return self::setLibXmlLoaderOptions(null); } return self::$libXmlLoaderOptions; } /** - * Enable/Disable the entity loader for libxml loader. - * Allow/disallow libxml_disable_entity_loader() call when not thread safe. - * Default behaviour is to do the check, but if you're running PHP versions - * 7.2 < 7.2.1 - * 7.1 < 7.1.13 - * 7.0 < 7.0.27 - * then you may need to disable this check to prevent unwanted behaviour in other threads - * SECURITY WARNING: Changing this flag to false is not recommended. + * Deprecated, has no effect. * * @param bool $state + * + * @deprecated will be removed without replacement as it is no longer necessary on PHP 7.3.0+ + * + * @codeCoverageIgnore */ - public static function setLibXmlDisableEntityLoader($state) + public static function setLibXmlDisableEntityLoader(/** @scrutinizer ignore-unused */ $state): void { - self::$libXmlDisableEntityLoader = (bool) $state; + // noop } /** - * Return the state of the entity loader (disabled/enabled) for libxml loader. + * Deprecated, has no effect. * * @return bool $state + * + * @deprecated will be removed without replacement as it is no longer necessary on PHP 7.3.0+ + * + * @codeCoverageIgnore */ - public static function getLibXmlDisableEntityLoader() + public static function getLibXmlDisableEntityLoader(): bool { - return self::$libXmlDisableEntityLoader; + return true; } /** * Sets the implementation of cache that should be used for cell collection. - * - * @param CacheInterface $cache */ - public static function setCache($cache) + public static function setCache(CacheInterface $cache): void { self::$cache = $cache; } /** - * Gets the implementation of cache that should be used for cell collection. - * - * @return CacheInterface + * Gets the implementation of cache that is being used for cell collection. */ - public static function getCache() + public static function getCache(): CacheInterface { if (!self::$cache) { - self::$cache = new Memory(); + self::$cache = self::useSimpleCacheVersion3() ? new Memory\SimpleCache3() : new Memory\SimpleCache1(); } return self::$cache; } + + public static function useSimpleCacheVersion3(): bool + { + return + PHP_MAJOR_VERSION === 8 && + (new ReflectionClass(CacheInterface::class))->getMethod('get')->getReturnType() !== null; + } + + /** + * Set the HTTP client implementation to be used for network request. + */ + public static function setHttpClient(ClientInterface $httpClient, RequestFactoryInterface $requestFactory): void + { + self::$httpClient = $httpClient; + self::$requestFactory = $requestFactory; + } + + /** + * Unset the HTTP client configuration. + */ + public static function unsetHttpClient(): void + { + self::$httpClient = null; + self::$requestFactory = null; + } + + /** + * Get the HTTP client implementation to be used for network request. + */ + public static function getHttpClient(): ClientInterface + { + if (!self::$httpClient || !self::$requestFactory) { + throw new Exception('HTTP client must be configured via Settings::setHttpClient() to be able to use WEBSERVICE function.'); + } + + return self::$httpClient; + } + + /** + * Get the HTTP request factory. + */ + public static function getRequestFactory(): RequestFactoryInterface + { + if (!self::$httpClient || !self::$requestFactory) { + throw new Exception('HTTP client must be configured via Settings::setHttpClient() to be able to use WEBSERVICE function.'); + } + + return self::$requestFactory; + } } diff --git a/PhpOffice/PhpSpreadsheet/Shared/CodePage.php b/PhpOffice/PhpSpreadsheet/Shared/CodePage.php old mode 100755 new mode 100644 index 4b57824..8718a61 --- a/PhpOffice/PhpSpreadsheet/Shared/CodePage.php +++ b/PhpOffice/PhpSpreadsheet/Shared/CodePage.php @@ -6,133 +6,109 @@ use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; class CodePage { + public const DEFAULT_CODE_PAGE = 'CP1252'; + + /** @var array */ + private static $pageArray = [ + 0 => 'CP1252', // CodePage is not always correctly set when the xls file was saved by Apple's Numbers program + 367 => 'ASCII', // ASCII + 437 => 'CP437', // OEM US + //720 => 'notsupported', // OEM Arabic + 737 => 'CP737', // OEM Greek + 775 => 'CP775', // OEM Baltic + 850 => 'CP850', // OEM Latin I + 852 => 'CP852', // OEM Latin II (Central European) + 855 => 'CP855', // OEM Cyrillic + 857 => 'CP857', // OEM Turkish + 858 => 'CP858', // OEM Multilingual Latin I with Euro + 860 => 'CP860', // OEM Portugese + 861 => 'CP861', // OEM Icelandic + 862 => 'CP862', // OEM Hebrew + 863 => 'CP863', // OEM Canadian (French) + 864 => 'CP864', // OEM Arabic + 865 => 'CP865', // OEM Nordic + 866 => 'CP866', // OEM Cyrillic (Russian) + 869 => 'CP869', // OEM Greek (Modern) + 874 => 'CP874', // ANSI Thai + 932 => 'CP932', // ANSI Japanese Shift-JIS + 936 => 'CP936', // ANSI Chinese Simplified GBK + 949 => 'CP949', // ANSI Korean (Wansung) + 950 => 'CP950', // ANSI Chinese Traditional BIG5 + 1200 => 'UTF-16LE', // UTF-16 (BIFF8) + 1250 => 'CP1250', // ANSI Latin II (Central European) + 1251 => 'CP1251', // ANSI Cyrillic + 1252 => 'CP1252', // ANSI Latin I (BIFF4-BIFF7) + 1253 => 'CP1253', // ANSI Greek + 1254 => 'CP1254', // ANSI Turkish + 1255 => 'CP1255', // ANSI Hebrew + 1256 => 'CP1256', // ANSI Arabic + 1257 => 'CP1257', // ANSI Baltic + 1258 => 'CP1258', // ANSI Vietnamese + 1361 => 'CP1361', // ANSI Korean (Johab) + 10000 => 'MAC', // Apple Roman + 10001 => 'CP932', // Macintosh Japanese + 10002 => 'CP950', // Macintosh Chinese Traditional + 10003 => 'CP1361', // Macintosh Korean + 10004 => 'MACARABIC', // Apple Arabic + 10005 => 'MACHEBREW', // Apple Hebrew + 10006 => 'MACGREEK', // Macintosh Greek + 10007 => 'MACCYRILLIC', // Macintosh Cyrillic + 10008 => 'CP936', // Macintosh - Simplified Chinese (GB 2312) + 10010 => 'MACROMANIA', // Macintosh Romania + 10017 => 'MACUKRAINE', // Macintosh Ukraine + 10021 => 'MACTHAI', // Macintosh Thai + 10029 => ['MACCENTRALEUROPE', 'MAC-CENTRALEUROPE'], // Macintosh Central Europe + 10079 => 'MACICELAND', // Macintosh Icelandic + 10081 => 'MACTURKISH', // Macintosh Turkish + 10082 => 'MACCROATIAN', // Macintosh Croatian + 21010 => 'UTF-16LE', // UTF-16 (BIFF8) This isn't correct, but some Excel writer libraries erroneously use Codepage 21010 for UTF-16LE + 32768 => 'MAC', // Apple Roman + //32769 => 'unsupported', // ANSI Latin I (BIFF2-BIFF3) + 65000 => 'UTF-7', // Unicode (UTF-7) + 65001 => 'UTF-8', // Unicode (UTF-8) + 99999 => ['unsupported'], // Unicode (UTF-8) + ]; + + public static function validate(string $codePage): bool + { + return in_array($codePage, self::$pageArray, true); + } + /** * Convert Microsoft Code Page Identifier to Code Page Name which iconv * and mbstring understands. * * @param int $codePage Microsoft Code Page Indentifier * - * @throws PhpSpreadsheetException - * * @return string Code Page Name */ - public static function numberToName($codePage) + public static function numberToName(int $codePage): string { - switch ($codePage) { - case 367: - return 'ASCII'; // ASCII - case 437: - return 'CP437'; // OEM US - case 720: - throw new PhpSpreadsheetException('Code page 720 not supported.'); // OEM Arabic - case 737: - return 'CP737'; // OEM Greek - case 775: - return 'CP775'; // OEM Baltic - case 850: - return 'CP850'; // OEM Latin I - case 852: - return 'CP852'; // OEM Latin II (Central European) - case 855: - return 'CP855'; // OEM Cyrillic - case 857: - return 'CP857'; // OEM Turkish - case 858: - return 'CP858'; // OEM Multilingual Latin I with Euro - case 860: - return 'CP860'; // OEM Portugese - case 861: - return 'CP861'; // OEM Icelandic - case 862: - return 'CP862'; // OEM Hebrew - case 863: - return 'CP863'; // OEM Canadian (French) - case 864: - return 'CP864'; // OEM Arabic - case 865: - return 'CP865'; // OEM Nordic - case 866: - return 'CP866'; // OEM Cyrillic (Russian) - case 869: - return 'CP869'; // OEM Greek (Modern) - case 874: - return 'CP874'; // ANSI Thai - case 932: - return 'CP932'; // ANSI Japanese Shift-JIS - case 936: - return 'CP936'; // ANSI Chinese Simplified GBK - case 949: - return 'CP949'; // ANSI Korean (Wansung) - case 950: - return 'CP950'; // ANSI Chinese Traditional BIG5 - case 1200: - return 'UTF-16LE'; // UTF-16 (BIFF8) - case 1250: - return 'CP1250'; // ANSI Latin II (Central European) - case 1251: - return 'CP1251'; // ANSI Cyrillic - case 0: - // CodePage is not always correctly set when the xls file was saved by Apple's Numbers program - case 1252: - return 'CP1252'; // ANSI Latin I (BIFF4-BIFF7) - case 1253: - return 'CP1253'; // ANSI Greek - case 1254: - return 'CP1254'; // ANSI Turkish - case 1255: - return 'CP1255'; // ANSI Hebrew - case 1256: - return 'CP1256'; // ANSI Arabic - case 1257: - return 'CP1257'; // ANSI Baltic - case 1258: - return 'CP1258'; // ANSI Vietnamese - case 1361: - return 'CP1361'; // ANSI Korean (Johab) - case 10000: - return 'MAC'; // Apple Roman - case 10001: - return 'CP932'; // Macintosh Japanese - case 10002: - return 'CP950'; // Macintosh Chinese Traditional - case 10003: - return 'CP1361'; // Macintosh Korean - case 10004: - return 'MACARABIC'; // Apple Arabic - case 10005: - return 'MACHEBREW'; // Apple Hebrew - case 10006: - return 'MACGREEK'; // Macintosh Greek - case 10007: - return 'MACCYRILLIC'; // Macintosh Cyrillic - case 10008: - return 'CP936'; // Macintosh - Simplified Chinese (GB 2312) - case 10010: - return 'MACROMANIA'; // Macintosh Romania - case 10017: - return 'MACUKRAINE'; // Macintosh Ukraine - case 10021: - return 'MACTHAI'; // Macintosh Thai - case 10029: - return 'MACCENTRALEUROPE'; // Macintosh Central Europe - case 10079: - return 'MACICELAND'; // Macintosh Icelandic - case 10081: - return 'MACTURKISH'; // Macintosh Turkish - case 10082: - return 'MACCROATIAN'; // Macintosh Croatian - case 21010: - return 'UTF-16LE'; // UTF-16 (BIFF8) This isn't correct, but some Excel writer libraries erroneously use Codepage 21010 for UTF-16LE - case 32768: - return 'MAC'; // Apple Roman - case 32769: - throw new PhpSpreadsheetException('Code page 32769 not supported.'); // ANSI Latin I (BIFF2-BIFF3) - case 65000: - return 'UTF-7'; // Unicode (UTF-7) - case 65001: - return 'UTF-8'; // Unicode (UTF-8) + if (array_key_exists($codePage, self::$pageArray)) { + $value = self::$pageArray[$codePage]; + if (is_array($value)) { + foreach ($value as $encoding) { + if (@iconv('UTF-8', $encoding, ' ') !== false) { + self::$pageArray[$codePage] = $encoding; + + return $encoding; + } + } + + throw new PhpSpreadsheetException("Code page $codePage not implemented on this system."); + } else { + return $value; + } + } + if ($codePage == 720 || $codePage == 32769) { + throw new PhpSpreadsheetException("Code page $codePage not supported."); // OEM Arabic } throw new PhpSpreadsheetException('Unknown codepage: ' . $codePage); } + + public static function getEncodings(): array + { + return self::$pageArray; + } } diff --git a/PhpOffice/PhpSpreadsheet/Shared/Date.php b/PhpOffice/PhpSpreadsheet/Shared/Date.php old mode 100755 new mode 100644 index 5d2deb3..0ae5f1e --- a/PhpOffice/PhpSpreadsheet/Shared/Date.php +++ b/PhpOffice/PhpSpreadsheet/Shared/Date.php @@ -2,11 +2,15 @@ namespace PhpOffice\PhpSpreadsheet\Shared; +use DateTime; use DateTimeInterface; use DateTimeZone; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; +use PhpOffice\PhpSpreadsheet\Calculation\DateTimeExcel; use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\Cell\Cell; +use PhpOffice\PhpSpreadsheet\Exception; +use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; +use PhpOffice\PhpSpreadsheet\Shared\Date as SharedDate; use PhpOffice\PhpSpreadsheet\Style\NumberFormat; class Date @@ -57,22 +61,24 @@ class Date /** * Default timezone to use for DateTime objects. * - * @var null|\DateTimeZone + * @var null|DateTimeZone */ protected static $defaultTimeZone; /** * Set the Excel calendar (Windows 1900 or Mac 1904). * - * @param int $baseDate Excel base date (1900 or 1904) + * @param int $baseYear Excel base date (1900 or 1904) * * @return bool Success or failure */ - public static function setExcelCalendar($baseDate) + public static function setExcelCalendar($baseYear) { - if (($baseDate == self::CALENDAR_WINDOWS_1900) || - ($baseDate == self::CALENDAR_MAC_1904)) { - self::$excelCalendar = $baseDate; + if ( + ($baseYear == self::CALENDAR_WINDOWS_1900) || + ($baseYear == self::CALENDAR_MAC_1904) + ) { + self::$excelCalendar = $baseYear; return true; } @@ -93,57 +99,94 @@ class Date /** * Set the Default timezone to use for dates. * - * @param DateTimeZone|string $timeZone The timezone to set for all Excel datetimestamp to PHP DateTime Object conversions + * @param null|DateTimeZone|string $timeZone The timezone to set for all Excel datetimestamp to PHP DateTime Object conversions * - * @throws \Exception - * - * @return bool Success or failure * @return bool Success or failure */ public static function setDefaultTimezone($timeZone) { - if ($timeZone = self::validateTimeZone($timeZone)) { + try { + $timeZone = self::validateTimeZone($timeZone); self::$defaultTimeZone = $timeZone; - - return true; + $retval = true; + } catch (PhpSpreadsheetException $e) { + $retval = false; } - return false; + return $retval; } /** - * Return the Default timezone being used for dates. - * - * @return DateTimeZone The timezone being used as default for Excel timestamp to PHP DateTime object + * Return the Default timezone, or UTC if default not set. */ - public static function getDefaultTimezone() + public static function getDefaultTimezone(): DateTimeZone { - if (self::$defaultTimeZone === null) { - self::$defaultTimeZone = new DateTimeZone('UTC'); - } + return self::$defaultTimeZone ?? new DateTimeZone('UTC'); + } + /** + * Return the Default timezone, or local timezone if default is not set. + */ + public static function getDefaultOrLocalTimezone(): DateTimeZone + { + return self::$defaultTimeZone ?? new DateTimeZone(date_default_timezone_get()); + } + + /** + * Return the Default timezone even if null. + */ + public static function getDefaultTimezoneOrNull(): ?DateTimeZone + { return self::$defaultTimeZone; } /** * Validate a timezone. * - * @param DateTimeZone|string $timeZone The timezone to validate, either as a timezone string or object + * @param null|DateTimeZone|string $timeZone The timezone to validate, either as a timezone string or object * - * @throws \Exception - * - * @return DateTimeZone The timezone as a timezone object - * @return DateTimeZone The timezone as a timezone object + * @return ?DateTimeZone The timezone as a timezone object */ - protected static function validateTimeZone($timeZone) + private static function validateTimeZone($timeZone) { - if (is_object($timeZone) && $timeZone instanceof DateTimeZone) { + if ($timeZone instanceof DateTimeZone || $timeZone === null) { return $timeZone; - } elseif (is_string($timeZone)) { + } + if (in_array($timeZone, DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC))) { return new DateTimeZone($timeZone); } - throw new \Exception('Invalid timezone'); + throw new PhpSpreadsheetException('Invalid timezone'); + } + + /** + * @param mixed $value + * + * @return float|int + */ + public static function convertIsoDate($value) + { + if (!is_string($value)) { + throw new Exception('Non-string value supplied for Iso Date conversion'); + } + + $date = new DateTime($value); + $dateErrors = DateTime::getLastErrors(); + + if (is_array($dateErrors) && ($dateErrors['warning_count'] > 0 || $dateErrors['error_count'] > 0)) { + throw new Exception("Invalid string $value supplied for datatype Date"); + } + + $newValue = SharedDate::PHPToExcel($date); + if ($newValue === false) { + throw new Exception("Invalid string $value supplied for datatype Date"); + } + + if (preg_match('/^\\d\\d:\\d\\d:\\d\\d/', $value) == 1) { + $newValue = fmod($newValue, 1.0); + } + + return $newValue; } /** @@ -152,30 +195,28 @@ class Date * @param float|int $excelTimestamp MS Excel serialized date/time value * @param null|DateTimeZone|string $timeZone The timezone to assume for the Excel timestamp, * if you don't want to treat it as a UTC value - * Use the default (UST) unless you absolutely need a conversion + * Use the default (UTC) unless you absolutely need a conversion * - * @throws \Exception - * - * @return \DateTime PHP date/time object + * @return DateTime PHP date/time object */ public static function excelToDateTimeObject($excelTimestamp, $timeZone = null) { $timeZone = ($timeZone === null) ? self::getDefaultTimezone() : self::validateTimeZone($timeZone); if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_EXCEL) { - if ($excelTimestamp < 1.0) { + if ($excelTimestamp < 1 && self::$excelCalendar === self::CALENDAR_WINDOWS_1900) { // Unix timestamp base date - $baseDate = new \DateTime('1970-01-01', $timeZone); + $baseDate = new DateTime('1970-01-01', $timeZone); } else { // MS Excel calendar base dates if (self::$excelCalendar == self::CALENDAR_WINDOWS_1900) { // Allow adjustment for 1900 Leap Year in MS Excel - $baseDate = ($excelTimestamp < 60) ? new \DateTime('1899-12-31', $timeZone) : new \DateTime('1899-12-30', $timeZone); + $baseDate = ($excelTimestamp < 60) ? new DateTime('1899-12-31', $timeZone) : new DateTime('1899-12-30', $timeZone); } else { - $baseDate = new \DateTime('1904-01-01', $timeZone); + $baseDate = new DateTime('1904-01-01', $timeZone); } } } else { - $baseDate = new \DateTime('1899-12-30', $timeZone); + $baseDate = new DateTime('1899-12-30', $timeZone); } $days = floor($excelTimestamp); @@ -197,13 +238,13 @@ class Date /** * Convert a MS serialized datetime value from Excel to a unix timestamp. + * The use of Unix timestamps, and therefore this function, is discouraged. + * They are not Y2038-safe on a 32-bit system, and have no timezone info. * * @param float|int $excelTimestamp MS Excel serialized date/time value * @param null|DateTimeZone|string $timeZone The timezone to assume for the Excel timestamp, * if you don't want to treat it as a UTC value - * Use the default (UST) unless you absolutely need a conversion - * - * @throws \Exception + * Use the default (UTC) unless you absolutely need a conversion * * @return int Unix timetamp for this date/time */ @@ -216,9 +257,10 @@ class Date /** * Convert a date from PHP to an MS Excel serialized date/time value. * - * @param mixed $dateValue Unix Timestamp or PHP DateTime object or a string + * @param mixed $dateValue PHP DateTime object or a string - Unix timestamp is also permitted, but discouraged; + * not Y2038-safe on a 32-bit system, and no timezone info * - * @return bool|float Excel date/time value + * @return false|float Excel date/time value * or boolean FALSE on failure */ public static function PHPToExcel($dateValue) @@ -255,18 +297,20 @@ class Date /** * Convert a Unix timestamp to an MS Excel serialized date/time value. + * The use of Unix timestamps, and therefore this function, is discouraged. + * They are not Y2038-safe on a 32-bit system, and have no timezone info. * - * @param int $dateValue Unix Timestamp + * @param float|int|string $unixTimestamp Unix Timestamp * - * @return float MS Excel serialized date/time value + * @return false|float MS Excel serialized date/time value */ - public static function timestampToExcel($dateValue) + public static function timestampToExcel($unixTimestamp) { - if (!is_numeric($dateValue)) { + if (!is_numeric($unixTimestamp)) { return false; } - return self::dateTimeToExcel(new \DateTime('@' . $dateValue)); + return self::dateTimeToExcel(new DateTime('@' . $unixTimestamp)); } /** @@ -307,8 +351,8 @@ class Date } // Calculate the Julian Date, then subtract the Excel base date (JD 2415020 = 31-Dec-1899 Giving Excel Date of 0) - $century = substr($year, 0, 2); - $decade = substr($year, 2, 2); + $century = (int) substr((string) $year, 0, 2); + $decade = (int) substr((string) $year, 2, 2); $excelDate = floor((146097 * $century) / 4) + floor((1461 * $decade) / 4) + floor((153 * $month + 2) / 5) + $day + 1721119 - $myexcelBaseDate + $excel1900isLeapYear; $excelTime = (($hours * 3600) + ($minutes * 60) + $seconds) / 86400; @@ -319,94 +363,91 @@ class Date /** * Is a given cell a date/time? * - * @param Cell $pCell + * @param mixed $value * * @return bool */ - public static function isDateTime(Cell $pCell) + public static function isDateTime(Cell $cell, $value = null, bool $dateWithoutTimeOkay = true) { - return is_numeric($pCell->getValue()) && - self::isDateTimeFormat( - $pCell->getWorksheet()->getStyle( - $pCell->getCoordinate() - )->getNumberFormat() - ); + $result = false; + $worksheet = $cell->getWorksheetOrNull(); + $spreadsheet = ($worksheet === null) ? null : $worksheet->getParent(); + if ($worksheet !== null && $spreadsheet !== null) { + $index = $spreadsheet->getActiveSheetIndex(); + $selected = $worksheet->getSelectedCells(); + $result = is_numeric($value ?? $cell->getCalculatedValue()) && + self::isDateTimeFormat( + $worksheet->getStyle( + $cell->getCoordinate() + )->getNumberFormat(), + $dateWithoutTimeOkay + ); + $worksheet->setSelectedCells($selected); + $spreadsheet->setActiveSheetIndex($index); + } + + return $result; } /** * Is a given number format a date/time? * - * @param NumberFormat $pFormat - * * @return bool */ - public static function isDateTimeFormat(NumberFormat $pFormat) + public static function isDateTimeFormat(NumberFormat $excelFormatCode, bool $dateWithoutTimeOkay = true) { - return self::isDateTimeFormatCode($pFormat->getFormatCode()); + return self::isDateTimeFormatCode((string) $excelFormatCode->getFormatCode(), $dateWithoutTimeOkay); } - private static $possibleDateFormatCharacters = 'eymdHs'; + private const POSSIBLE_DATETIME_FORMAT_CHARACTERS = 'eymdHs'; + private const POSSIBLE_TIME_FORMAT_CHARACTERS = 'Hs'; // note - no 'm' due to ambiguity /** * Is a given number format code a date/time? * - * @param string $pFormatCode + * @param string $excelFormatCode * * @return bool */ - public static function isDateTimeFormatCode($pFormatCode) + public static function isDateTimeFormatCode($excelFormatCode, bool $dateWithoutTimeOkay = true) { - if (strtolower($pFormatCode) === strtolower(NumberFormat::FORMAT_GENERAL)) { + if (strtolower($excelFormatCode) === strtolower(NumberFormat::FORMAT_GENERAL)) { // "General" contains an epoch letter 'e', so we trap for it explicitly here (case-insensitive check) return false; } - if (preg_match('/[0#]E[+-]0/i', $pFormatCode)) { + if (preg_match('/[0#]E[+-]0/i', $excelFormatCode)) { // Scientific format return false; } // Switch on formatcode - switch ($pFormatCode) { - // Explicitly defined date formats - case NumberFormat::FORMAT_DATE_YYYYMMDD: - case NumberFormat::FORMAT_DATE_YYYYMMDD2: - case NumberFormat::FORMAT_DATE_DDMMYYYY: - case NumberFormat::FORMAT_DATE_DMYSLASH: - case NumberFormat::FORMAT_DATE_DMYMINUS: - case NumberFormat::FORMAT_DATE_DMMINUS: - case NumberFormat::FORMAT_DATE_MYMINUS: - case NumberFormat::FORMAT_DATE_DATETIME: - case NumberFormat::FORMAT_DATE_TIME1: - case NumberFormat::FORMAT_DATE_TIME2: - case NumberFormat::FORMAT_DATE_TIME3: - case NumberFormat::FORMAT_DATE_TIME4: - case NumberFormat::FORMAT_DATE_TIME5: - case NumberFormat::FORMAT_DATE_TIME6: - case NumberFormat::FORMAT_DATE_TIME7: - case NumberFormat::FORMAT_DATE_TIME8: - case NumberFormat::FORMAT_DATE_YYYYMMDDSLASH: - case NumberFormat::FORMAT_DATE_XLSX14: - case NumberFormat::FORMAT_DATE_XLSX15: - case NumberFormat::FORMAT_DATE_XLSX16: - case NumberFormat::FORMAT_DATE_XLSX17: - case NumberFormat::FORMAT_DATE_XLSX22: - return true; + if (in_array($excelFormatCode, NumberFormat::DATE_TIME_OR_DATETIME_ARRAY, true)) { + return $dateWithoutTimeOkay || in_array($excelFormatCode, NumberFormat::TIME_OR_DATETIME_ARRAY); } // Typically number, currency or accounting (or occasionally fraction) formats - if ((substr($pFormatCode, 0, 1) == '_') || (substr($pFormatCode, 0, 2) == '0 ')) { + if ((substr($excelFormatCode, 0, 1) == '_') || (substr($excelFormatCode, 0, 2) == '0 ')) { return false; } + // Some "special formats" provided in German Excel versions were detected as date time value, + // so filter them out here - "\C\H\-00000" (Switzerland) and "\D-00000" (Germany). + if (\strpos($excelFormatCode, '-00000') !== false) { + return false; + } + $possibleFormatCharacters = $dateWithoutTimeOkay ? self::POSSIBLE_DATETIME_FORMAT_CHARACTERS : self::POSSIBLE_TIME_FORMAT_CHARACTERS; // Try checking for any of the date formatting characters that don't appear within square braces - if (preg_match('/(^|\])[^\[]*[' . self::$possibleDateFormatCharacters . ']/i', $pFormatCode)) { + if (preg_match('/(^|\])[^\[]*[' . $possibleFormatCharacters . ']/i', $excelFormatCode)) { // We might also have a format mask containing quoted strings... // we don't want to test for any of our characters within the quoted blocks - if (strpos($pFormatCode, '"') !== false) { + if (strpos($excelFormatCode, '"') !== false) { $segMatcher = false; - foreach (explode('"', $pFormatCode) as $subVal) { + foreach (explode('"', $excelFormatCode) as $subVal) { // Only test in alternate array entries (the non-quoted blocks) - if (($segMatcher = !$segMatcher) && - (preg_match('/(^|\])[^\[]*[' . self::$possibleDateFormatCharacters . ']/i', $subVal))) { + $segMatcher = $segMatcher === false; + if ( + $segMatcher && + (preg_match('/(^|\])[^\[]*[' . $possibleFormatCharacters . ']/i', $subVal)) + ) { return true; } } @@ -437,15 +478,15 @@ class Date return false; } - $dateValueNew = DateTime::DATEVALUE($dateValue); + $dateValueNew = DateTimeExcel\DateValue::fromString($dateValue); - if ($dateValueNew === Functions::VALUE()) { + if (!is_float($dateValueNew)) { return false; } if (strpos($dateValue, ':') !== false) { - $timeValue = DateTime::TIMEVALUE($dateValue); - if ($timeValue === Functions::VALUE()) { + $timeValue = DateTimeExcel\TimeValue::fromString($dateValue); + if (!is_float($timeValue)) { return false; } $dateValueNew += $timeValue; @@ -457,21 +498,21 @@ class Date /** * Converts a month name (either a long or a short name) to a month number. * - * @param string $month Month name or abbreviation + * @param string $monthName Month name or abbreviation * * @return int|string Month number (1 - 12), or the original string argument if it isn't a valid month name */ - public static function monthStringToNumber($month) + public static function monthStringToNumber($monthName) { $monthIndex = 1; foreach (self::$monthNames as $shortMonthName => $longMonthName) { - if (($month === $longMonthName) || ($month === $shortMonthName)) { + if (($monthName === $longMonthName) || ($monthName === $shortMonthName)) { return $monthIndex; } ++$monthIndex; } - return $month; + return $monthName; } /** @@ -490,4 +531,19 @@ class Date return $day; } + + public static function dateTimeFromTimestamp(string $date, ?DateTimeZone $timeZone = null): DateTime + { + $dtobj = DateTime::createFromFormat('U', $date) ?: new DateTime(); + $dtobj->setTimeZone($timeZone ?? self::getDefaultOrLocalTimezone()); + + return $dtobj; + } + + public static function formattedDateTimeFromTimestamp(string $date, string $format, ?DateTimeZone $timeZone = null): string + { + $dtobj = self::dateTimeFromTimestamp($date, $timeZone); + + return $dtobj->format($format); + } } diff --git a/PhpOffice/PhpSpreadsheet/Shared/Drawing.php b/PhpOffice/PhpSpreadsheet/Shared/Drawing.php old mode 100755 new mode 100644 index 25d6910..f69310f --- a/PhpOffice/PhpSpreadsheet/Shared/Drawing.php +++ b/PhpOffice/PhpSpreadsheet/Shared/Drawing.php @@ -2,31 +2,36 @@ namespace PhpOffice\PhpSpreadsheet\Shared; +use GdImage; +use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException; +use SimpleXMLElement; + class Drawing { /** * Convert pixels to EMU. * - * @param int $pValue Value in pixels + * @param int $pixelValue Value in pixels * * @return int Value in EMU */ - public static function pixelsToEMU($pValue) + public static function pixelsToEMU($pixelValue) { - return round($pValue * 9525); + return $pixelValue * 9525; } /** * Convert EMU to pixels. * - * @param int $pValue Value in EMU + * @param int|SimpleXMLElement $emuValue Value in EMU * * @return int Value in pixels */ - public static function EMUToPixels($pValue) + public static function EMUToPixels($emuValue) { - if ($pValue != 0) { - return round($pValue / 9525); + $emuValue = (int) $emuValue; + if ($emuValue != 0) { + return (int) round($emuValue / 9525); } return 0; @@ -37,50 +42,51 @@ class Drawing * By inspection of a real Excel file using Calibri 11, one finds 1000px ~ 142.85546875 * This gives a conversion factor of 7. Also, we assume that pixels and font size are proportional. * - * @param int $pValue Value in pixels - * @param \PhpOffice\PhpSpreadsheet\Style\Font $pDefaultFont Default font of the workbook + * @param int $pixelValue Value in pixels * - * @return int Value in cell dimension + * @return float|int Value in cell dimension */ - public static function pixelsToCellDimension($pValue, \PhpOffice\PhpSpreadsheet\Style\Font $pDefaultFont) + public static function pixelsToCellDimension($pixelValue, \PhpOffice\PhpSpreadsheet\Style\Font $defaultFont) { // Font name and size - $name = $pDefaultFont->getName(); - $size = $pDefaultFont->getSize(); + $name = $defaultFont->getName(); + $size = $defaultFont->getSize(); if (isset(Font::$defaultColumnWidths[$name][$size])) { // Exact width can be determined - $colWidth = $pValue * Font::$defaultColumnWidths[$name][$size]['width'] / Font::$defaultColumnWidths[$name][$size]['px']; - } else { - // We don't have data for this particular font and size, use approximation by - // extrapolating from Calibri 11 - $colWidth = $pValue * 11 * Font::$defaultColumnWidths['Calibri'][11]['width'] / Font::$defaultColumnWidths['Calibri'][11]['px'] / $size; + return $pixelValue * Font::$defaultColumnWidths[$name][$size]['width'] + / Font::$defaultColumnWidths[$name][$size]['px']; } - return $colWidth; + // We don't have data for this particular font and size, use approximation by + // extrapolating from Calibri 11 + return $pixelValue * 11 * Font::$defaultColumnWidths['Calibri'][11]['width'] + / Font::$defaultColumnWidths['Calibri'][11]['px'] / $size; } /** * Convert column width from (intrinsic) Excel units to pixels. * - * @param float $pValue Value in cell dimension - * @param \PhpOffice\PhpSpreadsheet\Style\Font $pDefaultFont Default font of the workbook + * @param float $cellWidth Value in cell dimension + * @param \PhpOffice\PhpSpreadsheet\Style\Font $defaultFont Default font of the workbook * * @return int Value in pixels */ - public static function cellDimensionToPixels($pValue, \PhpOffice\PhpSpreadsheet\Style\Font $pDefaultFont) + public static function cellDimensionToPixels($cellWidth, \PhpOffice\PhpSpreadsheet\Style\Font $defaultFont) { // Font name and size - $name = $pDefaultFont->getName(); - $size = $pDefaultFont->getSize(); + $name = $defaultFont->getName(); + $size = $defaultFont->getSize(); if (isset(Font::$defaultColumnWidths[$name][$size])) { // Exact width can be determined - $colWidth = $pValue * Font::$defaultColumnWidths[$name][$size]['px'] / Font::$defaultColumnWidths[$name][$size]['width']; + $colWidth = $cellWidth * Font::$defaultColumnWidths[$name][$size]['px'] + / Font::$defaultColumnWidths[$name][$size]['width']; } else { // We don't have data for this particular font and size, use approximation by // extrapolating from Calibri 11 - $colWidth = $pValue * $size * Font::$defaultColumnWidths['Calibri'][11]['px'] / Font::$defaultColumnWidths['Calibri'][11]['width'] / 11; + $colWidth = $cellWidth * $size * Font::$defaultColumnWidths['Calibri'][11]['px'] + / Font::$defaultColumnWidths['Calibri'][11]['width'] / 11; } // Round pixels to closest integer @@ -92,26 +98,26 @@ class Drawing /** * Convert pixels to points. * - * @param int $pValue Value in pixels + * @param int $pixelValue Value in pixels * * @return float Value in points */ - public static function pixelsToPoints($pValue) + public static function pixelsToPoints($pixelValue) { - return $pValue * 0.67777777; + return $pixelValue * 0.75; } /** * Convert points to pixels. * - * @param int $pValue Value in points + * @param int $pointValue Value in points * * @return int Value in pixels */ - public static function pointsToPixels($pValue) + public static function pointsToPixels($pointValue) { - if ($pValue != 0) { - return (int) ceil($pValue * 1.333333333); + if ($pointValue != 0) { + return (int) ceil($pointValue / 0.75); } return 0; @@ -120,26 +126,27 @@ class Drawing /** * Convert degrees to angle. * - * @param int $pValue Degrees + * @param int $degrees Degrees * * @return int Angle */ - public static function degreesToAngle($pValue) + public static function degreesToAngle($degrees) { - return (int) round($pValue * 60000); + return (int) round($degrees * 60000); } /** * Convert angle to degrees. * - * @param int $pValue Angle + * @param int|SimpleXMLElement $angle Angle * * @return int Degrees */ - public static function angleToDegrees($pValue) + public static function angleToDegrees($angle) { - if ($pValue != 0) { - return round($pValue / 60000); + $angle = (int) $angle; + if ($angle != 0) { + return (int) round($angle / 60000); } return 0; @@ -150,100 +157,21 @@ class Drawing * * @see http://www.php.net/manual/en/function.imagecreatefromwbmp.php#86214 * - * @param string $p_sFile Path to Windows DIB (BMP) image + * @param string $bmpFilename Path to Windows DIB (BMP) image * - * @return resource + * @return GdImage|resource + * + * @deprecated 1.26 use Php function imagecreatefrombmp instead + * + * @codeCoverageIgnore */ - public static function imagecreatefrombmp($p_sFile) + public static function imagecreatefrombmp($bmpFilename) { - // Load the image into a string - $file = fopen($p_sFile, 'rb'); - $read = fread($file, 10); - while (!feof($file) && ($read != '')) { - $read .= fread($file, 1024); + $retVal = @imagecreatefrombmp($bmpFilename); + if ($retVal === false) { + throw new ReaderException("Unable to create image from $bmpFilename"); } - $temp = unpack('H*', $read); - $hex = $temp[1]; - $header = substr($hex, 0, 108); - - // Process the header - // Structure: http://www.fastgraph.com/help/bmp_header_format.html - if (substr($header, 0, 4) == '424d') { - // Cut it in parts of 2 bytes - $header_parts = str_split($header, 2); - - // Get the width 4 bytes - $width = hexdec($header_parts[19] . $header_parts[18]); - - // Get the height 4 bytes - $height = hexdec($header_parts[23] . $header_parts[22]); - - // Unset the header params - unset($header_parts); - } - - // Define starting X and Y - $x = 0; - $y = 1; - - // Create newimage - $image = imagecreatetruecolor($width, $height); - - // Grab the body from the image - $body = substr($hex, 108); - - // Calculate if padding at the end-line is needed - // Divided by two to keep overview. - // 1 byte = 2 HEX-chars - $body_size = (strlen($body) / 2); - $header_size = ($width * $height); - - // Use end-line padding? Only when needed - $usePadding = ($body_size > ($header_size * 3) + 4); - - // Using a for-loop with index-calculation instaid of str_split to avoid large memory consumption - // Calculate the next DWORD-position in the body - for ($i = 0; $i < $body_size; $i += 3) { - // Calculate line-ending and padding - if ($x >= $width) { - // If padding needed, ignore image-padding - // Shift i to the ending of the current 32-bit-block - if ($usePadding) { - $i += $width % 4; - } - - // Reset horizontal position - $x = 0; - - // Raise the height-position (bottom-up) - ++$y; - - // Reached the image-height? Break the for-loop - if ($y > $height) { - break; - } - } - - // Calculation of the RGB-pixel (defined as BGR in image-data) - // Define $i_pos as absolute position in the body - $i_pos = $i * 2; - $r = hexdec($body[$i_pos + 4] . $body[$i_pos + 5]); - $g = hexdec($body[$i_pos + 2] . $body[$i_pos + 3]); - $b = hexdec($body[$i_pos] . $body[$i_pos + 1]); - - // Calculate and draw the pixel - $color = imagecolorallocate($image, $r, $g, $b); - imagesetpixel($image, $x, $height - $y, $color); - - // Raise the horizontal position - ++$x; - } - - // Unset the body / free the memory - unset($body); - - // Return image-object - return $image; + return $retVal; } } diff --git a/PhpOffice/PhpSpreadsheet/Shared/Escher.php b/PhpOffice/PhpSpreadsheet/Shared/Escher.php old mode 100755 new mode 100644 diff --git a/PhpOffice/PhpSpreadsheet/Shared/Escher/DgContainer.php b/PhpOffice/PhpSpreadsheet/Shared/Escher/DgContainer.php old mode 100755 new mode 100644 index e9d387d..b0d75d7 --- a/PhpOffice/PhpSpreadsheet/Shared/Escher/DgContainer.php +++ b/PhpOffice/PhpSpreadsheet/Shared/Escher/DgContainer.php @@ -25,7 +25,7 @@ class DgContainer return $this->dgId; } - public function setDgId($value) + public function setDgId($value): void { $this->dgId = $value; } @@ -35,7 +35,7 @@ class DgContainer return $this->lastSpId; } - public function setLastSpId($value) + public function setLastSpId($value): void { $this->lastSpId = $value; } diff --git a/PhpOffice/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer.php b/PhpOffice/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer.php old mode 100755 new mode 100644 index 7e2c346..6bdc8f7 --- a/PhpOffice/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer.php +++ b/PhpOffice/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer.php @@ -7,7 +7,7 @@ class SpgrContainer /** * Parent Shape Group Container. * - * @var \PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer + * @var null|SpgrContainer */ private $parent; @@ -20,20 +20,16 @@ class SpgrContainer /** * Set parent Shape Group Container. - * - * @param \PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer $parent */ - public function setParent($parent) + public function setParent(?self $parent): void { $this->parent = $parent; } /** * Get the parent Shape Group Container if any. - * - * @return null|\PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer */ - public function getParent() + public function getParent(): ?self { return $this->parent; } @@ -43,7 +39,7 @@ class SpgrContainer * * @param mixed $child */ - public function addChild($child) + public function addChild($child): void { $this->children[] = $child; $child->setParent($this); diff --git a/PhpOffice/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer/SpContainer.php b/PhpOffice/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer/SpContainer.php old mode 100755 new mode 100644 index bbf51df..8a81ff5 --- a/PhpOffice/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer/SpContainer.php +++ b/PhpOffice/PhpSpreadsheet/Shared/Escher/DgContainer/SpgrContainer/SpContainer.php @@ -95,7 +95,7 @@ class SpContainer * * @param SpgrContainer $parent */ - public function setParent($parent) + public function setParent($parent): void { $this->parent = $parent; } @@ -115,7 +115,7 @@ class SpContainer * * @param bool $value */ - public function setSpgr($value) + public function setSpgr($value): void { $this->spgr = $value; } @@ -135,7 +135,7 @@ class SpContainer * * @param int $value */ - public function setSpType($value) + public function setSpType($value): void { $this->spType = $value; } @@ -155,7 +155,7 @@ class SpContainer * * @param int $value */ - public function setSpFlag($value) + public function setSpFlag($value): void { $this->spFlag = $value; } @@ -175,7 +175,7 @@ class SpContainer * * @param int $value */ - public function setSpId($value) + public function setSpId($value): void { $this->spId = $value; } @@ -196,7 +196,7 @@ class SpContainer * @param int $property The number specifies the option * @param mixed $value */ - public function setOPT($property, $value) + public function setOPT($property, $value): void { $this->OPT[$property] = $value; } @@ -232,7 +232,7 @@ class SpContainer * * @param string $value eg: 'A1' */ - public function setStartCoordinates($value) + public function setStartCoordinates($value): void { $this->startCoordinates = $value; } @@ -252,7 +252,7 @@ class SpContainer * * @param int $startOffsetX */ - public function setStartOffsetX($startOffsetX) + public function setStartOffsetX($startOffsetX): void { $this->startOffsetX = $startOffsetX; } @@ -272,7 +272,7 @@ class SpContainer * * @param int $startOffsetY */ - public function setStartOffsetY($startOffsetY) + public function setStartOffsetY($startOffsetY): void { $this->startOffsetY = $startOffsetY; } @@ -292,7 +292,7 @@ class SpContainer * * @param string $value eg: 'A1' */ - public function setEndCoordinates($value) + public function setEndCoordinates($value): void { $this->endCoordinates = $value; } @@ -312,7 +312,7 @@ class SpContainer * * @param int $endOffsetX */ - public function setEndOffsetX($endOffsetX) + public function setEndOffsetX($endOffsetX): void { $this->endOffsetX = $endOffsetX; } @@ -332,7 +332,7 @@ class SpContainer * * @param int $endOffsetY */ - public function setEndOffsetY($endOffsetY) + public function setEndOffsetY($endOffsetY): void { $this->endOffsetY = $endOffsetY; } diff --git a/PhpOffice/PhpSpreadsheet/Shared/Escher/DggContainer.php b/PhpOffice/PhpSpreadsheet/Shared/Escher/DggContainer.php old mode 100755 new mode 100644 index 96da321..36806aa --- a/PhpOffice/PhpSpreadsheet/Shared/Escher/DggContainer.php +++ b/PhpOffice/PhpSpreadsheet/Shared/Escher/DggContainer.php @@ -61,7 +61,7 @@ class DggContainer * * @param int $value */ - public function setSpIdMax($value) + public function setSpIdMax($value): void { $this->spIdMax = $value; } @@ -81,7 +81,7 @@ class DggContainer * * @param int $value */ - public function setCDgSaved($value) + public function setCDgSaved($value): void { $this->cDgSaved = $value; } @@ -101,7 +101,7 @@ class DggContainer * * @param int $value */ - public function setCSpSaved($value) + public function setCSpSaved($value): void { $this->cSpSaved = $value; } @@ -121,7 +121,7 @@ class DggContainer * * @param DggContainer\BstoreContainer $bstoreContainer */ - public function setBstoreContainer($bstoreContainer) + public function setBstoreContainer($bstoreContainer): void { $this->bstoreContainer = $bstoreContainer; } @@ -132,7 +132,7 @@ class DggContainer * @param int $property The number specifies the option * @param mixed $value */ - public function setOPT($property, $value) + public function setOPT($property, $value): void { $this->OPT[$property] = $value; } @@ -166,10 +166,10 @@ class DggContainer /** * Set identifier clusters. [ => , ...]. * - * @param array $pValue + * @param array $IDCLs */ - public function setIDCLs($pValue) + public function setIDCLs($IDCLs): void { - $this->IDCLs = $pValue; + $this->IDCLs = $IDCLs; } } diff --git a/PhpOffice/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer.php b/PhpOffice/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer.php old mode 100755 new mode 100644 index 9d1e68e..7203b66 --- a/PhpOffice/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer.php +++ b/PhpOffice/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer.php @@ -7,16 +7,14 @@ class BstoreContainer /** * BLIP Store Entries. Each of them holds one BLIP (Big Large Image or Picture). * - * @var array + * @var BstoreContainer\BSE[] */ private $BSECollection = []; /** * Add a BLIP Store Entry. - * - * @param BstoreContainer\BSE $BSE */ - public function addBSE($BSE) + public function addBSE(BstoreContainer\BSE $BSE): void { $this->BSECollection[] = $BSE; $BSE->setParent($this); diff --git a/PhpOffice/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE.php b/PhpOffice/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE.php old mode 100755 new mode 100644 index f83bdc7..d24af3f --- a/PhpOffice/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE.php +++ b/PhpOffice/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE.php @@ -2,6 +2,8 @@ namespace PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer; +use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer; + class BSE { const BLIPTYPE_ERROR = 0x00; @@ -18,7 +20,7 @@ class BSE /** * The parent BLIP Store Entry Container. * - * @var \PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer + * @var BstoreContainer */ private $parent; @@ -38,10 +40,8 @@ class BSE /** * Set parent BLIP Store Entry Container. - * - * @param \PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer $parent */ - public function setParent($parent) + public function setParent(BstoreContainer $parent): void { $this->parent = $parent; } @@ -58,10 +58,8 @@ class BSE /** * Set the BLIP. - * - * @param BSE\Blip $blip */ - public function setBlip($blip) + public function setBlip(BSE\Blip $blip): void { $this->blip = $blip; $blip->setParent($this); @@ -82,7 +80,7 @@ class BSE * * @param int $blipType */ - public function setBlipType($blipType) + public function setBlipType($blipType): void { $this->blipType = $blipType; } diff --git a/PhpOffice/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE/Blip.php b/PhpOffice/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE/Blip.php old mode 100755 new mode 100644 index 88bc117..03b261f --- a/PhpOffice/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE/Blip.php +++ b/PhpOffice/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE/Blip.php @@ -2,12 +2,14 @@ namespace PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE; +use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE; + class Blip { /** * The parent BSE. * - * @var \PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE + * @var BSE */ private $parent; @@ -33,27 +35,23 @@ class Blip * * @param string $data */ - public function setData($data) + public function setData($data): void { $this->data = $data; } /** * Set parent BSE. - * - * @param \PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE $parent */ - public function setParent($parent) + public function setParent(BSE $parent): void { $this->parent = $parent; } /** * Get parent BSE. - * - * @return \PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE $parent */ - public function getParent() + public function getParent(): BSE { return $this->parent; } diff --git a/PhpOffice/PhpSpreadsheet/Shared/File.php b/PhpOffice/PhpSpreadsheet/Shared/File.php old mode 100755 new mode 100644 index 239c837..f2fe8ca --- a/PhpOffice/PhpSpreadsheet/Shared/File.php +++ b/PhpOffice/PhpSpreadsheet/Shared/File.php @@ -2,7 +2,8 @@ namespace PhpOffice\PhpSpreadsheet\Shared; -use InvalidArgumentException; +use PhpOffice\PhpSpreadsheet\Exception; +use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException; use ZipArchive; class File @@ -16,75 +17,81 @@ class File /** * Set the flag indicating whether the File Upload Temp directory should be used for temporary files. - * - * @param bool $useUploadTempDir Use File Upload Temporary directory (true or false) */ - public static function setUseUploadTempDirectory($useUploadTempDir) + public static function setUseUploadTempDirectory(bool $useUploadTempDir): void { self::$useUploadTempDirectory = (bool) $useUploadTempDir; } /** * Get the flag indicating whether the File Upload Temp directory should be used for temporary files. - * - * @return bool Use File Upload Temporary directory (true or false) */ - public static function getUseUploadTempDirectory() + public static function getUseUploadTempDirectory(): bool { return self::$useUploadTempDirectory; } + // https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT + // Section 4.3.7 + // Looks like there might be endian-ness considerations + private const ZIP_FIRST_4 = [ + "\x50\x4b\x03\x04", // what it looks like on my system + "\x04\x03\x4b\x50", // what it says in documentation + ]; + + private static function validateZipFirst4(string $zipFile): bool + { + $contents = @file_get_contents($zipFile, false, null, 0, 4); + + return in_array($contents, self::ZIP_FIRST_4, true); + } + /** * Verify if a file exists. - * - * @param string $pFilename Filename - * - * @return bool */ - public static function fileExists($pFilename) + public static function fileExists(string $filename): bool { // Sick construction, but it seems that // file_exists returns strange values when // doing the original file_exists on ZIP archives... - if (strtolower(substr($pFilename, 0, 3)) == 'zip') { + if (strtolower(substr($filename, 0, 6)) == 'zip://') { // Open ZIP file and verify if the file exists - $zipFile = substr($pFilename, 6, strpos($pFilename, '#') - 6); - $archiveFile = substr($pFilename, strpos($pFilename, '#') + 1); + $zipFile = substr($filename, 6, strrpos($filename, '#') - 6); + $archiveFile = substr($filename, strrpos($filename, '#') + 1); - $zip = new ZipArchive(); - if ($zip->open($zipFile) === true) { - $returnValue = ($zip->getFromName($archiveFile) !== false); - $zip->close(); + if (self::validateZipFirst4($zipFile)) { + $zip = new ZipArchive(); + $res = $zip->open($zipFile); + if ($res === true) { + $returnValue = ($zip->getFromName($archiveFile) !== false); + $zip->close(); - return $returnValue; + return $returnValue; + } } return false; } - return file_exists($pFilename); + return file_exists($filename); } /** * Returns canonicalized absolute pathname, also for ZIP archives. - * - * @param string $pFilename - * - * @return string */ - public static function realpath($pFilename) + public static function realpath(string $filename): string { // Returnvalue $returnValue = ''; // Try using realpath() - if (file_exists($pFilename)) { - $returnValue = realpath($pFilename); + if (file_exists($filename)) { + $returnValue = realpath($filename) ?: ''; } // Found something? - if ($returnValue == '' || ($returnValue === null)) { - $pathArray = explode('/', $pFilename); + if ($returnValue === '') { + $pathArray = explode('/', $filename); while (in_array('..', $pathArray) && $pathArray[0] != '..') { $iMax = count($pathArray); for ($i = 0; $i < $iMax; ++$i) { @@ -104,41 +111,75 @@ class File /** * Get the systems temporary directory. - * - * @return string */ - public static function sysGetTempDir() + public static function sysGetTempDir(): string { + $path = sys_get_temp_dir(); if (self::$useUploadTempDirectory) { // use upload-directory when defined to allow running on environments having very restricted // open_basedir configs if (ini_get('upload_tmp_dir') !== false) { if ($temp = ini_get('upload_tmp_dir')) { if (file_exists($temp)) { - return realpath($temp); + $path = $temp; } } } } - return realpath(sys_get_temp_dir()); + return realpath($path) ?: ''; + } + + public static function temporaryFilename(): string + { + $filename = tempnam(self::sysGetTempDir(), 'phpspreadsheet'); + if ($filename === false) { + throw new Exception('Could not create temporary file'); + } + + return $filename; } /** * Assert that given path is an existing file and is readable, otherwise throw exception. - * - * @param string $filename - * - * @throws InvalidArgumentException */ - public static function assertFile($filename) + public static function assertFile(string $filename, string $zipMember = ''): void { if (!is_file($filename)) { - throw new InvalidArgumentException('File "' . $filename . '" does not exist.'); + throw new ReaderException('File "' . $filename . '" does not exist.'); } if (!is_readable($filename)) { - throw new InvalidArgumentException('Could not open "' . $filename . '" for reading.'); + throw new ReaderException('Could not open "' . $filename . '" for reading.'); + } + + if ($zipMember !== '') { + $zipfile = "zip://$filename#$zipMember"; + if (!self::fileExists($zipfile)) { + throw new ReaderException("Could not find zip member $zipfile"); + } } } + + /** + * Same as assertFile, except return true/false and don't throw Exception. + */ + public static function testFileNoThrow(string $filename, ?string $zipMember = null): bool + { + if (!is_file($filename)) { + return false; + } + if (!is_readable($filename)) { + return false; + } + if ($zipMember === null) { + return true; + } + // validate zip, but don't check specific member + if ($zipMember === '') { + return self::validateZipFirst4($filename); + } + + return self::fileExists("zip://$filename#$zipMember"); + } } diff --git a/PhpOffice/PhpSpreadsheet/Shared/Font.php b/PhpOffice/PhpSpreadsheet/Shared/Font.php old mode 100755 new mode 100644 index 8abcef2..dfe9f77 --- a/PhpOffice/PhpSpreadsheet/Shared/Font.php +++ b/PhpOffice/PhpSpreadsheet/Shared/Font.php @@ -4,6 +4,8 @@ namespace PhpOffice\PhpSpreadsheet\Shared; use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; use PhpOffice\PhpSpreadsheet\RichText\RichText; +use PhpOffice\PhpSpreadsheet\Style\Alignment; +use PhpOffice\PhpSpreadsheet\Style\Font as FontStyle; class Font { @@ -11,7 +13,7 @@ class Font const AUTOSIZE_METHOD_APPROX = 'approx'; const AUTOSIZE_METHOD_EXACT = 'exact'; - private static $autoSizeMethods = [ + private const AUTOSIZE_METHODS = [ self::AUTOSIZE_METHOD_APPROX, self::AUTOSIZE_METHOD_EXACT, ]; @@ -44,10 +46,10 @@ class Font const ARIAL_ITALIC = 'ariali.ttf'; const ARIAL_BOLD_ITALIC = 'arialbi.ttf'; - const CALIBRI = 'CALIBRI.TTF'; - const CALIBRI_BOLD = 'CALIBRIB.TTF'; - const CALIBRI_ITALIC = 'CALIBRII.TTF'; - const CALIBRI_BOLD_ITALIC = 'CALIBRIZ.TTF'; + const CALIBRI = 'calibri.ttf'; + const CALIBRI_BOLD = 'calibrib.ttf'; + const CALIBRI_ITALIC = 'calibrii.ttf'; + const CALIBRI_BOLD_ITALIC = 'calibriz.ttf'; const COMIC_SANS_MS = 'comic.ttf'; const COMIC_SANS_MS_BOLD = 'comicbd.ttf'; @@ -99,6 +101,105 @@ class Font const VERDANA_ITALIC = 'verdanai.ttf'; const VERDANA_BOLD_ITALIC = 'verdanaz.ttf'; + const FONT_FILE_NAMES = [ + 'Arial' => [ + 'x' => self::ARIAL, + 'xb' => self::ARIAL_BOLD, + 'xi' => self::ARIAL_ITALIC, + 'xbi' => self::ARIAL_BOLD_ITALIC, + ], + 'Calibri' => [ + 'x' => self::CALIBRI, + 'xb' => self::CALIBRI_BOLD, + 'xi' => self::CALIBRI_ITALIC, + 'xbi' => self::CALIBRI_BOLD_ITALIC, + ], + 'Comic Sans MS' => [ + 'x' => self::COMIC_SANS_MS, + 'xb' => self::COMIC_SANS_MS_BOLD, + 'xi' => self::COMIC_SANS_MS, + 'xbi' => self::COMIC_SANS_MS_BOLD, + ], + 'Courier New' => [ + 'x' => self::COURIER_NEW, + 'xb' => self::COURIER_NEW_BOLD, + 'xi' => self::COURIER_NEW_ITALIC, + 'xbi' => self::COURIER_NEW_BOLD_ITALIC, + ], + 'Georgia' => [ + 'x' => self::GEORGIA, + 'xb' => self::GEORGIA_BOLD, + 'xi' => self::GEORGIA_ITALIC, + 'xbi' => self::GEORGIA_BOLD_ITALIC, + ], + 'Impact' => [ + 'x' => self::IMPACT, + 'xb' => self::IMPACT, + 'xi' => self::IMPACT, + 'xbi' => self::IMPACT, + ], + 'Liberation Sans' => [ + 'x' => self::LIBERATION_SANS, + 'xb' => self::LIBERATION_SANS_BOLD, + 'xi' => self::LIBERATION_SANS_ITALIC, + 'xbi' => self::LIBERATION_SANS_BOLD_ITALIC, + ], + 'Lucida Console' => [ + 'x' => self::LUCIDA_CONSOLE, + 'xb' => self::LUCIDA_CONSOLE, + 'xi' => self::LUCIDA_CONSOLE, + 'xbi' => self::LUCIDA_CONSOLE, + ], + 'Lucida Sans Unicode' => [ + 'x' => self::LUCIDA_SANS_UNICODE, + 'xb' => self::LUCIDA_SANS_UNICODE, + 'xi' => self::LUCIDA_SANS_UNICODE, + 'xbi' => self::LUCIDA_SANS_UNICODE, + ], + 'Microsoft Sans Serif' => [ + 'x' => self::MICROSOFT_SANS_SERIF, + 'xb' => self::MICROSOFT_SANS_SERIF, + 'xi' => self::MICROSOFT_SANS_SERIF, + 'xbi' => self::MICROSOFT_SANS_SERIF, + ], + 'Palatino Linotype' => [ + 'x' => self::PALATINO_LINOTYPE, + 'xb' => self::PALATINO_LINOTYPE_BOLD, + 'xi' => self::PALATINO_LINOTYPE_ITALIC, + 'xbi' => self::PALATINO_LINOTYPE_BOLD_ITALIC, + ], + 'Symbol' => [ + 'x' => self::SYMBOL, + 'xb' => self::SYMBOL, + 'xi' => self::SYMBOL, + 'xbi' => self::SYMBOL, + ], + 'Tahoma' => [ + 'x' => self::TAHOMA, + 'xb' => self::TAHOMA_BOLD, + 'xi' => self::TAHOMA, + 'xbi' => self::TAHOMA_BOLD, + ], + 'Times New Roman' => [ + 'x' => self::TIMES_NEW_ROMAN, + 'xb' => self::TIMES_NEW_ROMAN_BOLD, + 'xi' => self::TIMES_NEW_ROMAN_ITALIC, + 'xbi' => self::TIMES_NEW_ROMAN_BOLD_ITALIC, + ], + 'Trebuchet MS' => [ + 'x' => self::TREBUCHET_MS, + 'xb' => self::TREBUCHET_MS_BOLD, + 'xi' => self::TREBUCHET_MS_ITALIC, + 'xbi' => self::TREBUCHET_MS_BOLD_ITALIC, + ], + 'Verdana' => [ + 'x' => self::VERDANA, + 'xb' => self::VERDANA_BOLD, + 'xi' => self::VERDANA_ITALIC, + 'xbi' => self::VERDANA_BOLD_ITALIC, + ], + ]; + /** * AutoSize method. * @@ -111,68 +212,79 @@ class Font * * @var string */ - private static $trueTypeFontPath = null; + private static $trueTypeFontPath = ''; /** * How wide is a default column for a given default font and size? * Empirical data found by inspecting real Excel files and reading off the pixel width * in Microsoft Office Excel 2007. - * - * @var array + * Added height in points. */ - public static $defaultColumnWidths = [ + public const DEFAULT_COLUMN_WIDTHS = [ 'Arial' => [ - 1 => ['px' => 24, 'width' => 12.00000000], - 2 => ['px' => 24, 'width' => 12.00000000], - 3 => ['px' => 32, 'width' => 10.66406250], - 4 => ['px' => 32, 'width' => 10.66406250], - 5 => ['px' => 40, 'width' => 10.00000000], - 6 => ['px' => 48, 'width' => 9.59765625], - 7 => ['px' => 48, 'width' => 9.59765625], - 8 => ['px' => 56, 'width' => 9.33203125], - 9 => ['px' => 64, 'width' => 9.14062500], - 10 => ['px' => 64, 'width' => 9.14062500], + 1 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25], + 2 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25], + 3 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.0], + + 4 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.75], + 5 => ['px' => 40, 'width' => 10.00000000, 'height' => 8.25], + 6 => ['px' => 48, 'width' => 9.59765625, 'height' => 8.25], + 7 => ['px' => 48, 'width' => 9.59765625, 'height' => 9.0], + 8 => ['px' => 56, 'width' => 9.33203125, 'height' => 11.25], + 9 => ['px' => 64, 'width' => 9.14062500, 'height' => 12.0], + 10 => ['px' => 64, 'width' => 9.14062500, 'height' => 12.75], ], 'Calibri' => [ - 1 => ['px' => 24, 'width' => 12.00000000], - 2 => ['px' => 24, 'width' => 12.00000000], - 3 => ['px' => 32, 'width' => 10.66406250], - 4 => ['px' => 32, 'width' => 10.66406250], - 5 => ['px' => 40, 'width' => 10.00000000], - 6 => ['px' => 48, 'width' => 9.59765625], - 7 => ['px' => 48, 'width' => 9.59765625], - 8 => ['px' => 56, 'width' => 9.33203125], - 9 => ['px' => 56, 'width' => 9.33203125], - 10 => ['px' => 64, 'width' => 9.14062500], - 11 => ['px' => 64, 'width' => 9.14062500], + 1 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25], + 2 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25], + 3 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.00], + 4 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.75], + 5 => ['px' => 40, 'width' => 10.00000000, 'height' => 8.25], + 6 => ['px' => 48, 'width' => 9.59765625, 'height' => 8.25], + 7 => ['px' => 48, 'width' => 9.59765625, 'height' => 9.0], + 8 => ['px' => 56, 'width' => 9.33203125, 'height' => 11.25], + 9 => ['px' => 56, 'width' => 9.33203125, 'height' => 12.0], + 10 => ['px' => 64, 'width' => 9.14062500, 'height' => 12.75], + 11 => ['px' => 64, 'width' => 9.14062500, 'height' => 15.0], ], 'Verdana' => [ - 1 => ['px' => 24, 'width' => 12.00000000], - 2 => ['px' => 24, 'width' => 12.00000000], - 3 => ['px' => 32, 'width' => 10.66406250], - 4 => ['px' => 32, 'width' => 10.66406250], - 5 => ['px' => 40, 'width' => 10.00000000], - 6 => ['px' => 48, 'width' => 9.59765625], - 7 => ['px' => 48, 'width' => 9.59765625], - 8 => ['px' => 64, 'width' => 9.14062500], - 9 => ['px' => 72, 'width' => 9.00000000], - 10 => ['px' => 72, 'width' => 9.00000000], + 1 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25], + 2 => ['px' => 24, 'width' => 12.00000000, 'height' => 5.25], + 3 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.0], + 4 => ['px' => 32, 'width' => 10.66406250, 'height' => 6.75], + 5 => ['px' => 40, 'width' => 10.00000000, 'height' => 8.25], + 6 => ['px' => 48, 'width' => 9.59765625, 'height' => 8.25], + 7 => ['px' => 48, 'width' => 9.59765625, 'height' => 9.0], + 8 => ['px' => 64, 'width' => 9.14062500, 'height' => 10.5], + 9 => ['px' => 72, 'width' => 9.00000000, 'height' => 11.25], + 10 => ['px' => 72, 'width' => 9.00000000, 'height' => 12.75], ], ]; + /** + * List of column widths. Replaced by constant; + * previously it was public and updateable, allowing + * user to make inappropriate alterations. + * + * @deprecated 1.25.0 Use DEFAULT_COLUMN_WIDTHS constant instead. + * + * @var array + */ + public static $defaultColumnWidths = self::DEFAULT_COLUMN_WIDTHS; + /** * Set autoSize method. * - * @param string $pValue see self::AUTOSIZE_METHOD_* + * @param string $method see self::AUTOSIZE_METHOD_* * * @return bool Success or failure */ - public static function setAutoSizeMethod($pValue) + public static function setAutoSizeMethod($method) { - if (!in_array($pValue, self::$autoSizeMethods)) { + if (!in_array($method, self::AUTOSIZE_METHODS)) { return false; } - self::$autoSizeMethod = $pValue; + self::$autoSizeMethod = $method; return true; } @@ -196,11 +308,11 @@ class Font *

  • ~/.fonts/
  • * . * - * @param string $pValue + * @param string $folderPath */ - public static function setTrueTypeFontPath($pValue) + public static function setTrueTypeFontPath($folderPath): void { - self::$trueTypeFontPath = $pValue; + self::$trueTypeFontPath = $folderPath; } /** @@ -216,35 +328,48 @@ class Font /** * Calculate an (approximate) OpenXML column width, based on font size and text contained. * - * @param \PhpOffice\PhpSpreadsheet\Style\Font $font Font object - * @param RichText|string $cellText Text to calculate width + * @param FontStyle $font Font object + * @param null|RichText|string $cellText Text to calculate width * @param int $rotation Rotation angle - * @param null|\PhpOffice\PhpSpreadsheet\Style\Font $defaultFont Font object - * - * @return int Column width + * @param null|FontStyle $defaultFont Font object + * @param bool $filterAdjustment Add space for Autofilter or Table dropdown */ - public static function calculateColumnWidth(\PhpOffice\PhpSpreadsheet\Style\Font $font, $cellText = '', $rotation = 0, \PhpOffice\PhpSpreadsheet\Style\Font $defaultFont = null) - { + public static function calculateColumnWidth( + FontStyle $font, + $cellText = '', + $rotation = 0, + ?FontStyle $defaultFont = null, + bool $filterAdjustment = false, + int $indentAdjustment = 0 + ): int { // If it is rich text, use plain text if ($cellText instanceof RichText) { $cellText = $cellText->getPlainText(); } // Special case if there are one or more newline characters ("\n") + $cellText = (string) $cellText; if (strpos($cellText, "\n") !== false) { $lineTexts = explode("\n", $cellText); $lineWidths = []; foreach ($lineTexts as $lineText) { - $lineWidths[] = self::calculateColumnWidth($font, $lineText, $rotation = 0, $defaultFont); + $lineWidths[] = self::calculateColumnWidth($font, $lineText, $rotation = 0, $defaultFont, $filterAdjustment); } return max($lineWidths); // width of longest line in cell } // Try to get the exact text width in pixels - $approximate = self::$autoSizeMethod == self::AUTOSIZE_METHOD_APPROX; + $approximate = self::$autoSizeMethod === self::AUTOSIZE_METHOD_APPROX; + $columnWidth = 0; if (!$approximate) { - $columnWidthAdjust = ceil(self::getTextWidthPixelsExact('n', $font, 0) * 1.07); + $columnWidthAdjust = ceil( + self::getTextWidthPixelsExact( + str_repeat('n', 1 * (($filterAdjustment ? 3 : 1) + ($indentAdjustment * 2))), + $font, + 0 + ) * 1.07 + ); try { // Width of text in pixels excl. padding @@ -256,31 +381,27 @@ class Font } if ($approximate) { - $columnWidthAdjust = self::getTextWidthPixelsApprox('n', $font, 0); + $columnWidthAdjust = self::getTextWidthPixelsApprox( + str_repeat('n', 1 * (($filterAdjustment ? 3 : 1) + ($indentAdjustment * 2))), + $font, + 0 + ); // Width of text in pixels excl. padding, approximation // and addition because Excel adds some padding, just use approx width of 'n' glyph $columnWidth = self::getTextWidthPixelsApprox($cellText, $font, $rotation) + $columnWidthAdjust; } // Convert from pixel width to column width - $columnWidth = Drawing::pixelsToCellDimension($columnWidth, $defaultFont); + $columnWidth = Drawing::pixelsToCellDimension((int) $columnWidth, $defaultFont ?? new FontStyle()); // Return - return round($columnWidth, 6); + return (int) round($columnWidth, 6); } /** * Get GD text width in pixels for a string of text in a certain font at a certain rotation angle. - * - * @param string $text - * @param \PhpOffice\PhpSpreadsheet\Style\Font - * @param int $rotation - * - * @throws PhpSpreadsheetException - * - * @return int */ - public static function getTextWidthPixelsExact($text, \PhpOffice\PhpSpreadsheet\Style\Font $font, $rotation = 0) + public static function getTextWidthPixelsExact(string $text, FontStyle $font, int $rotation = 0): int { if (!function_exists('imagettfbbox')) { throw new PhpSpreadsheetException('GD library needs to be enabled'); @@ -289,7 +410,12 @@ class Font // font size should really be supplied in pixels in GD2, // but since GD2 seems to assume 72dpi, pixels and points are the same $fontFile = self::getTrueTypeFontFileFromFont($font); - $textBox = imagettfbbox($font->getSize(), $rotation, $fontFile, $text); + $textBox = imagettfbbox($font->getSize() ?? 10.0, $rotation, $fontFile, $text); + if ($textBox === false) { + // @codeCoverageIgnoreStart + throw new PhpSpreadsheetException('imagettfbbox failed'); + // @codeCoverageIgnoreEnd + } // Get corners positions $lowerLeftCornerX = $textBox[0]; @@ -298,21 +424,18 @@ class Font $upperLeftCornerX = $textBox[6]; // Consider the rotation when calculating the width - $textWidth = max($lowerRightCornerX - $upperLeftCornerX, $upperRightCornerX - $lowerLeftCornerX); - - return $textWidth; + return max($lowerRightCornerX - $upperLeftCornerX, $upperRightCornerX - $lowerLeftCornerX); } /** * Get approximate width in pixels for a string of text in a certain font at a certain rotation angle. * * @param string $columnText - * @param \PhpOffice\PhpSpreadsheet\Style\Font $font * @param int $rotation * * @return int Text width in pixels (no padding added) */ - public static function getTextWidthPixelsApprox($columnText, \PhpOffice\PhpSpreadsheet\Style\Font $font, $rotation = 0) + public static function getTextWidthPixelsApprox($columnText, FontStyle $font, $rotation = 0) { $fontName = $font->getName(); $fontSize = $font->getSize(); @@ -323,27 +446,31 @@ class Font // value 8.26 was found via interpolation by inspecting real Excel files with Calibri 11 font. $columnWidth = (int) (8.26 * StringHelper::countCharacters($columnText)); $columnWidth = $columnWidth * $fontSize / 11; // extrapolate from font size + break; case 'Arial': // value 8 was set because of experience in different exports at Arial 10 font. $columnWidth = (int) (8 * StringHelper::countCharacters($columnText)); $columnWidth = $columnWidth * $fontSize / 10; // extrapolate from font size + break; case 'Verdana': // value 8 was found via interpolation by inspecting real Excel files with Verdana 10 font. $columnWidth = (int) (8 * StringHelper::countCharacters($columnText)); $columnWidth = $columnWidth * $fontSize / 10; // extrapolate from font size + break; default: // just assume Calibri $columnWidth = (int) (8.26 * StringHelper::countCharacters($columnText)); $columnWidth = $columnWidth * $fontSize / 11; // extrapolate from font size + break; } // Calculate approximate rotated column width if ($rotation !== 0) { - if ($rotation == -165) { + if ($rotation == Alignment::TEXTROTATION_STACK_PHPSPREADSHEET) { // stacked text $columnWidth = 4; // approximation } else { @@ -396,183 +523,88 @@ class Font /** * Returns the font path given the font. * - * @param \PhpOffice\PhpSpreadsheet\Style\Font $font - * * @return string Path to TrueType font file */ - public static function getTrueTypeFontFileFromFont($font) + public static function getTrueTypeFontFileFromFont(FontStyle $font, bool $checkPath = true) { - if (!file_exists(self::$trueTypeFontPath) || !is_dir(self::$trueTypeFontPath)) { + if ($checkPath && (!file_exists(self::$trueTypeFontPath) || !is_dir(self::$trueTypeFontPath))) { throw new PhpSpreadsheetException('Valid directory to TrueType Font files not specified'); } $name = $font->getName(); + if (!isset(self::FONT_FILE_NAMES[$name])) { + throw new PhpSpreadsheetException('Unknown font name "' . $name . '". Cannot map to TrueType font file'); + } $bold = $font->getBold(); $italic = $font->getItalic(); - - // Check if we can map font to true type font file - switch ($name) { - case 'Arial': - $fontFile = ( - $bold ? ($italic ? self::ARIAL_BOLD_ITALIC : self::ARIAL_BOLD) - : ($italic ? self::ARIAL_ITALIC : self::ARIAL) - ); - - break; - case 'Calibri': - $fontFile = ( - $bold ? ($italic ? self::CALIBRI_BOLD_ITALIC : self::CALIBRI_BOLD) - : ($italic ? self::CALIBRI_ITALIC : self::CALIBRI) - ); - - break; - case 'Courier New': - $fontFile = ( - $bold ? ($italic ? self::COURIER_NEW_BOLD_ITALIC : self::COURIER_NEW_BOLD) - : ($italic ? self::COURIER_NEW_ITALIC : self::COURIER_NEW) - ); - - break; - case 'Comic Sans MS': - $fontFile = ( - $bold ? self::COMIC_SANS_MS_BOLD : self::COMIC_SANS_MS - ); - - break; - case 'Georgia': - $fontFile = ( - $bold ? ($italic ? self::GEORGIA_BOLD_ITALIC : self::GEORGIA_BOLD) - : ($italic ? self::GEORGIA_ITALIC : self::GEORGIA) - ); - - break; - case 'Impact': - $fontFile = self::IMPACT; - - break; - case 'Liberation Sans': - $fontFile = ( - $bold ? ($italic ? self::LIBERATION_SANS_BOLD_ITALIC : self::LIBERATION_SANS_BOLD) - : ($italic ? self::LIBERATION_SANS_ITALIC : self::LIBERATION_SANS) - ); - - break; - case 'Lucida Console': - $fontFile = self::LUCIDA_CONSOLE; - - break; - case 'Lucida Sans Unicode': - $fontFile = self::LUCIDA_SANS_UNICODE; - - break; - case 'Microsoft Sans Serif': - $fontFile = self::MICROSOFT_SANS_SERIF; - - break; - case 'Palatino Linotype': - $fontFile = ( - $bold ? ($italic ? self::PALATINO_LINOTYPE_BOLD_ITALIC : self::PALATINO_LINOTYPE_BOLD) - : ($italic ? self::PALATINO_LINOTYPE_ITALIC : self::PALATINO_LINOTYPE) - ); - - break; - case 'Symbol': - $fontFile = self::SYMBOL; - - break; - case 'Tahoma': - $fontFile = ( - $bold ? self::TAHOMA_BOLD : self::TAHOMA - ); - - break; - case 'Times New Roman': - $fontFile = ( - $bold ? ($italic ? self::TIMES_NEW_ROMAN_BOLD_ITALIC : self::TIMES_NEW_ROMAN_BOLD) - : ($italic ? self::TIMES_NEW_ROMAN_ITALIC : self::TIMES_NEW_ROMAN) - ); - - break; - case 'Trebuchet MS': - $fontFile = ( - $bold ? ($italic ? self::TREBUCHET_MS_BOLD_ITALIC : self::TREBUCHET_MS_BOLD) - : ($italic ? self::TREBUCHET_MS_ITALIC : self::TREBUCHET_MS) - ); - - break; - case 'Verdana': - $fontFile = ( - $bold ? ($italic ? self::VERDANA_BOLD_ITALIC : self::VERDANA_BOLD) - : ($italic ? self::VERDANA_ITALIC : self::VERDANA) - ); - - break; - default: - throw new PhpSpreadsheetException('Unknown font name "' . $name . '". Cannot map to TrueType font file'); - - break; + $index = 'x'; + if ($bold) { + $index .= 'b'; } + if ($italic) { + $index .= 'i'; + } + $fontFile = self::FONT_FILE_NAMES[$name][$index]; - $fontFile = self::$trueTypeFontPath . $fontFile; + $separator = ''; + if (mb_strlen(self::$trueTypeFontPath) > 1 && mb_substr(self::$trueTypeFontPath, -1) !== '/' && mb_substr(self::$trueTypeFontPath, -1) !== '\\') { + $separator = DIRECTORY_SEPARATOR; + } + $fontFile = self::$trueTypeFontPath . $separator . $fontFile; // Check if file actually exists - if (!file_exists($fontFile)) { + if ($checkPath && !file_exists($fontFile)) { throw new PhpSpreadsheetException('TrueType Font file not found'); } return $fontFile; } + public const CHARSET_FROM_FONT_NAME = [ + 'EucrosiaUPC' => self::CHARSET_ANSI_THAI, + 'Wingdings' => self::CHARSET_SYMBOL, + 'Wingdings 2' => self::CHARSET_SYMBOL, + 'Wingdings 3' => self::CHARSET_SYMBOL, + ]; + /** * Returns the associated charset for the font name. * - * @param string $name Font name + * @param string $fontName Font name * * @return int Character set code */ - public static function getCharsetFromFontName($name) + public static function getCharsetFromFontName($fontName) { - switch ($name) { - // Add more cases. Check FONT records in real Excel files. - case 'EucrosiaUPC': - return self::CHARSET_ANSI_THAI; - case 'Wingdings': - return self::CHARSET_SYMBOL; - case 'Wingdings 2': - return self::CHARSET_SYMBOL; - case 'Wingdings 3': - return self::CHARSET_SYMBOL; - default: - return self::CHARSET_ANSI_LATIN; - } + return self::CHARSET_FROM_FONT_NAME[$fontName] ?? self::CHARSET_ANSI_LATIN; } /** * Get the effective column width for columns without a column dimension or column with width -1 * For example, for Calibri 11 this is 9.140625 (64 px). * - * @param \PhpOffice\PhpSpreadsheet\Style\Font $font The workbooks default font - * @param bool $pPixels true = return column width in pixels, false = return in OOXML units + * @param FontStyle $font The workbooks default font + * @param bool $returnAsPixels true = return column width in pixels, false = return in OOXML units * * @return mixed Column width */ - public static function getDefaultColumnWidthByFont(\PhpOffice\PhpSpreadsheet\Style\Font $font, $pPixels = false) + public static function getDefaultColumnWidthByFont(FontStyle $font, $returnAsPixels = false) { - if (isset(self::$defaultColumnWidths[$font->getName()][$font->getSize()])) { + if (isset(self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$font->getSize()])) { // Exact width can be determined - $columnWidth = $pPixels ? - self::$defaultColumnWidths[$font->getName()][$font->getSize()]['px'] - : self::$defaultColumnWidths[$font->getName()][$font->getSize()]['width']; + $columnWidth = $returnAsPixels ? + self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$font->getSize()]['px'] + : self::DEFAULT_COLUMN_WIDTHS[$font->getName()][$font->getSize()]['width']; } else { // We don't have data for this particular font and size, use approximation by // extrapolating from Calibri 11 - $columnWidth = $pPixels ? - self::$defaultColumnWidths['Calibri'][11]['px'] - : self::$defaultColumnWidths['Calibri'][11]['width']; + $columnWidth = $returnAsPixels ? + self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['px'] + : self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['width']; $columnWidth = $columnWidth * $font->getSize() / 11; // Round pixels to closest integer - if ($pPixels) { + if ($returnAsPixels) { $columnWidth = (int) round($columnWidth); } } @@ -584,179 +616,20 @@ class Font * Get the effective row height for rows without a row dimension or rows with height -1 * For example, for Calibri 11 this is 15 points. * - * @param \PhpOffice\PhpSpreadsheet\Style\Font $font The workbooks default font + * @param FontStyle $font The workbooks default font * * @return float Row height in points */ - public static function getDefaultRowHeightByFont(\PhpOffice\PhpSpreadsheet\Style\Font $font) + public static function getDefaultRowHeightByFont(FontStyle $font) { - switch ($font->getName()) { - case 'Arial': - switch ($font->getSize()) { - case 10: - // inspection of Arial 10 workbook says 12.75pt ~17px - $rowHeight = 12.75; - - break; - case 9: - // inspection of Arial 9 workbook says 12.00pt ~16px - $rowHeight = 12; - - break; - case 8: - // inspection of Arial 8 workbook says 11.25pt ~15px - $rowHeight = 11.25; - - break; - case 7: - // inspection of Arial 7 workbook says 9.00pt ~12px - $rowHeight = 9; - - break; - case 6: - case 5: - // inspection of Arial 5,6 workbook says 8.25pt ~11px - $rowHeight = 8.25; - - break; - case 4: - // inspection of Arial 4 workbook says 6.75pt ~9px - $rowHeight = 6.75; - - break; - case 3: - // inspection of Arial 3 workbook says 6.00pt ~8px - $rowHeight = 6; - - break; - case 2: - case 1: - // inspection of Arial 1,2 workbook says 5.25pt ~7px - $rowHeight = 5.25; - - break; - default: - // use Arial 10 workbook as an approximation, extrapolation - $rowHeight = 12.75 * $font->getSize() / 10; - - break; - } - - break; - case 'Calibri': - switch ($font->getSize()) { - case 11: - // inspection of Calibri 11 workbook says 15.00pt ~20px - $rowHeight = 15; - - break; - case 10: - // inspection of Calibri 10 workbook says 12.75pt ~17px - $rowHeight = 12.75; - - break; - case 9: - // inspection of Calibri 9 workbook says 12.00pt ~16px - $rowHeight = 12; - - break; - case 8: - // inspection of Calibri 8 workbook says 11.25pt ~15px - $rowHeight = 11.25; - - break; - case 7: - // inspection of Calibri 7 workbook says 9.00pt ~12px - $rowHeight = 9; - - break; - case 6: - case 5: - // inspection of Calibri 5,6 workbook says 8.25pt ~11px - $rowHeight = 8.25; - - break; - case 4: - // inspection of Calibri 4 workbook says 6.75pt ~9px - $rowHeight = 6.75; - - break; - case 3: - // inspection of Calibri 3 workbook says 6.00pt ~8px - $rowHeight = 6.00; - - break; - case 2: - case 1: - // inspection of Calibri 1,2 workbook says 5.25pt ~7px - $rowHeight = 5.25; - - break; - default: - // use Calibri 11 workbook as an approximation, extrapolation - $rowHeight = 15 * $font->getSize() / 11; - - break; - } - - break; - case 'Verdana': - switch ($font->getSize()) { - case 10: - // inspection of Verdana 10 workbook says 12.75pt ~17px - $rowHeight = 12.75; - - break; - case 9: - // inspection of Verdana 9 workbook says 11.25pt ~15px - $rowHeight = 11.25; - - break; - case 8: - // inspection of Verdana 8 workbook says 10.50pt ~14px - $rowHeight = 10.50; - - break; - case 7: - // inspection of Verdana 7 workbook says 9.00pt ~12px - $rowHeight = 9.00; - - break; - case 6: - case 5: - // inspection of Verdana 5,6 workbook says 8.25pt ~11px - $rowHeight = 8.25; - - break; - case 4: - // inspection of Verdana 4 workbook says 6.75pt ~9px - $rowHeight = 6.75; - - break; - case 3: - // inspection of Verdana 3 workbook says 6.00pt ~8px - $rowHeight = 6; - - break; - case 2: - case 1: - // inspection of Verdana 1,2 workbook says 5.25pt ~7px - $rowHeight = 5.25; - - break; - default: - // use Verdana 10 workbook as an approximation, extrapolation - $rowHeight = 12.75 * $font->getSize() / 10; - - break; - } - - break; - default: - // just use Calibri as an approximation - $rowHeight = 15 * $font->getSize() / 11; - - break; + $name = $font->getName(); + $size = $font->getSize(); + if (isset(self::DEFAULT_COLUMN_WIDTHS[$name][$size])) { + $rowHeight = self::DEFAULT_COLUMN_WIDTHS[$name][$size]['height']; + } elseif ($name === 'Arial' || $name === 'Verdana') { + $rowHeight = self::DEFAULT_COLUMN_WIDTHS[$name][10]['height'] * $size / 10.0; + } else { + $rowHeight = self::DEFAULT_COLUMN_WIDTHS['Calibri'][11]['height'] * $size / 11.0; } return $rowHeight; diff --git a/PhpOffice/PhpSpreadsheet/Shared/IntOrFloat.php b/PhpOffice/PhpSpreadsheet/Shared/IntOrFloat.php new file mode 100644 index 0000000..060f09c --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Shared/IntOrFloat.php @@ -0,0 +1,21 @@ +L = $A->getArray(); - $this->m = $A->getRowDimension(); - - for ($i = 0; $i < $this->m; ++$i) { - for ($j = $i; $j < $this->m; ++$j) { - for ($sum = $this->L[$i][$j], $k = $i - 1; $k >= 0; --$k) { - $sum -= $this->L[$i][$k] * $this->L[$j][$k]; - } - if ($i == $j) { - if ($sum >= 0) { - $this->L[$i][$i] = sqrt($sum); - } else { - $this->isspd = false; - } - } else { - if ($this->L[$i][$i] != 0) { - $this->L[$j][$i] = $sum / $this->L[$i][$i]; - } - } - } - - for ($k = $i + 1; $k < $this->m; ++$k) { - $this->L[$i][$k] = 0.0; - } - } - } - - /** - * Is the matrix symmetric and positive definite? - * - * @return bool - */ - public function isSPD() - { - return $this->isspd; - } - - /** - * getL. - * - * Return triangular factor. - * - * @return Matrix Lower triangular matrix - */ - public function getL() - { - return new Matrix($this->L); - } - - /** - * Solve A*X = B. - * - * @param $B Row-equal matrix - * - * @return Matrix L * L' * X = B - */ - public function solve(Matrix $B) - { - if ($B->getRowDimension() == $this->m) { - if ($this->isspd) { - $X = $B->getArrayCopy(); - $nx = $B->getColumnDimension(); - - for ($k = 0; $k < $this->m; ++$k) { - for ($i = $k + 1; $i < $this->m; ++$i) { - for ($j = 0; $j < $nx; ++$j) { - $X[$i][$j] -= $X[$k][$j] * $this->L[$i][$k]; - } - } - for ($j = 0; $j < $nx; ++$j) { - $X[$k][$j] /= $this->L[$k][$k]; - } - } - - for ($k = $this->m - 1; $k >= 0; --$k) { - for ($j = 0; $j < $nx; ++$j) { - $X[$k][$j] /= $this->L[$k][$k]; - } - for ($i = 0; $i < $k; ++$i) { - for ($j = 0; $j < $nx; ++$j) { - $X[$i][$j] -= $X[$k][$j] * $this->L[$k][$i]; - } - } - } - - return new Matrix($X, $this->m, $nx); - } - - throw new CalculationException(Matrix::MATRIX_SPD_EXCEPTION); - } - - throw new CalculationException(Matrix::MATRIX_DIMENSION_EXCEPTION); - } -} diff --git a/PhpOffice/PhpSpreadsheet/Shared/JAMA/EigenvalueDecomposition.php b/PhpOffice/PhpSpreadsheet/Shared/JAMA/EigenvalueDecomposition.php deleted file mode 100755 index ba59e0e..0000000 --- a/PhpOffice/PhpSpreadsheet/Shared/JAMA/EigenvalueDecomposition.php +++ /dev/null @@ -1,861 +0,0 @@ -d = $this->V[$this->n - 1]; - // Householder reduction to tridiagonal form. - for ($i = $this->n - 1; $i > 0; --$i) { - $i_ = $i - 1; - // Scale to avoid under/overflow. - $h = $scale = 0.0; - $scale += array_sum(array_map('abs', $this->d)); - if ($scale == 0.0) { - $this->e[$i] = $this->d[$i_]; - $this->d = array_slice($this->V[$i_], 0, $i_); - for ($j = 0; $j < $i; ++$j) { - $this->V[$j][$i] = $this->V[$i][$j] = 0.0; - } - } else { - // Generate Householder vector. - for ($k = 0; $k < $i; ++$k) { - $this->d[$k] /= $scale; - $h += pow($this->d[$k], 2); - } - $f = $this->d[$i_]; - $g = sqrt($h); - if ($f > 0) { - $g = -$g; - } - $this->e[$i] = $scale * $g; - $h = $h - $f * $g; - $this->d[$i_] = $f - $g; - for ($j = 0; $j < $i; ++$j) { - $this->e[$j] = 0.0; - } - // Apply similarity transformation to remaining columns. - for ($j = 0; $j < $i; ++$j) { - $f = $this->d[$j]; - $this->V[$j][$i] = $f; - $g = $this->e[$j] + $this->V[$j][$j] * $f; - for ($k = $j + 1; $k <= $i_; ++$k) { - $g += $this->V[$k][$j] * $this->d[$k]; - $this->e[$k] += $this->V[$k][$j] * $f; - } - $this->e[$j] = $g; - } - $f = 0.0; - for ($j = 0; $j < $i; ++$j) { - $this->e[$j] /= $h; - $f += $this->e[$j] * $this->d[$j]; - } - $hh = $f / (2 * $h); - for ($j = 0; $j < $i; ++$j) { - $this->e[$j] -= $hh * $this->d[$j]; - } - for ($j = 0; $j < $i; ++$j) { - $f = $this->d[$j]; - $g = $this->e[$j]; - for ($k = $j; $k <= $i_; ++$k) { - $this->V[$k][$j] -= ($f * $this->e[$k] + $g * $this->d[$k]); - } - $this->d[$j] = $this->V[$i - 1][$j]; - $this->V[$i][$j] = 0.0; - } - } - $this->d[$i] = $h; - } - - // Accumulate transformations. - for ($i = 0; $i < $this->n - 1; ++$i) { - $this->V[$this->n - 1][$i] = $this->V[$i][$i]; - $this->V[$i][$i] = 1.0; - $h = $this->d[$i + 1]; - if ($h != 0.0) { - for ($k = 0; $k <= $i; ++$k) { - $this->d[$k] = $this->V[$k][$i + 1] / $h; - } - for ($j = 0; $j <= $i; ++$j) { - $g = 0.0; - for ($k = 0; $k <= $i; ++$k) { - $g += $this->V[$k][$i + 1] * $this->V[$k][$j]; - } - for ($k = 0; $k <= $i; ++$k) { - $this->V[$k][$j] -= $g * $this->d[$k]; - } - } - } - for ($k = 0; $k <= $i; ++$k) { - $this->V[$k][$i + 1] = 0.0; - } - } - - $this->d = $this->V[$this->n - 1]; - $this->V[$this->n - 1] = array_fill(0, $j, 0.0); - $this->V[$this->n - 1][$this->n - 1] = 1.0; - $this->e[0] = 0.0; - } - - /** - * Symmetric tridiagonal QL algorithm. - * - * This is derived from the Algol procedures tql2, by - * Bowdler, Martin, Reinsch, and Wilkinson, Handbook for - * Auto. Comp., Vol.ii-Linear Algebra, and the corresponding - * Fortran subroutine in EISPACK. - */ - private function tql2() - { - for ($i = 1; $i < $this->n; ++$i) { - $this->e[$i - 1] = $this->e[$i]; - } - $this->e[$this->n - 1] = 0.0; - $f = 0.0; - $tst1 = 0.0; - $eps = pow(2.0, -52.0); - - for ($l = 0; $l < $this->n; ++$l) { - // Find small subdiagonal element - $tst1 = max($tst1, abs($this->d[$l]) + abs($this->e[$l])); - $m = $l; - while ($m < $this->n) { - if (abs($this->e[$m]) <= $eps * $tst1) { - break; - } - ++$m; - } - // If m == l, $this->d[l] is an eigenvalue, - // otherwise, iterate. - if ($m > $l) { - $iter = 0; - do { - // Could check iteration count here. - $iter += 1; - // Compute implicit shift - $g = $this->d[$l]; - $p = ($this->d[$l + 1] - $g) / (2.0 * $this->e[$l]); - $r = hypo($p, 1.0); - if ($p < 0) { - $r *= -1; - } - $this->d[$l] = $this->e[$l] / ($p + $r); - $this->d[$l + 1] = $this->e[$l] * ($p + $r); - $dl1 = $this->d[$l + 1]; - $h = $g - $this->d[$l]; - for ($i = $l + 2; $i < $this->n; ++$i) { - $this->d[$i] -= $h; - } - $f += $h; - // Implicit QL transformation. - $p = $this->d[$m]; - $c = 1.0; - $c2 = $c3 = $c; - $el1 = $this->e[$l + 1]; - $s = $s2 = 0.0; - for ($i = $m - 1; $i >= $l; --$i) { - $c3 = $c2; - $c2 = $c; - $s2 = $s; - $g = $c * $this->e[$i]; - $h = $c * $p; - $r = hypo($p, $this->e[$i]); - $this->e[$i + 1] = $s * $r; - $s = $this->e[$i] / $r; - $c = $p / $r; - $p = $c * $this->d[$i] - $s * $g; - $this->d[$i + 1] = $h + $s * ($c * $g + $s * $this->d[$i]); - // Accumulate transformation. - for ($k = 0; $k < $this->n; ++$k) { - $h = $this->V[$k][$i + 1]; - $this->V[$k][$i + 1] = $s * $this->V[$k][$i] + $c * $h; - $this->V[$k][$i] = $c * $this->V[$k][$i] - $s * $h; - } - } - $p = -$s * $s2 * $c3 * $el1 * $this->e[$l] / $dl1; - $this->e[$l] = $s * $p; - $this->d[$l] = $c * $p; - // Check for convergence. - } while (abs($this->e[$l]) > $eps * $tst1); - } - $this->d[$l] = $this->d[$l] + $f; - $this->e[$l] = 0.0; - } - - // Sort eigenvalues and corresponding vectors. - for ($i = 0; $i < $this->n - 1; ++$i) { - $k = $i; - $p = $this->d[$i]; - for ($j = $i + 1; $j < $this->n; ++$j) { - if ($this->d[$j] < $p) { - $k = $j; - $p = $this->d[$j]; - } - } - if ($k != $i) { - $this->d[$k] = $this->d[$i]; - $this->d[$i] = $p; - for ($j = 0; $j < $this->n; ++$j) { - $p = $this->V[$j][$i]; - $this->V[$j][$i] = $this->V[$j][$k]; - $this->V[$j][$k] = $p; - } - } - } - } - - /** - * Nonsymmetric reduction to Hessenberg form. - * - * This is derived from the Algol procedures orthes and ortran, - * by Martin and Wilkinson, Handbook for Auto. Comp., - * Vol.ii-Linear Algebra, and the corresponding - * Fortran subroutines in EISPACK. - */ - private function orthes() - { - $low = 0; - $high = $this->n - 1; - - for ($m = $low + 1; $m <= $high - 1; ++$m) { - // Scale column. - $scale = 0.0; - for ($i = $m; $i <= $high; ++$i) { - $scale = $scale + abs($this->H[$i][$m - 1]); - } - if ($scale != 0.0) { - // Compute Householder transformation. - $h = 0.0; - for ($i = $high; $i >= $m; --$i) { - $this->ort[$i] = $this->H[$i][$m - 1] / $scale; - $h += $this->ort[$i] * $this->ort[$i]; - } - $g = sqrt($h); - if ($this->ort[$m] > 0) { - $g *= -1; - } - $h -= $this->ort[$m] * $g; - $this->ort[$m] -= $g; - // Apply Householder similarity transformation - // H = (I -u * u' / h) * H * (I -u * u') / h) - for ($j = $m; $j < $this->n; ++$j) { - $f = 0.0; - for ($i = $high; $i >= $m; --$i) { - $f += $this->ort[$i] * $this->H[$i][$j]; - } - $f /= $h; - for ($i = $m; $i <= $high; ++$i) { - $this->H[$i][$j] -= $f * $this->ort[$i]; - } - } - for ($i = 0; $i <= $high; ++$i) { - $f = 0.0; - for ($j = $high; $j >= $m; --$j) { - $f += $this->ort[$j] * $this->H[$i][$j]; - } - $f = $f / $h; - for ($j = $m; $j <= $high; ++$j) { - $this->H[$i][$j] -= $f * $this->ort[$j]; - } - } - $this->ort[$m] = $scale * $this->ort[$m]; - $this->H[$m][$m - 1] = $scale * $g; - } - } - - // Accumulate transformations (Algol's ortran). - for ($i = 0; $i < $this->n; ++$i) { - for ($j = 0; $j < $this->n; ++$j) { - $this->V[$i][$j] = ($i == $j ? 1.0 : 0.0); - } - } - for ($m = $high - 1; $m >= $low + 1; --$m) { - if ($this->H[$m][$m - 1] != 0.0) { - for ($i = $m + 1; $i <= $high; ++$i) { - $this->ort[$i] = $this->H[$i][$m - 1]; - } - for ($j = $m; $j <= $high; ++$j) { - $g = 0.0; - for ($i = $m; $i <= $high; ++$i) { - $g += $this->ort[$i] * $this->V[$i][$j]; - } - // Double division avoids possible underflow - $g = ($g / $this->ort[$m]) / $this->H[$m][$m - 1]; - for ($i = $m; $i <= $high; ++$i) { - $this->V[$i][$j] += $g * $this->ort[$i]; - } - } - } - } - } - - /** - * Performs complex division. - * - * @param mixed $xr - * @param mixed $xi - * @param mixed $yr - * @param mixed $yi - */ - private function cdiv($xr, $xi, $yr, $yi) - { - if (abs($yr) > abs($yi)) { - $r = $yi / $yr; - $d = $yr + $r * $yi; - $this->cdivr = ($xr + $r * $xi) / $d; - $this->cdivi = ($xi - $r * $xr) / $d; - } else { - $r = $yr / $yi; - $d = $yi + $r * $yr; - $this->cdivr = ($r * $xr + $xi) / $d; - $this->cdivi = ($r * $xi - $xr) / $d; - } - } - - /** - * Nonsymmetric reduction from Hessenberg to real Schur form. - * - * Code is derived from the Algol procedure hqr2, - * by Martin and Wilkinson, Handbook for Auto. Comp., - * Vol.ii-Linear Algebra, and the corresponding - * Fortran subroutine in EISPACK. - */ - private function hqr2() - { - // Initialize - $nn = $this->n; - $n = $nn - 1; - $low = 0; - $high = $nn - 1; - $eps = pow(2.0, -52.0); - $exshift = 0.0; - $p = $q = $r = $s = $z = 0; - // Store roots isolated by balanc and compute matrix norm - $norm = 0.0; - - for ($i = 0; $i < $nn; ++$i) { - if (($i < $low) or ($i > $high)) { - $this->d[$i] = $this->H[$i][$i]; - $this->e[$i] = 0.0; - } - for ($j = max($i - 1, 0); $j < $nn; ++$j) { - $norm = $norm + abs($this->H[$i][$j]); - } - } - - // Outer loop over eigenvalue index - $iter = 0; - while ($n >= $low) { - // Look for single small sub-diagonal element - $l = $n; - while ($l > $low) { - $s = abs($this->H[$l - 1][$l - 1]) + abs($this->H[$l][$l]); - if ($s == 0.0) { - $s = $norm; - } - if (abs($this->H[$l][$l - 1]) < $eps * $s) { - break; - } - --$l; - } - // Check for convergence - // One root found - if ($l == $n) { - $this->H[$n][$n] = $this->H[$n][$n] + $exshift; - $this->d[$n] = $this->H[$n][$n]; - $this->e[$n] = 0.0; - --$n; - $iter = 0; - // Two roots found - } elseif ($l == $n - 1) { - $w = $this->H[$n][$n - 1] * $this->H[$n - 1][$n]; - $p = ($this->H[$n - 1][$n - 1] - $this->H[$n][$n]) / 2.0; - $q = $p * $p + $w; - $z = sqrt(abs($q)); - $this->H[$n][$n] = $this->H[$n][$n] + $exshift; - $this->H[$n - 1][$n - 1] = $this->H[$n - 1][$n - 1] + $exshift; - $x = $this->H[$n][$n]; - // Real pair - if ($q >= 0) { - if ($p >= 0) { - $z = $p + $z; - } else { - $z = $p - $z; - } - $this->d[$n - 1] = $x + $z; - $this->d[$n] = $this->d[$n - 1]; - if ($z != 0.0) { - $this->d[$n] = $x - $w / $z; - } - $this->e[$n - 1] = 0.0; - $this->e[$n] = 0.0; - $x = $this->H[$n][$n - 1]; - $s = abs($x) + abs($z); - $p = $x / $s; - $q = $z / $s; - $r = sqrt($p * $p + $q * $q); - $p = $p / $r; - $q = $q / $r; - // Row modification - for ($j = $n - 1; $j < $nn; ++$j) { - $z = $this->H[$n - 1][$j]; - $this->H[$n - 1][$j] = $q * $z + $p * $this->H[$n][$j]; - $this->H[$n][$j] = $q * $this->H[$n][$j] - $p * $z; - } - // Column modification - for ($i = 0; $i <= $n; ++$i) { - $z = $this->H[$i][$n - 1]; - $this->H[$i][$n - 1] = $q * $z + $p * $this->H[$i][$n]; - $this->H[$i][$n] = $q * $this->H[$i][$n] - $p * $z; - } - // Accumulate transformations - for ($i = $low; $i <= $high; ++$i) { - $z = $this->V[$i][$n - 1]; - $this->V[$i][$n - 1] = $q * $z + $p * $this->V[$i][$n]; - $this->V[$i][$n] = $q * $this->V[$i][$n] - $p * $z; - } - // Complex pair - } else { - $this->d[$n - 1] = $x + $p; - $this->d[$n] = $x + $p; - $this->e[$n - 1] = $z; - $this->e[$n] = -$z; - } - $n = $n - 2; - $iter = 0; - // No convergence yet - } else { - // Form shift - $x = $this->H[$n][$n]; - $y = 0.0; - $w = 0.0; - if ($l < $n) { - $y = $this->H[$n - 1][$n - 1]; - $w = $this->H[$n][$n - 1] * $this->H[$n - 1][$n]; - } - // Wilkinson's original ad hoc shift - if ($iter == 10) { - $exshift += $x; - for ($i = $low; $i <= $n; ++$i) { - $this->H[$i][$i] -= $x; - } - $s = abs($this->H[$n][$n - 1]) + abs($this->H[$n - 1][$n - 2]); - $x = $y = 0.75 * $s; - $w = -0.4375 * $s * $s; - } - // MATLAB's new ad hoc shift - if ($iter == 30) { - $s = ($y - $x) / 2.0; - $s = $s * $s + $w; - if ($s > 0) { - $s = sqrt($s); - if ($y < $x) { - $s = -$s; - } - $s = $x - $w / (($y - $x) / 2.0 + $s); - for ($i = $low; $i <= $n; ++$i) { - $this->H[$i][$i] -= $s; - } - $exshift += $s; - $x = $y = $w = 0.964; - } - } - // Could check iteration count here. - $iter = $iter + 1; - // Look for two consecutive small sub-diagonal elements - $m = $n - 2; - while ($m >= $l) { - $z = $this->H[$m][$m]; - $r = $x - $z; - $s = $y - $z; - $p = ($r * $s - $w) / $this->H[$m + 1][$m] + $this->H[$m][$m + 1]; - $q = $this->H[$m + 1][$m + 1] - $z - $r - $s; - $r = $this->H[$m + 2][$m + 1]; - $s = abs($p) + abs($q) + abs($r); - $p = $p / $s; - $q = $q / $s; - $r = $r / $s; - if ($m == $l) { - break; - } - if (abs($this->H[$m][$m - 1]) * (abs($q) + abs($r)) < - $eps * (abs($p) * (abs($this->H[$m - 1][$m - 1]) + abs($z) + abs($this->H[$m + 1][$m + 1])))) { - break; - } - --$m; - } - for ($i = $m + 2; $i <= $n; ++$i) { - $this->H[$i][$i - 2] = 0.0; - if ($i > $m + 2) { - $this->H[$i][$i - 3] = 0.0; - } - } - // Double QR step involving rows l:n and columns m:n - for ($k = $m; $k <= $n - 1; ++$k) { - $notlast = ($k != $n - 1); - if ($k != $m) { - $p = $this->H[$k][$k - 1]; - $q = $this->H[$k + 1][$k - 1]; - $r = ($notlast ? $this->H[$k + 2][$k - 1] : 0.0); - $x = abs($p) + abs($q) + abs($r); - if ($x != 0.0) { - $p = $p / $x; - $q = $q / $x; - $r = $r / $x; - } - } - if ($x == 0.0) { - break; - } - $s = sqrt($p * $p + $q * $q + $r * $r); - if ($p < 0) { - $s = -$s; - } - if ($s != 0) { - if ($k != $m) { - $this->H[$k][$k - 1] = -$s * $x; - } elseif ($l != $m) { - $this->H[$k][$k - 1] = -$this->H[$k][$k - 1]; - } - $p = $p + $s; - $x = $p / $s; - $y = $q / $s; - $z = $r / $s; - $q = $q / $p; - $r = $r / $p; - // Row modification - for ($j = $k; $j < $nn; ++$j) { - $p = $this->H[$k][$j] + $q * $this->H[$k + 1][$j]; - if ($notlast) { - $p = $p + $r * $this->H[$k + 2][$j]; - $this->H[$k + 2][$j] = $this->H[$k + 2][$j] - $p * $z; - } - $this->H[$k][$j] = $this->H[$k][$j] - $p * $x; - $this->H[$k + 1][$j] = $this->H[$k + 1][$j] - $p * $y; - } - // Column modification - $iMax = min($n, $k + 3); - for ($i = 0; $i <= $iMax; ++$i) { - $p = $x * $this->H[$i][$k] + $y * $this->H[$i][$k + 1]; - if ($notlast) { - $p = $p + $z * $this->H[$i][$k + 2]; - $this->H[$i][$k + 2] = $this->H[$i][$k + 2] - $p * $r; - } - $this->H[$i][$k] = $this->H[$i][$k] - $p; - $this->H[$i][$k + 1] = $this->H[$i][$k + 1] - $p * $q; - } - // Accumulate transformations - for ($i = $low; $i <= $high; ++$i) { - $p = $x * $this->V[$i][$k] + $y * $this->V[$i][$k + 1]; - if ($notlast) { - $p = $p + $z * $this->V[$i][$k + 2]; - $this->V[$i][$k + 2] = $this->V[$i][$k + 2] - $p * $r; - } - $this->V[$i][$k] = $this->V[$i][$k] - $p; - $this->V[$i][$k + 1] = $this->V[$i][$k + 1] - $p * $q; - } - } // ($s != 0) - } // k loop - } // check convergence - } // while ($n >= $low) - - // Backsubstitute to find vectors of upper triangular form - if ($norm == 0.0) { - return; - } - - for ($n = $nn - 1; $n >= 0; --$n) { - $p = $this->d[$n]; - $q = $this->e[$n]; - // Real vector - if ($q == 0) { - $l = $n; - $this->H[$n][$n] = 1.0; - for ($i = $n - 1; $i >= 0; --$i) { - $w = $this->H[$i][$i] - $p; - $r = 0.0; - for ($j = $l; $j <= $n; ++$j) { - $r = $r + $this->H[$i][$j] * $this->H[$j][$n]; - } - if ($this->e[$i] < 0.0) { - $z = $w; - $s = $r; - } else { - $l = $i; - if ($this->e[$i] == 0.0) { - if ($w != 0.0) { - $this->H[$i][$n] = -$r / $w; - } else { - $this->H[$i][$n] = -$r / ($eps * $norm); - } - // Solve real equations - } else { - $x = $this->H[$i][$i + 1]; - $y = $this->H[$i + 1][$i]; - $q = ($this->d[$i] - $p) * ($this->d[$i] - $p) + $this->e[$i] * $this->e[$i]; - $t = ($x * $s - $z * $r) / $q; - $this->H[$i][$n] = $t; - if (abs($x) > abs($z)) { - $this->H[$i + 1][$n] = (-$r - $w * $t) / $x; - } else { - $this->H[$i + 1][$n] = (-$s - $y * $t) / $z; - } - } - // Overflow control - $t = abs($this->H[$i][$n]); - if (($eps * $t) * $t > 1) { - for ($j = $i; $j <= $n; ++$j) { - $this->H[$j][$n] = $this->H[$j][$n] / $t; - } - } - } - } - // Complex vector - } elseif ($q < 0) { - $l = $n - 1; - // Last vector component imaginary so matrix is triangular - if (abs($this->H[$n][$n - 1]) > abs($this->H[$n - 1][$n])) { - $this->H[$n - 1][$n - 1] = $q / $this->H[$n][$n - 1]; - $this->H[$n - 1][$n] = -($this->H[$n][$n] - $p) / $this->H[$n][$n - 1]; - } else { - $this->cdiv(0.0, -$this->H[$n - 1][$n], $this->H[$n - 1][$n - 1] - $p, $q); - $this->H[$n - 1][$n - 1] = $this->cdivr; - $this->H[$n - 1][$n] = $this->cdivi; - } - $this->H[$n][$n - 1] = 0.0; - $this->H[$n][$n] = 1.0; - for ($i = $n - 2; $i >= 0; --$i) { - // double ra,sa,vr,vi; - $ra = 0.0; - $sa = 0.0; - for ($j = $l; $j <= $n; ++$j) { - $ra = $ra + $this->H[$i][$j] * $this->H[$j][$n - 1]; - $sa = $sa + $this->H[$i][$j] * $this->H[$j][$n]; - } - $w = $this->H[$i][$i] - $p; - if ($this->e[$i] < 0.0) { - $z = $w; - $r = $ra; - $s = $sa; - } else { - $l = $i; - if ($this->e[$i] == 0) { - $this->cdiv(-$ra, -$sa, $w, $q); - $this->H[$i][$n - 1] = $this->cdivr; - $this->H[$i][$n] = $this->cdivi; - } else { - // Solve complex equations - $x = $this->H[$i][$i + 1]; - $y = $this->H[$i + 1][$i]; - $vr = ($this->d[$i] - $p) * ($this->d[$i] - $p) + $this->e[$i] * $this->e[$i] - $q * $q; - $vi = ($this->d[$i] - $p) * 2.0 * $q; - if ($vr == 0.0 & $vi == 0.0) { - $vr = $eps * $norm * (abs($w) + abs($q) + abs($x) + abs($y) + abs($z)); - } - $this->cdiv($x * $r - $z * $ra + $q * $sa, $x * $s - $z * $sa - $q * $ra, $vr, $vi); - $this->H[$i][$n - 1] = $this->cdivr; - $this->H[$i][$n] = $this->cdivi; - if (abs($x) > (abs($z) + abs($q))) { - $this->H[$i + 1][$n - 1] = (-$ra - $w * $this->H[$i][$n - 1] + $q * $this->H[$i][$n]) / $x; - $this->H[$i + 1][$n] = (-$sa - $w * $this->H[$i][$n] - $q * $this->H[$i][$n - 1]) / $x; - } else { - $this->cdiv(-$r - $y * $this->H[$i][$n - 1], -$s - $y * $this->H[$i][$n], $z, $q); - $this->H[$i + 1][$n - 1] = $this->cdivr; - $this->H[$i + 1][$n] = $this->cdivi; - } - } - // Overflow control - $t = max(abs($this->H[$i][$n - 1]), abs($this->H[$i][$n])); - if (($eps * $t) * $t > 1) { - for ($j = $i; $j <= $n; ++$j) { - $this->H[$j][$n - 1] = $this->H[$j][$n - 1] / $t; - $this->H[$j][$n] = $this->H[$j][$n] / $t; - } - } - } // end else - } // end for - } // end else for complex case - } // end for - - // Vectors of isolated roots - for ($i = 0; $i < $nn; ++$i) { - if ($i < $low | $i > $high) { - for ($j = $i; $j < $nn; ++$j) { - $this->V[$i][$j] = $this->H[$i][$j]; - } - } - } - - // Back transformation to get eigenvectors of original matrix - for ($j = $nn - 1; $j >= $low; --$j) { - for ($i = $low; $i <= $high; ++$i) { - $z = 0.0; - $kMax = min($j, $high); - for ($k = $low; $k <= $kMax; ++$k) { - $z = $z + $this->V[$i][$k] * $this->H[$k][$j]; - } - $this->V[$i][$j] = $z; - } - } - } - - // end hqr2 - - /** - * Constructor: Check for symmetry, then construct the eigenvalue decomposition. - * - * @param mixed $Arg A Square matrix - */ - public function __construct($Arg) - { - $this->A = $Arg->getArray(); - $this->n = $Arg->getColumnDimension(); - - $issymmetric = true; - for ($j = 0; ($j < $this->n) & $issymmetric; ++$j) { - for ($i = 0; ($i < $this->n) & $issymmetric; ++$i) { - $issymmetric = ($this->A[$i][$j] == $this->A[$j][$i]); - } - } - - if ($issymmetric) { - $this->V = $this->A; - // Tridiagonalize. - $this->tred2(); - // Diagonalize. - $this->tql2(); - } else { - $this->H = $this->A; - $this->ort = []; - // Reduce to Hessenberg form. - $this->orthes(); - // Reduce Hessenberg to real Schur form. - $this->hqr2(); - } - } - - /** - * Return the eigenvector matrix. - * - * @return Matrix V - */ - public function getV() - { - return new Matrix($this->V, $this->n, $this->n); - } - - /** - * Return the real parts of the eigenvalues. - * - * @return array real(diag(D)) - */ - public function getRealEigenvalues() - { - return $this->d; - } - - /** - * Return the imaginary parts of the eigenvalues. - * - * @return array imag(diag(D)) - */ - public function getImagEigenvalues() - { - return $this->e; - } - - /** - * Return the block diagonal eigenvalue matrix. - * - * @return Matrix D - */ - public function getD() - { - for ($i = 0; $i < $this->n; ++$i) { - $D[$i] = array_fill(0, $this->n, 0.0); - $D[$i][$i] = $this->d[$i]; - if ($this->e[$i] == 0) { - continue; - } - $o = ($this->e[$i] > 0) ? $i + 1 : $i - 1; - $D[$i][$o] = $this->e[$i]; - } - - return new Matrix($D); - } -} diff --git a/PhpOffice/PhpSpreadsheet/Shared/JAMA/LUDecomposition.php b/PhpOffice/PhpSpreadsheet/Shared/JAMA/LUDecomposition.php deleted file mode 100755 index bb2b4b0..0000000 --- a/PhpOffice/PhpSpreadsheet/Shared/JAMA/LUDecomposition.php +++ /dev/null @@ -1,285 +0,0 @@ -= n, the LU decomposition is an m-by-n - * unit lower triangular matrix L, an n-by-n upper triangular matrix U, - * and a permutation vector piv of length m so that A(piv,:) = L*U. - * If m < n, then L is m-by-m and U is m-by-n. - * - * The LU decompostion with pivoting always exists, even if the matrix is - * singular, so the constructor will never fail. The primary use of the - * LU decomposition is in the solution of square systems of simultaneous - * linear equations. This will fail if isNonsingular() returns false. - * - * @author Paul Meagher - * @author Bartosz Matosiuk - * @author Michael Bommarito - * - * @version 1.1 - */ -class LUDecomposition -{ - const MATRIX_SINGULAR_EXCEPTION = 'Can only perform operation on singular matrix.'; - const MATRIX_SQUARE_EXCEPTION = 'Mismatched Row dimension'; - - /** - * Decomposition storage. - * - * @var array - */ - private $LU = []; - - /** - * Row dimension. - * - * @var int - */ - private $m; - - /** - * Column dimension. - * - * @var int - */ - private $n; - - /** - * Pivot sign. - * - * @var int - */ - private $pivsign; - - /** - * Internal storage of pivot vector. - * - * @var array - */ - private $piv = []; - - /** - * LU Decomposition constructor. - * - * @param Matrix $A Rectangular matrix - */ - public function __construct($A) - { - if ($A instanceof Matrix) { - // Use a "left-looking", dot-product, Crout/Doolittle algorithm. - $this->LU = $A->getArray(); - $this->m = $A->getRowDimension(); - $this->n = $A->getColumnDimension(); - for ($i = 0; $i < $this->m; ++$i) { - $this->piv[$i] = $i; - } - $this->pivsign = 1; - $LUrowi = $LUcolj = []; - - // Outer loop. - for ($j = 0; $j < $this->n; ++$j) { - // Make a copy of the j-th column to localize references. - for ($i = 0; $i < $this->m; ++$i) { - $LUcolj[$i] = &$this->LU[$i][$j]; - } - // Apply previous transformations. - for ($i = 0; $i < $this->m; ++$i) { - $LUrowi = $this->LU[$i]; - // Most of the time is spent in the following dot product. - $kmax = min($i, $j); - $s = 0.0; - for ($k = 0; $k < $kmax; ++$k) { - $s += $LUrowi[$k] * $LUcolj[$k]; - } - $LUrowi[$j] = $LUcolj[$i] -= $s; - } - // Find pivot and exchange if necessary. - $p = $j; - for ($i = $j + 1; $i < $this->m; ++$i) { - if (abs($LUcolj[$i]) > abs($LUcolj[$p])) { - $p = $i; - } - } - if ($p != $j) { - for ($k = 0; $k < $this->n; ++$k) { - $t = $this->LU[$p][$k]; - $this->LU[$p][$k] = $this->LU[$j][$k]; - $this->LU[$j][$k] = $t; - } - $k = $this->piv[$p]; - $this->piv[$p] = $this->piv[$j]; - $this->piv[$j] = $k; - $this->pivsign = $this->pivsign * -1; - } - // Compute multipliers. - if (($j < $this->m) && ($this->LU[$j][$j] != 0.0)) { - for ($i = $j + 1; $i < $this->m; ++$i) { - $this->LU[$i][$j] /= $this->LU[$j][$j]; - } - } - } - } else { - throw new CalculationException(Matrix::ARGUMENT_TYPE_EXCEPTION); - } - } - - // function __construct() - - /** - * Get lower triangular factor. - * - * @return Matrix Lower triangular factor - */ - public function getL() - { - for ($i = 0; $i < $this->m; ++$i) { - for ($j = 0; $j < $this->n; ++$j) { - if ($i > $j) { - $L[$i][$j] = $this->LU[$i][$j]; - } elseif ($i == $j) { - $L[$i][$j] = 1.0; - } else { - $L[$i][$j] = 0.0; - } - } - } - - return new Matrix($L); - } - - // function getL() - - /** - * Get upper triangular factor. - * - * @return Matrix Upper triangular factor - */ - public function getU() - { - for ($i = 0; $i < $this->n; ++$i) { - for ($j = 0; $j < $this->n; ++$j) { - if ($i <= $j) { - $U[$i][$j] = $this->LU[$i][$j]; - } else { - $U[$i][$j] = 0.0; - } - } - } - - return new Matrix($U); - } - - // function getU() - - /** - * Return pivot permutation vector. - * - * @return array Pivot vector - */ - public function getPivot() - { - return $this->piv; - } - - // function getPivot() - - /** - * Alias for getPivot. - * - * @see getPivot - */ - public function getDoublePivot() - { - return $this->getPivot(); - } - - // function getDoublePivot() - - /** - * Is the matrix nonsingular? - * - * @return bool true if U, and hence A, is nonsingular - */ - public function isNonsingular() - { - for ($j = 0; $j < $this->n; ++$j) { - if ($this->LU[$j][$j] == 0) { - return false; - } - } - - return true; - } - - // function isNonsingular() - - /** - * Count determinants. - * - * @return array d matrix deterninat - */ - public function det() - { - if ($this->m == $this->n) { - $d = $this->pivsign; - for ($j = 0; $j < $this->n; ++$j) { - $d *= $this->LU[$j][$j]; - } - - return $d; - } - - throw new CalculationException(Matrix::MATRIX_DIMENSION_EXCEPTION); - } - - // function det() - - /** - * Solve A*X = B. - * - * @param mixed $B a Matrix with as many rows as A and any number of columns - * - * @throws CalculationException illegalArgumentException Matrix row dimensions must agree - * @throws CalculationException runtimeException Matrix is singular - * - * @return Matrix X so that L*U*X = B(piv,:) - */ - public function solve($B) - { - if ($B->getRowDimension() == $this->m) { - if ($this->isNonsingular()) { - // Copy right hand side with pivoting - $nx = $B->getColumnDimension(); - $X = $B->getMatrix($this->piv, 0, $nx - 1); - // Solve L*Y = B(piv,:) - for ($k = 0; $k < $this->n; ++$k) { - for ($i = $k + 1; $i < $this->n; ++$i) { - for ($j = 0; $j < $nx; ++$j) { - $X->A[$i][$j] -= $X->A[$k][$j] * $this->LU[$i][$k]; - } - } - } - // Solve U*X = Y; - for ($k = $this->n - 1; $k >= 0; --$k) { - for ($j = 0; $j < $nx; ++$j) { - $X->A[$k][$j] /= $this->LU[$k][$k]; - } - for ($i = 0; $i < $k; ++$i) { - for ($j = 0; $j < $nx; ++$j) { - $X->A[$i][$j] -= $X->A[$k][$j] * $this->LU[$i][$k]; - } - } - } - - return $X; - } - - throw new CalculationException(self::MATRIX_SINGULAR_EXCEPTION); - } - - throw new CalculationException(self::MATRIX_SQUARE_EXCEPTION); - } -} diff --git a/PhpOffice/PhpSpreadsheet/Shared/JAMA/Matrix.php b/PhpOffice/PhpSpreadsheet/Shared/JAMA/Matrix.php deleted file mode 100755 index 17f3210..0000000 --- a/PhpOffice/PhpSpreadsheet/Shared/JAMA/Matrix.php +++ /dev/null @@ -1,1233 +0,0 @@ - 0) { - $match = implode(',', array_map('gettype', $args)); - - switch ($match) { - //Rectangular matrix - m x n initialized from 2D array - case 'array': - $this->m = count($args[0]); - $this->n = count($args[0][0]); - $this->A = $args[0]; - - break; - //Square matrix - n x n - case 'integer': - $this->m = $args[0]; - $this->n = $args[0]; - $this->A = array_fill(0, $this->m, array_fill(0, $this->n, 0)); - - break; - //Rectangular matrix - m x n - case 'integer,integer': - $this->m = $args[0]; - $this->n = $args[1]; - $this->A = array_fill(0, $this->m, array_fill(0, $this->n, 0)); - - break; - //Rectangular matrix - m x n initialized from packed array - case 'array,integer': - $this->m = $args[1]; - if ($this->m != 0) { - $this->n = count($args[0]) / $this->m; - } else { - $this->n = 0; - } - if (($this->m * $this->n) == count($args[0])) { - for ($i = 0; $i < $this->m; ++$i) { - for ($j = 0; $j < $this->n; ++$j) { - $this->A[$i][$j] = $args[0][$i + $j * $this->m]; - } - } - } else { - throw new CalculationException(self::ARRAY_LENGTH_EXCEPTION); - } - - break; - default: - throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); - - break; - } - } else { - throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); - } - } - - /** - * getArray. - * - * @return array Matrix array - */ - public function getArray() - { - return $this->A; - } - - /** - * getRowDimension. - * - * @return int Row dimension - */ - public function getRowDimension() - { - return $this->m; - } - - /** - * getColumnDimension. - * - * @return int Column dimension - */ - public function getColumnDimension() - { - return $this->n; - } - - /** - * get. - * - * Get the i,j-th element of the matrix. - * - * @param int $i Row position - * @param int $j Column position - * - * @return mixed Element (int/float/double) - */ - public function get($i = null, $j = null) - { - return $this->A[$i][$j]; - } - - /** - * getMatrix. - * - * Get a submatrix - * - * @param int $i0 Initial row index - * @param int $iF Final row index - * @param int $j0 Initial column index - * @param int $jF Final column index - * - * @return Matrix Submatrix - */ - public function getMatrix(...$args) - { - if (count($args) > 0) { - $match = implode(',', array_map('gettype', $args)); - - switch ($match) { - //A($i0...; $j0...) - case 'integer,integer': - [$i0, $j0] = $args; - if ($i0 >= 0) { - $m = $this->m - $i0; - } else { - throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION); - } - if ($j0 >= 0) { - $n = $this->n - $j0; - } else { - throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION); - } - $R = new self($m, $n); - for ($i = $i0; $i < $this->m; ++$i) { - for ($j = $j0; $j < $this->n; ++$j) { - $R->set($i, $j, $this->A[$i][$j]); - } - } - - return $R; - - break; - //A($i0...$iF; $j0...$jF) - case 'integer,integer,integer,integer': - [$i0, $iF, $j0, $jF] = $args; - if (($iF > $i0) && ($this->m >= $iF) && ($i0 >= 0)) { - $m = $iF - $i0; - } else { - throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION); - } - if (($jF > $j0) && ($this->n >= $jF) && ($j0 >= 0)) { - $n = $jF - $j0; - } else { - throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION); - } - $R = new self($m + 1, $n + 1); - for ($i = $i0; $i <= $iF; ++$i) { - for ($j = $j0; $j <= $jF; ++$j) { - $R->set($i - $i0, $j - $j0, $this->A[$i][$j]); - } - } - - return $R; - - break; - //$R = array of row indices; $C = array of column indices - case 'array,array': - [$RL, $CL] = $args; - if (count($RL) > 0) { - $m = count($RL); - } else { - throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION); - } - if (count($CL) > 0) { - $n = count($CL); - } else { - throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION); - } - $R = new self($m, $n); - for ($i = 0; $i < $m; ++$i) { - for ($j = 0; $j < $n; ++$j) { - $R->set($i, $j, $this->A[$RL[$i]][$CL[$j]]); - } - } - - return $R; - - break; - //A($i0...$iF); $CL = array of column indices - case 'integer,integer,array': - [$i0, $iF, $CL] = $args; - if (($iF > $i0) && ($this->m >= $iF) && ($i0 >= 0)) { - $m = $iF - $i0; - } else { - throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION); - } - if (count($CL) > 0) { - $n = count($CL); - } else { - throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION); - } - $R = new self($m, $n); - for ($i = $i0; $i < $iF; ++$i) { - for ($j = 0; $j < $n; ++$j) { - $R->set($i - $i0, $j, $this->A[$i][$CL[$j]]); - } - } - - return $R; - - break; - //$RL = array of row indices - case 'array,integer,integer': - [$RL, $j0, $jF] = $args; - if (count($RL) > 0) { - $m = count($RL); - } else { - throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION); - } - if (($jF >= $j0) && ($this->n >= $jF) && ($j0 >= 0)) { - $n = $jF - $j0; - } else { - throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION); - } - $R = new self($m, $n + 1); - for ($i = 0; $i < $m; ++$i) { - for ($j = $j0; $j <= $jF; ++$j) { - $R->set($i, $j - $j0, $this->A[$RL[$i]][$j]); - } - } - - return $R; - - break; - default: - throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); - - break; - } - } else { - throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); - } - } - - /** - * checkMatrixDimensions. - * - * Is matrix B the same size? - * - * @param Matrix $B Matrix B - * - * @return bool - */ - public function checkMatrixDimensions($B = null) - { - if ($B instanceof self) { - if (($this->m == $B->getRowDimension()) && ($this->n == $B->getColumnDimension())) { - return true; - } - - throw new CalculationException(self::MATRIX_DIMENSION_EXCEPTION); - } - - throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); - } - - // function checkMatrixDimensions() - - /** - * set. - * - * Set the i,j-th element of the matrix. - * - * @param int $i Row position - * @param int $j Column position - * @param mixed $c Int/float/double value - * - * @return mixed Element (int/float/double) - */ - public function set($i = null, $j = null, $c = null) - { - // Optimized set version just has this - $this->A[$i][$j] = $c; - } - - // function set() - - /** - * identity. - * - * Generate an identity matrix. - * - * @param int $m Row dimension - * @param int $n Column dimension - * - * @return Matrix Identity matrix - */ - public function identity($m = null, $n = null) - { - return $this->diagonal($m, $n, 1); - } - - /** - * diagonal. - * - * Generate a diagonal matrix - * - * @param int $m Row dimension - * @param int $n Column dimension - * @param mixed $c Diagonal value - * - * @return Matrix Diagonal matrix - */ - public function diagonal($m = null, $n = null, $c = 1) - { - $R = new self($m, $n); - for ($i = 0; $i < $m; ++$i) { - $R->set($i, $i, $c); - } - - return $R; - } - - /** - * getMatrixByRow. - * - * Get a submatrix by row index/range - * - * @param int $i0 Initial row index - * @param int $iF Final row index - * - * @return Matrix Submatrix - */ - public function getMatrixByRow($i0 = null, $iF = null) - { - if (is_int($i0)) { - if (is_int($iF)) { - return $this->getMatrix($i0, 0, $iF + 1, $this->n); - } - - return $this->getMatrix($i0, 0, $i0 + 1, $this->n); - } - - throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); - } - - /** - * getMatrixByCol. - * - * Get a submatrix by column index/range - * - * @param int $j0 Initial column index - * @param int $jF Final column index - * - * @return Matrix Submatrix - */ - public function getMatrixByCol($j0 = null, $jF = null) - { - if (is_int($j0)) { - if (is_int($jF)) { - return $this->getMatrix(0, $j0, $this->m, $jF + 1); - } - - return $this->getMatrix(0, $j0, $this->m, $j0 + 1); - } - - throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); - } - - /** - * transpose. - * - * Tranpose matrix - * - * @return Matrix Transposed matrix - */ - public function transpose() - { - $R = new self($this->n, $this->m); - for ($i = 0; $i < $this->m; ++$i) { - for ($j = 0; $j < $this->n; ++$j) { - $R->set($j, $i, $this->A[$i][$j]); - } - } - - return $R; - } - - // function transpose() - - /** - * trace. - * - * Sum of diagonal elements - * - * @return float Sum of diagonal elements - */ - public function trace() - { - $s = 0; - $n = min($this->m, $this->n); - for ($i = 0; $i < $n; ++$i) { - $s += $this->A[$i][$i]; - } - - return $s; - } - - /** - * uminus. - * - * Unary minus matrix -A - * - * @return Matrix Unary minus matrix - */ - public function uminus() - { - } - - /** - * plus. - * - * A + B - * - * @param mixed $B Matrix/Array - * - * @return Matrix Sum - */ - public function plus(...$args) - { - if (count($args) > 0) { - $match = implode(',', array_map('gettype', $args)); - - switch ($match) { - case 'object': - if ($args[0] instanceof self) { - $M = $args[0]; - } else { - throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); - } - - break; - case 'array': - $M = new self($args[0]); - - break; - default: - throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); - - break; - } - $this->checkMatrixDimensions($M); - for ($i = 0; $i < $this->m; ++$i) { - for ($j = 0; $j < $this->n; ++$j) { - $M->set($i, $j, $M->get($i, $j) + $this->A[$i][$j]); - } - } - - return $M; - } - - throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); - } - - /** - * plusEquals. - * - * A = A + B - * - * @param mixed $B Matrix/Array - * - * @return Matrix Sum - */ - public function plusEquals(...$args) - { - if (count($args) > 0) { - $match = implode(',', array_map('gettype', $args)); - - switch ($match) { - case 'object': - if ($args[0] instanceof self) { - $M = $args[0]; - } else { - throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); - } - - break; - case 'array': - $M = new self($args[0]); - - break; - default: - throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); - - break; - } - $this->checkMatrixDimensions($M); - for ($i = 0; $i < $this->m; ++$i) { - for ($j = 0; $j < $this->n; ++$j) { - $validValues = true; - $value = $M->get($i, $j); - if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) { - $this->A[$i][$j] = trim($this->A[$i][$j], '"'); - $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]); - } - if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) { - $value = trim($value, '"'); - $validValues &= StringHelper::convertToNumberIfFraction($value); - } - if ($validValues) { - $this->A[$i][$j] += $value; - } else { - $this->A[$i][$j] = Functions::NAN(); - } - } - } - - return $this; - } - - throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); - } - - /** - * minus. - * - * A - B - * - * @param mixed $B Matrix/Array - * - * @return Matrix Sum - */ - public function minus(...$args) - { - if (count($args) > 0) { - $match = implode(',', array_map('gettype', $args)); - - switch ($match) { - case 'object': - if ($args[0] instanceof self) { - $M = $args[0]; - } else { - throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); - } - - break; - case 'array': - $M = new self($args[0]); - - break; - default: - throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); - - break; - } - $this->checkMatrixDimensions($M); - for ($i = 0; $i < $this->m; ++$i) { - for ($j = 0; $j < $this->n; ++$j) { - $M->set($i, $j, $M->get($i, $j) - $this->A[$i][$j]); - } - } - - return $M; - } - - throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); - } - - /** - * minusEquals. - * - * A = A - B - * - * @param mixed $B Matrix/Array - * - * @return Matrix Sum - */ - public function minusEquals(...$args) - { - if (count($args) > 0) { - $match = implode(',', array_map('gettype', $args)); - - switch ($match) { - case 'object': - if ($args[0] instanceof self) { - $M = $args[0]; - } else { - throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); - } - - break; - case 'array': - $M = new self($args[0]); - - break; - default: - throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); - - break; - } - $this->checkMatrixDimensions($M); - for ($i = 0; $i < $this->m; ++$i) { - for ($j = 0; $j < $this->n; ++$j) { - $validValues = true; - $value = $M->get($i, $j); - if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) { - $this->A[$i][$j] = trim($this->A[$i][$j], '"'); - $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]); - } - if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) { - $value = trim($value, '"'); - $validValues &= StringHelper::convertToNumberIfFraction($value); - } - if ($validValues) { - $this->A[$i][$j] -= $value; - } else { - $this->A[$i][$j] = Functions::NAN(); - } - } - } - - return $this; - } - - throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); - } - - /** - * arrayTimes. - * - * Element-by-element multiplication - * Cij = Aij * Bij - * - * @param mixed $B Matrix/Array - * - * @return Matrix Matrix Cij - */ - public function arrayTimes(...$args) - { - if (count($args) > 0) { - $match = implode(',', array_map('gettype', $args)); - - switch ($match) { - case 'object': - if ($args[0] instanceof self) { - $M = $args[0]; - } else { - throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); - } - - break; - case 'array': - $M = new self($args[0]); - - break; - default: - throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); - - break; - } - $this->checkMatrixDimensions($M); - for ($i = 0; $i < $this->m; ++$i) { - for ($j = 0; $j < $this->n; ++$j) { - $M->set($i, $j, $M->get($i, $j) * $this->A[$i][$j]); - } - } - - return $M; - } - - throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); - } - - /** - * arrayTimesEquals. - * - * Element-by-element multiplication - * Aij = Aij * Bij - * - * @param mixed $B Matrix/Array - * - * @return Matrix Matrix Aij - */ - public function arrayTimesEquals(...$args) - { - if (count($args) > 0) { - $match = implode(',', array_map('gettype', $args)); - - switch ($match) { - case 'object': - if ($args[0] instanceof self) { - $M = $args[0]; - } else { - throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); - } - - break; - case 'array': - $M = new self($args[0]); - - break; - default: - throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); - - break; - } - $this->checkMatrixDimensions($M); - for ($i = 0; $i < $this->m; ++$i) { - for ($j = 0; $j < $this->n; ++$j) { - $validValues = true; - $value = $M->get($i, $j); - if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) { - $this->A[$i][$j] = trim($this->A[$i][$j], '"'); - $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]); - } - if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) { - $value = trim($value, '"'); - $validValues &= StringHelper::convertToNumberIfFraction($value); - } - if ($validValues) { - $this->A[$i][$j] *= $value; - } else { - $this->A[$i][$j] = Functions::NAN(); - } - } - } - - return $this; - } - - throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); - } - - /** - * arrayRightDivide. - * - * Element-by-element right division - * A / B - * - * @param Matrix $B Matrix B - * - * @return Matrix Division result - */ - public function arrayRightDivide(...$args) - { - if (count($args) > 0) { - $match = implode(',', array_map('gettype', $args)); - - switch ($match) { - case 'object': - if ($args[0] instanceof self) { - $M = $args[0]; - } else { - throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); - } - - break; - case 'array': - $M = new self($args[0]); - - break; - default: - throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); - - break; - } - $this->checkMatrixDimensions($M); - for ($i = 0; $i < $this->m; ++$i) { - for ($j = 0; $j < $this->n; ++$j) { - $validValues = true; - $value = $M->get($i, $j); - if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) { - $this->A[$i][$j] = trim($this->A[$i][$j], '"'); - $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]); - } - if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) { - $value = trim($value, '"'); - $validValues &= StringHelper::convertToNumberIfFraction($value); - } - if ($validValues) { - if ($value == 0) { - // Trap for Divide by Zero error - $M->set($i, $j, '#DIV/0!'); - } else { - $M->set($i, $j, $this->A[$i][$j] / $value); - } - } else { - $M->set($i, $j, Functions::NAN()); - } - } - } - - return $M; - } - - throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); - } - - /** - * arrayRightDivideEquals. - * - * Element-by-element right division - * Aij = Aij / Bij - * - * @param mixed $B Matrix/Array - * - * @return Matrix Matrix Aij - */ - public function arrayRightDivideEquals(...$args) - { - if (count($args) > 0) { - $match = implode(',', array_map('gettype', $args)); - - switch ($match) { - case 'object': - if ($args[0] instanceof self) { - $M = $args[0]; - } else { - throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); - } - - break; - case 'array': - $M = new self($args[0]); - - break; - default: - throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); - - break; - } - $this->checkMatrixDimensions($M); - for ($i = 0; $i < $this->m; ++$i) { - for ($j = 0; $j < $this->n; ++$j) { - $this->A[$i][$j] = $this->A[$i][$j] / $M->get($i, $j); - } - } - - return $M; - } - - throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); - } - - /** - * arrayLeftDivide. - * - * Element-by-element Left division - * A / B - * - * @param Matrix $B Matrix B - * - * @return Matrix Division result - */ - public function arrayLeftDivide(...$args) - { - if (count($args) > 0) { - $match = implode(',', array_map('gettype', $args)); - - switch ($match) { - case 'object': - if ($args[0] instanceof self) { - $M = $args[0]; - } else { - throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); - } - - break; - case 'array': - $M = new self($args[0]); - - break; - default: - throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); - - break; - } - $this->checkMatrixDimensions($M); - for ($i = 0; $i < $this->m; ++$i) { - for ($j = 0; $j < $this->n; ++$j) { - $M->set($i, $j, $M->get($i, $j) / $this->A[$i][$j]); - } - } - - return $M; - } - - throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); - } - - /** - * arrayLeftDivideEquals. - * - * Element-by-element Left division - * Aij = Aij / Bij - * - * @param mixed $B Matrix/Array - * - * @return Matrix Matrix Aij - */ - public function arrayLeftDivideEquals(...$args) - { - if (count($args) > 0) { - $match = implode(',', array_map('gettype', $args)); - - switch ($match) { - case 'object': - if ($args[0] instanceof self) { - $M = $args[0]; - } else { - throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); - } - - break; - case 'array': - $M = new self($args[0]); - - break; - default: - throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); - - break; - } - $this->checkMatrixDimensions($M); - for ($i = 0; $i < $this->m; ++$i) { - for ($j = 0; $j < $this->n; ++$j) { - $this->A[$i][$j] = $M->get($i, $j) / $this->A[$i][$j]; - } - } - - return $M; - } - - throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); - } - - /** - * times. - * - * Matrix multiplication - * - * @param mixed $n Matrix/Array/Scalar - * - * @return Matrix Product - */ - public function times(...$args) - { - if (count($args) > 0) { - $match = implode(',', array_map('gettype', $args)); - - switch ($match) { - case 'object': - if ($args[0] instanceof self) { - $B = $args[0]; - } else { - throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); - } - if ($this->n == $B->m) { - $C = new self($this->m, $B->n); - for ($j = 0; $j < $B->n; ++$j) { - $Bcolj = []; - for ($k = 0; $k < $this->n; ++$k) { - $Bcolj[$k] = $B->A[$k][$j]; - } - for ($i = 0; $i < $this->m; ++$i) { - $Arowi = $this->A[$i]; - $s = 0; - for ($k = 0; $k < $this->n; ++$k) { - $s += $Arowi[$k] * $Bcolj[$k]; - } - $C->A[$i][$j] = $s; - } - } - - return $C; - } - - throw new CalculationException(self::MATRIX_DIMENSION_EXCEPTION); - case 'array': - $B = new self($args[0]); - if ($this->n == $B->m) { - $C = new self($this->m, $B->n); - for ($i = 0; $i < $C->m; ++$i) { - for ($j = 0; $j < $C->n; ++$j) { - $s = '0'; - for ($k = 0; $k < $C->n; ++$k) { - $s += $this->A[$i][$k] * $B->A[$k][$j]; - } - $C->A[$i][$j] = $s; - } - } - - return $C; - } - - throw new CalculationException(self::MATRIX_DIMENSION_EXCEPTION); - case 'integer': - $C = new self($this->A); - for ($i = 0; $i < $C->m; ++$i) { - for ($j = 0; $j < $C->n; ++$j) { - $C->A[$i][$j] *= $args[0]; - } - } - - return $C; - case 'double': - $C = new self($this->m, $this->n); - for ($i = 0; $i < $C->m; ++$i) { - for ($j = 0; $j < $C->n; ++$j) { - $C->A[$i][$j] = $args[0] * $this->A[$i][$j]; - } - } - - return $C; - case 'float': - $C = new self($this->A); - for ($i = 0; $i < $C->m; ++$i) { - for ($j = 0; $j < $C->n; ++$j) { - $C->A[$i][$j] *= $args[0]; - } - } - - return $C; - default: - throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); - } - } else { - throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); - } - } - - /** - * power. - * - * A = A ^ B - * - * @param mixed $B Matrix/Array - * - * @return Matrix Sum - */ - public function power(...$args) - { - if (count($args) > 0) { - $match = implode(',', array_map('gettype', $args)); - - switch ($match) { - case 'object': - if ($args[0] instanceof self) { - $M = $args[0]; - } else { - throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); - } - - break; - case 'array': - $M = new self($args[0]); - - break; - default: - throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); - - break; - } - $this->checkMatrixDimensions($M); - for ($i = 0; $i < $this->m; ++$i) { - for ($j = 0; $j < $this->n; ++$j) { - $validValues = true; - $value = $M->get($i, $j); - if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) { - $this->A[$i][$j] = trim($this->A[$i][$j], '"'); - $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]); - } - if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) { - $value = trim($value, '"'); - $validValues &= StringHelper::convertToNumberIfFraction($value); - } - if ($validValues) { - $this->A[$i][$j] = pow($this->A[$i][$j], $value); - } else { - $this->A[$i][$j] = Functions::NAN(); - } - } - } - - return $this; - } - - throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); - } - - /** - * concat. - * - * A = A & B - * - * @param mixed $B Matrix/Array - * - * @return Matrix Sum - */ - public function concat(...$args) - { - if (count($args) > 0) { - $match = implode(',', array_map('gettype', $args)); - - switch ($match) { - case 'object': - if ($args[0] instanceof self) { - $M = $args[0]; - } else { - throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION); - } - - break; - case 'array': - $M = new self($args[0]); - - break; - default: - throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); - - break; - } - $this->checkMatrixDimensions($M); - for ($i = 0; $i < $this->m; ++$i) { - for ($j = 0; $j < $this->n; ++$j) { - $this->A[$i][$j] = trim($this->A[$i][$j], '"') . trim($M->get($i, $j), '"'); - } - } - - return $this; - } - - throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION); - } - - /** - * Solve A*X = B. - * - * @param Matrix $B Right hand side - * - * @return Matrix ... Solution if A is square, least squares solution otherwise - */ - public function solve($B) - { - if ($this->m == $this->n) { - $LU = new LUDecomposition($this); - - return $LU->solve($B); - } - $QR = new QRDecomposition($this); - - return $QR->solve($B); - } - - /** - * Matrix inverse or pseudoinverse. - * - * @return Matrix ... Inverse(A) if A is square, pseudoinverse otherwise. - */ - public function inverse() - { - return $this->solve($this->identity($this->m, $this->m)); - } - - /** - * det. - * - * Calculate determinant - * - * @return float Determinant - */ - public function det() - { - $L = new LUDecomposition($this); - - return $L->det(); - } -} diff --git a/PhpOffice/PhpSpreadsheet/Shared/JAMA/QRDecomposition.php b/PhpOffice/PhpSpreadsheet/Shared/JAMA/QRDecomposition.php deleted file mode 100755 index e666d74..0000000 --- a/PhpOffice/PhpSpreadsheet/Shared/JAMA/QRDecomposition.php +++ /dev/null @@ -1,249 +0,0 @@ -= n, the QR decomposition is an m-by-n - * orthogonal matrix Q and an n-by-n upper triangular matrix R so that - * A = Q*R. - * - * The QR decompostion always exists, even if the matrix does not have - * full rank, so the constructor will never fail. The primary use of the - * QR decomposition is in the least squares solution of nonsquare systems - * of simultaneous linear equations. This will fail if isFullRank() - * returns false. - * - * @author Paul Meagher - * - * @version 1.1 - */ -class QRDecomposition -{ - const MATRIX_RANK_EXCEPTION = 'Can only perform operation on full-rank matrix.'; - - /** - * Array for internal storage of decomposition. - * - * @var array - */ - private $QR = []; - - /** - * Row dimension. - * - * @var int - */ - private $m; - - /** - * Column dimension. - * - * @var int - */ - private $n; - - /** - * Array for internal storage of diagonal of R. - * - * @var array - */ - private $Rdiag = []; - - /** - * QR Decomposition computed by Householder reflections. - * - * @param matrix $A Rectangular matrix - */ - public function __construct($A) - { - if ($A instanceof Matrix) { - // Initialize. - $this->QR = $A->getArrayCopy(); - $this->m = $A->getRowDimension(); - $this->n = $A->getColumnDimension(); - // Main loop. - for ($k = 0; $k < $this->n; ++$k) { - // Compute 2-norm of k-th column without under/overflow. - $nrm = 0.0; - for ($i = $k; $i < $this->m; ++$i) { - $nrm = hypo($nrm, $this->QR[$i][$k]); - } - if ($nrm != 0.0) { - // Form k-th Householder vector. - if ($this->QR[$k][$k] < 0) { - $nrm = -$nrm; - } - for ($i = $k; $i < $this->m; ++$i) { - $this->QR[$i][$k] /= $nrm; - } - $this->QR[$k][$k] += 1.0; - // Apply transformation to remaining columns. - for ($j = $k + 1; $j < $this->n; ++$j) { - $s = 0.0; - for ($i = $k; $i < $this->m; ++$i) { - $s += $this->QR[$i][$k] * $this->QR[$i][$j]; - } - $s = -$s / $this->QR[$k][$k]; - for ($i = $k; $i < $this->m; ++$i) { - $this->QR[$i][$j] += $s * $this->QR[$i][$k]; - } - } - } - $this->Rdiag[$k] = -$nrm; - } - } else { - throw new CalculationException(Matrix::ARGUMENT_TYPE_EXCEPTION); - } - } - - // function __construct() - - /** - * Is the matrix full rank? - * - * @return bool true if R, and hence A, has full rank, else false - */ - public function isFullRank() - { - for ($j = 0; $j < $this->n; ++$j) { - if ($this->Rdiag[$j] == 0) { - return false; - } - } - - return true; - } - - // function isFullRank() - - /** - * Return the Householder vectors. - * - * @return Matrix Lower trapezoidal matrix whose columns define the reflections - */ - public function getH() - { - $H = []; - for ($i = 0; $i < $this->m; ++$i) { - for ($j = 0; $j < $this->n; ++$j) { - if ($i >= $j) { - $H[$i][$j] = $this->QR[$i][$j]; - } else { - $H[$i][$j] = 0.0; - } - } - } - - return new Matrix($H); - } - - // function getH() - - /** - * Return the upper triangular factor. - * - * @return Matrix upper triangular factor - */ - public function getR() - { - $R = []; - for ($i = 0; $i < $this->n; ++$i) { - for ($j = 0; $j < $this->n; ++$j) { - if ($i < $j) { - $R[$i][$j] = $this->QR[$i][$j]; - } elseif ($i == $j) { - $R[$i][$j] = $this->Rdiag[$i]; - } else { - $R[$i][$j] = 0.0; - } - } - } - - return new Matrix($R); - } - - // function getR() - - /** - * Generate and return the (economy-sized) orthogonal factor. - * - * @return Matrix orthogonal factor - */ - public function getQ() - { - $Q = []; - for ($k = $this->n - 1; $k >= 0; --$k) { - for ($i = 0; $i < $this->m; ++$i) { - $Q[$i][$k] = 0.0; - } - $Q[$k][$k] = 1.0; - for ($j = $k; $j < $this->n; ++$j) { - if ($this->QR[$k][$k] != 0) { - $s = 0.0; - for ($i = $k; $i < $this->m; ++$i) { - $s += $this->QR[$i][$k] * $Q[$i][$j]; - } - $s = -$s / $this->QR[$k][$k]; - for ($i = $k; $i < $this->m; ++$i) { - $Q[$i][$j] += $s * $this->QR[$i][$k]; - } - } - } - } - - return new Matrix($Q); - } - - // function getQ() - - /** - * Least squares solution of A*X = B. - * - * @param Matrix $B a Matrix with as many rows as A and any number of columns - * - * @return Matrix matrix that minimizes the two norm of Q*R*X-B - */ - public function solve($B) - { - if ($B->getRowDimension() == $this->m) { - if ($this->isFullRank()) { - // Copy right hand side - $nx = $B->getColumnDimension(); - $X = $B->getArrayCopy(); - // Compute Y = transpose(Q)*B - for ($k = 0; $k < $this->n; ++$k) { - for ($j = 0; $j < $nx; ++$j) { - $s = 0.0; - for ($i = $k; $i < $this->m; ++$i) { - $s += $this->QR[$i][$k] * $X[$i][$j]; - } - $s = -$s / $this->QR[$k][$k]; - for ($i = $k; $i < $this->m; ++$i) { - $X[$i][$j] += $s * $this->QR[$i][$k]; - } - } - } - // Solve R*X = Y; - for ($k = $this->n - 1; $k >= 0; --$k) { - for ($j = 0; $j < $nx; ++$j) { - $X[$k][$j] /= $this->Rdiag[$k]; - } - for ($i = 0; $i < $k; ++$i) { - for ($j = 0; $j < $nx; ++$j) { - $X[$i][$j] -= $X[$k][$j] * $this->QR[$i][$k]; - } - } - } - $X = new Matrix($X); - - return $X->getMatrix(0, $this->n - 1, 0, $nx); - } - - throw new CalculationException(self::MATRIX_RANK_EXCEPTION); - } - - throw new CalculationException(Matrix::MATRIX_DIMENSION_EXCEPTION); - } -} diff --git a/PhpOffice/PhpSpreadsheet/Shared/JAMA/SingularValueDecomposition.php b/PhpOffice/PhpSpreadsheet/Shared/JAMA/SingularValueDecomposition.php deleted file mode 100755 index 3ca9561..0000000 --- a/PhpOffice/PhpSpreadsheet/Shared/JAMA/SingularValueDecomposition.php +++ /dev/null @@ -1,528 +0,0 @@ -= n, the singular value decomposition is - * an m-by-n orthogonal matrix U, an n-by-n diagonal matrix S, and - * an n-by-n orthogonal matrix V so that A = U*S*V'. - * - * The singular values, sigma[$k] = S[$k][$k], are ordered so that - * sigma[0] >= sigma[1] >= ... >= sigma[n-1]. - * - * The singular value decompostion always exists, so the constructor will - * never fail. The matrix condition number and the effective numerical - * rank can be computed from this decomposition. - * - * @author Paul Meagher - * - * @version 1.1 - */ -class SingularValueDecomposition -{ - /** - * Internal storage of U. - * - * @var array - */ - private $U = []; - - /** - * Internal storage of V. - * - * @var array - */ - private $V = []; - - /** - * Internal storage of singular values. - * - * @var array - */ - private $s = []; - - /** - * Row dimension. - * - * @var int - */ - private $m; - - /** - * Column dimension. - * - * @var int - */ - private $n; - - /** - * Construct the singular value decomposition. - * - * Derived from LINPACK code. - * - * @param mixed $Arg Rectangular matrix - */ - public function __construct($Arg) - { - // Initialize. - $A = $Arg->getArrayCopy(); - $this->m = $Arg->getRowDimension(); - $this->n = $Arg->getColumnDimension(); - $nu = min($this->m, $this->n); - $e = []; - $work = []; - $wantu = true; - $wantv = true; - $nct = min($this->m - 1, $this->n); - $nrt = max(0, min($this->n - 2, $this->m)); - - // Reduce A to bidiagonal form, storing the diagonal elements - // in s and the super-diagonal elements in e. - $kMax = max($nct, $nrt); - for ($k = 0; $k < $kMax; ++$k) { - if ($k < $nct) { - // Compute the transformation for the k-th column and - // place the k-th diagonal in s[$k]. - // Compute 2-norm of k-th column without under/overflow. - $this->s[$k] = 0; - for ($i = $k; $i < $this->m; ++$i) { - $this->s[$k] = hypo($this->s[$k], $A[$i][$k]); - } - if ($this->s[$k] != 0.0) { - if ($A[$k][$k] < 0.0) { - $this->s[$k] = -$this->s[$k]; - } - for ($i = $k; $i < $this->m; ++$i) { - $A[$i][$k] /= $this->s[$k]; - } - $A[$k][$k] += 1.0; - } - $this->s[$k] = -$this->s[$k]; - } - - for ($j = $k + 1; $j < $this->n; ++$j) { - if (($k < $nct) & ($this->s[$k] != 0.0)) { - // Apply the transformation. - $t = 0; - for ($i = $k; $i < $this->m; ++$i) { - $t += $A[$i][$k] * $A[$i][$j]; - } - $t = -$t / $A[$k][$k]; - for ($i = $k; $i < $this->m; ++$i) { - $A[$i][$j] += $t * $A[$i][$k]; - } - // Place the k-th row of A into e for the - // subsequent calculation of the row transformation. - $e[$j] = $A[$k][$j]; - } - } - - if ($wantu and ($k < $nct)) { - // Place the transformation in U for subsequent back - // multiplication. - for ($i = $k; $i < $this->m; ++$i) { - $this->U[$i][$k] = $A[$i][$k]; - } - } - - if ($k < $nrt) { - // Compute the k-th row transformation and place the - // k-th super-diagonal in e[$k]. - // Compute 2-norm without under/overflow. - $e[$k] = 0; - for ($i = $k + 1; $i < $this->n; ++$i) { - $e[$k] = hypo($e[$k], $e[$i]); - } - if ($e[$k] != 0.0) { - if ($e[$k + 1] < 0.0) { - $e[$k] = -$e[$k]; - } - for ($i = $k + 1; $i < $this->n; ++$i) { - $e[$i] /= $e[$k]; - } - $e[$k + 1] += 1.0; - } - $e[$k] = -$e[$k]; - if (($k + 1 < $this->m) and ($e[$k] != 0.0)) { - // Apply the transformation. - for ($i = $k + 1; $i < $this->m; ++$i) { - $work[$i] = 0.0; - } - for ($j = $k + 1; $j < $this->n; ++$j) { - for ($i = $k + 1; $i < $this->m; ++$i) { - $work[$i] += $e[$j] * $A[$i][$j]; - } - } - for ($j = $k + 1; $j < $this->n; ++$j) { - $t = -$e[$j] / $e[$k + 1]; - for ($i = $k + 1; $i < $this->m; ++$i) { - $A[$i][$j] += $t * $work[$i]; - } - } - } - if ($wantv) { - // Place the transformation in V for subsequent - // back multiplication. - for ($i = $k + 1; $i < $this->n; ++$i) { - $this->V[$i][$k] = $e[$i]; - } - } - } - } - - // Set up the final bidiagonal matrix or order p. - $p = min($this->n, $this->m + 1); - if ($nct < $this->n) { - $this->s[$nct] = $A[$nct][$nct]; - } - if ($this->m < $p) { - $this->s[$p - 1] = 0.0; - } - if ($nrt + 1 < $p) { - $e[$nrt] = $A[$nrt][$p - 1]; - } - $e[$p - 1] = 0.0; - // If required, generate U. - if ($wantu) { - for ($j = $nct; $j < $nu; ++$j) { - for ($i = 0; $i < $this->m; ++$i) { - $this->U[$i][$j] = 0.0; - } - $this->U[$j][$j] = 1.0; - } - for ($k = $nct - 1; $k >= 0; --$k) { - if ($this->s[$k] != 0.0) { - for ($j = $k + 1; $j < $nu; ++$j) { - $t = 0; - for ($i = $k; $i < $this->m; ++$i) { - $t += $this->U[$i][$k] * $this->U[$i][$j]; - } - $t = -$t / $this->U[$k][$k]; - for ($i = $k; $i < $this->m; ++$i) { - $this->U[$i][$j] += $t * $this->U[$i][$k]; - } - } - for ($i = $k; $i < $this->m; ++$i) { - $this->U[$i][$k] = -$this->U[$i][$k]; - } - $this->U[$k][$k] = 1.0 + $this->U[$k][$k]; - for ($i = 0; $i < $k - 1; ++$i) { - $this->U[$i][$k] = 0.0; - } - } else { - for ($i = 0; $i < $this->m; ++$i) { - $this->U[$i][$k] = 0.0; - } - $this->U[$k][$k] = 1.0; - } - } - } - - // If required, generate V. - if ($wantv) { - for ($k = $this->n - 1; $k >= 0; --$k) { - if (($k < $nrt) and ($e[$k] != 0.0)) { - for ($j = $k + 1; $j < $nu; ++$j) { - $t = 0; - for ($i = $k + 1; $i < $this->n; ++$i) { - $t += $this->V[$i][$k] * $this->V[$i][$j]; - } - $t = -$t / $this->V[$k + 1][$k]; - for ($i = $k + 1; $i < $this->n; ++$i) { - $this->V[$i][$j] += $t * $this->V[$i][$k]; - } - } - } - for ($i = 0; $i < $this->n; ++$i) { - $this->V[$i][$k] = 0.0; - } - $this->V[$k][$k] = 1.0; - } - } - - // Main iteration loop for the singular values. - $pp = $p - 1; - $iter = 0; - $eps = pow(2.0, -52.0); - - while ($p > 0) { - // Here is where a test for too many iterations would go. - // This section of the program inspects for negligible - // elements in the s and e arrays. On completion the - // variables kase and k are set as follows: - // kase = 1 if s(p) and e[k-1] are negligible and k

    = -1; --$k) { - if ($k == -1) { - break; - } - if (abs($e[$k]) <= $eps * (abs($this->s[$k]) + abs($this->s[$k + 1]))) { - $e[$k] = 0.0; - - break; - } - } - if ($k == $p - 2) { - $kase = 4; - } else { - for ($ks = $p - 1; $ks >= $k; --$ks) { - if ($ks == $k) { - break; - } - $t = ($ks != $p ? abs($e[$ks]) : 0.) + ($ks != $k + 1 ? abs($e[$ks - 1]) : 0.); - if (abs($this->s[$ks]) <= $eps * $t) { - $this->s[$ks] = 0.0; - - break; - } - } - if ($ks == $k) { - $kase = 3; - } elseif ($ks == $p - 1) { - $kase = 1; - } else { - $kase = 2; - $k = $ks; - } - } - ++$k; - - // Perform the task indicated by kase. - switch ($kase) { - // Deflate negligible s(p). - case 1: - $f = $e[$p - 2]; - $e[$p - 2] = 0.0; - for ($j = $p - 2; $j >= $k; --$j) { - $t = hypo($this->s[$j], $f); - $cs = $this->s[$j] / $t; - $sn = $f / $t; - $this->s[$j] = $t; - if ($j != $k) { - $f = -$sn * $e[$j - 1]; - $e[$j - 1] = $cs * $e[$j - 1]; - } - if ($wantv) { - for ($i = 0; $i < $this->n; ++$i) { - $t = $cs * $this->V[$i][$j] + $sn * $this->V[$i][$p - 1]; - $this->V[$i][$p - 1] = -$sn * $this->V[$i][$j] + $cs * $this->V[$i][$p - 1]; - $this->V[$i][$j] = $t; - } - } - } - - break; - // Split at negligible s(k). - case 2: - $f = $e[$k - 1]; - $e[$k - 1] = 0.0; - for ($j = $k; $j < $p; ++$j) { - $t = hypo($this->s[$j], $f); - $cs = $this->s[$j] / $t; - $sn = $f / $t; - $this->s[$j] = $t; - $f = -$sn * $e[$j]; - $e[$j] = $cs * $e[$j]; - if ($wantu) { - for ($i = 0; $i < $this->m; ++$i) { - $t = $cs * $this->U[$i][$j] + $sn * $this->U[$i][$k - 1]; - $this->U[$i][$k - 1] = -$sn * $this->U[$i][$j] + $cs * $this->U[$i][$k - 1]; - $this->U[$i][$j] = $t; - } - } - } - - break; - // Perform one qr step. - case 3: - // Calculate the shift. - $scale = max(max(max(max(abs($this->s[$p - 1]), abs($this->s[$p - 2])), abs($e[$p - 2])), abs($this->s[$k])), abs($e[$k])); - $sp = $this->s[$p - 1] / $scale; - $spm1 = $this->s[$p - 2] / $scale; - $epm1 = $e[$p - 2] / $scale; - $sk = $this->s[$k] / $scale; - $ek = $e[$k] / $scale; - $b = (($spm1 + $sp) * ($spm1 - $sp) + $epm1 * $epm1) / 2.0; - $c = ($sp * $epm1) * ($sp * $epm1); - $shift = 0.0; - if (($b != 0.0) || ($c != 0.0)) { - $shift = sqrt($b * $b + $c); - if ($b < 0.0) { - $shift = -$shift; - } - $shift = $c / ($b + $shift); - } - $f = ($sk + $sp) * ($sk - $sp) + $shift; - $g = $sk * $ek; - // Chase zeros. - for ($j = $k; $j < $p - 1; ++$j) { - $t = hypo($f, $g); - $cs = $f / $t; - $sn = $g / $t; - if ($j != $k) { - $e[$j - 1] = $t; - } - $f = $cs * $this->s[$j] + $sn * $e[$j]; - $e[$j] = $cs * $e[$j] - $sn * $this->s[$j]; - $g = $sn * $this->s[$j + 1]; - $this->s[$j + 1] = $cs * $this->s[$j + 1]; - if ($wantv) { - for ($i = 0; $i < $this->n; ++$i) { - $t = $cs * $this->V[$i][$j] + $sn * $this->V[$i][$j + 1]; - $this->V[$i][$j + 1] = -$sn * $this->V[$i][$j] + $cs * $this->V[$i][$j + 1]; - $this->V[$i][$j] = $t; - } - } - $t = hypo($f, $g); - $cs = $f / $t; - $sn = $g / $t; - $this->s[$j] = $t; - $f = $cs * $e[$j] + $sn * $this->s[$j + 1]; - $this->s[$j + 1] = -$sn * $e[$j] + $cs * $this->s[$j + 1]; - $g = $sn * $e[$j + 1]; - $e[$j + 1] = $cs * $e[$j + 1]; - if ($wantu && ($j < $this->m - 1)) { - for ($i = 0; $i < $this->m; ++$i) { - $t = $cs * $this->U[$i][$j] + $sn * $this->U[$i][$j + 1]; - $this->U[$i][$j + 1] = -$sn * $this->U[$i][$j] + $cs * $this->U[$i][$j + 1]; - $this->U[$i][$j] = $t; - } - } - } - $e[$p - 2] = $f; - $iter = $iter + 1; - - break; - // Convergence. - case 4: - // Make the singular values positive. - if ($this->s[$k] <= 0.0) { - $this->s[$k] = ($this->s[$k] < 0.0 ? -$this->s[$k] : 0.0); - if ($wantv) { - for ($i = 0; $i <= $pp; ++$i) { - $this->V[$i][$k] = -$this->V[$i][$k]; - } - } - } - // Order the singular values. - while ($k < $pp) { - if ($this->s[$k] >= $this->s[$k + 1]) { - break; - } - $t = $this->s[$k]; - $this->s[$k] = $this->s[$k + 1]; - $this->s[$k + 1] = $t; - if ($wantv and ($k < $this->n - 1)) { - for ($i = 0; $i < $this->n; ++$i) { - $t = $this->V[$i][$k + 1]; - $this->V[$i][$k + 1] = $this->V[$i][$k]; - $this->V[$i][$k] = $t; - } - } - if ($wantu and ($k < $this->m - 1)) { - for ($i = 0; $i < $this->m; ++$i) { - $t = $this->U[$i][$k + 1]; - $this->U[$i][$k + 1] = $this->U[$i][$k]; - $this->U[$i][$k] = $t; - } - } - ++$k; - } - $iter = 0; - --$p; - - break; - } // end switch - } // end while - } - - /** - * Return the left singular vectors. - * - * @return Matrix U - */ - public function getU() - { - return new Matrix($this->U, $this->m, min($this->m + 1, $this->n)); - } - - /** - * Return the right singular vectors. - * - * @return Matrix V - */ - public function getV() - { - return new Matrix($this->V); - } - - /** - * Return the one-dimensional array of singular values. - * - * @return array diagonal of S - */ - public function getSingularValues() - { - return $this->s; - } - - /** - * Return the diagonal matrix of singular values. - * - * @return Matrix S - */ - public function getS() - { - for ($i = 0; $i < $this->n; ++$i) { - for ($j = 0; $j < $this->n; ++$j) { - $S[$i][$j] = 0.0; - } - $S[$i][$i] = $this->s[$i]; - } - - return new Matrix($S); - } - - /** - * Two norm. - * - * @return float max(S) - */ - public function norm2() - { - return $this->s[0]; - } - - /** - * Two norm condition number. - * - * @return float max(S)/min(S) - */ - public function cond() - { - return $this->s[0] / $this->s[min($this->m, $this->n) - 1]; - } - - /** - * Effective numerical matrix rank. - * - * @return int Number of nonnegligible singular values - */ - public function rank() - { - $eps = pow(2.0, -52.0); - $tol = max($this->m, $this->n) * $this->s[0] * $eps; - $r = 0; - $iMax = count($this->s); - for ($i = 0; $i < $iMax; ++$i) { - if ($this->s[$i] > $tol) { - ++$r; - } - } - - return $r; - } -} diff --git a/PhpOffice/PhpSpreadsheet/Shared/JAMA/utils/Maths.php b/PhpOffice/PhpSpreadsheet/Shared/JAMA/utils/Maths.php deleted file mode 100755 index 68c3864..0000000 --- a/PhpOffice/PhpSpreadsheet/Shared/JAMA/utils/Maths.php +++ /dev/null @@ -1,30 +0,0 @@ - abs($b)) { - $r = $b / $a; - $r = abs($a) * sqrt(1 + $r * $r); - } elseif ($b != 0) { - $r = $a / $b; - $r = abs($b) * sqrt(1 + $r * $r); - } else { - $r = 0.0; - } - - return $r; -} diff --git a/PhpOffice/PhpSpreadsheet/Shared/OLE.php b/PhpOffice/PhpSpreadsheet/Shared/OLE.php old mode 100755 new mode 100644 index 7f6a1fb..815b1c1 --- a/PhpOffice/PhpSpreadsheet/Shared/OLE.php +++ b/PhpOffice/PhpSpreadsheet/Shared/OLE.php @@ -21,6 +21,7 @@ namespace PhpOffice\PhpSpreadsheet\Shared; // +----------------------------------------------------------------------+ // +use PhpOffice\PhpSpreadsheet\Exception; use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException; use PhpOffice\PhpSpreadsheet\Shared\OLE\ChainedBlockStream; use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\Root; @@ -38,8 +39,6 @@ $GLOBALS['_OLE_INSTANCES'] = []; * * @author Xavier Noguer * @author Christian Schmidt - * - * @category PhpSpreadsheet */ class OLE { @@ -111,17 +110,15 @@ class OLE * * @acces public * - * @param string $file - * - * @throws ReaderException + * @param string $filename * * @return bool true on success, PEAR_Error on failure */ - public function read($file) + public function read($filename) { - $fh = fopen($file, 'r'); - if (!$fh) { - throw new ReaderException("Can't open file $file"); + $fh = fopen($filename, 'rb'); + if ($fh === false) { + throw new ReaderException("Can't open file $filename"); } $this->_file_handle = $fh; @@ -135,55 +132,55 @@ class OLE throw new ReaderException('Only Little-Endian encoding is supported.'); } // Size of blocks and short blocks in bytes - $this->bigBlockSize = pow(2, self::_readInt2($fh)); - $this->smallBlockSize = pow(2, self::_readInt2($fh)); + $this->bigBlockSize = 2 ** self::readInt2($fh); + $this->smallBlockSize = 2 ** self::readInt2($fh); // Skip UID, revision number and version number fseek($fh, 44); // Number of blocks in Big Block Allocation Table - $bbatBlockCount = self::_readInt4($fh); + $bbatBlockCount = self::readInt4($fh); // Root chain 1st block - $directoryFirstBlockId = self::_readInt4($fh); + $directoryFirstBlockId = self::readInt4($fh); // Skip unused bytes fseek($fh, 56); // Streams shorter than this are stored using small blocks - $this->bigBlockThreshold = self::_readInt4($fh); + $this->bigBlockThreshold = self::readInt4($fh); // Block id of first sector in Short Block Allocation Table - $sbatFirstBlockId = self::_readInt4($fh); + $sbatFirstBlockId = self::readInt4($fh); // Number of blocks in Short Block Allocation Table - $sbbatBlockCount = self::_readInt4($fh); + $sbbatBlockCount = self::readInt4($fh); // Block id of first sector in Master Block Allocation Table - $mbatFirstBlockId = self::_readInt4($fh); + $mbatFirstBlockId = self::readInt4($fh); // Number of blocks in Master Block Allocation Table - $mbbatBlockCount = self::_readInt4($fh); + $mbbatBlockCount = self::readInt4($fh); $this->bbat = []; // Remaining 4 * 109 bytes of current block is beginning of Master // Block Allocation Table $mbatBlocks = []; for ($i = 0; $i < 109; ++$i) { - $mbatBlocks[] = self::_readInt4($fh); + $mbatBlocks[] = self::readInt4($fh); } // Read rest of Master Block Allocation Table (if any is left) - $pos = $this->_getBlockOffset($mbatFirstBlockId); + $pos = $this->getBlockOffset($mbatFirstBlockId); for ($i = 0; $i < $mbbatBlockCount; ++$i) { fseek($fh, $pos); for ($j = 0; $j < $this->bigBlockSize / 4 - 1; ++$j) { - $mbatBlocks[] = self::_readInt4($fh); + $mbatBlocks[] = self::readInt4($fh); } // Last block id in each block points to next block - $pos = $this->_getBlockOffset(self::_readInt4($fh)); + $pos = $this->getBlockOffset(self::readInt4($fh)); } // Read Big Block Allocation Table according to chain specified by $mbatBlocks for ($i = 0; $i < $bbatBlockCount; ++$i) { - $pos = $this->_getBlockOffset($mbatBlocks[$i]); + $pos = $this->getBlockOffset($mbatBlocks[$i]); fseek($fh, $pos); for ($j = 0; $j < $this->bigBlockSize / 4; ++$j) { - $this->bbat[] = self::_readInt4($fh); + $this->bbat[] = self::readInt4($fh); } } @@ -192,11 +189,11 @@ class OLE $shortBlockCount = $sbbatBlockCount * $this->bigBlockSize / 4; $sbatFh = $this->getStream($sbatFirstBlockId); for ($blockId = 0; $blockId < $shortBlockCount; ++$blockId) { - $this->sbat[$blockId] = self::_readInt4($sbatFh); + $this->sbat[$blockId] = self::readInt4($sbatFh); } fclose($sbatFh); - $this->_readPpsWks($directoryFirstBlockId); + $this->readPpsWks($directoryFirstBlockId); return true; } @@ -206,7 +203,7 @@ class OLE * * @return int */ - public function _getBlockOffset($blockId) + public function getBlockOffset($blockId) { return 512 + $blockId * $this->bigBlockSize; } @@ -231,7 +228,8 @@ class OLE // in OLE_ChainedBlockStream::stream_open(). // Object is removed from self::$instances in OLE_Stream::close(). $GLOBALS['_OLE_INSTANCES'][] = $this; - $instanceId = end(array_keys($GLOBALS['_OLE_INSTANCES'])); + $keys = array_keys($GLOBALS['_OLE_INSTANCES']); + $instanceId = end($keys); $path = 'ole-chainedblockstream://oleInstanceId=' . $instanceId; if ($blockIdOrPps instanceof OLE\PPS) { @@ -241,19 +239,20 @@ class OLE $path .= '&blockId=' . $blockIdOrPps; } - return fopen($path, 'r'); + return fopen($path, 'rb'); } /** * Reads a signed char. * - * @param resource $fh file handle + * @param resource $fileHandle file handle * * @return int */ - private static function _readInt1($fh) + private static function readInt1($fileHandle) { - [, $tmp] = unpack('c', fread($fh, 1)); + // @phpstan-ignore-next-line + [, $tmp] = unpack('c', fread($fileHandle, 1)); return $tmp; } @@ -261,13 +260,14 @@ class OLE /** * Reads an unsigned short (2 octets). * - * @param resource $fh file handle + * @param resource $fileHandle file handle * * @return int */ - private static function _readInt2($fh) + private static function readInt2($fileHandle) { - [, $tmp] = unpack('v', fread($fh, 2)); + // @phpstan-ignore-next-line + [, $tmp] = unpack('v', fread($fileHandle, 2)); return $tmp; } @@ -275,13 +275,14 @@ class OLE /** * Reads an unsigned long (4 octets). * - * @param resource $fh file handle + * @param resource $fileHandle file handle * * @return int */ - private static function _readInt4($fh) + private static function readInt4($fileHandle) { - [, $tmp] = unpack('V', fread($fh, 4)); + // @phpstan-ignore-next-line + [, $tmp] = unpack('V', fread($fileHandle, 4)); return $tmp; } @@ -294,17 +295,17 @@ class OLE * * @return bool true on success, PEAR_Error on failure */ - public function _readPpsWks($blockId) + public function readPpsWks($blockId) { $fh = $this->getStream($blockId); for ($pos = 0; true; $pos += 128) { fseek($fh, $pos, SEEK_SET); $nameUtf16 = fread($fh, 64); - $nameLength = self::_readInt2($fh); + $nameLength = self::readInt2($fh); $nameUtf16 = substr($nameUtf16, 0, $nameLength - 2); // Simple conversion from UTF-16LE to ISO-8859-1 $name = str_replace("\x00", '', $nameUtf16); - $type = self::_readInt1($fh); + $type = self::readInt1($fh); switch ($type) { case self::OLE_PPS_TYPE_ROOT: $pps = new OLE\PPS\Root(null, null, []); @@ -320,24 +321,24 @@ class OLE break; default: - break; + throw new Exception('Unsupported PPS type'); } fseek($fh, 1, SEEK_CUR); $pps->Type = $type; $pps->Name = $name; - $pps->PrevPps = self::_readInt4($fh); - $pps->NextPps = self::_readInt4($fh); - $pps->DirPps = self::_readInt4($fh); + $pps->PrevPps = self::readInt4($fh); + $pps->NextPps = self::readInt4($fh); + $pps->DirPps = self::readInt4($fh); fseek($fh, 20, SEEK_CUR); $pps->Time1st = self::OLE2LocalDate(fread($fh, 8)); $pps->Time2nd = self::OLE2LocalDate(fread($fh, 8)); - $pps->startBlock = self::_readInt4($fh); - $pps->Size = self::_readInt4($fh); + $pps->startBlock = self::readInt4($fh); + $pps->Size = self::readInt4($fh); $pps->No = count($this->_list); $this->_list[] = $pps; // check if the PPS tree (starting from root) is complete - if (isset($this->root) && $this->_ppsTreeComplete($this->root->No)) { + if (isset($this->root) && $this->ppsTreeComplete($this->root->No)) { break; } } @@ -348,7 +349,7 @@ class OLE if ($pps->Type == self::OLE_PPS_TYPE_DIR || $pps->Type == self::OLE_PPS_TYPE_ROOT) { $nos = [$pps->DirPps]; $pps->children = []; - while ($nos) { + while (!empty($nos)) { $no = array_pop($nos); if ($no != -1) { $childPps = $this->_list[$no]; @@ -371,16 +372,16 @@ class OLE * * @return bool Whether the PPS tree for the given PPS is complete */ - public function _ppsTreeComplete($index) + private function ppsTreeComplete($index) { return isset($this->_list[$index]) && ($pps = $this->_list[$index]) && ($pps->PrevPps == -1 || - $this->_ppsTreeComplete($pps->PrevPps)) && + $this->ppsTreeComplete($pps->PrevPps)) && ($pps->NextPps == -1 || - $this->_ppsTreeComplete($pps->NextPps)) && + $this->ppsTreeComplete($pps->NextPps)) && ($pps->DirPps == -1 || - $this->_ppsTreeComplete($pps->DirPps)); + $this->ppsTreeComplete($pps->DirPps)); } /** @@ -493,42 +494,33 @@ class OLE * Utility function * Returns a string for the OLE container with the date given. * - * @param int $date A timestamp + * @param float|int $date A timestamp * * @return string The string for the OLE container */ public static function localDateToOLE($date) { - if (!isset($date)) { + if (!$date) { return "\x00\x00\x00\x00\x00\x00\x00\x00"; } - - // factor used for separating numbers into 4 bytes parts - $factor = pow(2, 32); + $dateTime = Date::dateTimeFromTimestamp("$date"); // days from 1-1-1601 until the beggining of UNIX era $days = 134774; // calculate seconds - $big_date = $days * 24 * 3600 + gmmktime(date('H', $date), date('i', $date), date('s', $date), date('m', $date), date('d', $date), date('Y', $date)); + $big_date = $days * 24 * 3600 + (float) $dateTime->format('U'); // multiply just to make MS happy $big_date *= 10000000; - $high_part = floor($big_date / $factor); - // lower 4 bytes - $low_part = floor((($big_date / $factor) - $high_part) * $factor); - // Make HEX string $res = ''; - for ($i = 0; $i < 4; ++$i) { - $hex = (int) (((int) $low_part) % 0x100); - $res .= pack('c', $hex); - $low_part /= 0x100; - } - for ($i = 0; $i < 4; ++$i) { - $hex = (int) (((int) $high_part) % 0x100); - $res .= pack('c', $hex); - $high_part /= 0x100; + $factor = 2 ** 56; + while ($factor >= 1) { + $hex = (int) floor($big_date / $factor); + $res = pack('c', $hex) . $res; + $big_date = fmod($big_date, $factor); + $factor /= 256; } return $res; @@ -539,9 +531,7 @@ class OLE * * @param string $oleTimestamp A binary string with the encoded date * - * @throws ReaderException - * - * @return int The Unix timestamp corresponding to the string + * @return float|int The Unix timestamp corresponding to the string */ public static function OLE2LocalDate($oleTimestamp) { @@ -564,10 +554,6 @@ class OLE // translate to seconds since 1970: $unixTimestamp = floor(65536.0 * 65536.0 * $timestampHigh + $timestampLow - $days * 24 * 3600 + 0.5); - if ((int) $unixTimestamp == $unixTimestamp) { - return (int) $unixTimestamp; - } - - return $unixTimestamp >= 0.0 ? PHP_INT_MAX : PHP_INT_MIN; + return IntOrFloat::evaluate($unixTimestamp); } } diff --git a/PhpOffice/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php b/PhpOffice/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php old mode 100755 new mode 100644 index e6ba724..ee93c05 --- a/PhpOffice/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php +++ b/PhpOffice/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php @@ -9,7 +9,7 @@ class ChainedBlockStream /** * The OLE container of the file that is being read. * - * @var OLE + * @var null|OLE */ public $ole; @@ -42,7 +42,7 @@ class ChainedBlockStream * ole-chainedblockstream://oleInstanceId=1 * @param string $mode only "r" is supported * @param int $options mask of STREAM_REPORT_ERRORS and STREAM_USE_PATH - * @param string &$openedPath absolute path of the opened stream (out parameter) + * @param string $openedPath absolute path of the opened stream (out parameter) * * @return bool true on success */ @@ -71,7 +71,7 @@ class ChainedBlockStream $this->data = ''; if (isset($this->params['size']) && $this->params['size'] < $this->ole->bigBlockThreshold && $blockId != $this->ole->root->startBlock) { // Block id refers to small blocks - $rootPos = $this->ole->_getBlockOffset($this->ole->root->startBlock); + $rootPos = $this->ole->getBlockOffset($this->ole->root->startBlock); while ($blockId != -2) { $pos = $rootPos + $blockId * $this->ole->bigBlockSize; $blockId = $this->ole->sbat[$blockId]; @@ -81,7 +81,7 @@ class ChainedBlockStream } else { // Block id refers to big blocks while ($blockId != -2) { - $pos = $this->ole->_getBlockOffset($blockId); + $pos = $this->ole->getBlockOffset($blockId); fseek($this->ole->_file_handle, $pos); $this->data .= fread($this->ole->_file_handle, $this->ole->bigBlockSize); $blockId = $this->ole->bbat[$blockId]; @@ -101,7 +101,7 @@ class ChainedBlockStream /** * Implements support for fclose(). */ - public function stream_close() // @codingStandardsIgnoreLine + public function stream_close(): void // @codingStandardsIgnoreLine { $this->ole = null; unset($GLOBALS['_OLE_INSTANCES']); @@ -112,7 +112,7 @@ class ChainedBlockStream * * @param int $count maximum number of bytes to read * - * @return string + * @return false|string */ public function stream_read($count) // @codingStandardsIgnoreLine { @@ -160,7 +160,8 @@ class ChainedBlockStream $this->pos = $offset; } elseif ($whence == SEEK_CUR && -$offset <= $this->pos) { $this->pos += $offset; - } elseif ($whence == SEEK_END && -$offset <= count($this->data)) { + // @phpstan-ignore-next-line + } elseif ($whence == SEEK_END && -$offset <= count(/** @scrutinizer ignore-type */ $this->data)) { $this->pos = strlen($this->data) + $offset; } else { return false; @@ -179,7 +180,7 @@ class ChainedBlockStream { return [ 'size' => strlen($this->data), - ]; + ]; } // Methods used by stream_wrapper_register() that are not implemented: diff --git a/PhpOffice/PhpSpreadsheet/Shared/OLE/PPS.php b/PhpOffice/PhpSpreadsheet/Shared/OLE/PPS.php old mode 100755 new mode 100644 index e53f257..d3d86f5 --- a/PhpOffice/PhpSpreadsheet/Shared/OLE/PPS.php +++ b/PhpOffice/PhpSpreadsheet/Shared/OLE/PPS.php @@ -26,8 +26,6 @@ use PhpOffice\PhpSpreadsheet\Shared\OLE; * Class for creating PPS's for OLE containers. * * @author Xavier Noguer - * - * @category PhpSpreadsheet */ class PPS { @@ -76,14 +74,14 @@ class PPS /** * A timestamp. * - * @var int + * @var float|int */ public $Time1st; /** * A timestamp. * - * @var int + * @var float|int */ public $Time2nd; @@ -131,8 +129,8 @@ class PPS * @param int $prev The index of the previous PPS * @param int $next The index of the next PPS * @param int $dir The index of it's first child if this is a Dir or Root PPS - * @param int $time_1st A timestamp - * @param int $time_2nd A timestamp + * @param null|float|int $time_1st A timestamp + * @param null|float|int $time_2nd A timestamp * @param string $data The (usually binary) source data of the PPS * @param array $children Array containing children PPS for this PPS */ @@ -144,8 +142,8 @@ class PPS $this->PrevPps = $prev; $this->NextPps = $next; $this->DirPps = $dir; - $this->Time1st = $time_1st; - $this->Time2nd = $time_2nd; + $this->Time1st = $time_1st ?? 0; + $this->Time2nd = $time_2nd ?? 0; $this->_data = $data; $this->children = $children; if ($data != '') { @@ -174,7 +172,7 @@ class PPS * * @return string The binary string */ - public function _getPpsWk() + public function getPpsWk() { $ret = str_pad($this->Name, 64, "\x00"); @@ -191,9 +189,10 @@ class PPS . "\x00\x00\x00\x00" // 100 . OLE::localDateToOLE($this->Time1st) // 108 . OLE::localDateToOLE($this->Time2nd) // 116 - . pack('V', isset($this->startBlock) ? $this->startBlock : 0) // 120 + . pack('V', $this->startBlock ?? 0) // 120 . pack('V', $this->Size) // 124 . pack('V', 0); // 128 + return $ret; } @@ -201,14 +200,14 @@ class PPS * Updates index and pointers to previous, next and children PPS's for this * PPS. I don't think it'll work with Dir PPS's. * - * @param array &$raList Reference to the array of PPS's for the whole OLE + * @param array $raList Reference to the array of PPS's for the whole OLE * container * @param mixed $to_save * @param mixed $depth * * @return int The index for this PPS */ - public static function _savePpsSetPnt(&$raList, $to_save, $depth = 0) + public static function savePpsSetPnt(&$raList, $to_save, $depth = 0) { if (!is_array($to_save) || (empty($to_save))) { return 0xFFFFFFFF; @@ -219,18 +218,18 @@ class PPS $raList[$cnt]->No = $cnt; $raList[$cnt]->PrevPps = 0xFFFFFFFF; $raList[$cnt]->NextPps = 0xFFFFFFFF; - $raList[$cnt]->DirPps = self::_savePpsSetPnt($raList, @$raList[$cnt]->children, $depth++); + $raList[$cnt]->DirPps = self::savePpsSetPnt($raList, @$raList[$cnt]->children, $depth++); } else { - $iPos = floor(count($to_save) / 2); + $iPos = (int) floor(count($to_save) / 2); $aPrev = array_slice($to_save, 0, $iPos); $aNext = array_slice($to_save, $iPos + 1); $cnt = count($raList); // If the first entry, it's the root... Don't clone it! $raList[$cnt] = ($depth == 0) ? $to_save[$iPos] : clone $to_save[$iPos]; $raList[$cnt]->No = $cnt; - $raList[$cnt]->PrevPps = self::_savePpsSetPnt($raList, $aPrev, $depth++); - $raList[$cnt]->NextPps = self::_savePpsSetPnt($raList, $aNext, $depth++); - $raList[$cnt]->DirPps = self::_savePpsSetPnt($raList, @$raList[$cnt]->children, $depth++); + $raList[$cnt]->PrevPps = self::savePpsSetPnt($raList, $aPrev, $depth++); + $raList[$cnt]->NextPps = self::savePpsSetPnt($raList, $aNext, $depth++); + $raList[$cnt]->DirPps = self::savePpsSetPnt($raList, @$raList[$cnt]->children, $depth++); } return $cnt; diff --git a/PhpOffice/PhpSpreadsheet/Shared/OLE/PPS/File.php b/PhpOffice/PhpSpreadsheet/Shared/OLE/PPS/File.php old mode 100755 new mode 100644 index 68f50a5..dd1cda2 --- a/PhpOffice/PhpSpreadsheet/Shared/OLE/PPS/File.php +++ b/PhpOffice/PhpSpreadsheet/Shared/OLE/PPS/File.php @@ -27,8 +27,6 @@ use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS; * Class for creating File PPS's for OLE containers. * * @author Xavier Noguer - * - * @category PhpSpreadsheet */ class File extends PPS { @@ -59,7 +57,7 @@ class File extends PPS * * @param string $data The data to append */ - public function append($data) + public function append($data): void { $this->_data .= $data; } diff --git a/PhpOffice/PhpSpreadsheet/Shared/OLE/PPS/Root.php b/PhpOffice/PhpSpreadsheet/Shared/OLE/PPS/Root.php old mode 100755 new mode 100644 index c52cea2..3fe8af2 --- a/PhpOffice/PhpSpreadsheet/Shared/OLE/PPS/Root.php +++ b/PhpOffice/PhpSpreadsheet/Shared/OLE/PPS/Root.php @@ -22,34 +22,19 @@ namespace PhpOffice\PhpSpreadsheet\Shared\OLE\PPS; // use PhpOffice\PhpSpreadsheet\Shared\OLE; use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS; -use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException; /** * Class for creating Root PPS's for OLE containers. * * @author Xavier Noguer - * - * @category PhpSpreadsheet */ class Root extends PPS { - /** - * Directory for temporary files. - * - * @var string - */ - protected $tempDirectory; - /** * @var resource */ private $fileHandle; - /** - * @var string - */ - private $tempFilename; - /** * @var int */ @@ -61,14 +46,12 @@ class Root extends PPS private $bigBlockSize; /** - * @param int $time_1st A timestamp - * @param int $time_2nd A timestamp + * @param null|float|int $time_1st A timestamp + * @param null|float|int $time_2nd A timestamp * @param File[] $raChild */ public function __construct($time_1st, $time_2nd, $raChild) { - $this->tempDirectory = \PhpOffice\PhpSpreadsheet\Shared\File::sysGetTempDir(); - parent::__construct(null, OLE::ascToUcs('Root Entry'), OLE::OLE_PPS_TYPE_ROOT, null, null, null, $time_1st, $time_2nd, null, $raChild); } @@ -79,62 +62,39 @@ class Root extends PPS * If a resource pointer to a stream created by fopen() is passed * it will be used, but you have to close such stream by yourself. * - * @param resource|string $filename the name of the file or stream where to save the OLE container - * - * @throws WriterException + * @param resource $fileHandle the name of the file or stream where to save the OLE container * * @return bool true on success */ - public function save($filename) + public function save($fileHandle) { - // Initial Setting for saving - $this->bigBlockSize = pow( - 2, - (isset($this->bigBlockSize)) ? self::adjust2($this->bigBlockSize) : 9 - ); - $this->smallBlockSize = pow( - 2, - (isset($this->smallBlockSize)) ? self::adjust2($this->smallBlockSize) : 6 - ); + $this->fileHandle = $fileHandle; + + // Initial Setting for saving + $this->bigBlockSize = (int) (2 ** ( + (isset($this->bigBlockSize)) ? self::adjust2($this->bigBlockSize) : 9 + )); + $this->smallBlockSize = (int) (2 ** ( + (isset($this->smallBlockSize)) ? self::adjust2($this->smallBlockSize) : 6 + )); - if (is_resource($filename)) { - $this->fileHandle = $filename; - } elseif ($filename == '-' || $filename == '') { - if ($this->tempDirectory === null) { - $this->tempDirectory = \PhpOffice\PhpSpreadsheet\Shared\File::sysGetTempDir(); - } - $this->tempFilename = tempnam($this->tempDirectory, 'OLE_PPS_Root'); - $this->fileHandle = fopen($this->tempFilename, 'w+b'); - if ($this->fileHandle == false) { - throw new WriterException("Can't create temporary file."); - } - } else { - $this->fileHandle = fopen($filename, 'wb'); - } - if ($this->fileHandle == false) { - throw new WriterException("Can't open $filename. It may be in use or protected."); - } // Make an array of PPS's (for Save) $aList = []; - PPS::_savePpsSetPnt($aList, [$this]); + PPS::savePpsSetPnt($aList, [$this]); // calculate values for header - [$iSBDcnt, $iBBcnt, $iPPScnt] = $this->_calcSize($aList); //, $rhInfo); + [$iSBDcnt, $iBBcnt, $iPPScnt] = $this->calcSize($aList); //, $rhInfo); // Save Header - $this->_saveHeader($iSBDcnt, $iBBcnt, $iPPScnt); + $this->saveHeader((int) $iSBDcnt, (int) $iBBcnt, (int) $iPPScnt); // Make Small Data string (write SBD) - $this->_data = $this->_makeSmallData($aList); + $this->_data = $this->makeSmallData($aList); // Write BB - $this->_saveBigData($iSBDcnt, $aList); + $this->saveBigData((int) $iSBDcnt, $aList); // Write PPS - $this->_savePps($aList); + $this->savePps($aList); // Write Big Block Depot and BDList and Adding Header informations - $this->_saveBbd($iSBDcnt, $iBBcnt, $iPPScnt); - - if (!is_resource($filename)) { - fclose($this->fileHandle); - } + $this->saveBbd((int) $iSBDcnt, (int) $iBBcnt, (int) $iPPScnt); return true; } @@ -146,11 +106,10 @@ class Root extends PPS * * @return float[] The array of numbers */ - public function _calcSize(&$raList) + private function calcSize(&$raList) { // Calculate Basic Setting [$iSBDcnt, $iBBcnt, $iPPScnt] = [0, 0, 0]; - $iSmallLen = 0; $iSBcnt = 0; $iCount = count($raList); for ($i = 0; $i < $iCount; ++$i) { @@ -158,7 +117,7 @@ class Root extends PPS $raList[$i]->Size = $raList[$i]->getDataLen(); if ($raList[$i]->Size < OLE::OLE_DATA_SIZE_SMALL) { $iSBcnt += floor($raList[$i]->Size / $this->smallBlockSize) - + (($raList[$i]->Size % $this->smallBlockSize) ? 1 : 0); + + (($raList[$i]->Size % $this->smallBlockSize) ? 1 : 0); } else { $iBBcnt += (floor($raList[$i]->Size / $this->bigBlockSize) + (($raList[$i]->Size % $this->bigBlockSize) ? 1 : 0)); @@ -169,7 +128,7 @@ class Root extends PPS $iSlCnt = floor($this->bigBlockSize / OLE::OLE_LONG_INT_SIZE); $iSBDcnt = floor($iSBcnt / $iSlCnt) + (($iSBcnt % $iSlCnt) ? 1 : 0); $iBBcnt += (floor($iSmallLen / $this->bigBlockSize) + - (($iSmallLen % $this->bigBlockSize) ? 1 : 0)); + (($iSmallLen % $this->bigBlockSize) ? 1 : 0)); $iCnt = count($raList); $iBdCnt = $this->bigBlockSize / OLE::OLE_PPS_SIZE; $iPPScnt = (floor($iCnt / $iBdCnt) + (($iCnt % $iBdCnt) ? 1 : 0)); @@ -182,9 +141,9 @@ class Root extends PPS * * @param int $i2 The argument * - * @see save() - * * @return float + * + * @see save() */ private static function adjust2($i2) { @@ -200,7 +159,7 @@ class Root extends PPS * @param int $iBBcnt * @param int $iPPScnt */ - public function _saveHeader($iSBDcnt, $iBBcnt, $iPPScnt) + private function saveHeader($iSBDcnt, $iBBcnt, $iPPScnt): void { $FILE = $this->fileHandle; @@ -277,9 +236,9 @@ class Root extends PPS * Saving big data (PPS's with data bigger than \PhpOffice\PhpSpreadsheet\Shared\OLE::OLE_DATA_SIZE_SMALL). * * @param int $iStBlk - * @param array &$raList Reference to array of PPS's + * @param array $raList Reference to array of PPS's */ - public function _saveBigData($iStBlk, &$raList) + private function saveBigData($iStBlk, &$raList): void { $FILE = $this->fileHandle; @@ -297,8 +256,8 @@ class Root extends PPS // Set For PPS $raList[$i]->startBlock = $iStBlk; $iStBlk += - (floor($raList[$i]->Size / $this->bigBlockSize) + - (($raList[$i]->Size % $this->bigBlockSize) ? 1 : 0)); + (floor($raList[$i]->Size / $this->bigBlockSize) + + (($raList[$i]->Size % $this->bigBlockSize) ? 1 : 0)); } } } @@ -307,11 +266,11 @@ class Root extends PPS /** * get small data (PPS's with data smaller than \PhpOffice\PhpSpreadsheet\Shared\OLE::OLE_DATA_SIZE_SMALL). * - * @param array &$raList Reference to array of PPS's + * @param array $raList Reference to array of PPS's * * @return string */ - public function _makeSmallData(&$raList) + private function makeSmallData(&$raList) { $sRes = ''; $FILE = $this->fileHandle; @@ -326,7 +285,7 @@ class Root extends PPS } if ($raList[$i]->Size < OLE::OLE_DATA_SIZE_SMALL) { $iSmbCnt = floor($raList[$i]->Size / $this->smallBlockSize) - + (($raList[$i]->Size % $this->smallBlockSize) ? 1 : 0); + + (($raList[$i]->Size % $this->smallBlockSize) ? 1 : 0); // Add to SBD $jB = $iSmbCnt - 1; for ($j = 0; $j < $jB; ++$j) { @@ -361,12 +320,12 @@ class Root extends PPS * * @param array $raList Reference to an array with all PPS's */ - public function _savePps(&$raList) + private function savePps(&$raList): void { // Save each PPS WK $iC = count($raList); for ($i = 0; $i < $iC; ++$i) { - fwrite($this->fileHandle, $raList[$i]->_getPpsWk()); + fwrite($this->fileHandle, $raList[$i]->getPpsWk()); } // Adjust for Block $iCnt = count($raList); @@ -383,7 +342,7 @@ class Root extends PPS * @param int $iBsize * @param int $iPpsCnt */ - public function _saveBbd($iSbdSize, $iBsize, $iPpsCnt) + private function saveBbd($iSbdSize, $iBsize, $iPpsCnt): void { $FILE = $this->fileHandle; // Calculate Basic Setting diff --git a/PhpOffice/PhpSpreadsheet/Shared/OLERead.php b/PhpOffice/PhpSpreadsheet/Shared/OLERead.php old mode 100755 new mode 100644 index 3af3970..b3e35c5 --- a/PhpOffice/PhpSpreadsheet/Shared/OLERead.php +++ b/PhpOffice/PhpSpreadsheet/Shared/OLERead.php @@ -92,27 +92,23 @@ class OLERead /** * Read the file. - * - * @param $pFilename string Filename - * - * @throws ReaderException */ - public function read($pFilename) + public function read(string $filename): void { - File::assertFile($pFilename); + File::assertFile($filename); // Get the file identifier // Don't bother reading the whole file until we know it's a valid OLE file - $this->data = file_get_contents($pFilename, false, null, 0, 8); + $this->data = file_get_contents($filename, false, null, 0, 8); // Check OLE identifier $identifierOle = pack('CCCCCCCC', 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1); if ($this->data != $identifierOle) { - throw new ReaderException('The filename ' . $pFilename . ' is not recognised as an OLE file'); + throw new ReaderException('The filename ' . $filename . ' is not recognised as an OLE file'); } // Get the file data - $this->data = file_get_contents($pFilename); + $this->data = file_get_contents($filename); // Total number of sectors used for the SAT $this->numBigBlockDepotBlocks = self::getInt4d($this->data, self::NUM_BIG_BLOCK_DEPOT_BLOCKS_POS); @@ -168,7 +164,6 @@ class OLERead $pos += 4 * $bbs; } - $pos = 0; $sbdBlock = $this->sbdStartBlock; $this->smallBlockChain = ''; while ($sbdBlock != -2) { @@ -182,7 +177,7 @@ class OLERead // read the directory stream $block = $this->rootStartBlock; - $this->entry = $this->_readData($block); + $this->entry = $this->readData($block); $this->readPropertySets(); } @@ -190,9 +185,9 @@ class OLERead /** * Extract binary stream data. * - * @param int $stream + * @param ?int $stream * - * @return string + * @return null|string */ public function getStream($stream) { @@ -203,7 +198,7 @@ class OLERead $streamData = ''; if ($this->props[$stream]['size'] < self::SMALL_BLOCK_THRESHOLD) { - $rootdata = $this->_readData($this->props[$this->rootentry]['startBlock']); + $rootdata = $this->readData($this->props[$this->rootentry]['startBlock']); $block = $this->props[$stream]['startBlock']; @@ -239,13 +234,12 @@ class OLERead /** * Read a standard stream (by joining sectors using information from SAT). * - * @param int $bl Sector ID where the stream starts + * @param int $block Sector ID where the stream starts * * @return string Data for standard stream */ - private function _readData($bl) + private function readData($block) { - $block = $bl; $data = ''; while ($block != -2) { @@ -260,7 +254,7 @@ class OLERead /** * Read entries in the directory stream. */ - private function readPropertySets() + private function readPropertySets(): void { $offset = 0; diff --git a/PhpOffice/PhpSpreadsheet/Shared/PasswordHasher.php b/PhpOffice/PhpSpreadsheet/Shared/PasswordHasher.php old mode 100755 new mode 100644 index 9b0080b..e9414f9 --- a/PhpOffice/PhpSpreadsheet/Shared/PasswordHasher.php +++ b/PhpOffice/PhpSpreadsheet/Shared/PasswordHasher.php @@ -2,36 +2,108 @@ namespace PhpOffice\PhpSpreadsheet\Shared; +use PhpOffice\PhpSpreadsheet\Exception as SpException; +use PhpOffice\PhpSpreadsheet\Worksheet\Protection; + class PasswordHasher { + const MAX_PASSWORD_LENGTH = 255; + + /** + * Get algorithm name for PHP. + */ + private static function getAlgorithm(string $algorithmName): string + { + if (!$algorithmName) { + return ''; + } + + // Mapping between algorithm name in Excel and algorithm name in PHP + $mapping = [ + Protection::ALGORITHM_MD2 => 'md2', + Protection::ALGORITHM_MD4 => 'md4', + Protection::ALGORITHM_MD5 => 'md5', + Protection::ALGORITHM_SHA_1 => 'sha1', + Protection::ALGORITHM_SHA_256 => 'sha256', + Protection::ALGORITHM_SHA_384 => 'sha384', + Protection::ALGORITHM_SHA_512 => 'sha512', + Protection::ALGORITHM_RIPEMD_128 => 'ripemd128', + Protection::ALGORITHM_RIPEMD_160 => 'ripemd160', + Protection::ALGORITHM_WHIRLPOOL => 'whirlpool', + ]; + + if (array_key_exists($algorithmName, $mapping)) { + return $mapping[$algorithmName]; + } + + throw new SpException('Unsupported password algorithm: ' . $algorithmName); + } + /** * Create a password hash from a given string. * - * This method is based on the algorithm provided by + * This method is based on the spec at: + * https://interoperability.blob.core.windows.net/files/MS-OFFCRYPTO/[MS-OFFCRYPTO].pdf + * 2.3.7.1 Binary Document Password Verifier Derivation Method 1 + * + * It replaces a method based on the algorithm provided by * Daniel Rentz of OpenOffice and the PEAR package * Spreadsheet_Excel_Writer by Xavier Noguer . * - * @param string $pPassword Password to hash + * Scrutinizer will squawk at the use of bitwise operations here, + * but it should ultimately pass. + * + * @param string $password Password to hash + */ + private static function defaultHashPassword(string $password): string + { + $verifier = 0; + $pwlen = strlen($password); + $passwordArray = pack('c', $pwlen) . $password; + for ($i = $pwlen; $i >= 0; --$i) { + $intermediate1 = (($verifier & 0x4000) === 0) ? 0 : 1; + $intermediate2 = 2 * $verifier; + $intermediate2 = $intermediate2 & 0x7fff; + $intermediate3 = $intermediate1 | $intermediate2; + $verifier = $intermediate3 ^ ord($passwordArray[$i]); + } + $verifier ^= 0xCE4B; + + return strtoupper(dechex($verifier)); + } + + /** + * Create a password hash from a given string by a specific algorithm. + * + * 2.4.2.4 ISO Write Protection Method + * + * @see https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-offcrypto/1357ea58-646e-4483-92ef-95d718079d6f + * + * @param string $password Password to hash + * @param string $algorithm Hash algorithm used to compute the password hash value + * @param string $salt Pseudorandom string + * @param int $spinCount Number of times to iterate on a hash of a password * * @return string Hashed password */ - public static function hashPassword($pPassword) + public static function hashPassword(string $password, string $algorithm = '', string $salt = '', int $spinCount = 10000): string { - $password = 0x0000; - $charPos = 1; // char position - - // split the plain text password in its component characters - $chars = preg_split('//', $pPassword, -1, PREG_SPLIT_NO_EMPTY); - foreach ($chars as $char) { - $value = ord($char) << $charPos++; // shifted ASCII value - $rotated_bits = $value >> 15; // rotated bits beyond bit 15 - $value &= 0x7fff; // first 15 bits - $password ^= ($value | $rotated_bits); + if (strlen($password) > self::MAX_PASSWORD_LENGTH) { + throw new SpException('Password exceeds ' . self::MAX_PASSWORD_LENGTH . ' characters'); + } + $phpAlgorithm = self::getAlgorithm($algorithm); + if (!$phpAlgorithm) { + return self::defaultHashPassword($password); } - $password ^= strlen($pPassword); - $password ^= 0xCE4B; + $saltValue = base64_decode($salt); + $encodedPassword = mb_convert_encoding($password, 'UCS-2LE', 'UTF-8'); - return strtoupper(dechex($password)); + $hashValue = hash($phpAlgorithm, $saltValue . /** @scrutinizer ignore-type */ $encodedPassword, true); + for ($i = 0; $i < $spinCount; ++$i) { + $hashValue = hash($phpAlgorithm, $hashValue . pack('L', $i), true); + } + + return base64_encode($hashValue); } } diff --git a/PhpOffice/PhpSpreadsheet/Shared/StringHelper.php b/PhpOffice/PhpSpreadsheet/Shared/StringHelper.php old mode 100755 new mode 100644 index a570277..30bd8c5 --- a/PhpOffice/PhpSpreadsheet/Shared/StringHelper.php +++ b/PhpOffice/PhpSpreadsheet/Shared/StringHelper.php @@ -2,15 +2,8 @@ namespace PhpOffice\PhpSpreadsheet\Shared; -use PhpOffice\PhpSpreadsheet\Calculation\Calculation; - class StringHelper { - /** Constants */ - /** Regular Expressions */ - // Fraction - const STRING_REGEXP_FRACTION = '(-?)(\d+)\s+(\d+\/\d+)'; - /** * Control characters array. * @@ -28,14 +21,14 @@ class StringHelper /** * Decimal separator. * - * @var string + * @var ?string */ private static $decimalSeparator; /** * Thousands separator. * - * @var string + * @var ?string */ private static $thousandsSeparator; @@ -49,7 +42,7 @@ class StringHelper /** * Is iconv extension avalable? * - * @var bool + * @var ?bool */ private static $isIconvEnabled; @@ -63,7 +56,7 @@ class StringHelper /** * Build control characters array. */ - private static function buildControlCharacters() + private static function buildControlCharacters(): void { for ($i = 0; $i <= 31; ++$i) { if ($i != 9 && $i != 10 && $i != 13) { @@ -77,7 +70,7 @@ class StringHelper /** * Build SYLK characters array. */ - private static function buildSYLKCharacters() + private static function buildSYLKCharacters(): void { self::$SYLKCharacters = [ "\x1B 0" => chr(0), @@ -272,7 +265,7 @@ class StringHelper return self::$isIconvEnabled; } - private static function buildCharacterSets() + private static function buildCharacterSets(): void { if (empty(self::$controlCharacters)) { self::buildControlCharacters(); @@ -294,15 +287,15 @@ class StringHelper * So you could end up with something like _x0008_ in a string (either in a cell value () * element or in the shared string element. * - * @param string $value Value to unescape + * @param string $textValue Value to unescape * * @return string */ - public static function controlCharacterOOXML2PHP($value) + public static function controlCharacterOOXML2PHP($textValue) { self::buildCharacterSets(); - return str_replace(array_keys(self::$controlCharacters), array_values(self::$controlCharacters), $value); + return str_replace(array_keys(self::$controlCharacters), array_values(self::$controlCharacters), $textValue); } /** @@ -316,64 +309,63 @@ class StringHelper * So you could end up with something like _x0008_ in a string (either in a cell value () * element or in the shared string element. * - * @param string $value Value to escape + * @param string $textValue Value to escape * * @return string */ - public static function controlCharacterPHP2OOXML($value) + public static function controlCharacterPHP2OOXML($textValue) { self::buildCharacterSets(); - return str_replace(array_values(self::$controlCharacters), array_keys(self::$controlCharacters), $value); + return str_replace(array_values(self::$controlCharacters), array_keys(self::$controlCharacters), $textValue); } /** - * Try to sanitize UTF8, stripping invalid byte sequences. Not perfect. Does not surrogate characters. - * - * @param string $value - * - * @return string + * Try to sanitize UTF8, replacing invalid sequences with Unicode substitution characters. */ - public static function sanitizeUTF8($value) + public static function sanitizeUTF8(string $textValue): string { - if (self::getIsIconvEnabled()) { - $value = @iconv('UTF-8', 'UTF-8', $value); + $textValue = str_replace(["\xef\xbf\xbe", "\xef\xbf\xbf"], "\xef\xbf\xbd", $textValue); + $subst = mb_substitute_character(); // default is question mark + mb_substitute_character(65533); // Unicode substitution character + // Phpstan does not think this can return false. + $returnValue = mb_convert_encoding($textValue, 'UTF-8', 'UTF-8'); + mb_substitute_character(/** @scrutinizer ignore-type */ $subst); - return $value; - } + return self::returnString($returnValue); + } - $value = mb_convert_encoding($value, 'UTF-8', 'UTF-8'); - - return $value; + /** + * Strictly to satisfy Scrutinizer. + * + * @param mixed $value + */ + private static function returnString($value): string + { + return is_string($value) ? $value : ''; } /** * Check if a string contains UTF8 data. - * - * @param string $value - * - * @return bool */ - public static function isUTF8($value) + public static function isUTF8(string $textValue): bool { - return $value === '' || preg_match('/^./su', $value) === 1; + return $textValue === self::sanitizeUTF8($textValue); } /** * Formats a numeric value as a string for output in various output writers forcing * point as decimal separator in case locale is other than English. * - * @param mixed $value - * - * @return string + * @param float|int|string $numericValue */ - public static function formatNumber($value) + public static function formatNumber($numericValue): string { - if (is_float($value)) { - return str_replace(',', '.', $value); + if (is_float($numericValue)) { + return str_replace(',', '.', (string) $numericValue); } - return (string) $value; + return (string) $numericValue; } /** @@ -383,25 +375,23 @@ class StringHelper * although this will give wrong results for non-ASCII strings * see OpenOffice.org's Documentation of the Microsoft Excel File Format, sect. 2.5.3. * - * @param string $value UTF-8 encoded string + * @param string $textValue UTF-8 encoded string * @param mixed[] $arrcRuns Details of rich text runs in $value - * - * @return string */ - public static function UTF8toBIFF8UnicodeShort($value, $arrcRuns = []) + public static function UTF8toBIFF8UnicodeShort(string $textValue, array $arrcRuns = []): string { // character count - $ln = self::countCharacters($value, 'UTF-8'); + $ln = self::countCharacters($textValue, 'UTF-8'); // option flags if (empty($arrcRuns)) { $data = pack('CC', $ln, 0x0001); // characters - $data .= self::convertEncoding($value, 'UTF-16LE', 'UTF-8'); + $data .= self::convertEncoding($textValue, 'UTF-16LE', 'UTF-8'); } else { $data = pack('vC', $ln, 0x09); $data .= pack('v', count($arrcRuns)); // characters - $data .= self::convertEncoding($value, 'UTF-16LE', 'UTF-8'); + $data .= self::convertEncoding($textValue, 'UTF-16LE', 'UTF-8'); foreach ($arrcRuns as $cRun) { $data .= pack('v', $cRun['strlen']); $data .= pack('v', $cRun['fontidx']); @@ -418,131 +408,118 @@ class StringHelper * although this will give wrong results for non-ASCII strings * see OpenOffice.org's Documentation of the Microsoft Excel File Format, sect. 2.5.3. * - * @param string $value UTF-8 encoded string - * - * @return string + * @param string $textValue UTF-8 encoded string */ - public static function UTF8toBIFF8UnicodeLong($value) + public static function UTF8toBIFF8UnicodeLong(string $textValue): string { // character count - $ln = self::countCharacters($value, 'UTF-8'); + $ln = self::countCharacters($textValue, 'UTF-8'); // characters - $chars = self::convertEncoding($value, 'UTF-16LE', 'UTF-8'); + $chars = self::convertEncoding($textValue, 'UTF-16LE', 'UTF-8'); - $data = pack('vC', $ln, 0x0001) . $chars; - - return $data; + return pack('vC', $ln, 0x0001) . $chars; } /** * Convert string from one encoding to another. * - * @param string $value * @param string $to Encoding to convert to, e.g. 'UTF-8' * @param string $from Encoding to convert from, e.g. 'UTF-16LE' - * - * @return string */ - public static function convertEncoding($value, $to, $from) + public static function convertEncoding(string $textValue, string $to, string $from): string { if (self::getIsIconvEnabled()) { - $result = iconv($from, $to . self::$iconvOptions, $value); + $result = iconv($from, $to . self::$iconvOptions, $textValue); if (false !== $result) { return $result; } } - return mb_convert_encoding($value, $to, $from); + return self::returnString(mb_convert_encoding($textValue, $to, $from)); } /** * Get character count. * - * @param string $value - * @param string $enc Encoding + * @param string $encoding Encoding * * @return int Character count */ - public static function countCharacters($value, $enc = 'UTF-8') + public static function countCharacters(string $textValue, string $encoding = 'UTF-8'): int { - return mb_strlen($value, $enc); + return mb_strlen($textValue, $encoding); } /** * Get a substring of a UTF-8 encoded string. * - * @param string $pValue UTF-8 encoded string - * @param int $pStart Start offset - * @param int $pLength Maximum number of characters in substring - * - * @return string + * @param string $textValue UTF-8 encoded string + * @param int $offset Start offset + * @param ?int $length Maximum number of characters in substring */ - public static function substring($pValue, $pStart, $pLength = 0) + public static function substring(string $textValue, int $offset, ?int $length = 0): string { - return mb_substr($pValue, $pStart, $pLength, 'UTF-8'); + return mb_substr($textValue, $offset, $length, 'UTF-8'); } /** * Convert a UTF-8 encoded string to upper case. * - * @param string $pValue UTF-8 encoded string - * - * @return string + * @param string $textValue UTF-8 encoded string */ - public static function strToUpper($pValue) + public static function strToUpper(string $textValue): string { - return mb_convert_case($pValue, MB_CASE_UPPER, 'UTF-8'); + return mb_convert_case($textValue, MB_CASE_UPPER, 'UTF-8'); } /** * Convert a UTF-8 encoded string to lower case. * - * @param string $pValue UTF-8 encoded string - * - * @return string + * @param string $textValue UTF-8 encoded string */ - public static function strToLower($pValue) + public static function strToLower(string $textValue): string { - return mb_convert_case($pValue, MB_CASE_LOWER, 'UTF-8'); + return mb_convert_case($textValue, MB_CASE_LOWER, 'UTF-8'); } /** * Convert a UTF-8 encoded string to title/proper case * (uppercase every first character in each word, lower case all other characters). * - * @param string $pValue UTF-8 encoded string - * - * @return string + * @param string $textValue UTF-8 encoded string */ - public static function strToTitle($pValue) + public static function strToTitle(string $textValue): string { - return mb_convert_case($pValue, MB_CASE_TITLE, 'UTF-8'); + return mb_convert_case($textValue, MB_CASE_TITLE, 'UTF-8'); } - public static function mbIsUpper($char) + public static function mbIsUpper(string $character): bool { - return mb_strtolower($char, 'UTF-8') != $char; + return mb_strtolower($character, 'UTF-8') !== $character; } - public static function mbStrSplit($string) + /** + * Splits a UTF-8 string into an array of individual characters. + */ + public static function mbStrSplit(string $string): array { // Split at all position not after the start: ^ // and not before the end: $ - return preg_split('/(?_calculateFormulaValue($fractionFormula); - - return true; - } - - return false; - } - - // function convertToNumberIfFraction() - /** * Get the decimal separator. If it has not yet been set explicitly, try to obtain number * formatting information from locale. - * - * @return string */ - public static function getDecimalSeparator() + public static function getDecimalSeparator(): string { if (!isset(self::$decimalSeparator)) { $localeconv = localeconv(); @@ -603,20 +555,18 @@ class StringHelper * Set the decimal separator. Only used by NumberFormat::toFormattedString() * to format output by \PhpOffice\PhpSpreadsheet\Writer\Html and \PhpOffice\PhpSpreadsheet\Writer\Pdf. * - * @param string $pValue Character for decimal separator + * @param string $separator Character for decimal separator */ - public static function setDecimalSeparator($pValue) + public static function setDecimalSeparator(string $separator): void { - self::$decimalSeparator = $pValue; + self::$decimalSeparator = $separator; } /** * Get the thousands separator. If it has not yet been set explicitly, try to obtain number * formatting information from locale. - * - * @return string */ - public static function getThousandsSeparator() + public static function getThousandsSeparator(): string { if (!isset(self::$thousandsSeparator)) { $localeconv = localeconv(); @@ -636,20 +586,18 @@ class StringHelper * Set the thousands separator. Only used by NumberFormat::toFormattedString() * to format output by \PhpOffice\PhpSpreadsheet\Writer\Html and \PhpOffice\PhpSpreadsheet\Writer\Pdf. * - * @param string $pValue Character for thousands separator + * @param string $separator Character for thousands separator */ - public static function setThousandsSeparator($pValue) + public static function setThousandsSeparator(string $separator): void { - self::$thousandsSeparator = $pValue; + self::$thousandsSeparator = $separator; } /** * Get the currency code. If it has not yet been set explicitly, try to obtain the * symbol information from locale. - * - * @return string */ - public static function getCurrencyCode() + public static function getCurrencyCode(): string { if (!empty(self::$currencyCode)) { return self::$currencyCode; @@ -674,51 +622,51 @@ class StringHelper * Set the currency code. Only used by NumberFormat::toFormattedString() * to format output by \PhpOffice\PhpSpreadsheet\Writer\Html and \PhpOffice\PhpSpreadsheet\Writer\Pdf. * - * @param string $pValue Character for currency code + * @param string $currencyCode Character for currency code */ - public static function setCurrencyCode($pValue) + public static function setCurrencyCode(string $currencyCode): void { - self::$currencyCode = $pValue; + self::$currencyCode = $currencyCode; } /** * Convert SYLK encoded string to UTF-8. * - * @param string $pValue + * @param string $textValue SYLK encoded string * * @return string UTF-8 encoded string */ - public static function SYLKtoUTF8($pValue) + public static function SYLKtoUTF8(string $textValue): string { self::buildCharacterSets(); // If there is no escape character in the string there is nothing to do - if (strpos($pValue, '') === false) { - return $pValue; + if (strpos($textValue, '') === false) { + return $textValue; } foreach (self::$SYLKCharacters as $k => $v) { - $pValue = str_replace($k, $v, $pValue); + $textValue = str_replace($k, $v, $textValue); } - return $pValue; + return $textValue; } /** * Retrieve any leading numeric part of a string, or return the full string if no leading numeric * (handles basic integer or float, but not exponent or non decimal). * - * @param string $value + * @param string $textValue * * @return mixed string or only the leading numeric part of the string */ - public static function testStringAsNumeric($value) + public static function testStringAsNumeric($textValue) { - if (is_numeric($value)) { - return $value; + if (is_numeric($textValue)) { + return $textValue; } - $v = (float) $value; + $v = (float) $textValue; - return (is_numeric(substr($value, 0, strlen($v)))) ? $v : $value; + return (is_numeric(substr($textValue, 0, strlen((string) $v)))) ? $v : $textValue; } } diff --git a/PhpOffice/PhpSpreadsheet/Shared/TimeZone.php b/PhpOffice/PhpSpreadsheet/Shared/TimeZone.php old mode 100755 new mode 100644 index e5a99b9..324e342 --- a/PhpOffice/PhpSpreadsheet/Shared/TimeZone.php +++ b/PhpOffice/PhpSpreadsheet/Shared/TimeZone.php @@ -17,26 +17,26 @@ class TimeZone /** * Validate a Timezone name. * - * @param string $timezone Time zone (e.g. 'Europe/London') + * @param string $timezoneName Time zone (e.g. 'Europe/London') * * @return bool Success or failure */ - private static function validateTimeZone($timezone) + private static function validateTimeZone(string $timezoneName): bool { - return in_array($timezone, DateTimeZone::listIdentifiers()); + return in_array($timezoneName, DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC), true); } /** * Set the Default Timezone used for date/time conversions. * - * @param string $timezone Time zone (e.g. 'Europe/London') + * @param string $timezoneName Time zone (e.g. 'Europe/London') * * @return bool Success or failure */ - public static function setTimeZone($timezone) + public static function setTimeZone(string $timezoneName): bool { - if (self::validateTimezone($timezone)) { - self::$timezone = $timezone; + if (self::validateTimeZone($timezoneName)) { + self::$timezone = $timezoneName; return true; } @@ -49,7 +49,7 @@ class TimeZone * * @return string Timezone (e.g. 'Europe/London') */ - public static function getTimeZone() + public static function getTimeZone(): string { return self::$timezone; } @@ -58,30 +58,20 @@ class TimeZone * Return the Timezone offset used for date/time conversions to/from UST * This requires both the timezone and the calculated date/time to allow for local DST. * - * @param string $timezone The timezone for finding the adjustment to UST - * @param int $timestamp PHP date/time value - * - * @throws PhpSpreadsheetException + * @param ?string $timezoneName The timezone for finding the adjustment to UST + * @param float|int $timestamp PHP date/time value * * @return int Number of seconds for timezone adjustment */ - public static function getTimeZoneAdjustment($timezone, $timestamp) + public static function getTimeZoneAdjustment(?string $timezoneName, $timestamp): int { - if ($timezone !== null) { - if (!self::validateTimezone($timezone)) { - throw new PhpSpreadsheetException('Invalid timezone ' . $timezone); - } - } else { - $timezone = self::$timezone; + $timezoneName = $timezoneName ?? self::$timezone; + $dtobj = Date::dateTimeFromTimestamp("$timestamp"); + if (!self::validateTimeZone($timezoneName)) { + throw new PhpSpreadsheetException("Invalid timezone $timezoneName"); } + $dtobj->setTimeZone(new DateTimeZone($timezoneName)); - if ($timezone == 'UST') { - return 0; - } - - $objTimezone = new DateTimeZone($timezone); - $transitions = $objTimezone->getTransitions($timestamp, $timestamp); - - return (count($transitions) > 0) ? $transitions[0]['offset'] : 0; + return $dtobj->getOffset(); } } diff --git a/PhpOffice/PhpSpreadsheet/Shared/Trend/BestFit.php b/PhpOffice/PhpSpreadsheet/Shared/Trend/BestFit.php old mode 100755 new mode 100644 index d8e63d5..b2b0d94 --- a/PhpOffice/PhpSpreadsheet/Shared/Trend/BestFit.php +++ b/PhpOffice/PhpSpreadsheet/Shared/Trend/BestFit.php @@ -2,7 +2,7 @@ namespace PhpOffice\PhpSpreadsheet\Shared\Trend; -class BestFit +abstract class BestFit { /** * Indicator flag for a calculation error. @@ -96,24 +96,18 @@ class BestFit * * @param float $xValue X-Value * - * @return bool Y-Value + * @return float Y-Value */ - public function getValueOfYForX($xValue) - { - return false; - } + abstract public function getValueOfYForX($xValue); /** * Return the X-Value for a specified value of Y. * * @param float $yValue Y-Value * - * @return bool X-Value + * @return float X-Value */ - public function getValueOfXForY($yValue) - { - return false; - } + abstract public function getValueOfXForY($yValue); /** * Return the original set of X-Values. @@ -130,12 +124,9 @@ class BestFit * * @param int $dp Number of places of decimal precision to display * - * @return bool + * @return string */ - public function getEquation($dp = 0) - { - return false; - } + abstract public function getEquation($dp = 0); /** * Return the Slope of the line. @@ -341,20 +332,32 @@ class BestFit return $this->yBestFitValues; } - protected function calculateGoodnessOfFit($sumX, $sumY, $sumX2, $sumY2, $sumXY, $meanX, $meanY, $const) + /** @var mixed */ + private static $scrutinizerZeroPointZero = 0.0; + + /** + * @param mixed $x + * @param mixed $y + */ + private static function scrutinizerLooseCompare($x, $y): bool { - $SSres = $SScov = $SScor = $SStot = $SSsex = 0.0; + return $x == $y; + } + + protected function calculateGoodnessOfFit($sumX, $sumY, $sumX2, $sumY2, $sumXY, $meanX, $meanY, $const): void + { + $SSres = $SScov = $SStot = $SSsex = 0.0; foreach ($this->xValues as $xKey => $xValue) { $bestFitY = $this->yBestFitValues[$xKey] = $this->getValueOfYForX($xValue); $SSres += ($this->yValues[$xKey] - $bestFitY) * ($this->yValues[$xKey] - $bestFitY); - if ($const) { + if ($const === true) { $SStot += ($this->yValues[$xKey] - $meanY) * ($this->yValues[$xKey] - $meanY); } else { $SStot += $this->yValues[$xKey] * $this->yValues[$xKey]; } $SScov += ($this->xValues[$xKey] - $meanX) * ($this->yValues[$xKey] - $meanY); - if ($const) { + if ($const === true) { $SSsex += ($this->xValues[$xKey] - $meanX) * ($this->xValues[$xKey] - $meanX); } else { $SSsex += $this->xValues[$xKey] * $this->xValues[$xKey]; @@ -362,14 +365,15 @@ class BestFit } $this->SSResiduals = $SSres; - $this->DFResiduals = $this->valueCount - 1 - $const; + $this->DFResiduals = $this->valueCount - 1 - ($const === true ? 1 : 0); if ($this->DFResiduals == 0.0) { $this->stdevOfResiduals = 0.0; } else { $this->stdevOfResiduals = sqrt($SSres / $this->DFResiduals); } - if (($SStot == 0.0) || ($SSres == $SStot)) { + // Scrutinizer thinks $SSres == $SStot is always true. It is wrong. + if ($SStot == self::$scrutinizerZeroPointZero || self::scrutinizerLooseCompare($SSres, $SStot)) { $this->goodnessOfFit = 1; } else { $this->goodnessOfFit = 1 - ($SSres / $SStot); @@ -377,7 +381,7 @@ class BestFit $this->SSRegression = $this->goodnessOfFit * $SStot; $this->covariance = $SScov / $this->valueCount; - $this->correlation = ($this->valueCount * $sumXY - $sumX * $sumY) / sqrt(($this->valueCount * $sumX2 - pow($sumX, 2)) * ($this->valueCount * $sumY2 - pow($sumY, 2))); + $this->correlation = ($this->valueCount * $sumXY - $sumX * $sumY) / sqrt(($this->valueCount * $sumX2 - $sumX ** 2) * ($this->valueCount * $sumY2 - $sumY ** 2)); $this->slopeSE = $this->stdevOfResiduals / sqrt($SSsex); $this->intersectSE = $this->stdevOfResiduals * sqrt(1 / ($this->valueCount - ($sumX * $sumX) / $sumX2)); if ($this->SSResiduals != 0.0) { @@ -395,27 +399,39 @@ class BestFit } } + private function sumSquares(array $values) + { + return array_sum( + array_map( + function ($value) { + return $value ** 2; + }, + $values + ) + ); + } + /** * @param float[] $yValues * @param float[] $xValues - * @param bool $const */ - protected function leastSquareFit(array $yValues, array $xValues, $const) + protected function leastSquareFit(array $yValues, array $xValues, bool $const): void { // calculate sums - $x_sum = array_sum($xValues); - $y_sum = array_sum($yValues); - $meanX = $x_sum / $this->valueCount; - $meanY = $y_sum / $this->valueCount; - $mBase = $mDivisor = $xx_sum = $xy_sum = $yy_sum = 0.0; + $sumValuesX = array_sum($xValues); + $sumValuesY = array_sum($yValues); + $meanValueX = $sumValuesX / $this->valueCount; + $meanValueY = $sumValuesY / $this->valueCount; + $sumSquaresX = $this->sumSquares($xValues); + $sumSquaresY = $this->sumSquares($yValues); + $mBase = $mDivisor = 0.0; + $xy_sum = 0.0; for ($i = 0; $i < $this->valueCount; ++$i) { $xy_sum += $xValues[$i] * $yValues[$i]; - $xx_sum += $xValues[$i] * $xValues[$i]; - $yy_sum += $yValues[$i] * $yValues[$i]; - if ($const) { - $mBase += ($xValues[$i] - $meanX) * ($yValues[$i] - $meanY); - $mDivisor += ($xValues[$i] - $meanX) * ($xValues[$i] - $meanX); + if ($const === true) { + $mBase += ($xValues[$i] - $meanValueX) * ($yValues[$i] - $meanValueY); + $mDivisor += ($xValues[$i] - $meanValueX) * ($xValues[$i] - $meanValueX); } else { $mBase += $xValues[$i] * $yValues[$i]; $mDivisor += $xValues[$i] * $xValues[$i]; @@ -426,13 +442,9 @@ class BestFit $this->slope = $mBase / $mDivisor; // calculate intersect - if ($const) { - $this->intersect = $meanY - ($this->slope * $meanX); - } else { - $this->intersect = 0; - } + $this->intersect = ($const === true) ? $meanValueY - ($this->slope * $meanValueX) : 0.0; - $this->calculateGoodnessOfFit($x_sum, $y_sum, $xx_sum, $yy_sum, $xy_sum, $meanX, $meanY, $const); + $this->calculateGoodnessOfFit($sumValuesX, $sumValuesY, $sumSquaresX, $sumSquaresY, $xy_sum, $meanValueX, $meanValueY, $const); } /** @@ -440,23 +452,22 @@ class BestFit * * @param float[] $yValues The set of Y-values for this regression * @param float[] $xValues The set of X-values for this regression - * @param bool $const */ - public function __construct($yValues, $xValues = [], $const = true) + public function __construct($yValues, $xValues = []) { // Calculate number of points - $nY = count($yValues); - $nX = count($xValues); + $yValueCount = count($yValues); + $xValueCount = count($xValues); // Define X Values if necessary - if ($nX == 0) { - $xValues = range(1, $nY); - } elseif ($nY != $nX) { + if ($xValueCount === 0) { + $xValues = range(1, $yValueCount); + } elseif ($yValueCount !== $xValueCount) { // Ensure both arrays of points are the same size $this->error = true; } - $this->valueCount = $nY; + $this->valueCount = $yValueCount; $this->xValues = $xValues; $this->yValues = $yValues; } diff --git a/PhpOffice/PhpSpreadsheet/Shared/Trend/ExponentialBestFit.php b/PhpOffice/PhpSpreadsheet/Shared/Trend/ExponentialBestFit.php old mode 100755 new mode 100644 index 5b57f4b..eb8cd74 --- a/PhpOffice/PhpSpreadsheet/Shared/Trend/ExponentialBestFit.php +++ b/PhpOffice/PhpSpreadsheet/Shared/Trend/ExponentialBestFit.php @@ -21,7 +21,7 @@ class ExponentialBestFit extends BestFit */ public function getValueOfYForX($xValue) { - return $this->getIntersect() * pow($this->getSlope(), ($xValue - $this->xOffset)); + return $this->getIntersect() * $this->getSlope() ** ($xValue - $this->xOffset); } /** @@ -88,20 +88,17 @@ class ExponentialBestFit extends BestFit * * @param float[] $yValues The set of Y-values for this regression * @param float[] $xValues The set of X-values for this regression - * @param bool $const */ - private function exponentialRegression($yValues, $xValues, $const) + private function exponentialRegression(array $yValues, array $xValues, bool $const): void { - foreach ($yValues as &$value) { - if ($value < 0.0) { - $value = 0 - log(abs($value)); - } elseif ($value > 0.0) { - $value = log($value); - } - } - unset($value); + $adjustedYValues = array_map( + function ($value) { + return ($value < 0.0) ? 0 - log(abs($value)) : log($value); + }, + $yValues + ); - $this->leastSquareFit($yValues, $xValues, $const); + $this->leastSquareFit($adjustedYValues, $xValues, $const); } /** @@ -116,7 +113,7 @@ class ExponentialBestFit extends BestFit parent::__construct($yValues, $xValues); if (!$this->error) { - $this->exponentialRegression($yValues, $xValues, $const); + $this->exponentialRegression($yValues, $xValues, (bool) $const); } } } diff --git a/PhpOffice/PhpSpreadsheet/Shared/Trend/LinearBestFit.php b/PhpOffice/PhpSpreadsheet/Shared/Trend/LinearBestFit.php old mode 100755 new mode 100644 index 217f096..65d6b4f --- a/PhpOffice/PhpSpreadsheet/Shared/Trend/LinearBestFit.php +++ b/PhpOffice/PhpSpreadsheet/Shared/Trend/LinearBestFit.php @@ -56,9 +56,8 @@ class LinearBestFit extends BestFit * * @param float[] $yValues The set of Y-values for this regression * @param float[] $xValues The set of X-values for this regression - * @param bool $const */ - private function linearRegression($yValues, $xValues, $const) + private function linearRegression(array $yValues, array $xValues, bool $const): void { $this->leastSquareFit($yValues, $xValues, $const); } @@ -75,7 +74,7 @@ class LinearBestFit extends BestFit parent::__construct($yValues, $xValues); if (!$this->error) { - $this->linearRegression($yValues, $xValues, $const); + $this->linearRegression($yValues, $xValues, (bool) $const); } } } diff --git a/PhpOffice/PhpSpreadsheet/Shared/Trend/LogarithmicBestFit.php b/PhpOffice/PhpSpreadsheet/Shared/Trend/LogarithmicBestFit.php old mode 100755 new mode 100644 index 96ca2ed..2366dc6 --- a/PhpOffice/PhpSpreadsheet/Shared/Trend/LogarithmicBestFit.php +++ b/PhpOffice/PhpSpreadsheet/Shared/Trend/LogarithmicBestFit.php @@ -48,7 +48,7 @@ class LogarithmicBestFit extends BestFit $slope = $this->getSlope($dp); $intersect = $this->getIntersect($dp); - return 'Y = ' . $intersect . ' + ' . $slope . ' * log(X)'; + return 'Y = ' . $slope . ' * log(' . $intersect . ' * X)'; } /** @@ -56,20 +56,17 @@ class LogarithmicBestFit extends BestFit * * @param float[] $yValues The set of Y-values for this regression * @param float[] $xValues The set of X-values for this regression - * @param bool $const */ - private function logarithmicRegression($yValues, $xValues, $const) + private function logarithmicRegression(array $yValues, array $xValues, bool $const): void { - foreach ($xValues as &$value) { - if ($value < 0.0) { - $value = 0 - log(abs($value)); - } elseif ($value > 0.0) { - $value = log($value); - } - } - unset($value); + $adjustedYValues = array_map( + function ($value) { + return ($value < 0.0) ? 0 - log(abs($value)) : log($value); + }, + $yValues + ); - $this->leastSquareFit($yValues, $xValues, $const); + $this->leastSquareFit($adjustedYValues, $xValues, $const); } /** @@ -84,7 +81,7 @@ class LogarithmicBestFit extends BestFit parent::__construct($yValues, $xValues); if (!$this->error) { - $this->logarithmicRegression($yValues, $xValues, $const); + $this->logarithmicRegression($yValues, $xValues, (bool) $const); } } } diff --git a/PhpOffice/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php b/PhpOffice/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php old mode 100755 new mode 100644 index a151049..a216359 --- a/PhpOffice/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php +++ b/PhpOffice/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php @@ -2,8 +2,12 @@ namespace PhpOffice\PhpSpreadsheet\Shared\Trend; -use PhpOffice\PhpSpreadsheet\Shared\JAMA\Matrix; +use Matrix\Matrix; +// Phpstan and Scrutinizer seem to have legitimate complaints. +// $this->slope is specified where an array is expected in several places. +// But it seems that it should always be float. +// This code is probably not exercised at all in unit tests. class PolynomialBestFit extends BestFit { /** @@ -42,9 +46,11 @@ class PolynomialBestFit extends BestFit { $retVal = $this->getIntersect(); $slope = $this->getSlope(); + // Phpstan and Scrutinizer are both correct - getSlope returns float, not array. + // @phpstan-ignore-next-line foreach ($slope as $key => $value) { if ($value != 0.0) { - $retVal += $value * pow($xValue, $key + 1); + $retVal += $value * $xValue ** ($key + 1); } } @@ -76,6 +82,8 @@ class PolynomialBestFit extends BestFit $intersect = $this->getIntersect($dp); $equation = 'Y = ' . $intersect; + // Phpstan and Scrutinizer are both correct - getSlope returns float, not array. + // @phpstan-ignore-next-line foreach ($slope as $key => $value) { if ($value != 0.0) { $equation .= ' + ' . $value . ' * X'; @@ -93,16 +101,18 @@ class PolynomialBestFit extends BestFit * * @param int $dp Number of places of decimal precision to display * - * @return string + * @return float */ public function getSlope($dp = 0) { if ($dp != 0) { $coefficients = []; + // Scrutinizer is correct - $this->slope is float, not array. foreach ($this->slope as $coefficient) { $coefficients[] = round($coefficient, $dp); } + // @phpstan-ignore-next-line return $coefficients; } @@ -111,6 +121,8 @@ class PolynomialBestFit extends BestFit public function getCoefficients($dp = 0) { + // Phpstan and Scrutinizer are both correct - getSlope returns float, not array. + // @phpstan-ignore-next-line return array_merge([$this->getIntersect($dp)], $this->getSlope($dp)); } @@ -121,7 +133,7 @@ class PolynomialBestFit extends BestFit * @param float[] $yValues The set of Y-values for this regression * @param float[] $xValues The set of X-values for this regression */ - private function polynomialRegression($order, $yValues, $xValues) + private function polynomialRegression($order, $yValues, $xValues): void { // calculate sums $x_sum = array_sum($xValues); @@ -144,7 +156,7 @@ class PolynomialBestFit extends BestFit $B = []; for ($i = 0; $i < $this->valueCount; ++$i) { for ($j = 0; $j <= $order; ++$j) { - $A[$i][$j] = pow($xValues[$i], $j); + $A[$i][$j] = $xValues[$i] ** $j; } } for ($i = 0; $i < $this->valueCount; ++$i) { @@ -155,9 +167,9 @@ class PolynomialBestFit extends BestFit $C = $matrixA->solve($matrixB); $coefficients = []; - for ($i = 0; $i < $C->getRowDimension(); ++$i) { - $r = $C->get($i, 0); - if (abs($r) <= pow(10, -9)) { + for ($i = 0; $i < $C->rows; ++$i) { + $r = $C->getValue($i + 1, 1); // row and column are origin-1 + if (abs($r) <= 10 ** (-9)) { $r = 0; } $coefficients[] = $r; @@ -178,9 +190,8 @@ class PolynomialBestFit extends BestFit * @param int $order Order of Polynomial for this regression * @param float[] $yValues The set of Y-values for this regression * @param float[] $xValues The set of X-values for this regression - * @param bool $const */ - public function __construct($order, $yValues, $xValues = [], $const = true) + public function __construct($order, $yValues, $xValues = []) { parent::__construct($yValues, $xValues); diff --git a/PhpOffice/PhpSpreadsheet/Shared/Trend/PowerBestFit.php b/PhpOffice/PhpSpreadsheet/Shared/Trend/PowerBestFit.php old mode 100755 new mode 100644 index 4eefec8..cafd011 --- a/PhpOffice/PhpSpreadsheet/Shared/Trend/PowerBestFit.php +++ b/PhpOffice/PhpSpreadsheet/Shared/Trend/PowerBestFit.php @@ -21,7 +21,7 @@ class PowerBestFit extends BestFit */ public function getValueOfYForX($xValue) { - return $this->getIntersect() * pow(($xValue - $this->xOffset), $this->getSlope()); + return $this->getIntersect() * ($xValue - $this->xOffset) ** $this->getSlope(); } /** @@ -33,7 +33,7 @@ class PowerBestFit extends BestFit */ public function getValueOfXForY($yValue) { - return pow((($yValue + $this->yOffset) / $this->getIntersect()), (1 / $this->getSlope())); + return (($yValue + $this->yOffset) / $this->getIntersect()) ** (1 / $this->getSlope()); } /** @@ -72,28 +72,23 @@ class PowerBestFit extends BestFit * * @param float[] $yValues The set of Y-values for this regression * @param float[] $xValues The set of X-values for this regression - * @param bool $const */ - private function powerRegression($yValues, $xValues, $const) + private function powerRegression(array $yValues, array $xValues, bool $const): void { - foreach ($xValues as &$value) { - if ($value < 0.0) { - $value = 0 - log(abs($value)); - } elseif ($value > 0.0) { - $value = log($value); - } - } - unset($value); - foreach ($yValues as &$value) { - if ($value < 0.0) { - $value = 0 - log(abs($value)); - } elseif ($value > 0.0) { - $value = log($value); - } - } - unset($value); + $adjustedYValues = array_map( + function ($value) { + return ($value < 0.0) ? 0 - log(abs($value)) : log($value); + }, + $yValues + ); + $adjustedXValues = array_map( + function ($value) { + return ($value < 0.0) ? 0 - log(abs($value)) : log($value); + }, + $xValues + ); - $this->leastSquareFit($yValues, $xValues, $const); + $this->leastSquareFit($adjustedYValues, $adjustedXValues, $const); } /** @@ -108,7 +103,7 @@ class PowerBestFit extends BestFit parent::__construct($yValues, $xValues); if (!$this->error) { - $this->powerRegression($yValues, $xValues, $const); + $this->powerRegression($yValues, $xValues, (bool) $const); } } } diff --git a/PhpOffice/PhpSpreadsheet/Shared/Trend/Trend.php b/PhpOffice/PhpSpreadsheet/Shared/Trend/Trend.php old mode 100755 new mode 100644 index 1b7b390..929f59b --- a/PhpOffice/PhpSpreadsheet/Shared/Trend/Trend.php +++ b/PhpOffice/PhpSpreadsheet/Shared/Trend/Trend.php @@ -44,7 +44,7 @@ class Trend /** * Cached results for each method when trying to identify which provides the best fit. * - * @var bestFit[] + * @var BestFit[] */ private static $trendCache = []; @@ -55,10 +55,9 @@ class Trend $nX = count($xValues); // Define X Values if necessary - if ($nX == 0) { + if ($nX === 0) { $xValues = range(1, $nY); - $nX = $nY; - } elseif ($nY != $nX) { + } elseif ($nY !== $nX) { // Ensure both arrays of points are the same size trigger_error('Trend(): Number of elements in coordinate arrays do not match.', E_USER_ERROR); } @@ -73,6 +72,7 @@ class Trend case self::TREND_POWER: if (!isset(self::$trendCache[$key])) { $className = '\PhpOffice\PhpSpreadsheet\Shared\Trend\\' . $trendType . 'BestFit'; + // @phpstan-ignore-next-line self::$trendCache[$key] = new $className($yValues, $xValues, $const); } @@ -83,8 +83,8 @@ class Trend case self::TREND_POLYNOMIAL_5: case self::TREND_POLYNOMIAL_6: if (!isset(self::$trendCache[$key])) { - $order = substr($trendType, -1); - self::$trendCache[$key] = new PolynomialBestFit($order, $yValues, $xValues, $const); + $order = (int) substr($trendType, -1); + self::$trendCache[$key] = new PolynomialBestFit($order, $yValues, $xValues); } return self::$trendCache[$key]; @@ -92,6 +92,8 @@ class Trend case self::TREND_BEST_FIT_NO_POLY: // If the request is to determine the best fit regression, then we test each Trend line in turn // Start by generating an instance of each available Trend method + $bestFit = []; + $bestFitValue = []; foreach (self::$trendTypes as $trendMethod) { $className = '\PhpOffice\PhpSpreadsheet\Shared\Trend\\' . $trendType . 'BestFit'; $bestFit[$trendMethod] = new $className($yValues, $xValues, $const); @@ -99,8 +101,8 @@ class Trend } if ($trendType != self::TREND_BEST_FIT_NO_POLY) { foreach (self::$trendTypePolynomialOrders as $trendMethod) { - $order = substr($trendMethod, -1); - $bestFit[$trendMethod] = new PolynomialBestFit($order, $yValues, $xValues, $const); + $order = (int) substr($trendMethod, -1); + $bestFit[$trendMethod] = new PolynomialBestFit($order, $yValues, $xValues); if ($bestFit[$trendMethod]->getError()) { unset($bestFit[$trendMethod]); } else { diff --git a/PhpOffice/PhpSpreadsheet/Shared/XMLWriter.php b/PhpOffice/PhpSpreadsheet/Shared/XMLWriter.php old mode 100755 new mode 100644 index 4f7a6a0..65bd7ec --- a/PhpOffice/PhpSpreadsheet/Shared/XMLWriter.php +++ b/PhpOffice/PhpSpreadsheet/Shared/XMLWriter.php @@ -4,6 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Shared; class XMLWriter extends \XMLWriter { + /** @var bool */ public static $debugEnabled = false; /** Temporary storage method */ @@ -20,23 +21,23 @@ class XMLWriter extends \XMLWriter /** * Create a new XMLWriter instance. * - * @param int $pTemporaryStorage Temporary storage location - * @param string $pTemporaryStorageFolder Temporary storage folder + * @param int $temporaryStorage Temporary storage location + * @param string $temporaryStorageFolder Temporary storage folder */ - public function __construct($pTemporaryStorage = self::STORAGE_MEMORY, $pTemporaryStorageFolder = null) + public function __construct($temporaryStorage = self::STORAGE_MEMORY, $temporaryStorageFolder = null) { // Open temporary storage - if ($pTemporaryStorage == self::STORAGE_MEMORY) { + if ($temporaryStorage == self::STORAGE_MEMORY) { $this->openMemory(); } else { // Create temporary filename - if ($pTemporaryStorageFolder === null) { - $pTemporaryStorageFolder = File::sysGetTempDir(); + if ($temporaryStorageFolder === null) { + $temporaryStorageFolder = File::sysGetTempDir(); } - $this->tempFileName = @tempnam($pTemporaryStorageFolder, 'xml'); + $this->tempFileName = (string) @tempnam($temporaryStorageFolder, 'xml'); // Open storage - if ($this->openUri($this->tempFileName) === false) { + if (empty($this->tempFileName) || $this->openUri($this->tempFileName) === false) { // Fallback to memory... $this->openMemory(); } @@ -54,7 +55,9 @@ class XMLWriter extends \XMLWriter public function __destruct() { // Unlink temporary files + // There is nothing reasonable to do if unlink fails. if ($this->tempFileName != '') { + /** @scrutinizer ignore-unhandled */ @unlink($this->tempFileName); } } @@ -71,22 +74,22 @@ class XMLWriter extends \XMLWriter } $this->flush(); - return file_get_contents($this->tempFileName); + return file_get_contents($this->tempFileName) ?: ''; } /** * Wrapper method for writeRaw. * - * @param string|string[] $text + * @param null|string|string[] $rawTextData * * @return bool */ - public function writeRawData($text) + public function writeRawData($rawTextData) { - if (is_array($text)) { - $text = implode("\n", $text); + if (is_array($rawTextData)) { + $rawTextData = implode("\n", $rawTextData); } - return $this->writeRaw(htmlspecialchars($text)); + return $this->writeRaw(htmlspecialchars($rawTextData ?? '')); } } diff --git a/PhpOffice/PhpSpreadsheet/Shared/Xls.php b/PhpOffice/PhpSpreadsheet/Shared/Xls.php old mode 100755 new mode 100644 index 51407ad..2c3198b --- a/PhpOffice/PhpSpreadsheet/Shared/Xls.php +++ b/PhpOffice/PhpSpreadsheet/Shared/Xls.php @@ -3,6 +3,7 @@ namespace PhpOffice\PhpSpreadsheet\Shared; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; +use PhpOffice\PhpSpreadsheet\Helper\Dimension; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; class Xls @@ -12,27 +13,27 @@ class Xls * x is the width in intrinsic Excel units (measuring width in number of normal characters) * This holds for Arial 10. * - * @param Worksheet $sheet The sheet + * @param Worksheet $worksheet The sheet * @param string $col The column * * @return int The width in pixels */ - public static function sizeCol($sheet, $col = 'A') + public static function sizeCol(Worksheet $worksheet, $col = 'A') { // default font of the workbook - $font = $sheet->getParent()->getDefaultStyle()->getFont(); + $font = $worksheet->getParent()->getDefaultStyle()->getFont(); - $columnDimensions = $sheet->getColumnDimensions(); + $columnDimensions = $worksheet->getColumnDimensions(); // first find the true column width in pixels (uncollapsed and unhidden) - if (isset($columnDimensions[$col]) and $columnDimensions[$col]->getWidth() != -1) { + if (isset($columnDimensions[$col]) && $columnDimensions[$col]->getWidth() != -1) { // then we have column dimension with explicit width $columnDimension = $columnDimensions[$col]; $width = $columnDimension->getWidth(); $pixelWidth = Drawing::cellDimensionToPixels($width, $font); - } elseif ($sheet->getDefaultColumnDimension()->getWidth() != -1) { + } elseif ($worksheet->getDefaultColumnDimension()->getWidth() != -1) { // then we have default column dimension with explicit width - $defaultColumnDimension = $sheet->getDefaultColumnDimension(); + $defaultColumnDimension = $worksheet->getDefaultColumnDimension(); $width = $defaultColumnDimension->getWidth(); $pixelWidth = Drawing::cellDimensionToPixels($width, $font); } else { @@ -41,7 +42,7 @@ class Xls } // now find the effective column width in pixels - if (isset($columnDimensions[$col]) and !$columnDimensions[$col]->getVisible()) { + if (isset($columnDimensions[$col]) && !$columnDimensions[$col]->getVisible()) { $effectivePixelWidth = 0; } else { $effectivePixelWidth = $pixelWidth; @@ -55,50 +56,48 @@ class Xls * the relationship is: y = 4/3x. If the height hasn't been set by the user we * use the default value. If the row is hidden we use a value of zero. * - * @param Worksheet $sheet The sheet + * @param Worksheet $worksheet The sheet * @param int $row The row index (1-based) * * @return int The width in pixels */ - public static function sizeRow($sheet, $row = 1) + public static function sizeRow(Worksheet $worksheet, $row = 1) { // default font of the workbook - $font = $sheet->getParent()->getDefaultStyle()->getFont(); + $font = $worksheet->getParent()->getDefaultStyle()->getFont(); - $rowDimensions = $sheet->getRowDimensions(); + $rowDimensions = $worksheet->getRowDimensions(); // first find the true row height in pixels (uncollapsed and unhidden) - if (isset($rowDimensions[$row]) and $rowDimensions[$row]->getRowHeight() != -1) { + if (isset($rowDimensions[$row]) && $rowDimensions[$row]->getRowHeight() != -1) { // then we have a row dimension $rowDimension = $rowDimensions[$row]; $rowHeight = $rowDimension->getRowHeight(); $pixelRowHeight = (int) ceil(4 * $rowHeight / 3); // here we assume Arial 10 - } elseif ($sheet->getDefaultRowDimension()->getRowHeight() != -1) { + } elseif ($worksheet->getDefaultRowDimension()->getRowHeight() != -1) { // then we have a default row dimension with explicit height - $defaultRowDimension = $sheet->getDefaultRowDimension(); - $rowHeight = $defaultRowDimension->getRowHeight(); - $pixelRowHeight = Drawing::pointsToPixels($rowHeight); + $defaultRowDimension = $worksheet->getDefaultRowDimension(); + $pixelRowHeight = $defaultRowDimension->getRowHeight(Dimension::UOM_PIXELS); } else { // we don't even have any default row dimension. Height depends on default font $pointRowHeight = Font::getDefaultRowHeightByFont($font); - $pixelRowHeight = Font::fontSizeToPixels($pointRowHeight); + $pixelRowHeight = Font::fontSizeToPixels((int) $pointRowHeight); } // now find the effective row height in pixels - if (isset($rowDimensions[$row]) and !$rowDimensions[$row]->getVisible()) { + if (isset($rowDimensions[$row]) && !$rowDimensions[$row]->getVisible()) { $effectivePixelRowHeight = 0; } else { $effectivePixelRowHeight = $pixelRowHeight; } - return $effectivePixelRowHeight; + return (int) $effectivePixelRowHeight; } /** * Get the horizontal distance in pixels between two anchors * The distanceX is found as sum of all the spanning columns widths minus correction for the two offsets. * - * @param Worksheet $sheet * @param string $startColumn * @param int $startOffsetX Offset within start cell measured in 1/1024 of the cell width * @param string $endColumn @@ -106,7 +105,7 @@ class Xls * * @return int Horizontal measured in pixels */ - public static function getDistanceX(Worksheet $sheet, $startColumn = 'A', $startOffsetX = 0, $endColumn = 'A', $endOffsetX = 0) + public static function getDistanceX(Worksheet $worksheet, $startColumn = 'A', $startOffsetX = 0, $endColumn = 'A', $endOffsetX = 0) { $distanceX = 0; @@ -114,14 +113,14 @@ class Xls $startColumnIndex = Coordinate::columnIndexFromString($startColumn); $endColumnIndex = Coordinate::columnIndexFromString($endColumn); for ($i = $startColumnIndex; $i <= $endColumnIndex; ++$i) { - $distanceX += self::sizeCol($sheet, Coordinate::stringFromColumnIndex($i)); + $distanceX += self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($i)); } // correct for offsetX in startcell - $distanceX -= (int) floor(self::sizeCol($sheet, $startColumn) * $startOffsetX / 1024); + $distanceX -= (int) floor(self::sizeCol($worksheet, $startColumn) * $startOffsetX / 1024); // correct for offsetX in endcell - $distanceX -= (int) floor(self::sizeCol($sheet, $endColumn) * (1 - $endOffsetX / 1024)); + $distanceX -= (int) floor(self::sizeCol($worksheet, $endColumn) * (1 - $endOffsetX / 1024)); return $distanceX; } @@ -130,7 +129,6 @@ class Xls * Get the vertical distance in pixels between two anchors * The distanceY is found as sum of all the spanning rows minus two offsets. * - * @param Worksheet $sheet * @param int $startRow (1-based) * @param int $startOffsetY Offset within start cell measured in 1/256 of the cell height * @param int $endRow (1-based) @@ -138,20 +136,20 @@ class Xls * * @return int Vertical distance measured in pixels */ - public static function getDistanceY(Worksheet $sheet, $startRow = 1, $startOffsetY = 0, $endRow = 1, $endOffsetY = 0) + public static function getDistanceY(Worksheet $worksheet, $startRow = 1, $startOffsetY = 0, $endRow = 1, $endOffsetY = 0) { $distanceY = 0; // add the widths of the spanning rows for ($row = $startRow; $row <= $endRow; ++$row) { - $distanceY += self::sizeRow($sheet, $row); + $distanceY += self::sizeRow($worksheet, $row); } // correct for offsetX in startcell - $distanceY -= (int) floor(self::sizeRow($sheet, $startRow) * $startOffsetY / 256); + $distanceY -= (int) floor(self::sizeRow($worksheet, $startRow) * $startOffsetY / 256); // correct for offsetX in endcell - $distanceY -= (int) floor(self::sizeRow($sheet, $endRow) * (1 - $endOffsetY / 256)); + $distanceY -= (int) floor(self::sizeRow($worksheet, $endRow) * (1 - $endOffsetY / 256)); return $distanceY; } @@ -200,19 +198,17 @@ class Xls * W is the width of the cell * H is the height of the cell * - * @param Worksheet $sheet * @param string $coordinates E.g. 'A1' * @param int $offsetX Horizontal offset in pixels * @param int $offsetY Vertical offset in pixels * @param int $width Width in pixels * @param int $height Height in pixels * - * @return array + * @return null|array */ - public static function oneAnchor2twoAnchor($sheet, $coordinates, $offsetX, $offsetY, $width, $height) + public static function oneAnchor2twoAnchor(Worksheet $worksheet, $coordinates, $offsetX, $offsetY, $width, $height) { - [$column, $row] = Coordinate::coordinateFromString($coordinates); - $col_start = Coordinate::columnIndexFromString($column); + [$col_start, $row] = Coordinate::indexesFromString($coordinates); $row_start = $row - 1; $x1 = $offsetX; @@ -223,10 +219,10 @@ class Xls $row_end = $row_start; // Row containing bottom right corner of object // Zero the specified offset if greater than the cell dimensions - if ($x1 >= self::sizeCol($sheet, Coordinate::stringFromColumnIndex($col_start))) { + if ($x1 >= self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_start))) { $x1 = 0; } - if ($y1 >= self::sizeRow($sheet, $row_start + 1)) { + if ($y1 >= self::sizeRow($worksheet, $row_start + 1)) { $y1 = 0; } @@ -234,42 +230,42 @@ class Xls $height = $height + $y1 - 1; // Subtract the underlying cell widths to find the end cell of the image - while ($width >= self::sizeCol($sheet, Coordinate::stringFromColumnIndex($col_end))) { - $width -= self::sizeCol($sheet, Coordinate::stringFromColumnIndex($col_end)); + while ($width >= self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_end))) { + $width -= self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_end)); ++$col_end; } // Subtract the underlying cell heights to find the end cell of the image - while ($height >= self::sizeRow($sheet, $row_end + 1)) { - $height -= self::sizeRow($sheet, $row_end + 1); + while ($height >= self::sizeRow($worksheet, $row_end + 1)) { + $height -= self::sizeRow($worksheet, $row_end + 1); ++$row_end; } // Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell // with zero height or width. - if (self::sizeCol($sheet, Coordinate::stringFromColumnIndex($col_start)) == 0) { - return; + if (self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_start)) == 0) { + return null; } - if (self::sizeCol($sheet, Coordinate::stringFromColumnIndex($col_end)) == 0) { - return; + if (self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_end)) == 0) { + return null; } - if (self::sizeRow($sheet, $row_start + 1) == 0) { - return; + if (self::sizeRow($worksheet, $row_start + 1) == 0) { + return null; } - if (self::sizeRow($sheet, $row_end + 1) == 0) { - return; + if (self::sizeRow($worksheet, $row_end + 1) == 0) { + return null; } // Convert the pixel values to the percentage value expected by Excel - $x1 = $x1 / self::sizeCol($sheet, Coordinate::stringFromColumnIndex($col_start)) * 1024; - $y1 = $y1 / self::sizeRow($sheet, $row_start + 1) * 256; - $x2 = ($width + 1) / self::sizeCol($sheet, Coordinate::stringFromColumnIndex($col_end)) * 1024; // Distance to right side of object - $y2 = ($height + 1) / self::sizeRow($sheet, $row_end + 1) * 256; // Distance to bottom of object + $x1 = $x1 / self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_start)) * 1024; + $y1 = $y1 / self::sizeRow($worksheet, $row_start + 1) * 256; + $x2 = ($width + 1) / self::sizeCol($worksheet, Coordinate::stringFromColumnIndex($col_end)) * 1024; // Distance to right side of object + $y2 = ($height + 1) / self::sizeRow($worksheet, $row_end + 1) * 256; // Distance to bottom of object $startCoordinates = Coordinate::stringFromColumnIndex($col_start) . ($row_start + 1); $endCoordinates = Coordinate::stringFromColumnIndex($col_end) . ($row_end + 1); - $twoAnchor = [ + return [ 'startCoordinates' => $startCoordinates, 'startOffsetX' => $x1, 'startOffsetY' => $y1, @@ -277,7 +273,5 @@ class Xls 'endOffsetX' => $x2, 'endOffsetY' => $y2, ]; - - return $twoAnchor; } } diff --git a/PhpOffice/PhpSpreadsheet/Spreadsheet.php b/PhpOffice/PhpSpreadsheet/Spreadsheet.php old mode 100755 new mode 100644 index 04a9f1d..1432ba4 --- a/PhpOffice/PhpSpreadsheet/Spreadsheet.php +++ b/PhpOffice/PhpSpreadsheet/Spreadsheet.php @@ -2,19 +2,27 @@ namespace PhpOffice\PhpSpreadsheet; +use JsonSerializable; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader; +use PhpOffice\PhpSpreadsheet\Shared\File; +use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Style\Style; use PhpOffice\PhpSpreadsheet\Worksheet\Iterator; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter; -class Spreadsheet +class Spreadsheet implements JsonSerializable { // Allowable values for workbook window visilbity const VISIBILITY_VISIBLE = 'visible'; const VISIBILITY_HIDDEN = 'hidden'; const VISIBILITY_VERY_HIDDEN = 'veryHidden'; - private static $workbookViewVisibilityValues = [ + private const DEFINED_NAME_IS_RANGE = false; + private const DEFINED_NAME_IS_FORMULA = true; + + private const WORKBOOK_VIEW_VISIBILITY_VALUES = [ self::VISIBILITY_VISIBLE, self::VISIBILITY_HIDDEN, self::VISIBILITY_VERY_HIDDEN, @@ -51,7 +59,7 @@ class Spreadsheet /** * Calculation Engine. * - * @var Calculation + * @var null|Calculation */ private $calculationEngine; @@ -65,9 +73,9 @@ class Spreadsheet /** * Named ranges. * - * @var NamedRange[] + * @var DefinedName[] */ - private $namedRanges = []; + private $definedNames = []; /** * CellXf supervisor. @@ -100,21 +108,21 @@ class Spreadsheet /** * macrosCode : all macros code as binary data (the vbaProject.bin file, this include form, code, etc.), null if no macro. * - * @var string + * @var null|string */ private $macrosCode; /** * macrosCertificate : if macros are signed, contains binary data vbaProjectSignature.bin file, null if not signed. * - * @var string + * @var null|string */ private $macrosCertificate; /** * ribbonXMLData : null if workbook is'nt Excel 2007 or not contain a customized UI. * - * @var null|string + * @var null|array{target: string, data: string} */ private $ribbonXMLData; @@ -210,7 +218,7 @@ class Spreadsheet * * @param bool $hasMacros true|false */ - public function setHasMacros($hasMacros) + public function setHasMacros($hasMacros): void { $this->hasMacros = (bool) $hasMacros; } @@ -220,7 +228,7 @@ class Spreadsheet * * @param string $macroCode string|null */ - public function setMacrosCode($macroCode) + public function setMacrosCode($macroCode): void { $this->macrosCode = $macroCode; $this->setHasMacros($macroCode !== null); @@ -241,7 +249,7 @@ class Spreadsheet * * @param null|string $certificate */ - public function setMacrosCertificate($certificate) + public function setMacrosCertificate($certificate): void { $this->macrosCertificate = $certificate; } @@ -269,7 +277,7 @@ class Spreadsheet /** * Remove all macros, certificate from spreadsheet. */ - public function discardMacros() + public function discardMacros(): void { $this->hasMacros = false; $this->macrosCode = null; @@ -282,7 +290,7 @@ class Spreadsheet * @param null|mixed $target * @param null|mixed $xmlData */ - public function setRibbonXMLData($target, $xmlData) + public function setRibbonXMLData($target, $xmlData): void { if ($target !== null && $xmlData !== null) { $this->ribbonXMLData = ['target' => $target, 'data' => $xmlData]; @@ -294,11 +302,9 @@ class Spreadsheet /** * retrieve ribbon XML Data. * - * return string|null|array - * * @param string $what * - * @return string + * @return null|array|string */ public function getRibbonXMLData($what = 'all') //we need some constants here... { @@ -311,7 +317,7 @@ class Spreadsheet break; case 'target': case 'data': - if (is_array($this->ribbonXMLData) && isset($this->ribbonXMLData[$what])) { + if (is_array($this->ribbonXMLData)) { $returnData = $this->ribbonXMLData[$what]; } @@ -327,7 +333,7 @@ class Spreadsheet * @param null|mixed $BinObjectsNames * @param null|mixed $BinObjectsData */ - public function setRibbonBinObjects($BinObjectsNames, $BinObjectsData) + public function setRibbonBinObjects($BinObjectsNames, $BinObjectsData): void { if ($BinObjectsNames !== null && $BinObjectsData !== null) { $this->ribbonBinObjects = ['names' => $BinObjectsNames, 'data' => $BinObjectsData]; @@ -354,10 +360,8 @@ class Spreadsheet * It has to be minimized when the library start to support currently unparsed data. * * @internal - * - * @param array $unparsedLoadedData */ - public function setUnparsedLoadedData(array $unparsedLoadedData) + public function setUnparsedLoadedData(array $unparsedLoadedData): void { $this->unparsedLoadedData = $unparsedLoadedData; } @@ -371,7 +375,9 @@ class Spreadsheet */ private function getExtensionOnly($path) { - return pathinfo($path, PATHINFO_EXTENSION); + $extension = pathinfo($path, PATHINFO_EXTENSION); + + return substr(/** @scrutinizer ignore-type */$extension, 0); } /** @@ -388,8 +394,6 @@ class Spreadsheet switch ($what) { case 'all': return $this->ribbonBinObjects; - - break; case 'names': case 'data': if (is_array($this->ribbonBinObjects) && isset($this->ribbonBinObjects[$what])) { @@ -398,8 +402,10 @@ class Spreadsheet break; case 'types': - if (is_array($this->ribbonBinObjects) && - isset($this->ribbonBinObjects['data']) && is_array($this->ribbonBinObjects['data'])) { + if ( + is_array($this->ribbonBinObjects) && + isset($this->ribbonBinObjects['data']) && is_array($this->ribbonBinObjects['data']) + ) { $tmpTypes = array_keys($this->ribbonBinObjects['data']); $ReturnData = array_unique(array_map([$this, 'getExtensionOnly'], $tmpTypes)); } else { @@ -435,27 +441,27 @@ class Spreadsheet /** * Check if a sheet with a specified code name already exists. * - * @param string $pSheetCodeName Name of the worksheet to check + * @param string $codeName Name of the worksheet to check * * @return bool */ - public function sheetCodeNameExists($pSheetCodeName) + public function sheetCodeNameExists($codeName) { - return $this->getSheetByCodeName($pSheetCodeName) !== null; + return $this->getSheetByCodeName($codeName) !== null; } /** * Get sheet by code name. Warning : sheet don't have always a code name ! * - * @param string $pName Sheet name + * @param string $codeName Sheet name * - * @return Worksheet + * @return null|Worksheet */ - public function getSheetByCodeName($pName) + public function getSheetByCodeName($codeName) { $worksheetCount = count($this->workSheetCollection); for ($i = 0; $i < $worksheetCount; ++$i) { - if ($this->workSheetCollection[$i]->getCodeName() == $pName) { + if ($this->workSheetCollection[$i]->getCodeName() == $codeName) { return $this->workSheetCollection[$i]; } } @@ -482,8 +488,8 @@ class Spreadsheet // Create document security $this->security = new Document\Security(); - // Set named ranges - $this->namedRanges = []; + // Set defined names + $this->definedNames = []; // Create the cellXf supervisor $this->cellXfSupervisor = new Style(true); @@ -499,29 +505,29 @@ class Spreadsheet */ public function __destruct() { - $this->calculationEngine = null; $this->disconnectWorksheets(); + $this->calculationEngine = null; + $this->cellXfCollection = []; + $this->cellStyleXfCollection = []; } /** * Disconnect all worksheets from this PhpSpreadsheet workbook object, * typically so that the PhpSpreadsheet object can be unset. */ - public function disconnectWorksheets() + public function disconnectWorksheets(): void { - $worksheet = null; - foreach ($this->workSheetCollection as $k => &$worksheet) { + foreach ($this->workSheetCollection as $worksheet) { $worksheet->disconnectCells(); - $this->workSheetCollection[$k] = null; + unset($worksheet); } - unset($worksheet); $this->workSheetCollection = []; } /** * Return the calculation engine for this worksheet. * - * @return Calculation + * @return null|Calculation */ public function getCalculationEngine() { @@ -540,12 +546,10 @@ class Spreadsheet /** * Set properties. - * - * @param Document\Properties $pValue */ - public function setProperties(Document\Properties $pValue) + public function setProperties(Document\Properties $documentProperties): void { - $this->properties = $pValue; + $this->properties = $documentProperties; } /** @@ -560,19 +564,15 @@ class Spreadsheet /** * Set security. - * - * @param Document\Security $pValue */ - public function setSecurity(Document\Security $pValue) + public function setSecurity(Document\Security $documentSecurity): void { - $this->security = $pValue; + $this->security = $documentSecurity; } /** * Get active sheet. * - * @throws Exception - * * @return Worksheet */ public function getActiveSheet() @@ -585,8 +585,6 @@ class Spreadsheet * * @param null|int $sheetIndex Index where sheet should go (0,1,..., or null for last) * - * @throws Exception - * * @return Worksheet */ public function createSheet($sheetIndex = null) @@ -600,80 +598,78 @@ class Spreadsheet /** * Check if a sheet with a specified name already exists. * - * @param string $pSheetName Name of the worksheet to check + * @param string $worksheetName Name of the worksheet to check * * @return bool */ - public function sheetNameExists($pSheetName) + public function sheetNameExists($worksheetName) { - return $this->getSheetByName($pSheetName) !== null; + return $this->getSheetByName($worksheetName) !== null; } /** * Add sheet. * - * @param Worksheet $pSheet - * @param null|int $iSheetIndex Index where sheet should go (0,1,..., or null for last) - * - * @throws Exception + * @param Worksheet $worksheet The worksheet to add + * @param null|int $sheetIndex Index where sheet should go (0,1,..., or null for last) * * @return Worksheet */ - public function addSheet(Worksheet $pSheet, $iSheetIndex = null) + public function addSheet(Worksheet $worksheet, $sheetIndex = null) { - if ($this->sheetNameExists($pSheet->getTitle())) { + if ($this->sheetNameExists($worksheet->getTitle())) { throw new Exception( - "Workbook already contains a worksheet named '{$pSheet->getTitle()}'. Rename this worksheet first." + "Workbook already contains a worksheet named '{$worksheet->getTitle()}'. Rename this worksheet first." ); } - if ($iSheetIndex === null) { + if ($sheetIndex === null) { if ($this->activeSheetIndex < 0) { $this->activeSheetIndex = 0; } - $this->workSheetCollection[] = $pSheet; + $this->workSheetCollection[] = $worksheet; } else { // Insert the sheet at the requested index array_splice( $this->workSheetCollection, - $iSheetIndex, + $sheetIndex, 0, - [$pSheet] + [$worksheet] ); // Adjust active sheet index if necessary - if ($this->activeSheetIndex >= $iSheetIndex) { + if ($this->activeSheetIndex >= $sheetIndex) { ++$this->activeSheetIndex; } } - if ($pSheet->getParent() === null) { - $pSheet->rebindParent($this); + if ($worksheet->getParent() === null) { // @phpstan-ignore-line + $worksheet->rebindParent($this); } - return $pSheet; + return $worksheet; } /** * Remove sheet by index. * - * @param int $pIndex Active sheet index - * - * @throws Exception + * @param int $sheetIndex Index position of the worksheet to remove */ - public function removeSheetByIndex($pIndex) + public function removeSheetByIndex($sheetIndex): void { $numSheets = count($this->workSheetCollection); - if ($pIndex > $numSheets - 1) { + if ($sheetIndex > $numSheets - 1) { throw new Exception( - "You tried to remove a sheet by the out of bounds index: {$pIndex}. The actual number of sheets is {$numSheets}." + "You tried to remove a sheet by the out of bounds index: {$sheetIndex}. The actual number of sheets is {$numSheets}." ); } - array_splice($this->workSheetCollection, $pIndex, 1); + array_splice($this->workSheetCollection, $sheetIndex, 1); // Adjust active sheet index if necessary - if (($this->activeSheetIndex >= $pIndex) && - ($pIndex > count($this->workSheetCollection) - 1)) { + if ( + ($this->activeSheetIndex >= $sheetIndex) && + ($this->activeSheetIndex > 0 || $numSheets <= 1) + ) { --$this->activeSheetIndex; } } @@ -681,23 +677,21 @@ class Spreadsheet /** * Get sheet by index. * - * @param int $pIndex Sheet index - * - * @throws Exception + * @param int $sheetIndex Sheet index * * @return Worksheet */ - public function getSheet($pIndex) + public function getSheet($sheetIndex) { - if (!isset($this->workSheetCollection[$pIndex])) { + if (!isset($this->workSheetCollection[$sheetIndex])) { $numSheets = $this->getSheetCount(); throw new Exception( - "Your requested sheet index: {$pIndex} is out of bounds. The actual number of sheets is {$numSheets}." + "Your requested sheet index: {$sheetIndex} is out of bounds. The actual number of sheets is {$numSheets}." ); } - return $this->workSheetCollection[$pIndex]; + return $this->workSheetCollection[$sheetIndex]; } /** @@ -713,15 +707,15 @@ class Spreadsheet /** * Get sheet by name. * - * @param string $pName Sheet name + * @param string $worksheetName Sheet name * * @return null|Worksheet */ - public function getSheetByName($pName) + public function getSheetByName($worksheetName) { $worksheetCount = count($this->workSheetCollection); for ($i = 0; $i < $worksheetCount; ++$i) { - if ($this->workSheetCollection[$i]->getTitle() === trim($pName, "'")) { + if ($this->workSheetCollection[$i]->getTitle() === trim($worksheetName, "'")) { return $this->workSheetCollection[$i]; } } @@ -729,19 +723,28 @@ class Spreadsheet return null; } + /** + * Get sheet by name, throwing exception if not found. + */ + public function getSheetByNameOrThrow(string $worksheetName): Worksheet + { + $worksheet = $this->getSheetByName($worksheetName); + if ($worksheet === null) { + throw new Exception("Sheet $worksheetName does not exist."); + } + + return $worksheet; + } + /** * Get index for sheet. * - * @param Worksheet $pSheet - * - * @throws Exception - * * @return int index */ - public function getIndex(Worksheet $pSheet) + public function getIndex(Worksheet $worksheet) { foreach ($this->workSheetCollection as $key => $value) { - if ($value->getHashCode() == $pSheet->getHashCode()) { + if ($value->getHashCode() === $worksheet->getHashCode()) { return $key; } } @@ -752,29 +755,27 @@ class Spreadsheet /** * Set index for sheet by sheet name. * - * @param string $sheetName Sheet name to modify index for - * @param int $newIndex New index for the sheet - * - * @throws Exception + * @param string $worksheetName Sheet name to modify index for + * @param int $newIndexPosition New index for the sheet * * @return int New sheet index */ - public function setIndexByName($sheetName, $newIndex) + public function setIndexByName($worksheetName, $newIndexPosition) { - $oldIndex = $this->getIndex($this->getSheetByName($sheetName)); - $pSheet = array_splice( + $oldIndex = $this->getIndex($this->getSheetByNameOrThrow($worksheetName)); + $worksheet = array_splice( $this->workSheetCollection, $oldIndex, 1 ); array_splice( $this->workSheetCollection, - $newIndex, + $newIndexPosition, 0, - $pSheet + $worksheet ); - return $newIndex; + return $newIndexPosition; } /** @@ -800,22 +801,20 @@ class Spreadsheet /** * Set active sheet index. * - * @param int $pIndex Active sheet index - * - * @throws Exception + * @param int $worksheetIndex Active sheet index * * @return Worksheet */ - public function setActiveSheetIndex($pIndex) + public function setActiveSheetIndex($worksheetIndex) { $numSheets = count($this->workSheetCollection); - if ($pIndex > $numSheets - 1) { + if ($worksheetIndex > $numSheets - 1) { throw new Exception( - "You tried to set a sheet active by the out of bounds index: {$pIndex}. The actual number of sheets is {$numSheets}." + "You tried to set a sheet active by the out of bounds index: {$worksheetIndex}. The actual number of sheets is {$numSheets}." ); } - $this->activeSheetIndex = $pIndex; + $this->activeSheetIndex = $worksheetIndex; return $this->getActiveSheet(); } @@ -823,21 +822,19 @@ class Spreadsheet /** * Set active sheet index by name. * - * @param string $pValue Sheet title - * - * @throws Exception + * @param string $worksheetName Sheet title * * @return Worksheet */ - public function setActiveSheetIndexByName($pValue) + public function setActiveSheetIndexByName($worksheetName) { - if (($worksheet = $this->getSheetByName($pValue)) instanceof Worksheet) { + if (($worksheet = $this->getSheetByName($worksheetName)) instanceof Worksheet) { $this->setActiveSheetIndex($this->getIndex($worksheet)); return $worksheet; } - throw new Exception('Workbook does not contain sheet:' . $pValue); + throw new Exception('Workbook does not contain sheet:' . $worksheetName); } /** @@ -859,90 +856,204 @@ class Spreadsheet /** * Add external sheet. * - * @param Worksheet $pSheet External sheet to add - * @param null|int $iSheetIndex Index where sheet should go (0,1,..., or null for last) - * - * @throws Exception + * @param Worksheet $worksheet External sheet to add + * @param null|int $sheetIndex Index where sheet should go (0,1,..., or null for last) * * @return Worksheet */ - public function addExternalSheet(Worksheet $pSheet, $iSheetIndex = null) + public function addExternalSheet(Worksheet $worksheet, $sheetIndex = null) { - if ($this->sheetNameExists($pSheet->getTitle())) { - throw new Exception("Workbook already contains a worksheet named '{$pSheet->getTitle()}'. Rename the external sheet first."); + if ($this->sheetNameExists($worksheet->getTitle())) { + throw new Exception("Workbook already contains a worksheet named '{$worksheet->getTitle()}'. Rename the external sheet first."); } // count how many cellXfs there are in this workbook currently, we will need this below $countCellXfs = count($this->cellXfCollection); // copy all the shared cellXfs from the external workbook and append them to the current - foreach ($pSheet->getParent()->getCellXfCollection() as $cellXf) { + foreach ($worksheet->getParent()->getCellXfCollection() as $cellXf) { $this->addCellXf(clone $cellXf); } // move sheet to this workbook - $pSheet->rebindParent($this); + $worksheet->rebindParent($this); // update the cellXfs - foreach ($pSheet->getCoordinates(false) as $coordinate) { - $cell = $pSheet->getCell($coordinate); + foreach ($worksheet->getCoordinates(false) as $coordinate) { + $cell = $worksheet->getCell($coordinate); $cell->setXfIndex($cell->getXfIndex() + $countCellXfs); } - return $this->addSheet($pSheet, $iSheetIndex); - } - - /** - * Get named ranges. - * - * @return NamedRange[] - */ - public function getNamedRanges() - { - return $this->namedRanges; - } - - /** - * Add named range. - * - * @param NamedRange $namedRange - * - * @return bool - */ - public function addNamedRange(NamedRange $namedRange) - { - if ($namedRange->getScope() == null) { - // global scope - $this->namedRanges[$namedRange->getName()] = $namedRange; - } else { - // local scope - $this->namedRanges[$namedRange->getScope()->getTitle() . '!' . $namedRange->getName()] = $namedRange; + // update the column dimensions Xfs + foreach ($worksheet->getColumnDimensions() as $columnDimension) { + $columnDimension->setXfIndex($columnDimension->getXfIndex() + $countCellXfs); } - return true; + // update the row dimensions Xfs + foreach ($worksheet->getRowDimensions() as $rowDimension) { + $xfIndex = $rowDimension->getXfIndex(); + if ($xfIndex !== null) { + $rowDimension->setXfIndex($xfIndex + $countCellXfs); + } + } + + return $this->addSheet($worksheet, $sheetIndex); + } + + /** + * Get an array of all Named Ranges. + * + * @return DefinedName[] + */ + public function getNamedRanges(): array + { + return array_filter( + $this->definedNames, + function (DefinedName $definedName) { + return $definedName->isFormula() === self::DEFINED_NAME_IS_RANGE; + } + ); + } + + /** + * Get an array of all Named Formulae. + * + * @return DefinedName[] + */ + public function getNamedFormulae(): array + { + return array_filter( + $this->definedNames, + function (DefinedName $definedName) { + return $definedName->isFormula() === self::DEFINED_NAME_IS_FORMULA; + } + ); + } + + /** + * Get an array of all Defined Names (both named ranges and named formulae). + * + * @return DefinedName[] + */ + public function getDefinedNames(): array + { + return $this->definedNames; + } + + /** + * Add a named range. + * If a named range with this name already exists, then this will replace the existing value. + */ + public function addNamedRange(NamedRange $namedRange): void + { + $this->addDefinedName($namedRange); + } + + /** + * Add a named formula. + * If a named formula with this name already exists, then this will replace the existing value. + */ + public function addNamedFormula(NamedFormula $namedFormula): void + { + $this->addDefinedName($namedFormula); + } + + /** + * Add a defined name (either a named range or a named formula). + * If a defined named with this name already exists, then this will replace the existing value. + */ + public function addDefinedName(DefinedName $definedName): void + { + $upperCaseName = StringHelper::strToUpper($definedName->getName()); + if ($definedName->getScope() == null) { + // global scope + $this->definedNames[$upperCaseName] = $definedName; + } else { + // local scope + $this->definedNames[$definedName->getScope()->getTitle() . '!' . $upperCaseName] = $definedName; + } } /** * Get named range. * - * @param string $namedRange - * @param null|Worksheet $pSheet Scope. Use null for global scope - * - * @return null|NamedRange + * @param null|Worksheet $worksheet Scope. Use null for global scope */ - public function getNamedRange($namedRange, Worksheet $pSheet = null) + public function getNamedRange(string $namedRange, ?Worksheet $worksheet = null): ?NamedRange { $returnValue = null; - if ($namedRange != '' && ($namedRange !== null)) { + if ($namedRange !== '') { + $namedRange = StringHelper::strToUpper($namedRange); + // first look for global named range + $returnValue = $this->getGlobalDefinedNameByType($namedRange, self::DEFINED_NAME_IS_RANGE); + // then look for local named range (has priority over global named range if both names exist) + $returnValue = $this->getLocalDefinedNameByType($namedRange, self::DEFINED_NAME_IS_RANGE, $worksheet) ?: $returnValue; + } + + return $returnValue instanceof NamedRange ? $returnValue : null; + } + + /** + * Get named formula. + * + * @param null|Worksheet $worksheet Scope. Use null for global scope + */ + public function getNamedFormula(string $namedFormula, ?Worksheet $worksheet = null): ?NamedFormula + { + $returnValue = null; + + if ($namedFormula !== '') { + $namedFormula = StringHelper::strToUpper($namedFormula); + // first look for global named formula + $returnValue = $this->getGlobalDefinedNameByType($namedFormula, self::DEFINED_NAME_IS_FORMULA); + // then look for local named formula (has priority over global named formula if both names exist) + $returnValue = $this->getLocalDefinedNameByType($namedFormula, self::DEFINED_NAME_IS_FORMULA, $worksheet) ?: $returnValue; + } + + return $returnValue instanceof NamedFormula ? $returnValue : null; + } + + private function getGlobalDefinedNameByType(string $name, bool $type): ?DefinedName + { + if (isset($this->definedNames[$name]) && $this->definedNames[$name]->isFormula() === $type) { + return $this->definedNames[$name]; + } + + return null; + } + + private function getLocalDefinedNameByType(string $name, bool $type, ?Worksheet $worksheet = null): ?DefinedName + { + if ( + ($worksheet !== null) && isset($this->definedNames[$worksheet->getTitle() . '!' . $name]) + && $this->definedNames[$worksheet->getTitle() . '!' . $name]->isFormula() === $type + ) { + return $this->definedNames[$worksheet->getTitle() . '!' . $name]; + } + + return null; + } + + /** + * Get named range. + * + * @param null|Worksheet $worksheet Scope. Use null for global scope + */ + public function getDefinedName(string $definedName, ?Worksheet $worksheet = null): ?DefinedName + { + $returnValue = null; + + if ($definedName !== '') { + $definedName = StringHelper::strToUpper($definedName); // first look for global defined name - if (isset($this->namedRanges[$namedRange])) { - $returnValue = $this->namedRanges[$namedRange]; + if (isset($this->definedNames[$definedName])) { + $returnValue = $this->definedNames[$definedName]; } // then look for local defined name (has priority over global defined name if both names exist) - if (($pSheet !== null) && isset($this->namedRanges[$pSheet->getTitle() . '!' . $namedRange])) { - $returnValue = $this->namedRanges[$pSheet->getTitle() . '!' . $namedRange]; + if (($worksheet !== null) && isset($this->definedNames[$worksheet->getTitle() . '!' . $definedName])) { + $returnValue = $this->definedNames[$worksheet->getTitle() . '!' . $definedName]; } } @@ -952,20 +1063,55 @@ class Spreadsheet /** * Remove named range. * - * @param string $namedRange - * @param null|Worksheet $pSheet scope: use null for global scope + * @param null|Worksheet $worksheet scope: use null for global scope * - * @return Spreadsheet + * @return $this */ - public function removeNamedRange($namedRange, Worksheet $pSheet = null) + public function removeNamedRange(string $namedRange, ?Worksheet $worksheet = null): self { - if ($pSheet === null) { - if (isset($this->namedRanges[$namedRange])) { - unset($this->namedRanges[$namedRange]); + if ($this->getNamedRange($namedRange, $worksheet) === null) { + return $this; + } + + return $this->removeDefinedName($namedRange, $worksheet); + } + + /** + * Remove named formula. + * + * @param null|Worksheet $worksheet scope: use null for global scope + * + * @return $this + */ + public function removeNamedFormula(string $namedFormula, ?Worksheet $worksheet = null): self + { + if ($this->getNamedFormula($namedFormula, $worksheet) === null) { + return $this; + } + + return $this->removeDefinedName($namedFormula, $worksheet); + } + + /** + * Remove defined name. + * + * @param null|Worksheet $worksheet scope: use null for global scope + * + * @return $this + */ + public function removeDefinedName(string $definedName, ?Worksheet $worksheet = null): self + { + $definedName = StringHelper::strToUpper($definedName); + + if ($worksheet === null) { + if (isset($this->definedNames[$definedName])) { + unset($this->definedNames[$definedName]); } } else { - if (isset($this->namedRanges[$pSheet->getTitle() . '!' . $namedRange])) { - unset($this->namedRanges[$pSheet->getTitle() . '!' . $namedRange]); + if (isset($this->definedNames[$worksheet->getTitle() . '!' . $definedName])) { + unset($this->definedNames[$worksheet->getTitle() . '!' . $definedName]); + } elseif (isset($this->definedNames[$definedName])) { + unset($this->definedNames[$definedName]); } } @@ -989,27 +1135,24 @@ class Spreadsheet */ public function copy() { - $copied = clone $this; + $filename = File::temporaryFilename(); + $writer = new XlsxWriter($this); + $writer->setIncludeCharts(true); + $writer->save($filename); - $worksheetCount = count($this->workSheetCollection); - for ($i = 0; $i < $worksheetCount; ++$i) { - $this->workSheetCollection[$i] = $this->workSheetCollection[$i]->copy(); - $this->workSheetCollection[$i]->rebindParent($this); - } + $reader = new XlsxReader(); + $reader->setIncludeCharts(true); + $reloadedSpreadsheet = $reader->load($filename); + unlink($filename); - return $copied; + return $reloadedSpreadsheet; } - /** - * Implement PHP __clone to create a deep clone, not just a shallow copy. - */ public function __clone() { - foreach ($this as $key => $val) { - if (is_object($val) || (is_array($val))) { - $this->{$key} = unserialize(serialize($val)); - } - } + throw new Exception( + 'Do not use clone on spreadsheet. Use spreadsheet->copy() instead.' + ); } /** @@ -1025,26 +1168,26 @@ class Spreadsheet /** * Get cellXf by index. * - * @param int $pIndex + * @param int $cellStyleIndex * * @return Style */ - public function getCellXfByIndex($pIndex) + public function getCellXfByIndex($cellStyleIndex) { - return $this->cellXfCollection[$pIndex]; + return $this->cellXfCollection[$cellStyleIndex]; } /** * Get cellXf by hash code. * - * @param string $pValue + * @param string $hashcode * * @return false|Style */ - public function getCellXfByHashCode($pValue) + public function getCellXfByHashCode($hashcode) { foreach ($this->cellXfCollection as $cellXf) { - if ($cellXf->getHashCode() == $pValue) { + if ($cellXf->getHashCode() === $hashcode) { return $cellXf; } } @@ -1055,20 +1198,16 @@ class Spreadsheet /** * Check if style exists in style collection. * - * @param Style $pCellStyle - * * @return bool */ - public function cellXfExists($pCellStyle) + public function cellXfExists(Style $cellStyleIndex) { - return in_array($pCellStyle, $this->cellXfCollection, true); + return in_array($cellStyleIndex, $this->cellXfCollection, true); } /** * Get default style. * - * @throws Exception - * * @return Style */ public function getDefaultStyle() @@ -1082,10 +1221,8 @@ class Spreadsheet /** * Add a cellXf to the workbook. - * - * @param Style $style */ - public function addCellXf(Style $style) + public function addCellXf(Style $style): void { $this->cellXfCollection[] = $style; $style->setIndex(count($this->cellXfCollection) - 1); @@ -1094,28 +1231,26 @@ class Spreadsheet /** * Remove cellXf by index. It is ensured that all cells get their xf index updated. * - * @param int $pIndex Index to cellXf - * - * @throws Exception + * @param int $cellStyleIndex Index to cellXf */ - public function removeCellXfByIndex($pIndex) + public function removeCellXfByIndex($cellStyleIndex): void { - if ($pIndex > count($this->cellXfCollection) - 1) { + if ($cellStyleIndex > count($this->cellXfCollection) - 1) { throw new Exception('CellXf index is out of bounds.'); } // first remove the cellXf - array_splice($this->cellXfCollection, $pIndex, 1); + array_splice($this->cellXfCollection, $cellStyleIndex, 1); // then update cellXf indexes for cells foreach ($this->workSheetCollection as $worksheet) { foreach ($worksheet->getCoordinates(false) as $coordinate) { $cell = $worksheet->getCell($coordinate); $xfIndex = $cell->getXfIndex(); - if ($xfIndex > $pIndex) { + if ($xfIndex > $cellStyleIndex) { // decrease xf index by 1 $cell->setXfIndex($xfIndex - 1); - } elseif ($xfIndex == $pIndex) { + } elseif ($xfIndex == $cellStyleIndex) { // set to default xf index 0 $cell->setXfIndex(0); } @@ -1146,26 +1281,26 @@ class Spreadsheet /** * Get cellStyleXf by index. * - * @param int $pIndex Index to cellXf + * @param int $cellStyleIndex Index to cellXf * * @return Style */ - public function getCellStyleXfByIndex($pIndex) + public function getCellStyleXfByIndex($cellStyleIndex) { - return $this->cellStyleXfCollection[$pIndex]; + return $this->cellStyleXfCollection[$cellStyleIndex]; } /** * Get cellStyleXf by hash code. * - * @param string $pValue + * @param string $hashcode * * @return false|Style */ - public function getCellStyleXfByHashCode($pValue) + public function getCellStyleXfByHashCode($hashcode) { foreach ($this->cellStyleXfCollection as $cellStyleXf) { - if ($cellStyleXf->getHashCode() == $pValue) { + if ($cellStyleXf->getHashCode() === $hashcode) { return $cellStyleXf; } } @@ -1175,35 +1310,31 @@ class Spreadsheet /** * Add a cellStyleXf to the workbook. - * - * @param Style $pStyle */ - public function addCellStyleXf(Style $pStyle) + public function addCellStyleXf(Style $style): void { - $this->cellStyleXfCollection[] = $pStyle; - $pStyle->setIndex(count($this->cellStyleXfCollection) - 1); + $this->cellStyleXfCollection[] = $style; + $style->setIndex(count($this->cellStyleXfCollection) - 1); } /** * Remove cellStyleXf by index. * - * @param int $pIndex Index to cellXf - * - * @throws Exception + * @param int $cellStyleIndex Index to cellXf */ - public function removeCellStyleXfByIndex($pIndex) + public function removeCellStyleXfByIndex($cellStyleIndex): void { - if ($pIndex > count($this->cellStyleXfCollection) - 1) { + if ($cellStyleIndex > count($this->cellStyleXfCollection) - 1) { throw new Exception('CellStyleXf index is out of bounds.'); } - array_splice($this->cellStyleXfCollection, $pIndex, 1); + array_splice($this->cellStyleXfCollection, $cellStyleIndex, 1); } /** * Eliminate all unneeded cellXf and afterwards update the xfIndex for all cells * and columns in the workbook. */ - public function garbageCollect() + public function garbageCollect(): void { // how many references are there to each cellXf ? $countReferencesCellXf = []; @@ -1234,6 +1365,7 @@ class Spreadsheet // remove cellXfs without references and create mapping so we can update xfIndex // for all cells and columns $countNeededCellXfs = 0; + $map = []; foreach ($this->cellXfCollection as $index => $cellXf) { if ($countReferencesCellXf[$index] > 0 || $index == 0) { // we must never remove the first cellXf ++$countNeededCellXfs; @@ -1304,7 +1436,7 @@ class Spreadsheet * * @param bool $showHorizontalScroll True if horizonal scroll bar is visible */ - public function setShowHorizontalScroll($showHorizontalScroll) + public function setShowHorizontalScroll($showHorizontalScroll): void { $this->showHorizontalScroll = (bool) $showHorizontalScroll; } @@ -1324,7 +1456,7 @@ class Spreadsheet * * @param bool $showVerticalScroll True if vertical scroll bar is visible */ - public function setShowVerticalScroll($showVerticalScroll) + public function setShowVerticalScroll($showVerticalScroll): void { $this->showVerticalScroll = (bool) $showVerticalScroll; } @@ -1344,7 +1476,7 @@ class Spreadsheet * * @param bool $showSheetTabs True if sheet tabs are visible */ - public function setShowSheetTabs($showSheetTabs) + public function setShowSheetTabs($showSheetTabs): void { $this->showSheetTabs = (bool) $showSheetTabs; } @@ -1364,7 +1496,7 @@ class Spreadsheet * * @param bool $minimized true if workbook window is minimized */ - public function setMinimized($minimized) + public function setMinimized($minimized): void { $this->minimized = (bool) $minimized; } @@ -1386,7 +1518,7 @@ class Spreadsheet * * @param bool $autoFilterDateGrouping true if workbook window is minimized */ - public function setAutoFilterDateGrouping($autoFilterDateGrouping) + public function setAutoFilterDateGrouping($autoFilterDateGrouping): void { $this->autoFilterDateGrouping = (bool) $autoFilterDateGrouping; } @@ -1405,10 +1537,8 @@ class Spreadsheet * Set the first sheet in the book view. * * @param int $firstSheetIndex First sheet in book view - * - * @throws Exception if the given value is invalid */ - public function setFirstSheetIndex($firstSheetIndex) + public function setFirstSheetIndex($firstSheetIndex): void { if ($firstSheetIndex >= 0) { $this->firstSheetIndex = (int) $firstSheetIndex; @@ -1443,17 +1573,15 @@ class Spreadsheet * Workbook window is hidden and cannot be shown in the * user interface. * - * @param string $visibility visibility status of the workbook - * - * @throws Exception if the given value is invalid + * @param null|string $visibility visibility status of the workbook */ - public function setVisibility($visibility) + public function setVisibility($visibility): void { if ($visibility === null) { $visibility = self::VISIBILITY_VISIBLE; } - if (in_array($visibility, self::$workbookViewVisibilityValues)) { + if (in_array($visibility, self::WORKBOOK_VIEW_VISIBILITY_VALUES)) { $this->visibility = $visibility; } else { throw new Exception('Invalid visibility value.'); @@ -1476,15 +1604,54 @@ class Spreadsheet * TabRatio is assumed to be out of 1000 of the horizontal window width. * * @param int $tabRatio Ratio between the tabs bar and the horizontal scroll bar - * - * @throws Exception if the given value is invalid */ - public function setTabRatio($tabRatio) + public function setTabRatio($tabRatio): void { - if ($tabRatio >= 0 || $tabRatio <= 1000) { + if ($tabRatio >= 0 && $tabRatio <= 1000) { $this->tabRatio = (int) $tabRatio; } else { throw new Exception('Tab ratio must be between 0 and 1000.'); } } + + public function reevaluateAutoFilters(bool $resetToMax): void + { + foreach ($this->workSheetCollection as $sheet) { + $filter = $sheet->getAutoFilter(); + if (!empty($filter->getRange())) { + if ($resetToMax) { + $filter->setRangeToMaxRow(); + } + $filter->showHideRows(); + } + } + } + + /** + * Silliness to mollify Scrutinizer. + * + * @codeCoverageIgnore + */ + public function getSharedComponent(): Style + { + return new Style(); + } + + /** + * @throws Exception + * + * @return mixed + */ + public function __serialize() + { + throw new Exception('Spreadsheet objects cannot be serialized'); + } + + /** + * @throws Exception + */ + public function jsonSerialize(): mixed + { + throw new Exception('Spreadsheet objects cannot be json encoded'); + } } diff --git a/PhpOffice/PhpSpreadsheet/Style/Alignment.php b/PhpOffice/PhpSpreadsheet/Style/Alignment.php old mode 100755 new mode 100644 index b4df792..68edfac --- a/PhpOffice/PhpSpreadsheet/Style/Alignment.php +++ b/PhpOffice/PhpSpreadsheet/Style/Alignment.php @@ -15,6 +15,27 @@ class Alignment extends Supervisor const HORIZONTAL_JUSTIFY = 'justify'; const HORIZONTAL_FILL = 'fill'; const HORIZONTAL_DISTRIBUTED = 'distributed'; // Excel2007 only + private const HORIZONTAL_CENTER_CONTINUOUS_LC = 'centercontinuous'; + // Mapping for horizontal alignment + const HORIZONTAL_ALIGNMENT_FOR_XLSX = [ + self::HORIZONTAL_LEFT => self::HORIZONTAL_LEFT, + self::HORIZONTAL_RIGHT => self::HORIZONTAL_RIGHT, + self::HORIZONTAL_CENTER => self::HORIZONTAL_CENTER, + self::HORIZONTAL_CENTER_CONTINUOUS => self::HORIZONTAL_CENTER_CONTINUOUS, + self::HORIZONTAL_JUSTIFY => self::HORIZONTAL_JUSTIFY, + self::HORIZONTAL_FILL => self::HORIZONTAL_FILL, + self::HORIZONTAL_DISTRIBUTED => self::HORIZONTAL_DISTRIBUTED, + ]; + // Mapping for horizontal alignment CSS + const HORIZONTAL_ALIGNMENT_FOR_HTML = [ + self::HORIZONTAL_LEFT => self::HORIZONTAL_LEFT, + self::HORIZONTAL_RIGHT => self::HORIZONTAL_RIGHT, + self::HORIZONTAL_CENTER => self::HORIZONTAL_CENTER, + self::HORIZONTAL_CENTER_CONTINUOUS => self::HORIZONTAL_CENTER, + self::HORIZONTAL_JUSTIFY => self::HORIZONTAL_JUSTIFY, + //self::HORIZONTAL_FILL => self::HORIZONTAL_FILL, // no reasonable equivalent for fill + self::HORIZONTAL_DISTRIBUTED => self::HORIZONTAL_JUSTIFY, + ]; // Vertical alignment styles const VERTICAL_BOTTOM = 'bottom'; @@ -22,30 +43,73 @@ class Alignment extends Supervisor const VERTICAL_CENTER = 'center'; const VERTICAL_JUSTIFY = 'justify'; const VERTICAL_DISTRIBUTED = 'distributed'; // Excel2007 only + // Vertical alignment CSS + private const VERTICAL_BASELINE = 'baseline'; + private const VERTICAL_MIDDLE = 'middle'; + private const VERTICAL_SUB = 'sub'; + private const VERTICAL_SUPER = 'super'; + private const VERTICAL_TEXT_BOTTOM = 'text-bottom'; + private const VERTICAL_TEXT_TOP = 'text-top'; + + // Mapping for vertical alignment + const VERTICAL_ALIGNMENT_FOR_XLSX = [ + self::VERTICAL_BOTTOM => self::VERTICAL_BOTTOM, + self::VERTICAL_TOP => self::VERTICAL_TOP, + self::VERTICAL_CENTER => self::VERTICAL_CENTER, + self::VERTICAL_JUSTIFY => self::VERTICAL_JUSTIFY, + self::VERTICAL_DISTRIBUTED => self::VERTICAL_DISTRIBUTED, + // css settings that arent't in sync with Excel + self::VERTICAL_BASELINE => self::VERTICAL_BOTTOM, + self::VERTICAL_MIDDLE => self::VERTICAL_CENTER, + self::VERTICAL_SUB => self::VERTICAL_BOTTOM, + self::VERTICAL_SUPER => self::VERTICAL_TOP, + self::VERTICAL_TEXT_BOTTOM => self::VERTICAL_BOTTOM, + self::VERTICAL_TEXT_TOP => self::VERTICAL_TOP, + ]; + + // Mapping for vertical alignment for Html + const VERTICAL_ALIGNMENT_FOR_HTML = [ + self::VERTICAL_BOTTOM => self::VERTICAL_BOTTOM, + self::VERTICAL_TOP => self::VERTICAL_TOP, + self::VERTICAL_CENTER => self::VERTICAL_MIDDLE, + self::VERTICAL_JUSTIFY => self::VERTICAL_MIDDLE, + self::VERTICAL_DISTRIBUTED => self::VERTICAL_MIDDLE, + // css settings that arent't in sync with Excel + self::VERTICAL_BASELINE => self::VERTICAL_BASELINE, + self::VERTICAL_MIDDLE => self::VERTICAL_MIDDLE, + self::VERTICAL_SUB => self::VERTICAL_SUB, + self::VERTICAL_SUPER => self::VERTICAL_SUPER, + self::VERTICAL_TEXT_BOTTOM => self::VERTICAL_TEXT_BOTTOM, + self::VERTICAL_TEXT_TOP => self::VERTICAL_TEXT_TOP, + ]; // Read order const READORDER_CONTEXT = 0; const READORDER_LTR = 1; const READORDER_RTL = 2; + // Special value for Text Rotation + const TEXTROTATION_STACK_EXCEL = 255; + const TEXTROTATION_STACK_PHPSPREADSHEET = -165; // 90 - 255 + /** * Horizontal alignment. * - * @var string + * @var null|string */ protected $horizontal = self::HORIZONTAL_GENERAL; /** * Vertical alignment. * - * @var string + * @var null|string */ protected $vertical = self::VERTICAL_BOTTOM; /** * Text rotation. * - * @var int + * @var null|int */ protected $textRotation = 0; @@ -107,7 +171,10 @@ class Alignment extends Supervisor */ public function getSharedComponent() { - return $this->parent->getSharedComponent()->getAlignment(); + /** @var Style */ + $parent = $this->parent; + + return $parent->getSharedComponent()->getAlignment(); } /** @@ -136,38 +203,36 @@ class Alignment extends Supervisor * ); * * - * @param array $pStyles Array containing style information + * @param array $styleArray Array containing style information * - * @throws PhpSpreadsheetException - * - * @return Alignment + * @return $this */ - public function applyFromArray(array $pStyles) + public function applyFromArray(array $styleArray) { if ($this->isSupervisor) { $this->getActiveSheet()->getStyle($this->getSelectedCells()) - ->applyFromArray($this->getStyleArray($pStyles)); + ->applyFromArray($this->getStyleArray($styleArray)); } else { - if (isset($pStyles['horizontal'])) { - $this->setHorizontal($pStyles['horizontal']); + if (isset($styleArray['horizontal'])) { + $this->setHorizontal($styleArray['horizontal']); } - if (isset($pStyles['vertical'])) { - $this->setVertical($pStyles['vertical']); + if (isset($styleArray['vertical'])) { + $this->setVertical($styleArray['vertical']); } - if (isset($pStyles['textRotation'])) { - $this->setTextRotation($pStyles['textRotation']); + if (isset($styleArray['textRotation'])) { + $this->setTextRotation($styleArray['textRotation']); } - if (isset($pStyles['wrapText'])) { - $this->setWrapText($pStyles['wrapText']); + if (isset($styleArray['wrapText'])) { + $this->setWrapText($styleArray['wrapText']); } - if (isset($pStyles['shrinkToFit'])) { - $this->setShrinkToFit($pStyles['shrinkToFit']); + if (isset($styleArray['shrinkToFit'])) { + $this->setShrinkToFit($styleArray['shrinkToFit']); } - if (isset($pStyles['indent'])) { - $this->setIndent($pStyles['indent']); + if (isset($styleArray['indent'])) { + $this->setIndent($styleArray['indent']); } - if (isset($pStyles['readOrder'])) { - $this->setReadOrder($pStyles['readOrder']); + if (isset($styleArray['readOrder'])) { + $this->setReadOrder($styleArray['readOrder']); } } @@ -177,7 +242,7 @@ class Alignment extends Supervisor /** * Get Horizontal. * - * @return string + * @return null|string */ public function getHorizontal() { @@ -191,21 +256,22 @@ class Alignment extends Supervisor /** * Set Horizontal. * - * @param string $pValue see self::HORIZONTAL_* + * @param string $horizontalAlignment see self::HORIZONTAL_* * - * @return Alignment + * @return $this */ - public function setHorizontal($pValue) + public function setHorizontal(string $horizontalAlignment) { - if ($pValue == '') { - $pValue = self::HORIZONTAL_GENERAL; + $horizontalAlignment = strtolower($horizontalAlignment); + if ($horizontalAlignment === self::HORIZONTAL_CENTER_CONTINUOUS_LC) { + $horizontalAlignment = self::HORIZONTAL_CENTER_CONTINUOUS; } if ($this->isSupervisor) { - $styleArray = $this->getStyleArray(['horizontal' => $pValue]); + $styleArray = $this->getStyleArray(['horizontal' => $horizontalAlignment]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { - $this->horizontal = $pValue; + $this->horizontal = $horizontalAlignment; } return $this; @@ -214,7 +280,7 @@ class Alignment extends Supervisor /** * Get Vertical. * - * @return string + * @return null|string */ public function getVertical() { @@ -228,21 +294,19 @@ class Alignment extends Supervisor /** * Set Vertical. * - * @param string $pValue see self::VERTICAL_* + * @param string $verticalAlignment see self::VERTICAL_* * - * @return Alignment + * @return $this */ - public function setVertical($pValue) + public function setVertical($verticalAlignment) { - if ($pValue == '') { - $pValue = self::VERTICAL_BOTTOM; - } + $verticalAlignment = strtolower($verticalAlignment); if ($this->isSupervisor) { - $styleArray = $this->getStyleArray(['vertical' => $pValue]); + $styleArray = $this->getStyleArray(['vertical' => $verticalAlignment]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { - $this->vertical = $pValue; + $this->vertical = $verticalAlignment; } return $this; @@ -251,7 +315,7 @@ class Alignment extends Supervisor /** * Get TextRotation. * - * @return int + * @return null|int */ public function getTextRotation() { @@ -265,26 +329,24 @@ class Alignment extends Supervisor /** * Set TextRotation. * - * @param int $pValue + * @param int $angleInDegrees * - * @throws PhpSpreadsheetException - * - * @return Alignment + * @return $this */ - public function setTextRotation($pValue) + public function setTextRotation($angleInDegrees) { // Excel2007 value 255 => PhpSpreadsheet value -165 - if ($pValue == 255) { - $pValue = -165; + if ($angleInDegrees == self::TEXTROTATION_STACK_EXCEL) { + $angleInDegrees = self::TEXTROTATION_STACK_PHPSPREADSHEET; } // Set rotation - if (($pValue >= -90 && $pValue <= 90) || $pValue == -165) { + if (($angleInDegrees >= -90 && $angleInDegrees <= 90) || $angleInDegrees == self::TEXTROTATION_STACK_PHPSPREADSHEET) { if ($this->isSupervisor) { - $styleArray = $this->getStyleArray(['textRotation' => $pValue]); + $styleArray = $this->getStyleArray(['textRotation' => $angleInDegrees]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { - $this->textRotation = $pValue; + $this->textRotation = $angleInDegrees; } } else { throw new PhpSpreadsheetException('Text rotation should be a value between -90 and 90.'); @@ -310,20 +372,20 @@ class Alignment extends Supervisor /** * Set Wrap Text. * - * @param bool $pValue + * @param bool $wrapped * - * @return Alignment + * @return $this */ - public function setWrapText($pValue) + public function setWrapText($wrapped) { - if ($pValue == '') { - $pValue = false; + if ($wrapped == '') { + $wrapped = false; } if ($this->isSupervisor) { - $styleArray = $this->getStyleArray(['wrapText' => $pValue]); + $styleArray = $this->getStyleArray(['wrapText' => $wrapped]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { - $this->wrapText = $pValue; + $this->wrapText = $wrapped; } return $this; @@ -346,20 +408,20 @@ class Alignment extends Supervisor /** * Set Shrink to fit. * - * @param bool $pValue + * @param bool $shrink * - * @return Alignment + * @return $this */ - public function setShrinkToFit($pValue) + public function setShrinkToFit($shrink) { - if ($pValue == '') { - $pValue = false; + if ($shrink == '') { + $shrink = false; } if ($this->isSupervisor) { - $styleArray = $this->getStyleArray(['shrinkToFit' => $pValue]); + $styleArray = $this->getStyleArray(['shrinkToFit' => $shrink]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { - $this->shrinkToFit = $pValue; + $this->shrinkToFit = $shrink; } return $this; @@ -382,24 +444,27 @@ class Alignment extends Supervisor /** * Set indent. * - * @param int $pValue + * @param int $indent * - * @return Alignment + * @return $this */ - public function setIndent($pValue) + public function setIndent($indent) { - if ($pValue > 0) { - if ($this->getHorizontal() != self::HORIZONTAL_GENERAL && + if ($indent > 0) { + if ( + $this->getHorizontal() != self::HORIZONTAL_GENERAL && $this->getHorizontal() != self::HORIZONTAL_LEFT && - $this->getHorizontal() != self::HORIZONTAL_RIGHT) { - $pValue = 0; // indent not supported + $this->getHorizontal() != self::HORIZONTAL_RIGHT && + $this->getHorizontal() != self::HORIZONTAL_DISTRIBUTED + ) { + $indent = 0; // indent not supported } } if ($this->isSupervisor) { - $styleArray = $this->getStyleArray(['indent' => $pValue]); + $styleArray = $this->getStyleArray(['indent' => $indent]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { - $this->indent = $pValue; + $this->indent = $indent; } return $this; @@ -422,20 +487,20 @@ class Alignment extends Supervisor /** * Set read order. * - * @param int $pValue + * @param int $readOrder * - * @return Alignment + * @return $this */ - public function setReadOrder($pValue) + public function setReadOrder($readOrder) { - if ($pValue < 0 || $pValue > 2) { - $pValue = 0; + if ($readOrder < 0 || $readOrder > 2) { + $readOrder = 0; } if ($this->isSupervisor) { - $styleArray = $this->getStyleArray(['readOrder' => $pValue]); + $styleArray = $this->getStyleArray(['readOrder' => $readOrder]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { - $this->readOrder = $pValue; + $this->readOrder = $readOrder; } return $this; @@ -463,4 +528,18 @@ class Alignment extends Supervisor __CLASS__ ); } + + protected function exportArray1(): array + { + $exportedArray = []; + $this->exportArray2($exportedArray, 'horizontal', $this->getHorizontal()); + $this->exportArray2($exportedArray, 'indent', $this->getIndent()); + $this->exportArray2($exportedArray, 'readOrder', $this->getReadOrder()); + $this->exportArray2($exportedArray, 'shrinkToFit', $this->getShrinkToFit()); + $this->exportArray2($exportedArray, 'textRotation', $this->getTextRotation()); + $this->exportArray2($exportedArray, 'vertical', $this->getVertical()); + $this->exportArray2($exportedArray, 'wrapText', $this->getWrapText()); + + return $exportedArray; + } } diff --git a/PhpOffice/PhpSpreadsheet/Style/Border.php b/PhpOffice/PhpSpreadsheet/Style/Border.php old mode 100755 new mode 100644 index c957cf5..c6fb51f --- a/PhpOffice/PhpSpreadsheet/Style/Border.php +++ b/PhpOffice/PhpSpreadsheet/Style/Border.php @@ -37,7 +37,7 @@ class Border extends Supervisor protected $color; /** - * @var int + * @var null|int */ public $colorIndex; @@ -47,11 +47,8 @@ class Border extends Supervisor * @param bool $isSupervisor Flag indicating if this is a supervisor or not * Leave this value at default unless you understand exactly what * its ramifications are - * @param bool $isConditional Flag indicating if this is a conditional style or not - * Leave this value at default unless you understand exactly what - * its ramifications are */ - public function __construct($isSupervisor = false, $isConditional = false) + public function __construct($isSupervisor = false) { // Supervisor? parent::__construct($isSupervisor); @@ -69,32 +66,29 @@ class Border extends Supervisor * Get the shared style component for the currently active cell in currently active sheet. * Only used for style supervisor. * - * @throws PhpSpreadsheetException - * * @return Border */ public function getSharedComponent() { - switch ($this->parentPropertyName) { - case 'allBorders': - case 'horizontal': - case 'inside': - case 'outline': - case 'vertical': - throw new PhpSpreadsheetException('Cannot get shared component for a pseudo-border.'); + /** @var Style */ + $parent = $this->parent; - break; + /** @var Borders $sharedComponent */ + $sharedComponent = $parent->getSharedComponent(); + switch ($this->parentPropertyName) { case 'bottom': - return $this->parent->getSharedComponent()->getBottom(); + return $sharedComponent->getBottom(); case 'diagonal': - return $this->parent->getSharedComponent()->getDiagonal(); + return $sharedComponent->getDiagonal(); case 'left': - return $this->parent->getSharedComponent()->getLeft(); + return $sharedComponent->getLeft(); case 'right': - return $this->parent->getSharedComponent()->getRight(); + return $sharedComponent->getRight(); case 'top': - return $this->parent->getSharedComponent()->getTop(); + return $sharedComponent->getTop(); } + + throw new PhpSpreadsheetException('Cannot get shared component for a pseudo-border.'); } /** @@ -106,7 +100,10 @@ class Border extends Supervisor */ public function getStyleArray($array) { - return $this->parent->getStyleArray([$this->parentPropertyName => $array]); + /** @var Style */ + $parent = $this->parent; + + return $parent->/** @scrutinizer ignore-call */ getStyleArray([$this->parentPropertyName => $array]); } /** @@ -123,22 +120,20 @@ class Border extends Supervisor * ); * * - * @param array $pStyles Array containing style information + * @param array $styleArray Array containing style information * - * @throws PhpSpreadsheetException - * - * @return Border + * @return $this */ - public function applyFromArray(array $pStyles) + public function applyFromArray(array $styleArray) { if ($this->isSupervisor) { - $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($pStyles)); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray)); } else { - if (isset($pStyles['borderStyle'])) { - $this->setBorderStyle($pStyles['borderStyle']); + if (isset($styleArray['borderStyle'])) { + $this->setBorderStyle($styleArray['borderStyle']); } - if (isset($pStyles['color'])) { - $this->getColor()->applyFromArray($pStyles['color']); + if (isset($styleArray['color'])) { + $this->getColor()->applyFromArray($styleArray['color']); } } @@ -162,24 +157,25 @@ class Border extends Supervisor /** * Set Border style. * - * @param bool|string $pValue + * @param bool|string $style * When passing a boolean, FALSE equates Border::BORDER_NONE * and TRUE to Border::BORDER_MEDIUM * - * @return Border + * @return $this */ - public function setBorderStyle($pValue) + public function setBorderStyle($style) { - if (empty($pValue)) { - $pValue = self::BORDER_NONE; - } elseif (is_bool($pValue) && $pValue) { - $pValue = self::BORDER_MEDIUM; + if (empty($style)) { + $style = self::BORDER_NONE; + } elseif (is_bool($style)) { + $style = self::BORDER_MEDIUM; } + if ($this->isSupervisor) { - $styleArray = $this->getStyleArray(['borderStyle' => $pValue]); + $styleArray = $this->getStyleArray(['borderStyle' => $style]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { - $this->borderStyle = $pValue; + $this->borderStyle = $style; } return $this; @@ -198,16 +194,12 @@ class Border extends Supervisor /** * Set Border Color. * - * @param Color $pValue - * - * @throws PhpSpreadsheetException - * - * @return Border + * @return $this */ - public function setColor(Color $pValue) + public function setColor(Color $color) { // make sure parameter is a real color and not a supervisor - $color = $pValue->getIsSupervisor() ? $pValue->getSharedComponent() : $pValue; + $color = $color->getIsSupervisor() ? $color->getSharedComponent() : $color; if ($this->isSupervisor) { $styleArray = $this->getColor()->getStyleArray(['argb' => $color->getARGB()]); @@ -236,4 +228,13 @@ class Border extends Supervisor __CLASS__ ); } + + protected function exportArray1(): array + { + $exportedArray = []; + $this->exportArray2($exportedArray, 'borderStyle', $this->getBorderStyle()); + $this->exportArray2($exportedArray, 'color', $this->getColor()); + + return $exportedArray; + } } diff --git a/PhpOffice/PhpSpreadsheet/Style/Borders.php b/PhpOffice/PhpSpreadsheet/Style/Borders.php old mode 100755 new mode 100644 index a1d6759..56a5270 --- a/PhpOffice/PhpSpreadsheet/Style/Borders.php +++ b/PhpOffice/PhpSpreadsheet/Style/Borders.php @@ -95,21 +95,18 @@ class Borders extends Supervisor * @param bool $isSupervisor Flag indicating if this is a supervisor or not * Leave this value at default unless you understand exactly what * its ramifications are - * @param bool $isConditional Flag indicating if this is a conditional style or not - * Leave this value at default unless you understand exactly what - * its ramifications are */ - public function __construct($isSupervisor = false, $isConditional = false) + public function __construct($isSupervisor = false) { // Supervisor? parent::__construct($isSupervisor); // Initialise values - $this->left = new Border($isSupervisor, $isConditional); - $this->right = new Border($isSupervisor, $isConditional); - $this->top = new Border($isSupervisor, $isConditional); - $this->bottom = new Border($isSupervisor, $isConditional); - $this->diagonal = new Border($isSupervisor, $isConditional); + $this->left = new Border($isSupervisor); + $this->right = new Border($isSupervisor); + $this->top = new Border($isSupervisor); + $this->bottom = new Border($isSupervisor); + $this->diagonal = new Border($isSupervisor); $this->diagonalDirection = self::DIAGONAL_NONE; // Specially for supervisor @@ -143,7 +140,10 @@ class Borders extends Supervisor */ public function getSharedComponent() { - return $this->parent->getSharedComponent()->getBorders(); + /** @var Style */ + $parent = $this->parent; + + return $parent->getSharedComponent()->getBorders(); } /** @@ -193,40 +193,38 @@ class Borders extends Supervisor * ); * * - * @param array $pStyles Array containing style information + * @param array $styleArray Array containing style information * - * @throws PhpSpreadsheetException - * - * @return Borders + * @return $this */ - public function applyFromArray(array $pStyles) + public function applyFromArray(array $styleArray) { if ($this->isSupervisor) { - $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($pStyles)); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray)); } else { - if (isset($pStyles['left'])) { - $this->getLeft()->applyFromArray($pStyles['left']); + if (isset($styleArray['left'])) { + $this->getLeft()->applyFromArray($styleArray['left']); } - if (isset($pStyles['right'])) { - $this->getRight()->applyFromArray($pStyles['right']); + if (isset($styleArray['right'])) { + $this->getRight()->applyFromArray($styleArray['right']); } - if (isset($pStyles['top'])) { - $this->getTop()->applyFromArray($pStyles['top']); + if (isset($styleArray['top'])) { + $this->getTop()->applyFromArray($styleArray['top']); } - if (isset($pStyles['bottom'])) { - $this->getBottom()->applyFromArray($pStyles['bottom']); + if (isset($styleArray['bottom'])) { + $this->getBottom()->applyFromArray($styleArray['bottom']); } - if (isset($pStyles['diagonal'])) { - $this->getDiagonal()->applyFromArray($pStyles['diagonal']); + if (isset($styleArray['diagonal'])) { + $this->getDiagonal()->applyFromArray($styleArray['diagonal']); } - if (isset($pStyles['diagonalDirection'])) { - $this->setDiagonalDirection($pStyles['diagonalDirection']); + if (isset($styleArray['diagonalDirection'])) { + $this->setDiagonalDirection($styleArray['diagonalDirection']); } - if (isset($pStyles['allBorders'])) { - $this->getLeft()->applyFromArray($pStyles['allBorders']); - $this->getRight()->applyFromArray($pStyles['allBorders']); - $this->getTop()->applyFromArray($pStyles['allBorders']); - $this->getBottom()->applyFromArray($pStyles['allBorders']); + if (isset($styleArray['allBorders'])) { + $this->getLeft()->applyFromArray($styleArray['allBorders']); + $this->getRight()->applyFromArray($styleArray['allBorders']); + $this->getTop()->applyFromArray($styleArray['allBorders']); + $this->getBottom()->applyFromArray($styleArray['allBorders']); } } @@ -286,8 +284,6 @@ class Borders extends Supervisor /** * Get AllBorders (pseudo-border). Only applies to supervisor. * - * @throws PhpSpreadsheetException - * * @return Border */ public function getAllBorders() @@ -302,8 +298,6 @@ class Borders extends Supervisor /** * Get Outline (pseudo-border). Only applies to supervisor. * - * @throws PhpSpreadsheetException - * * @return Border */ public function getOutline() @@ -318,8 +312,6 @@ class Borders extends Supervisor /** * Get Inside (pseudo-border). Only applies to supervisor. * - * @throws PhpSpreadsheetException - * * @return Border */ public function getInside() @@ -334,8 +326,6 @@ class Borders extends Supervisor /** * Get Vertical (pseudo-border). Only applies to supervisor. * - * @throws PhpSpreadsheetException - * * @return Border */ public function getVertical() @@ -350,8 +340,6 @@ class Borders extends Supervisor /** * Get Horizontal (pseudo-border). Only applies to supervisor. * - * @throws PhpSpreadsheetException - * * @return Border */ public function getHorizontal() @@ -380,20 +368,20 @@ class Borders extends Supervisor /** * Set DiagonalDirection. * - * @param int $pValue see self::DIAGONAL_* + * @param int $direction see self::DIAGONAL_* * - * @return Borders + * @return $this */ - public function setDiagonalDirection($pValue) + public function setDiagonalDirection($direction) { - if ($pValue == '') { - $pValue = self::DIAGONAL_NONE; + if ($direction == '') { + $direction = self::DIAGONAL_NONE; } if ($this->isSupervisor) { - $styleArray = $this->getStyleArray(['diagonalDirection' => $pValue]); + $styleArray = $this->getStyleArray(['diagonalDirection' => $direction]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { - $this->diagonalDirection = $pValue; + $this->diagonalDirection = $direction; } return $this; @@ -420,4 +408,17 @@ class Borders extends Supervisor __CLASS__ ); } + + protected function exportArray1(): array + { + $exportedArray = []; + $this->exportArray2($exportedArray, 'bottom', $this->getBottom()); + $this->exportArray2($exportedArray, 'diagonal', $this->getDiagonal()); + $this->exportArray2($exportedArray, 'diagonalDirection', $this->getDiagonalDirection()); + $this->exportArray2($exportedArray, 'left', $this->getLeft()); + $this->exportArray2($exportedArray, 'right', $this->getRight()); + $this->exportArray2($exportedArray, 'top', $this->getTop()); + + return $exportedArray; + } } diff --git a/PhpOffice/PhpSpreadsheet/Style/Color.php b/PhpOffice/PhpSpreadsheet/Style/Color.php old mode 100755 new mode 100644 index 60e4a8c..3c002b2 --- a/PhpOffice/PhpSpreadsheet/Style/Color.php +++ b/PhpOffice/PhpSpreadsheet/Style/Color.php @@ -2,8 +2,6 @@ namespace PhpOffice\PhpSpreadsheet\Style; -use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; - class Color extends Supervisor { const NAMED_COLORS = [ @@ -28,25 +26,98 @@ class Color extends Supervisor const COLOR_DARKGREEN = 'FF008000'; const COLOR_YELLOW = 'FFFFFF00'; const COLOR_DARKYELLOW = 'FF808000'; + const COLOR_MAGENTA = 'FFFF00FF'; + const COLOR_CYAN = 'FF00FFFF'; - /** - * Indexed colors array. - * - * @var array - */ - protected static $indexedColors; + const NAMED_COLOR_TRANSLATIONS = [ + 'Black' => self::COLOR_BLACK, + 'White' => self::COLOR_WHITE, + 'Red' => self::COLOR_RED, + 'Green' => self::COLOR_GREEN, + 'Blue' => self::COLOR_BLUE, + 'Yellow' => self::COLOR_YELLOW, + 'Magenta' => self::COLOR_MAGENTA, + 'Cyan' => self::COLOR_CYAN, + ]; + + const VALIDATE_ARGB_SIZE = 8; + const VALIDATE_RGB_SIZE = 6; + const VALIDATE_COLOR_6 = '/^[A-F0-9]{6}$/i'; + const VALIDATE_COLOR_8 = '/^[A-F0-9]{8}$/i'; + + private const INDEXED_COLORS = [ + 1 => 'FF000000', // System Colour #1 - Black + 2 => 'FFFFFFFF', // System Colour #2 - White + 3 => 'FFFF0000', // System Colour #3 - Red + 4 => 'FF00FF00', // System Colour #4 - Green + 5 => 'FF0000FF', // System Colour #5 - Blue + 6 => 'FFFFFF00', // System Colour #6 - Yellow + 7 => 'FFFF00FF', // System Colour #7- Magenta + 8 => 'FF00FFFF', // System Colour #8- Cyan + 9 => 'FF800000', // Standard Colour #9 + 10 => 'FF008000', // Standard Colour #10 + 11 => 'FF000080', // Standard Colour #11 + 12 => 'FF808000', // Standard Colour #12 + 13 => 'FF800080', // Standard Colour #13 + 14 => 'FF008080', // Standard Colour #14 + 15 => 'FFC0C0C0', // Standard Colour #15 + 16 => 'FF808080', // Standard Colour #16 + 17 => 'FF9999FF', // Chart Fill Colour #17 + 18 => 'FF993366', // Chart Fill Colour #18 + 19 => 'FFFFFFCC', // Chart Fill Colour #19 + 20 => 'FFCCFFFF', // Chart Fill Colour #20 + 21 => 'FF660066', // Chart Fill Colour #21 + 22 => 'FFFF8080', // Chart Fill Colour #22 + 23 => 'FF0066CC', // Chart Fill Colour #23 + 24 => 'FFCCCCFF', // Chart Fill Colour #24 + 25 => 'FF000080', // Chart Line Colour #25 + 26 => 'FFFF00FF', // Chart Line Colour #26 + 27 => 'FFFFFF00', // Chart Line Colour #27 + 28 => 'FF00FFFF', // Chart Line Colour #28 + 29 => 'FF800080', // Chart Line Colour #29 + 30 => 'FF800000', // Chart Line Colour #30 + 31 => 'FF008080', // Chart Line Colour #31 + 32 => 'FF0000FF', // Chart Line Colour #32 + 33 => 'FF00CCFF', // Standard Colour #33 + 34 => 'FFCCFFFF', // Standard Colour #34 + 35 => 'FFCCFFCC', // Standard Colour #35 + 36 => 'FFFFFF99', // Standard Colour #36 + 37 => 'FF99CCFF', // Standard Colour #37 + 38 => 'FFFF99CC', // Standard Colour #38 + 39 => 'FFCC99FF', // Standard Colour #39 + 40 => 'FFFFCC99', // Standard Colour #40 + 41 => 'FF3366FF', // Standard Colour #41 + 42 => 'FF33CCCC', // Standard Colour #42 + 43 => 'FF99CC00', // Standard Colour #43 + 44 => 'FFFFCC00', // Standard Colour #44 + 45 => 'FFFF9900', // Standard Colour #45 + 46 => 'FFFF6600', // Standard Colour #46 + 47 => 'FF666699', // Standard Colour #47 + 48 => 'FF969696', // Standard Colour #48 + 49 => 'FF003366', // Standard Colour #49 + 50 => 'FF339966', // Standard Colour #50 + 51 => 'FF003300', // Standard Colour #51 + 52 => 'FF333300', // Standard Colour #52 + 53 => 'FF993300', // Standard Colour #53 + 54 => 'FF993366', // Standard Colour #54 + 55 => 'FF333399', // Standard Colour #55 + 56 => 'FF333333', // Standard Colour #56 + ]; /** * ARGB - Alpha RGB. * - * @var string + * @var null|string */ protected $argb; + /** @var bool */ + private $hasChanged = false; + /** * Create a new Color. * - * @param string $pARGB ARGB value for the colour + * @param string $colorValue ARGB value for the colour, or named colour * @param bool $isSupervisor Flag indicating if this is a supervisor or not * Leave this value at default unless you understand exactly what * its ramifications are @@ -54,14 +125,14 @@ class Color extends Supervisor * Leave this value at default unless you understand exactly what * its ramifications are */ - public function __construct($pARGB = self::COLOR_BLACK, $isSupervisor = false, $isConditional = false) + public function __construct($colorValue = self::COLOR_BLACK, $isSupervisor = false, $isConditional = false) { // Supervisor? parent::__construct($isSupervisor); // Initialise values if (!$isConditional) { - $this->argb = $pARGB; + $this->argb = $this->validateColor($colorValue) ?: self::COLOR_BLACK; } } @@ -73,14 +144,19 @@ class Color extends Supervisor */ public function getSharedComponent() { - switch ($this->parentPropertyName) { - case 'endColor': - return $this->parent->getSharedComponent()->getEndColor(); - case 'color': - return $this->parent->getSharedComponent()->getColor(); - case 'startColor': - return $this->parent->getSharedComponent()->getStartColor(); + /** @var Style */ + $parent = $this->parent; + /** @var Border|Fill $sharedComponent */ + $sharedComponent = $parent->getSharedComponent(); + if ($sharedComponent instanceof Fill) { + if ($this->parentPropertyName === 'endColor') { + return $sharedComponent->getEndColor(); + } + + return $sharedComponent->getStartColor(); } + + return $sharedComponent->getColor(); } /** @@ -92,7 +168,10 @@ class Color extends Supervisor */ public function getStyleArray($array) { - return $this->parent->getStyleArray([$this->parentPropertyName => $array]); + /** @var Style */ + $parent = $this->parent; + + return $parent->/** @scrutinizer ignore-call */ getStyleArray([$this->parentPropertyName => $array]); } /** @@ -102,34 +181,49 @@ class Color extends Supervisor * $spreadsheet->getActiveSheet()->getStyle('B2')->getFont()->getColor()->applyFromArray(['rgb' => '808080']); * * - * @param array $pStyles Array containing style information + * @param array $styleArray Array containing style information * - * @throws PhpSpreadsheetException - * - * @return Color + * @return $this */ - public function applyFromArray(array $pStyles) + public function applyFromArray(array $styleArray) { if ($this->isSupervisor) { - $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($pStyles)); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray)); } else { - if (isset($pStyles['rgb'])) { - $this->setRGB($pStyles['rgb']); + if (isset($styleArray['rgb'])) { + $this->setRGB($styleArray['rgb']); } - if (isset($pStyles['argb'])) { - $this->setARGB($pStyles['argb']); + if (isset($styleArray['argb'])) { + $this->setARGB($styleArray['argb']); } } return $this; } + private function validateColor(?string $colorValue): string + { + if ($colorValue === null || $colorValue === '') { + return self::COLOR_BLACK; + } + $named = ucfirst(strtolower($colorValue)); + if (array_key_exists($named, self::NAMED_COLOR_TRANSLATIONS)) { + return self::NAMED_COLOR_TRANSLATIONS[$named]; + } + if (preg_match(self::VALIDATE_COLOR_8, $colorValue) === 1) { + return $colorValue; + } + if (preg_match(self::VALIDATE_COLOR_6, $colorValue) === 1) { + return 'FF' . $colorValue; + } + + return ''; + } + /** * Get ARGB. - * - * @return string */ - public function getARGB() + public function getARGB(): ?string { if ($this->isSupervisor) { return $this->getSharedComponent()->getARGB(); @@ -141,20 +235,23 @@ class Color extends Supervisor /** * Set ARGB. * - * @param string $pValue see self::COLOR_* + * @param string $colorValue ARGB value, or a named color * - * @return Color + * @return $this */ - public function setARGB($pValue) + public function setARGB(?string $colorValue = self::COLOR_BLACK) { - if ($pValue == '') { - $pValue = self::COLOR_BLACK; + $this->hasChanged = true; + $colorValue = $this->validateColor($colorValue); + if ($colorValue === '') { + return $this; } + if ($this->isSupervisor) { - $styleArray = $this->getStyleArray(['argb' => $pValue]); + $styleArray = $this->getStyleArray(['argb' => $colorValue]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { - $this->argb = $pValue; + $this->argb = $colorValue; } return $this; @@ -162,114 +259,109 @@ class Color extends Supervisor /** * Get RGB. - * - * @return string */ - public function getRGB() + public function getRGB(): string { if ($this->isSupervisor) { return $this->getSharedComponent()->getRGB(); } - return substr($this->argb, 2); + return substr($this->argb ?? '', 2); } /** * Set RGB. * - * @param string $pValue RGB value + * @param string $colorValue RGB value, or a named color * - * @return Color + * @return $this */ - public function setRGB($pValue) + public function setRGB(?string $colorValue = self::COLOR_BLACK) { - if ($pValue == '') { - $pValue = '000000'; - } - if ($this->isSupervisor) { - $styleArray = $this->getStyleArray(['argb' => 'FF' . $pValue]); - $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); - } else { - $this->argb = 'FF' . $pValue; - } - - return $this; + return $this->setARGB($colorValue); } /** * Get a specified colour component of an RGB value. * - * @param string $RGB The colour as an RGB value (e.g. FF00CCCC or CCDDEE + * @param string $rgbValue The colour as an RGB value (e.g. FF00CCCC or CCDDEE * @param int $offset Position within the RGB value to extract * @param bool $hex Flag indicating whether the component should be returned as a hex or a * decimal value * - * @return string The extracted colour component + * @return int|string The extracted colour component */ - private static function getColourComponent($RGB, $offset, $hex = true) + private static function getColourComponent($rgbValue, $offset, $hex = true) { - $colour = substr($RGB, $offset, 2); + $colour = substr($rgbValue, $offset, 2) ?: ''; + if (preg_match('/^[0-9a-f]{2}$/i', $colour) !== 1) { + $colour = '00'; + } - return ($hex) ? $colour : hexdec($colour); + return ($hex) ? $colour : (int) hexdec($colour); } /** * Get the red colour component of an RGB value. * - * @param string $RGB The colour as an RGB value (e.g. FF00CCCC or CCDDEE + * @param string $rgbValue The colour as an RGB value (e.g. FF00CCCC or CCDDEE * @param bool $hex Flag indicating whether the component should be returned as a hex or a * decimal value * - * @return string The red colour component + * @return int|string The red colour component */ - public static function getRed($RGB, $hex = true) + public static function getRed($rgbValue, $hex = true) { - return self::getColourComponent($RGB, strlen($RGB) - 6, $hex); + return self::getColourComponent($rgbValue, strlen($rgbValue) - 6, $hex); } /** * Get the green colour component of an RGB value. * - * @param string $RGB The colour as an RGB value (e.g. FF00CCCC or CCDDEE + * @param string $rgbValue The colour as an RGB value (e.g. FF00CCCC or CCDDEE * @param bool $hex Flag indicating whether the component should be returned as a hex or a * decimal value * - * @return string The green colour component + * @return int|string The green colour component */ - public static function getGreen($RGB, $hex = true) + public static function getGreen($rgbValue, $hex = true) { - return self::getColourComponent($RGB, strlen($RGB) - 4, $hex); + return self::getColourComponent($rgbValue, strlen($rgbValue) - 4, $hex); } /** * Get the blue colour component of an RGB value. * - * @param string $RGB The colour as an RGB value (e.g. FF00CCCC or CCDDEE + * @param string $rgbValue The colour as an RGB value (e.g. FF00CCCC or CCDDEE * @param bool $hex Flag indicating whether the component should be returned as a hex or a * decimal value * - * @return string The blue colour component + * @return int|string The blue colour component */ - public static function getBlue($RGB, $hex = true) + public static function getBlue($rgbValue, $hex = true) { - return self::getColourComponent($RGB, strlen($RGB) - 2, $hex); + return self::getColourComponent($rgbValue, strlen($rgbValue) - 2, $hex); } /** * Adjust the brightness of a color. * - * @param string $hex The colour as an RGBA or RGB value (e.g. FF00CCCC or CCDDEE) + * @param string $hexColourValue The colour as an RGBA or RGB value (e.g. FF00CCCC or CCDDEE) * @param float $adjustPercentage The percentage by which to adjust the colour as a float from -1 to 1 * * @return string The adjusted colour as an RGBA or RGB value (e.g. FF00CCCC or CCDDEE) */ - public static function changeBrightness($hex, $adjustPercentage) + public static function changeBrightness($hexColourValue, $adjustPercentage) { - $rgba = (strlen($hex) === 8); + $rgba = (strlen($hexColourValue) === 8); + $adjustPercentage = max(-1.0, min(1.0, $adjustPercentage)); - $red = self::getRed($hex, false); - $green = self::getGreen($hex, false); - $blue = self::getBlue($hex, false); + /** @var int $red */ + $red = self::getRed($hexColourValue, false); + /** @var int $green */ + $green = self::getGreen($hexColourValue, false); + /** @var int $blue */ + $blue = self::getBlue($hexColourValue, false); if ($adjustPercentage > 0) { $red += (255 - $red) * $adjustPercentage; $green += (255 - $green) * $adjustPercentage; @@ -280,22 +372,6 @@ class Color extends Supervisor $blue += $blue * $adjustPercentage; } - if ($red < 0) { - $red = 0; - } elseif ($red > 255) { - $red = 255; - } - if ($green < 0) { - $green = 0; - } elseif ($green > 255) { - $green = 255; - } - if ($blue < 0) { - $blue = 0; - } elseif ($blue > 255) { - $blue = 255; - } - $rgb = strtoupper( str_pad(dechex((int) $red), 2, '0', 0) . str_pad(dechex((int) $green), 2, '0', 0) . @@ -308,88 +384,26 @@ class Color extends Supervisor /** * Get indexed color. * - * @param int $pIndex Index entry point into the colour array + * @param int $colorIndex Index entry point into the colour array * @param bool $background Flag to indicate whether default background or foreground colour * should be returned if the indexed colour doesn't exist - * - * @return Color */ - public static function indexedColor($pIndex, $background = false) + public static function indexedColor($colorIndex, $background = false, ?array $palette = null): self { // Clean parameter - $pIndex = (int) $pIndex; + $colorIndex = (int) $colorIndex; - // Indexed colors - if (self::$indexedColors === null) { - self::$indexedColors = [ - 1 => 'FF000000', // System Colour #1 - Black - 2 => 'FFFFFFFF', // System Colour #2 - White - 3 => 'FFFF0000', // System Colour #3 - Red - 4 => 'FF00FF00', // System Colour #4 - Green - 5 => 'FF0000FF', // System Colour #5 - Blue - 6 => 'FFFFFF00', // System Colour #6 - Yellow - 7 => 'FFFF00FF', // System Colour #7- Magenta - 8 => 'FF00FFFF', // System Colour #8- Cyan - 9 => 'FF800000', // Standard Colour #9 - 10 => 'FF008000', // Standard Colour #10 - 11 => 'FF000080', // Standard Colour #11 - 12 => 'FF808000', // Standard Colour #12 - 13 => 'FF800080', // Standard Colour #13 - 14 => 'FF008080', // Standard Colour #14 - 15 => 'FFC0C0C0', // Standard Colour #15 - 16 => 'FF808080', // Standard Colour #16 - 17 => 'FF9999FF', // Chart Fill Colour #17 - 18 => 'FF993366', // Chart Fill Colour #18 - 19 => 'FFFFFFCC', // Chart Fill Colour #19 - 20 => 'FFCCFFFF', // Chart Fill Colour #20 - 21 => 'FF660066', // Chart Fill Colour #21 - 22 => 'FFFF8080', // Chart Fill Colour #22 - 23 => 'FF0066CC', // Chart Fill Colour #23 - 24 => 'FFCCCCFF', // Chart Fill Colour #24 - 25 => 'FF000080', // Chart Line Colour #25 - 26 => 'FFFF00FF', // Chart Line Colour #26 - 27 => 'FFFFFF00', // Chart Line Colour #27 - 28 => 'FF00FFFF', // Chart Line Colour #28 - 29 => 'FF800080', // Chart Line Colour #29 - 30 => 'FF800000', // Chart Line Colour #30 - 31 => 'FF008080', // Chart Line Colour #31 - 32 => 'FF0000FF', // Chart Line Colour #32 - 33 => 'FF00CCFF', // Standard Colour #33 - 34 => 'FFCCFFFF', // Standard Colour #34 - 35 => 'FFCCFFCC', // Standard Colour #35 - 36 => 'FFFFFF99', // Standard Colour #36 - 37 => 'FF99CCFF', // Standard Colour #37 - 38 => 'FFFF99CC', // Standard Colour #38 - 39 => 'FFCC99FF', // Standard Colour #39 - 40 => 'FFFFCC99', // Standard Colour #40 - 41 => 'FF3366FF', // Standard Colour #41 - 42 => 'FF33CCCC', // Standard Colour #42 - 43 => 'FF99CC00', // Standard Colour #43 - 44 => 'FFFFCC00', // Standard Colour #44 - 45 => 'FFFF9900', // Standard Colour #45 - 46 => 'FFFF6600', // Standard Colour #46 - 47 => 'FF666699', // Standard Colour #47 - 48 => 'FF969696', // Standard Colour #48 - 49 => 'FF003366', // Standard Colour #49 - 50 => 'FF339966', // Standard Colour #50 - 51 => 'FF003300', // Standard Colour #51 - 52 => 'FF333300', // Standard Colour #52 - 53 => 'FF993300', // Standard Colour #53 - 54 => 'FF993366', // Standard Colour #54 - 55 => 'FF333399', // Standard Colour #55 - 56 => 'FF333333', // Standard Colour #56 - ]; + if (empty($palette)) { + if (isset(self::INDEXED_COLORS[$colorIndex])) { + return new self(self::INDEXED_COLORS[$colorIndex]); + } + } else { + if (isset($palette[$colorIndex])) { + return new self($palette[$colorIndex]); + } } - if (isset(self::$indexedColors[$pIndex])) { - return new self(self::$indexedColors[$pIndex]); - } - - if ($background) { - return new self(self::COLOR_WHITE); - } - - return new self(self::COLOR_BLACK); + return ($background) ? new self(self::COLOR_WHITE) : new self(self::COLOR_BLACK); } /** @@ -397,7 +411,7 @@ class Color extends Supervisor * * @return string Hash code */ - public function getHashCode() + public function getHashCode(): string { if ($this->isSupervisor) { return $this->getSharedComponent()->getHashCode(); @@ -408,4 +422,21 @@ class Color extends Supervisor __CLASS__ ); } + + protected function exportArray1(): array + { + $exportedArray = []; + $this->exportArray2($exportedArray, 'argb', $this->getARGB()); + + return $exportedArray; + } + + public function getHasChanged(): bool + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->hasChanged; + } + + return $this->hasChanged; + } } diff --git a/PhpOffice/PhpSpreadsheet/Style/Conditional.php b/PhpOffice/PhpSpreadsheet/Style/Conditional.php old mode 100755 new mode 100644 index 91a000d..019c064 --- a/PhpOffice/PhpSpreadsheet/Style/Conditional.php +++ b/PhpOffice/PhpSpreadsheet/Style/Conditional.php @@ -3,15 +3,44 @@ namespace PhpOffice\PhpSpreadsheet\Style; use PhpOffice\PhpSpreadsheet\IComparable; +use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalDataBar; class Conditional implements IComparable { // Condition types const CONDITION_NONE = 'none'; + const CONDITION_BEGINSWITH = 'beginsWith'; const CONDITION_CELLIS = 'cellIs'; - const CONDITION_CONTAINSTEXT = 'containsText'; - const CONDITION_EXPRESSION = 'expression'; const CONDITION_CONTAINSBLANKS = 'containsBlanks'; + const CONDITION_CONTAINSERRORS = 'containsErrors'; + const CONDITION_CONTAINSTEXT = 'containsText'; + const CONDITION_DATABAR = 'dataBar'; + const CONDITION_ENDSWITH = 'endsWith'; + const CONDITION_EXPRESSION = 'expression'; + const CONDITION_NOTCONTAINSBLANKS = 'notContainsBlanks'; + const CONDITION_NOTCONTAINSERRORS = 'notContainsErrors'; + const CONDITION_NOTCONTAINSTEXT = 'notContainsText'; + const CONDITION_TIMEPERIOD = 'timePeriod'; + const CONDITION_DUPLICATES = 'duplicateValues'; + const CONDITION_UNIQUE = 'uniqueValues'; + + private const CONDITION_TYPES = [ + self::CONDITION_BEGINSWITH, + self::CONDITION_CELLIS, + self::CONDITION_CONTAINSBLANKS, + self::CONDITION_CONTAINSERRORS, + self::CONDITION_CONTAINSTEXT, + self::CONDITION_DATABAR, + self::CONDITION_DUPLICATES, + self::CONDITION_ENDSWITH, + self::CONDITION_EXPRESSION, + self::CONDITION_NONE, + self::CONDITION_NOTCONTAINSBLANKS, + self::CONDITION_NOTCONTAINSERRORS, + self::CONDITION_NOTCONTAINSTEXT, + self::CONDITION_TIMEPERIOD, + self::CONDITION_UNIQUE, + ]; // Operator types const OPERATOR_NONE = ''; @@ -26,6 +55,18 @@ class Conditional implements IComparable const OPERATOR_CONTAINSTEXT = 'containsText'; const OPERATOR_NOTCONTAINS = 'notContains'; const OPERATOR_BETWEEN = 'between'; + const OPERATOR_NOTBETWEEN = 'notBetween'; + + const TIMEPERIOD_TODAY = 'today'; + const TIMEPERIOD_YESTERDAY = 'yesterday'; + const TIMEPERIOD_TOMORROW = 'tomorrow'; + const TIMEPERIOD_LAST_7_DAYS = 'last7Days'; + const TIMEPERIOD_LAST_WEEK = 'lastWeek'; + const TIMEPERIOD_THIS_WEEK = 'thisWeek'; + const TIMEPERIOD_NEXT_WEEK = 'nextWeek'; + const TIMEPERIOD_LAST_MONTH = 'lastMonth'; + const TIMEPERIOD_THIS_MONTH = 'thisMonth'; + const TIMEPERIOD_NEXT_MONTH = 'nextMonth'; /** * Condition type. @@ -58,10 +99,15 @@ class Conditional implements IComparable /** * Condition. * - * @var string[] + * @var (bool|float|int|string)[] */ private $condition = []; + /** + * @var ConditionalDataBar + */ + private $dataBar; + /** * Style. * @@ -91,13 +137,13 @@ class Conditional implements IComparable /** * Set Condition type. * - * @param string $pValue Condition type, see self::CONDITION_* + * @param string $type Condition type, see self::CONDITION_* * - * @return Conditional + * @return $this */ - public function setConditionType($pValue) + public function setConditionType($type) { - $this->conditionType = $pValue; + $this->conditionType = $type; return $this; } @@ -115,13 +161,13 @@ class Conditional implements IComparable /** * Set Operator type. * - * @param string $pValue Conditional operator type, see self::OPERATOR_* + * @param string $type Conditional operator type, see self::OPERATOR_* * - * @return Conditional + * @return $this */ - public function setOperatorType($pValue) + public function setOperatorType($type) { - $this->operatorType = $pValue; + $this->operatorType = $type; return $this; } @@ -139,13 +185,13 @@ class Conditional implements IComparable /** * Set text. * - * @param string $value + * @param string $text * - * @return Conditional + * @return $this */ - public function setText($value) + public function setText($text) { - $this->text = $value; + $this->text = $text; return $this; } @@ -163,13 +209,13 @@ class Conditional implements IComparable /** * Set StopIfTrue. * - * @param bool $value + * @param bool $stopIfTrue * - * @return Conditional + * @return $this */ - public function setStopIfTrue($value) + public function setStopIfTrue($stopIfTrue) { - $this->stopIfTrue = $value; + $this->stopIfTrue = $stopIfTrue; return $this; } @@ -177,7 +223,7 @@ class Conditional implements IComparable /** * Get Conditions. * - * @return string[] + * @return (bool|float|int|string)[] */ public function getConditions() { @@ -187,16 +233,16 @@ class Conditional implements IComparable /** * Set Conditions. * - * @param string[] $pValue Condition + * @param bool|float|int|string|(bool|float|int|string)[] $conditions Condition * - * @return Conditional + * @return $this */ - public function setConditions($pValue) + public function setConditions($conditions) { - if (!is_array($pValue)) { - $pValue = [$pValue]; + if (!is_array($conditions)) { + $conditions = [$conditions]; } - $this->condition = $pValue; + $this->condition = $conditions; return $this; } @@ -204,13 +250,13 @@ class Conditional implements IComparable /** * Add Condition. * - * @param string $pValue Condition + * @param bool|float|int|string $condition Condition * - * @return Conditional + * @return $this */ - public function addCondition($pValue) + public function addCondition($condition) { - $this->condition[] = $pValue; + $this->condition[] = $condition; return $this; } @@ -228,13 +274,33 @@ class Conditional implements IComparable /** * Set Style. * - * @param Style $pValue - * - * @return Conditional + * @return $this */ - public function setStyle(Style $pValue = null) + public function setStyle(Style $style) { - $this->style = $pValue; + $this->style = $style; + + return $this; + } + + /** + * get DataBar. + * + * @return null|ConditionalDataBar + */ + public function getDataBar() + { + return $this->dataBar; + } + + /** + * set DataBar. + * + * @return $this + */ + public function setDataBar(ConditionalDataBar $dataBar) + { + $this->dataBar = $dataBar; return $this; } @@ -269,4 +335,12 @@ class Conditional implements IComparable } } } + + /** + * Verify if param is valid condition type. + */ + public static function isValidConditionType(string $type): bool + { + return in_array($type, self::CONDITION_TYPES); + } } diff --git a/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/CellMatcher.php b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/CellMatcher.php new file mode 100644 index 0000000..0f6f523 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/CellMatcher.php @@ -0,0 +1,313 @@ + '=', + Conditional::OPERATOR_GREATERTHAN => '>', + Conditional::OPERATOR_GREATERTHANOREQUAL => '>=', + Conditional::OPERATOR_LESSTHAN => '<', + Conditional::OPERATOR_LESSTHANOREQUAL => '<=', + Conditional::OPERATOR_NOTEQUAL => '<>', + ]; + + public const COMPARISON_RANGE_OPERATORS = [ + Conditional::OPERATOR_BETWEEN => 'IF(AND(A1>=%s,A1<=%s),TRUE,FALSE)', + Conditional::OPERATOR_NOTBETWEEN => 'IF(AND(A1>=%s,A1<=%s),FALSE,TRUE)', + ]; + + public const COMPARISON_DUPLICATES_OPERATORS = [ + Conditional::CONDITION_DUPLICATES => "COUNTIF('%s'!%s,%s)>1", + Conditional::CONDITION_UNIQUE => "COUNTIF('%s'!%s,%s)=1", + ]; + + /** + * @var Cell + */ + protected $cell; + + /** + * @var int + */ + protected $cellRow; + + /** + * @var Worksheet + */ + protected $worksheet; + + /** + * @var int + */ + protected $cellColumn; + + /** + * @var string + */ + protected $conditionalRange; + + /** + * @var string + */ + protected $referenceCell; + + /** + * @var int + */ + protected $referenceRow; + + /** + * @var int + */ + protected $referenceColumn; + + /** + * @var Calculation + */ + protected $engine; + + public function __construct(Cell $cell, string $conditionalRange) + { + $this->cell = $cell; + $this->worksheet = $cell->getWorksheet(); + [$this->cellColumn, $this->cellRow] = Coordinate::indexesFromString($this->cell->getCoordinate()); + $this->setReferenceCellForExpressions($conditionalRange); + + $this->engine = Calculation::getInstance($this->worksheet->getParent()); + } + + protected function setReferenceCellForExpressions(string $conditionalRange): void + { + $conditionalRange = Coordinate::splitRange(str_replace('$', '', strtoupper($conditionalRange))); + [$this->referenceCell] = $conditionalRange[0]; + + [$this->referenceColumn, $this->referenceRow] = Coordinate::indexesFromString($this->referenceCell); + + // Convert our conditional range to an absolute conditional range, so it can be used "pinned" in formulae + $rangeSets = []; + foreach ($conditionalRange as $rangeSet) { + $absoluteRangeSet = array_map( + [Coordinate::class, 'absoluteCoordinate'], + $rangeSet + ); + $rangeSets[] = implode(':', $absoluteRangeSet); + } + $this->conditionalRange = implode(',', $rangeSets); + } + + public function evaluateConditional(Conditional $conditional): bool + { + // Some calculations may modify the stored cell; so reset it before every evaluation. + $cellColumn = Coordinate::stringFromColumnIndex($this->cellColumn); + $cellAddress = "{$cellColumn}{$this->cellRow}"; + $this->cell = $this->worksheet->getCell($cellAddress); + + switch ($conditional->getConditionType()) { + case Conditional::CONDITION_CELLIS: + return $this->processOperatorComparison($conditional); + case Conditional::CONDITION_DUPLICATES: + case Conditional::CONDITION_UNIQUE: + return $this->processDuplicatesComparison($conditional); + case Conditional::CONDITION_CONTAINSTEXT: + // Expression is NOT(ISERROR(SEARCH("",))) + case Conditional::CONDITION_NOTCONTAINSTEXT: + // Expression is ISERROR(SEARCH("",)) + case Conditional::CONDITION_BEGINSWITH: + // Expression is LEFT(,LEN(""))="" + case Conditional::CONDITION_ENDSWITH: + // Expression is RIGHT(,LEN(""))="" + case Conditional::CONDITION_CONTAINSBLANKS: + // Expression is LEN(TRIM())=0 + case Conditional::CONDITION_NOTCONTAINSBLANKS: + // Expression is LEN(TRIM())>0 + case Conditional::CONDITION_CONTAINSERRORS: + // Expression is ISERROR() + case Conditional::CONDITION_NOTCONTAINSERRORS: + // Expression is NOT(ISERROR()) + case Conditional::CONDITION_TIMEPERIOD: + // Expression varies, depending on specified timePeriod value, e.g. + // Yesterday FLOOR(,1)=TODAY()-1 + // Today FLOOR(,1)=TODAY() + // Tomorrow FLOOR(,1)=TODAY()+1 + // Last 7 Days AND(TODAY()-FLOOR(,1)<=6,FLOOR(,1)<=TODAY()) + case Conditional::CONDITION_EXPRESSION: + return $this->processExpression($conditional); + } + + return false; + } + + /** + * @param mixed $value + * + * @return float|int|string + */ + protected function wrapValue($value) + { + if (!is_numeric($value)) { + if (is_bool($value)) { + return $value ? 'TRUE' : 'FALSE'; + } elseif ($value === null) { + return 'NULL'; + } + + return '"' . $value . '"'; + } + + return $value; + } + + /** + * @return float|int|string + */ + protected function wrapCellValue() + { + return $this->wrapValue($this->cell->getCalculatedValue()); + } + + /** + * @return float|int|string + */ + protected function conditionCellAdjustment(array $matches) + { + $column = $matches[6]; + $row = $matches[7]; + + if (strpos($column, '$') === false) { + $column = Coordinate::columnIndexFromString($column); + $column += $this->cellColumn - $this->referenceColumn; + $column = Coordinate::stringFromColumnIndex($column); + } + + if (strpos($row, '$') === false) { + $row += $this->cellRow - $this->referenceRow; + } + + if (!empty($matches[4])) { + $worksheet = $this->worksheet->getParent()->getSheetByName(trim($matches[4], "'")); + if ($worksheet === null) { + return $this->wrapValue(null); + } + + return $this->wrapValue( + $worksheet + ->getCell(str_replace('$', '', "{$column}{$row}")) + ->getCalculatedValue() + ); + } + + return $this->wrapValue( + $this->worksheet + ->getCell(str_replace('$', '', "{$column}{$row}")) + ->getCalculatedValue() + ); + } + + protected function cellConditionCheck(string $condition): string + { + $splitCondition = explode(Calculation::FORMULA_STRING_QUOTE, $condition); + $i = false; + foreach ($splitCondition as &$value) { + // Only count/replace in alternating array entries (ie. not in quoted strings) + $i = $i === false; + if ($i) { + $value = (string) preg_replace_callback( + '/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/i', + [$this, 'conditionCellAdjustment'], + $value + ); + } + } + unset($value); + // Then rebuild the condition string to return it + return implode(Calculation::FORMULA_STRING_QUOTE, $splitCondition); + } + + protected function adjustConditionsForCellReferences(array $conditions): array + { + return array_map( + [$this, 'cellConditionCheck'], + $conditions + ); + } + + protected function processOperatorComparison(Conditional $conditional): bool + { + if (array_key_exists($conditional->getOperatorType(), self::COMPARISON_RANGE_OPERATORS)) { + return $this->processRangeOperator($conditional); + } + + $operator = self::COMPARISON_OPERATORS[$conditional->getOperatorType()]; + $conditions = $this->adjustConditionsForCellReferences($conditional->getConditions()); + $expression = sprintf('%s%s%s', (string) $this->wrapCellValue(), $operator, (string) array_pop($conditions)); + + return $this->evaluateExpression($expression); + } + + protected function processRangeOperator(Conditional $conditional): bool + { + $conditions = $this->adjustConditionsForCellReferences($conditional->getConditions()); + sort($conditions); + $expression = sprintf( + (string) preg_replace( + '/\bA1\b/i', + (string) $this->wrapCellValue(), + self::COMPARISON_RANGE_OPERATORS[$conditional->getOperatorType()] + ), + ...$conditions + ); + + return $this->evaluateExpression($expression); + } + + protected function processDuplicatesComparison(Conditional $conditional): bool + { + $worksheetName = $this->cell->getWorksheet()->getTitle(); + + $expression = sprintf( + self::COMPARISON_DUPLICATES_OPERATORS[$conditional->getConditionType()], + $worksheetName, + $this->conditionalRange, + $this->cellConditionCheck($this->cell->getCalculatedValue()) + ); + + return $this->evaluateExpression($expression); + } + + protected function processExpression(Conditional $conditional): bool + { + $conditions = $this->adjustConditionsForCellReferences($conditional->getConditions()); + $expression = array_pop($conditions); + + $expression = (string) preg_replace( + '/\b' . $this->referenceCell . '\b/i', + (string) $this->wrapCellValue(), + $expression + ); + + return $this->evaluateExpression($expression); + } + + protected function evaluateExpression(string $expression): bool + { + $expression = "={$expression}"; + + try { + $this->engine->flushInstance(); + $result = (bool) $this->engine->calculateFormula($expression); + } catch (Exception $e) { + return false; + } + + return $result; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/CellStyleAssessor.php b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/CellStyleAssessor.php new file mode 100644 index 0000000..4c000db --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/CellStyleAssessor.php @@ -0,0 +1,45 @@ +cellMatcher = new CellMatcher($cell, $conditionalRange); + $this->styleMerger = new StyleMerger($cell->getStyle()); + } + + /** + * @param Conditional[] $conditionalStyles + */ + public function matchConditions(array $conditionalStyles = []): Style + { + foreach ($conditionalStyles as $conditional) { + /** @var Conditional $conditional */ + if ($this->cellMatcher->evaluateConditional($conditional) === true) { + // Merging the conditional style into the base style goes in here + $this->styleMerger->mergeStyle($conditional->getStyle()); + if ($conditional->getStopIfTrue() === true) { + break; + } + } + } + + return $this->styleMerger->getStyle(); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php new file mode 100644 index 0000000..f7a2eee --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBar.php @@ -0,0 +1,93 @@ + attribute */ + + /** @var null|bool */ + private $showValue; + + /** children */ + + /** @var ?ConditionalFormatValueObject */ + private $minimumConditionalFormatValueObject; + + /** @var ?ConditionalFormatValueObject */ + private $maximumConditionalFormatValueObject; + + /** @var string */ + private $color; + + /** */ + + /** @var ?ConditionalFormattingRuleExtension */ + private $conditionalFormattingRuleExt; + + /** + * @return null|bool + */ + public function getShowValue() + { + return $this->showValue; + } + + /** + * @param bool $showValue + */ + public function setShowValue($showValue) + { + $this->showValue = $showValue; + + return $this; + } + + public function getMinimumConditionalFormatValueObject(): ?ConditionalFormatValueObject + { + return $this->minimumConditionalFormatValueObject; + } + + public function setMinimumConditionalFormatValueObject(ConditionalFormatValueObject $minimumConditionalFormatValueObject) + { + $this->minimumConditionalFormatValueObject = $minimumConditionalFormatValueObject; + + return $this; + } + + public function getMaximumConditionalFormatValueObject(): ?ConditionalFormatValueObject + { + return $this->maximumConditionalFormatValueObject; + } + + public function setMaximumConditionalFormatValueObject(ConditionalFormatValueObject $maximumConditionalFormatValueObject) + { + $this->maximumConditionalFormatValueObject = $maximumConditionalFormatValueObject; + + return $this; + } + + public function getColor(): string + { + return $this->color; + } + + public function setColor(string $color): self + { + $this->color = $color; + + return $this; + } + + public function getConditionalFormattingRuleExt(): ?ConditionalFormattingRuleExtension + { + return $this->conditionalFormattingRuleExt; + } + + public function setConditionalFormattingRuleExt(ConditionalFormattingRuleExtension $conditionalFormattingRuleExt) + { + $this->conditionalFormattingRuleExt = $conditionalFormattingRuleExt; + + return $this; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBarExtension.php b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBarExtension.php new file mode 100644 index 0000000..c709cf3 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalDataBarExtension.php @@ -0,0 +1,290 @@ + attributes */ + + /** @var int */ + private $minLength; + + /** @var int */ + private $maxLength; + + /** @var null|bool */ + private $border; + + /** @var null|bool */ + private $gradient; + + /** @var string */ + private $direction; + + /** @var null|bool */ + private $negativeBarBorderColorSameAsPositive; + + /** @var string */ + private $axisPosition; + + // children + + /** @var ConditionalFormatValueObject */ + private $maximumConditionalFormatValueObject; + + /** @var ConditionalFormatValueObject */ + private $minimumConditionalFormatValueObject; + + /** @var string */ + private $borderColor; + + /** @var string */ + private $negativeFillColor; + + /** @var string */ + private $negativeBorderColor; + + /** @var array */ + private $axisColor = [ + 'rgb' => null, + 'theme' => null, + 'tint' => null, + ]; + + public function getXmlAttributes() + { + $ret = []; + foreach (['minLength', 'maxLength', 'direction', 'axisPosition'] as $attrKey) { + if (null !== $this->{$attrKey}) { + $ret[$attrKey] = $this->{$attrKey}; + } + } + foreach (['border', 'gradient', 'negativeBarBorderColorSameAsPositive'] as $attrKey) { + if (null !== $this->{$attrKey}) { + $ret[$attrKey] = $this->{$attrKey} ? '1' : '0'; + } + } + + return $ret; + } + + public function getXmlElements() + { + $ret = []; + $elms = ['borderColor', 'negativeFillColor', 'negativeBorderColor']; + foreach ($elms as $elmKey) { + if (null !== $this->{$elmKey}) { + $ret[$elmKey] = ['rgb' => $this->{$elmKey}]; + } + } + foreach (array_filter($this->axisColor) as $attrKey => $axisColorAttr) { + if (!isset($ret['axisColor'])) { + $ret['axisColor'] = []; + } + $ret['axisColor'][$attrKey] = $axisColorAttr; + } + + return $ret; + } + + /** + * @return int + */ + public function getMinLength() + { + return $this->minLength; + } + + public function setMinLength(int $minLength): self + { + $this->minLength = $minLength; + + return $this; + } + + /** + * @return int + */ + public function getMaxLength() + { + return $this->maxLength; + } + + public function setMaxLength(int $maxLength): self + { + $this->maxLength = $maxLength; + + return $this; + } + + /** + * @return null|bool + */ + public function getBorder() + { + return $this->border; + } + + public function setBorder(bool $border): self + { + $this->border = $border; + + return $this; + } + + /** + * @return null|bool + */ + public function getGradient() + { + return $this->gradient; + } + + public function setGradient(bool $gradient): self + { + $this->gradient = $gradient; + + return $this; + } + + /** + * @return string + */ + public function getDirection() + { + return $this->direction; + } + + public function setDirection(string $direction): self + { + $this->direction = $direction; + + return $this; + } + + /** + * @return null|bool + */ + public function getNegativeBarBorderColorSameAsPositive() + { + return $this->negativeBarBorderColorSameAsPositive; + } + + public function setNegativeBarBorderColorSameAsPositive(bool $negativeBarBorderColorSameAsPositive): self + { + $this->negativeBarBorderColorSameAsPositive = $negativeBarBorderColorSameAsPositive; + + return $this; + } + + /** + * @return string + */ + public function getAxisPosition() + { + return $this->axisPosition; + } + + public function setAxisPosition(string $axisPosition): self + { + $this->axisPosition = $axisPosition; + + return $this; + } + + /** + * @return ConditionalFormatValueObject + */ + public function getMaximumConditionalFormatValueObject() + { + return $this->maximumConditionalFormatValueObject; + } + + public function setMaximumConditionalFormatValueObject(ConditionalFormatValueObject $maximumConditionalFormatValueObject) + { + $this->maximumConditionalFormatValueObject = $maximumConditionalFormatValueObject; + + return $this; + } + + /** + * @return ConditionalFormatValueObject + */ + public function getMinimumConditionalFormatValueObject() + { + return $this->minimumConditionalFormatValueObject; + } + + public function setMinimumConditionalFormatValueObject(ConditionalFormatValueObject $minimumConditionalFormatValueObject) + { + $this->minimumConditionalFormatValueObject = $minimumConditionalFormatValueObject; + + return $this; + } + + /** + * @return string + */ + public function getBorderColor() + { + return $this->borderColor; + } + + public function setBorderColor(string $borderColor): self + { + $this->borderColor = $borderColor; + + return $this; + } + + /** + * @return string + */ + public function getNegativeFillColor() + { + return $this->negativeFillColor; + } + + public function setNegativeFillColor(string $negativeFillColor): self + { + $this->negativeFillColor = $negativeFillColor; + + return $this; + } + + /** + * @return string + */ + public function getNegativeBorderColor() + { + return $this->negativeBorderColor; + } + + public function setNegativeBorderColor(string $negativeBorderColor): self + { + $this->negativeBorderColor = $negativeBorderColor; + + return $this; + } + + public function getAxisColor(): array + { + return $this->axisColor; + } + + /** + * @param mixed $rgb + * @param null|mixed $theme + * @param null|mixed $tint + */ + public function setAxisColor($rgb, $theme = null, $tint = null): self + { + $this->axisColor = [ + 'rgb' => $rgb, + 'theme' => $theme, + 'tint' => $tint, + ]; + + return $this; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php new file mode 100644 index 0000000..107969b --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormatValueObject.php @@ -0,0 +1,78 @@ +type = $type; + $this->value = $value; + $this->cellFormula = $cellFormula; + } + + /** + * @return mixed + */ + public function getType() + { + return $this->type; + } + + /** + * @param mixed $type + */ + public function setType($type) + { + $this->type = $type; + + return $this; + } + + /** + * @return mixed + */ + public function getValue() + { + return $this->value; + } + + /** + * @param mixed $value + */ + public function setValue($value) + { + $this->value = $value; + + return $this; + } + + /** + * @return mixed + */ + public function getCellFormula() + { + return $this->cellFormula; + } + + /** + * @param mixed $cellFormula + */ + public function setCellFormula($cellFormula) + { + $this->cellFormula = $cellFormula; + + return $this; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php new file mode 100644 index 0000000..34c9cbc --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/ConditionalFormattingRuleExtension.php @@ -0,0 +1,209 @@ + attributes */ + private $id; + + /** @var string Conditional Formatting Rule */ + private $cfRule; + + /** children */ + + /** @var ConditionalDataBarExtension */ + private $dataBar; + + /** @var string Sequence of References */ + private $sqref; + + /** + * ConditionalFormattingRuleExtension constructor. + */ + public function __construct($id = null, string $cfRule = self::CONDITION_EXTENSION_DATABAR) + { + if (null === $id) { + $this->id = '{' . $this->generateUuid() . '}'; + } else { + $this->id = $id; + } + $this->cfRule = $cfRule; + } + + private function generateUuid() + { + $chars = str_split('xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'); + + foreach ($chars as $i => $char) { + if ($char === 'x') { + $chars[$i] = dechex(random_int(0, 15)); + } elseif ($char === 'y') { + $chars[$i] = dechex(random_int(8, 11)); + } + } + + return implode('', /** @scrutinizer ignore-type */ $chars); + } + + public static function parseExtLstXml($extLstXml) + { + $conditionalFormattingRuleExtensions = []; + $conditionalFormattingRuleExtensionXml = null; + if ($extLstXml instanceof SimpleXMLElement) { + foreach ((count($extLstXml) > 0 ? $extLstXml : [$extLstXml]) as $extLst) { + //this uri is conditionalFormattings + //https://docs.microsoft.com/en-us/openspecs/office_standards/ms-xlsx/07d607af-5618-4ca2-b683-6a78dc0d9627 + if (isset($extLst->ext['uri']) && (string) $extLst->ext['uri'] === '{78C0D931-6437-407d-A8EE-F0AAD7539E65}') { + $conditionalFormattingRuleExtensionXml = $extLst->ext; + } + } + + if ($conditionalFormattingRuleExtensionXml) { + $ns = $conditionalFormattingRuleExtensionXml->getNamespaces(true); + $extFormattingsXml = $conditionalFormattingRuleExtensionXml->children($ns['x14']); + + foreach ($extFormattingsXml->children($ns['x14']) as $extFormattingXml) { + $extCfRuleXml = $extFormattingXml->cfRule; + $attributes = $extCfRuleXml->attributes(); + if (!$attributes || ((string) $attributes->type) !== Conditional::CONDITION_DATABAR) { + continue; + } + + $extFormattingRuleObj = new self((string) $attributes->id); + $extFormattingRuleObj->setSqref((string) $extFormattingXml->children($ns['xm'])->sqref); + $conditionalFormattingRuleExtensions[$extFormattingRuleObj->getId()] = $extFormattingRuleObj; + + $extDataBarObj = new ConditionalDataBarExtension(); + $extFormattingRuleObj->setDataBarExt($extDataBarObj); + $dataBarXml = $extCfRuleXml->dataBar; + self::parseExtDataBarAttributesFromXml($extDataBarObj, $dataBarXml); + self::parseExtDataBarElementChildrenFromXml($extDataBarObj, $dataBarXml, $ns); + } + } + } + + return $conditionalFormattingRuleExtensions; + } + + private static function parseExtDataBarAttributesFromXml( + ConditionalDataBarExtension $extDataBarObj, + SimpleXMLElement $dataBarXml + ): void { + $dataBarAttribute = $dataBarXml->attributes(); + if ($dataBarAttribute->minLength) { + $extDataBarObj->setMinLength((int) $dataBarAttribute->minLength); + } + if ($dataBarAttribute->maxLength) { + $extDataBarObj->setMaxLength((int) $dataBarAttribute->maxLength); + } + if ($dataBarAttribute->border) { + $extDataBarObj->setBorder((bool) (string) $dataBarAttribute->border); + } + if ($dataBarAttribute->gradient) { + $extDataBarObj->setGradient((bool) (string) $dataBarAttribute->gradient); + } + if ($dataBarAttribute->direction) { + $extDataBarObj->setDirection((string) $dataBarAttribute->direction); + } + if ($dataBarAttribute->negativeBarBorderColorSameAsPositive) { + $extDataBarObj->setNegativeBarBorderColorSameAsPositive((bool) (string) $dataBarAttribute->negativeBarBorderColorSameAsPositive); + } + if ($dataBarAttribute->axisPosition) { + $extDataBarObj->setAxisPosition((string) $dataBarAttribute->axisPosition); + } + } + + private static function parseExtDataBarElementChildrenFromXml(ConditionalDataBarExtension $extDataBarObj, SimpleXMLElement $dataBarXml, $ns): void + { + if ($dataBarXml->borderColor) { + $extDataBarObj->setBorderColor((string) $dataBarXml->borderColor->attributes()['rgb']); + } + if ($dataBarXml->negativeFillColor) { + $extDataBarObj->setNegativeFillColor((string) $dataBarXml->negativeFillColor->attributes()['rgb']); + } + if ($dataBarXml->negativeBorderColor) { + $extDataBarObj->setNegativeBorderColor((string) $dataBarXml->negativeBorderColor->attributes()['rgb']); + } + if ($dataBarXml->axisColor) { + $axisColorAttr = $dataBarXml->axisColor->attributes(); + $extDataBarObj->setAxisColor((string) $axisColorAttr['rgb'], (string) $axisColorAttr['theme'], (string) $axisColorAttr['tint']); + } + $cfvoIndex = 0; + foreach ($dataBarXml->cfvo as $cfvo) { + $f = (string) $cfvo->/** @scrutinizer ignore-call */ children($ns['xm'])->f; + /** @scrutinizer ignore-call */ + $attributes = $cfvo->attributes(); + if (!($attributes)) { + continue; + } + + if ($cfvoIndex === 0) { + $extDataBarObj->setMinimumConditionalFormatValueObject(new ConditionalFormatValueObject((string) $attributes['type'], null, (empty($f) ? null : $f))); + } + if ($cfvoIndex === 1) { + $extDataBarObj->setMaximumConditionalFormatValueObject(new ConditionalFormatValueObject((string) $attributes['type'], null, (empty($f) ? null : $f))); + } + ++$cfvoIndex; + } + } + + /** + * @return mixed + */ + public function getId() + { + return $this->id; + } + + /** + * @param mixed $id + */ + public function setId($id): self + { + $this->id = $id; + + return $this; + } + + public function getCfRule(): string + { + return $this->cfRule; + } + + public function setCfRule(string $cfRule): self + { + $this->cfRule = $cfRule; + + return $this; + } + + public function getDataBarExt(): ConditionalDataBarExtension + { + return $this->dataBar; + } + + public function setDataBarExt(ConditionalDataBarExtension $dataBar): self + { + $this->dataBar = $dataBar; + + return $this; + } + + public function getSqref(): string + { + return $this->sqref; + } + + public function setSqref(string $sqref): self + { + $this->sqref = $sqref; + + return $this; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/StyleMerger.php b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/StyleMerger.php new file mode 100644 index 0000000..0ddc07f --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/StyleMerger.php @@ -0,0 +1,118 @@ +baseStyle = $baseStyle; + } + + public function getStyle(): Style + { + return $this->baseStyle; + } + + public function mergeStyle(Style $style): void + { + if ($style->getNumberFormat() !== null && $style->getNumberFormat()->getFormatCode() !== null) { + $this->baseStyle->getNumberFormat()->setFormatCode($style->getNumberFormat()->getFormatCode()); + } + + if ($style->getFont() !== null) { + $this->mergeFontStyle($this->baseStyle->getFont(), $style->getFont()); + } + + if ($style->getFill() !== null) { + $this->mergeFillStyle($this->baseStyle->getFill(), $style->getFill()); + } + + if ($style->getBorders() !== null) { + $this->mergeBordersStyle($this->baseStyle->getBorders(), $style->getBorders()); + } + } + + protected function mergeFontStyle(Font $baseFontStyle, Font $fontStyle): void + { + if ($fontStyle->getBold() !== null) { + $baseFontStyle->setBold($fontStyle->getBold()); + } + + if ($fontStyle->getItalic() !== null) { + $baseFontStyle->setItalic($fontStyle->getItalic()); + } + + if ($fontStyle->getStrikethrough() !== null) { + $baseFontStyle->setStrikethrough($fontStyle->getStrikethrough()); + } + + if ($fontStyle->getUnderline() !== null) { + $baseFontStyle->setUnderline($fontStyle->getUnderline()); + } + + if ($fontStyle->getColor() !== null && $fontStyle->getColor()->getARGB() !== null) { + $baseFontStyle->setColor($fontStyle->getColor()); + } + } + + protected function mergeFillStyle(Fill $baseFillStyle, Fill $fillStyle): void + { + if ($fillStyle->getFillType() !== null) { + $baseFillStyle->setFillType($fillStyle->getFillType()); + } + + //if ($fillStyle->getRotation() !== null) { + $baseFillStyle->setRotation($fillStyle->getRotation()); + //} + + if ($fillStyle->getStartColor() !== null && $fillStyle->getStartColor()->getARGB() !== null) { + $baseFillStyle->setStartColor($fillStyle->getStartColor()); + } + + if ($fillStyle->getEndColor() !== null && $fillStyle->getEndColor()->getARGB() !== null) { + $baseFillStyle->setEndColor($fillStyle->getEndColor()); + } + } + + protected function mergeBordersStyle(Borders $baseBordersStyle, Borders $bordersStyle): void + { + if ($bordersStyle->getTop() !== null) { + $this->mergeBorderStyle($baseBordersStyle->getTop(), $bordersStyle->getTop()); + } + + if ($bordersStyle->getBottom() !== null) { + $this->mergeBorderStyle($baseBordersStyle->getBottom(), $bordersStyle->getBottom()); + } + + if ($bordersStyle->getLeft() !== null) { + $this->mergeBorderStyle($baseBordersStyle->getLeft(), $bordersStyle->getLeft()); + } + + if ($bordersStyle->getRight() !== null) { + $this->mergeBorderStyle($baseBordersStyle->getRight(), $bordersStyle->getRight()); + } + } + + protected function mergeBorderStyle(Border $baseBorderStyle, Border $borderStyle): void + { + //if ($borderStyle->getBorderStyle() !== null) { + $baseBorderStyle->setBorderStyle($borderStyle->getBorderStyle()); + //} + + if ($borderStyle->getColor() !== null && $borderStyle->getColor()->getARGB() !== null) { + $baseBorderStyle->setColor($borderStyle->getColor()); + } + } +} diff --git a/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard.php b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard.php new file mode 100644 index 0000000..d5d56a9 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard.php @@ -0,0 +1,95 @@ +cellRange = $cellRange; + } + + public function newRule(string $ruleType): WizardInterface + { + switch ($ruleType) { + case self::CELL_VALUE: + return new Wizard\CellValue($this->cellRange); + case self::TEXT_VALUE: + return new Wizard\TextValue($this->cellRange); + case self::BLANKS: + return new Wizard\Blanks($this->cellRange, true); + case self::NOT_BLANKS: + return new Wizard\Blanks($this->cellRange, false); + case self::ERRORS: + return new Wizard\Errors($this->cellRange, true); + case self::NOT_ERRORS: + return new Wizard\Errors($this->cellRange, false); + case self::EXPRESSION: + case self::FORMULA: + return new Wizard\Expression($this->cellRange); + case self::DATES_OCCURRING: + return new Wizard\DateValue($this->cellRange); + case self::DUPLICATES: + return new Wizard\Duplicates($this->cellRange, false); + case self::UNIQUE: + return new Wizard\Duplicates($this->cellRange, true); + default: + throw new Exception('No wizard exists for this CF rule type'); + } + } + + public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface + { + $conditionalType = $conditional->getConditionType(); + + switch ($conditionalType) { + case Conditional::CONDITION_CELLIS: + return Wizard\CellValue::fromConditional($conditional, $cellRange); + case Conditional::CONDITION_CONTAINSTEXT: + case Conditional::CONDITION_NOTCONTAINSTEXT: + case Conditional::CONDITION_BEGINSWITH: + case Conditional::CONDITION_ENDSWITH: + return Wizard\TextValue::fromConditional($conditional, $cellRange); + case Conditional::CONDITION_CONTAINSBLANKS: + case Conditional::CONDITION_NOTCONTAINSBLANKS: + return Wizard\Blanks::fromConditional($conditional, $cellRange); + case Conditional::CONDITION_CONTAINSERRORS: + case Conditional::CONDITION_NOTCONTAINSERRORS: + return Wizard\Errors::fromConditional($conditional, $cellRange); + case Conditional::CONDITION_TIMEPERIOD: + return Wizard\DateValue::fromConditional($conditional, $cellRange); + case Conditional::CONDITION_EXPRESSION: + return Wizard\Expression::fromConditional($conditional, $cellRange); + case Conditional::CONDITION_DUPLICATES: + case Conditional::CONDITION_UNIQUE: + return Wizard\Duplicates::fromConditional($conditional, $cellRange); + default: + throw new Exception('No wizard exists for this CF rule type'); + } + } +} diff --git a/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Blanks.php b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Blanks.php new file mode 100644 index 0000000..14f30d3 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Blanks.php @@ -0,0 +1,99 @@ + false, + 'isBlank' => true, + 'notEmpty' => false, + 'empty' => true, + ]; + + protected const EXPRESSIONS = [ + Wizard::NOT_BLANKS => 'LEN(TRIM(%s))>0', + Wizard::BLANKS => 'LEN(TRIM(%s))=0', + ]; + + /** + * @var bool + */ + protected $inverse; + + public function __construct(string $cellRange, bool $inverse = false) + { + parent::__construct($cellRange); + $this->inverse = $inverse; + } + + protected function inverse(bool $inverse): void + { + $this->inverse = $inverse; + } + + protected function getExpression(): void + { + $this->expression = sprintf( + self::EXPRESSIONS[$this->inverse ? Wizard::BLANKS : Wizard::NOT_BLANKS], + $this->referenceCell + ); + } + + public function getConditional(): Conditional + { + $this->getExpression(); + + $conditional = new Conditional(); + $conditional->setConditionType( + $this->inverse ? Conditional::CONDITION_CONTAINSBLANKS : Conditional::CONDITION_NOTCONTAINSBLANKS + ); + $conditional->setConditions([$this->expression]); + $conditional->setStyle($this->getStyle()); + $conditional->setStopIfTrue($this->getStopIfTrue()); + + return $conditional; + } + + public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface + { + if ( + $conditional->getConditionType() !== Conditional::CONDITION_CONTAINSBLANKS && + $conditional->getConditionType() !== Conditional::CONDITION_NOTCONTAINSBLANKS + ) { + throw new Exception('Conditional is not a Blanks CF Rule conditional'); + } + + $wizard = new self($cellRange); + $wizard->style = $conditional->getStyle(); + $wizard->stopIfTrue = $conditional->getStopIfTrue(); + $wizard->inverse = $conditional->getConditionType() === Conditional::CONDITION_CONTAINSBLANKS; + + return $wizard; + } + + /** + * @param string $methodName + * @param mixed[] $arguments + */ + public function __call($methodName, $arguments): self + { + if (!array_key_exists($methodName, self::OPERATORS)) { + throw new Exception('Invalid Operation for Blanks CF Rule Wizard'); + } + + $this->inverse(self::OPERATORS[$methodName]); + + return $this; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/CellValue.php b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/CellValue.php new file mode 100644 index 0000000..265b241 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/CellValue.php @@ -0,0 +1,200 @@ + Conditional::OPERATOR_EQUAL, + 'notEquals' => Conditional::OPERATOR_NOTEQUAL, + 'greaterThan' => Conditional::OPERATOR_GREATERTHAN, + 'greaterThanOrEqual' => Conditional::OPERATOR_GREATERTHANOREQUAL, + 'lessThan' => Conditional::OPERATOR_LESSTHAN, + 'lessThanOrEqual' => Conditional::OPERATOR_LESSTHANOREQUAL, + 'between' => Conditional::OPERATOR_BETWEEN, + 'notBetween' => Conditional::OPERATOR_NOTBETWEEN, + ]; + + protected const SINGLE_OPERATORS = CellMatcher::COMPARISON_OPERATORS; + + protected const RANGE_OPERATORS = CellMatcher::COMPARISON_RANGE_OPERATORS; + + /** @var string */ + protected $operator = Conditional::OPERATOR_EQUAL; + + /** @var array */ + protected $operand = [0]; + + /** + * @var string[] + */ + protected $operandValueType = []; + + public function __construct(string $cellRange) + { + parent::__construct($cellRange); + } + + protected function operator(string $operator): void + { + if ((!isset(self::SINGLE_OPERATORS[$operator])) && (!isset(self::RANGE_OPERATORS[$operator]))) { + throw new Exception('Invalid Operator for Cell Value CF Rule Wizard'); + } + + $this->operator = $operator; + } + + /** + * @param mixed $operand + */ + protected function operand(int $index, $operand, string $operandValueType = Wizard::VALUE_TYPE_LITERAL): void + { + if (is_string($operand)) { + $operand = $this->validateOperand($operand, $operandValueType); + } + + $this->operand[$index] = $operand; + $this->operandValueType[$index] = $operandValueType; + } + + /** + * @param mixed $value + * + * @return float|int|string + */ + protected function wrapValue($value, string $operandValueType) + { + if (!is_numeric($value) && !is_bool($value) && null !== $value) { + if ($operandValueType === Wizard::VALUE_TYPE_LITERAL) { + return '"' . str_replace('"', '""', $value) . '"'; + } + + return $this->cellConditionCheck($value); + } + + if (null === $value) { + $value = 'NULL'; + } elseif (is_bool($value)) { + $value = $value ? 'TRUE' : 'FALSE'; + } + + return $value; + } + + public function getConditional(): Conditional + { + if (!isset(self::RANGE_OPERATORS[$this->operator])) { + unset($this->operand[1], $this->operandValueType[1]); + } + $values = array_map([$this, 'wrapValue'], $this->operand, $this->operandValueType); + + $conditional = new Conditional(); + $conditional->setConditionType(Conditional::CONDITION_CELLIS); + $conditional->setOperatorType($this->operator); + $conditional->setConditions($values); + $conditional->setStyle($this->getStyle()); + $conditional->setStopIfTrue($this->getStopIfTrue()); + + return $conditional; + } + + protected static function unwrapString(string $condition): string + { + if ((strpos($condition, '"') === 0) && (strpos(strrev($condition), '"') === 0)) { + $condition = substr($condition, 1, -1); + } + + return str_replace('""', '"', $condition); + } + + public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface + { + if ($conditional->getConditionType() !== Conditional::CONDITION_CELLIS) { + throw new Exception('Conditional is not a Cell Value CF Rule conditional'); + } + + $wizard = new self($cellRange); + $wizard->style = $conditional->getStyle(); + $wizard->stopIfTrue = $conditional->getStopIfTrue(); + + $wizard->operator = $conditional->getOperatorType(); + $conditions = $conditional->getConditions(); + foreach ($conditions as $index => $condition) { + // Best-guess to try and identify if the text is a string literal, a cell reference or a formula? + $operandValueType = Wizard::VALUE_TYPE_LITERAL; + if (is_string($condition)) { + if (Calculation::keyInExcelConstants($condition)) { + $condition = Calculation::getExcelConstants($condition); + } elseif (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '$/i', $condition)) { + $operandValueType = Wizard::VALUE_TYPE_CELL; + $condition = self::reverseAdjustCellRef($condition, $cellRange); + } elseif ( + preg_match('/\(\)/', $condition) || + preg_match('/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/i', $condition) + ) { + $operandValueType = Wizard::VALUE_TYPE_FORMULA; + $condition = self::reverseAdjustCellRef($condition, $cellRange); + } else { + $condition = self::unwrapString($condition); + } + } + $wizard->operand($index, $condition, $operandValueType); + } + + return $wizard; + } + + /** + * @param string $methodName + * @param mixed[] $arguments + */ + public function __call($methodName, $arguments): self + { + if (!isset(self::MAGIC_OPERATIONS[$methodName]) && $methodName !== 'and') { + throw new Exception('Invalid Operator for Cell Value CF Rule Wizard'); + } + + if ($methodName === 'and') { + if (!isset(self::RANGE_OPERATORS[$this->operator])) { + throw new Exception('AND Value is only appropriate for range operators'); + } + + // Scrutinizer ignores its own suggested workaround. + //$this->operand(1, /** @scrutinizer ignore-type */ ...$arguments); + if (count($arguments) < 2) { + $this->operand(1, $arguments[0]); + } else { + $this->operand(1, $arguments[0], $arguments[1]); + } + + return $this; + } + + $this->operator(self::MAGIC_OPERATIONS[$methodName]); + //$this->operand(0, ...$arguments); + if (count($arguments) < 2) { + $this->operand(0, $arguments[0]); + } else { + $this->operand(0, $arguments[0], $arguments[1]); + } + + return $this; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/DateValue.php b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/DateValue.php new file mode 100644 index 0000000..453dcdf --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/DateValue.php @@ -0,0 +1,111 @@ + Conditional::TIMEPERIOD_YESTERDAY, + 'today' => Conditional::TIMEPERIOD_TODAY, + 'tomorrow' => Conditional::TIMEPERIOD_TOMORROW, + 'lastSevenDays' => Conditional::TIMEPERIOD_LAST_7_DAYS, + 'last7Days' => Conditional::TIMEPERIOD_LAST_7_DAYS, + 'lastWeek' => Conditional::TIMEPERIOD_LAST_WEEK, + 'thisWeek' => Conditional::TIMEPERIOD_THIS_WEEK, + 'nextWeek' => Conditional::TIMEPERIOD_NEXT_WEEK, + 'lastMonth' => Conditional::TIMEPERIOD_LAST_MONTH, + 'thisMonth' => Conditional::TIMEPERIOD_THIS_MONTH, + 'nextMonth' => Conditional::TIMEPERIOD_NEXT_MONTH, + ]; + + protected const EXPRESSIONS = [ + Conditional::TIMEPERIOD_YESTERDAY => 'FLOOR(%s,1)=TODAY()-1', + Conditional::TIMEPERIOD_TODAY => 'FLOOR(%s,1)=TODAY()', + Conditional::TIMEPERIOD_TOMORROW => 'FLOOR(%s,1)=TODAY()+1', + Conditional::TIMEPERIOD_LAST_7_DAYS => 'AND(TODAY()-FLOOR(%s,1)<=6,FLOOR(%s,1)<=TODAY())', + Conditional::TIMEPERIOD_LAST_WEEK => 'AND(TODAY()-ROUNDDOWN(%s,0)>=(WEEKDAY(TODAY())),TODAY()-ROUNDDOWN(%s,0)<(WEEKDAY(TODAY())+7))', + Conditional::TIMEPERIOD_THIS_WEEK => 'AND(TODAY()-ROUNDDOWN(%s,0)<=WEEKDAY(TODAY())-1,ROUNDDOWN(%s,0)-TODAY()<=7-WEEKDAY(TODAY()))', + Conditional::TIMEPERIOD_NEXT_WEEK => 'AND(ROUNDDOWN(%s,0)-TODAY()>(7-WEEKDAY(TODAY())),ROUNDDOWN(%s,0)-TODAY()<(15-WEEKDAY(TODAY())))', + Conditional::TIMEPERIOD_LAST_MONTH => 'AND(MONTH(%s)=MONTH(EDATE(TODAY(),0-1)),YEAR(%s)=YEAR(EDATE(TODAY(),0-1)))', + Conditional::TIMEPERIOD_THIS_MONTH => 'AND(MONTH(%s)=MONTH(TODAY()),YEAR(%s)=YEAR(TODAY()))', + Conditional::TIMEPERIOD_NEXT_MONTH => 'AND(MONTH(%s)=MONTH(EDATE(TODAY(),0+1)),YEAR(%s)=YEAR(EDATE(TODAY(),0+1)))', + ]; + + /** @var string */ + protected $operator; + + public function __construct(string $cellRange) + { + parent::__construct($cellRange); + } + + protected function operator(string $operator): void + { + $this->operator = $operator; + } + + protected function setExpression(): void + { + $referenceCount = substr_count(self::EXPRESSIONS[$this->operator], '%s'); + $references = array_fill(0, $referenceCount, $this->referenceCell); + $this->expression = sprintf(self::EXPRESSIONS[$this->operator], ...$references); + } + + public function getConditional(): Conditional + { + $this->setExpression(); + + $conditional = new Conditional(); + $conditional->setConditionType(Conditional::CONDITION_TIMEPERIOD); + $conditional->setText($this->operator); + $conditional->setConditions([$this->expression]); + $conditional->setStyle($this->getStyle()); + $conditional->setStopIfTrue($this->getStopIfTrue()); + + return $conditional; + } + + public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface + { + if ($conditional->getConditionType() !== Conditional::CONDITION_TIMEPERIOD) { + throw new Exception('Conditional is not a Date Value CF Rule conditional'); + } + + $wizard = new self($cellRange); + $wizard->style = $conditional->getStyle(); + $wizard->stopIfTrue = $conditional->getStopIfTrue(); + $wizard->operator = $conditional->getText(); + + return $wizard; + } + + /** + * @param string $methodName + * @param mixed[] $arguments + */ + public function __call($methodName, $arguments): self + { + if (!isset(self::MAGIC_OPERATIONS[$methodName])) { + throw new Exception('Invalid Operation for Date Value CF Rule Wizard'); + } + + $this->operator(self::MAGIC_OPERATIONS[$methodName]); + + return $this; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Duplicates.php b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Duplicates.php new file mode 100644 index 0000000..3f063fe --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Duplicates.php @@ -0,0 +1,78 @@ + false, + 'unique' => true, + ]; + + /** + * @var bool + */ + protected $inverse; + + public function __construct(string $cellRange, bool $inverse = false) + { + parent::__construct($cellRange); + $this->inverse = $inverse; + } + + protected function inverse(bool $inverse): void + { + $this->inverse = $inverse; + } + + public function getConditional(): Conditional + { + $conditional = new Conditional(); + $conditional->setConditionType( + $this->inverse ? Conditional::CONDITION_UNIQUE : Conditional::CONDITION_DUPLICATES + ); + $conditional->setStyle($this->getStyle()); + $conditional->setStopIfTrue($this->getStopIfTrue()); + + return $conditional; + } + + public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface + { + if ( + $conditional->getConditionType() !== Conditional::CONDITION_DUPLICATES && + $conditional->getConditionType() !== Conditional::CONDITION_UNIQUE + ) { + throw new Exception('Conditional is not a Duplicates CF Rule conditional'); + } + + $wizard = new self($cellRange); + $wizard->style = $conditional->getStyle(); + $wizard->stopIfTrue = $conditional->getStopIfTrue(); + $wizard->inverse = $conditional->getConditionType() === Conditional::CONDITION_UNIQUE; + + return $wizard; + } + + /** + * @param string $methodName + * @param mixed[] $arguments + */ + public function __call($methodName, $arguments): self + { + if (!array_key_exists($methodName, self::OPERATORS)) { + throw new Exception('Invalid Operation for Errors CF Rule Wizard'); + } + + $this->inverse(self::OPERATORS[$methodName]); + + return $this; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Errors.php b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Errors.php new file mode 100644 index 0000000..56b9086 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Errors.php @@ -0,0 +1,95 @@ + false, + 'isError' => true, + ]; + + protected const EXPRESSIONS = [ + Wizard::NOT_ERRORS => 'NOT(ISERROR(%s))', + Wizard::ERRORS => 'ISERROR(%s)', + ]; + + /** + * @var bool + */ + protected $inverse; + + public function __construct(string $cellRange, bool $inverse = false) + { + parent::__construct($cellRange); + $this->inverse = $inverse; + } + + protected function inverse(bool $inverse): void + { + $this->inverse = $inverse; + } + + protected function getExpression(): void + { + $this->expression = sprintf( + self::EXPRESSIONS[$this->inverse ? Wizard::ERRORS : Wizard::NOT_ERRORS], + $this->referenceCell + ); + } + + public function getConditional(): Conditional + { + $this->getExpression(); + + $conditional = new Conditional(); + $conditional->setConditionType( + $this->inverse ? Conditional::CONDITION_CONTAINSERRORS : Conditional::CONDITION_NOTCONTAINSERRORS + ); + $conditional->setConditions([$this->expression]); + $conditional->setStyle($this->getStyle()); + $conditional->setStopIfTrue($this->getStopIfTrue()); + + return $conditional; + } + + public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface + { + if ( + $conditional->getConditionType() !== Conditional::CONDITION_CONTAINSERRORS && + $conditional->getConditionType() !== Conditional::CONDITION_NOTCONTAINSERRORS + ) { + throw new Exception('Conditional is not an Errors CF Rule conditional'); + } + + $wizard = new self($cellRange); + $wizard->style = $conditional->getStyle(); + $wizard->stopIfTrue = $conditional->getStopIfTrue(); + $wizard->inverse = $conditional->getConditionType() === Conditional::CONDITION_CONTAINSERRORS; + + return $wizard; + } + + /** + * @param string $methodName + * @param mixed[] $arguments + */ + public function __call($methodName, $arguments): self + { + if (!array_key_exists($methodName, self::OPERATORS)) { + throw new Exception('Invalid Operation for Errors CF Rule Wizard'); + } + + $this->inverse(self::OPERATORS[$methodName]); + + return $this; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Expression.php b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Expression.php new file mode 100644 index 0000000..e6d29f7 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Expression.php @@ -0,0 +1,75 @@ +validateOperand($expression, Wizard::VALUE_TYPE_FORMULA); + $this->expression = $expression; + + return $this; + } + + public function getConditional(): Conditional + { + $expression = $this->adjustConditionsForCellReferences([$this->expression]); + + $conditional = new Conditional(); + $conditional->setConditionType(Conditional::CONDITION_EXPRESSION); + $conditional->setConditions($expression); + $conditional->setStyle($this->getStyle()); + $conditional->setStopIfTrue($this->getStopIfTrue()); + + return $conditional; + } + + public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface + { + if ($conditional->getConditionType() !== Conditional::CONDITION_EXPRESSION) { + throw new Exception('Conditional is not an Expression CF Rule conditional'); + } + + $wizard = new self($cellRange); + $wizard->style = $conditional->getStyle(); + $wizard->stopIfTrue = $conditional->getStopIfTrue(); + $wizard->expression = self::reverseAdjustCellRef((string) ($conditional->getConditions()[0]), $cellRange); + + return $wizard; + } + + /** + * @param string $methodName + * @param mixed[] $arguments + */ + public function __call($methodName, $arguments): self + { + if ($methodName !== 'formula') { + throw new Exception('Invalid Operation for Expression CF Rule Wizard'); + } + + // Scrutinizer ignores its own recommendation + //$this->expression(/** @scrutinizer ignore-type */ ...$arguments); + $this->expression($arguments[0]); + + return $this; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/TextValue.php b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/TextValue.php new file mode 100644 index 0000000..987cfbf --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/TextValue.php @@ -0,0 +1,164 @@ + Conditional::OPERATOR_CONTAINSTEXT, + 'doesntContain' => Conditional::OPERATOR_NOTCONTAINS, + 'doesNotContain' => Conditional::OPERATOR_NOTCONTAINS, + 'beginsWith' => Conditional::OPERATOR_BEGINSWITH, + 'startsWith' => Conditional::OPERATOR_BEGINSWITH, + 'endsWith' => Conditional::OPERATOR_ENDSWITH, + ]; + + protected const OPERATORS = [ + Conditional::OPERATOR_CONTAINSTEXT => Conditional::CONDITION_CONTAINSTEXT, + Conditional::OPERATOR_NOTCONTAINS => Conditional::CONDITION_NOTCONTAINSTEXT, + Conditional::OPERATOR_BEGINSWITH => Conditional::CONDITION_BEGINSWITH, + Conditional::OPERATOR_ENDSWITH => Conditional::CONDITION_ENDSWITH, + ]; + + protected const EXPRESSIONS = [ + Conditional::OPERATOR_CONTAINSTEXT => 'NOT(ISERROR(SEARCH(%s,%s)))', + Conditional::OPERATOR_NOTCONTAINS => 'ISERROR(SEARCH(%s,%s))', + Conditional::OPERATOR_BEGINSWITH => 'LEFT(%s,LEN(%s))=%s', + Conditional::OPERATOR_ENDSWITH => 'RIGHT(%s,LEN(%s))=%s', + ]; + + /** @var string */ + protected $operator; + + /** @var string */ + protected $operand; + + /** + * @var string + */ + protected $operandValueType; + + public function __construct(string $cellRange) + { + parent::__construct($cellRange); + } + + protected function operator(string $operator): void + { + if (!isset(self::OPERATORS[$operator])) { + throw new Exception('Invalid Operator for Text Value CF Rule Wizard'); + } + + $this->operator = $operator; + } + + protected function operand(string $operand, string $operandValueType = Wizard::VALUE_TYPE_LITERAL): void + { + $operand = $this->validateOperand($operand, $operandValueType); + + $this->operand = $operand; + $this->operandValueType = $operandValueType; + } + + protected function wrapValue(string $value): string + { + return '"' . $value . '"'; + } + + protected function setExpression(): void + { + $operand = $this->operandValueType === Wizard::VALUE_TYPE_LITERAL + ? $this->wrapValue(str_replace('"', '""', $this->operand)) + : $this->cellConditionCheck($this->operand); + + if ( + $this->operator === Conditional::OPERATOR_CONTAINSTEXT || + $this->operator === Conditional::OPERATOR_NOTCONTAINS + ) { + $this->expression = sprintf(self::EXPRESSIONS[$this->operator], $operand, $this->referenceCell); + } else { + $this->expression = sprintf(self::EXPRESSIONS[$this->operator], $this->referenceCell, $operand, $operand); + } + } + + public function getConditional(): Conditional + { + $this->setExpression(); + + $conditional = new Conditional(); + $conditional->setConditionType(self::OPERATORS[$this->operator]); + $conditional->setOperatorType($this->operator); + $conditional->setText( + $this->operandValueType !== Wizard::VALUE_TYPE_LITERAL + ? $this->cellConditionCheck($this->operand) + : $this->operand + ); + $conditional->setConditions([$this->expression]); + $conditional->setStyle($this->getStyle()); + $conditional->setStopIfTrue($this->getStopIfTrue()); + + return $conditional; + } + + public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface + { + if (!in_array($conditional->getConditionType(), self::OPERATORS, true)) { + throw new Exception('Conditional is not a Text Value CF Rule conditional'); + } + + $wizard = new self($cellRange); + $wizard->operator = (string) array_search($conditional->getConditionType(), self::OPERATORS, true); + $wizard->style = $conditional->getStyle(); + $wizard->stopIfTrue = $conditional->getStopIfTrue(); + + // Best-guess to try and identify if the text is a string literal, a cell reference or a formula? + $wizard->operandValueType = Wizard::VALUE_TYPE_LITERAL; + $condition = $conditional->getText(); + if (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '$/i', $condition)) { + $wizard->operandValueType = Wizard::VALUE_TYPE_CELL; + $condition = self::reverseAdjustCellRef($condition, $cellRange); + } elseif ( + preg_match('/\(\)/', $condition) || + preg_match('/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/i', $condition) + ) { + $wizard->operandValueType = Wizard::VALUE_TYPE_FORMULA; + } + $wizard->operand = $condition; + + return $wizard; + } + + /** + * @param string $methodName + * @param mixed[] $arguments + */ + public function __call($methodName, $arguments): self + { + if (!isset(self::MAGIC_OPERATIONS[$methodName])) { + throw new Exception('Invalid Operation for Text Value CF Rule Wizard'); + } + + $this->operator(self::MAGIC_OPERATIONS[$methodName]); + //$this->operand(...$arguments); + if (count($arguments) < 2) { + $this->operand($arguments[0]); + } else { + $this->operand($arguments[0], $arguments[1]); + } + + return $this; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardAbstract.php b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardAbstract.php new file mode 100644 index 0000000..5644aab --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardAbstract.php @@ -0,0 +1,199 @@ +setCellRange($cellRange); + } + + public function getCellRange(): string + { + return $this->cellRange; + } + + public function setCellRange(string $cellRange): void + { + $this->cellRange = $cellRange; + $this->setReferenceCellForExpressions($cellRange); + } + + protected function setReferenceCellForExpressions(string $conditionalRange): void + { + $conditionalRange = Coordinate::splitRange(str_replace('$', '', strtoupper($conditionalRange))); + [$this->referenceCell] = $conditionalRange[0]; + + [$this->referenceColumn, $this->referenceRow] = Coordinate::indexesFromString($this->referenceCell); + } + + public function getStopIfTrue(): bool + { + return $this->stopIfTrue; + } + + public function setStopIfTrue(bool $stopIfTrue): void + { + $this->stopIfTrue = $stopIfTrue; + } + + public function getStyle(): Style + { + return $this->style ?? new Style(false, true); + } + + public function setStyle(Style $style): void + { + $this->style = $style; + } + + protected function validateOperand(string $operand, string $operandValueType = Wizard::VALUE_TYPE_LITERAL): string + { + if ( + $operandValueType === Wizard::VALUE_TYPE_LITERAL && + substr($operand, 0, 1) === '"' && + substr($operand, -1) === '"' + ) { + $operand = str_replace('""', '"', substr($operand, 1, -1)); + } elseif ($operandValueType === Wizard::VALUE_TYPE_FORMULA && substr($operand, 0, 1) === '=') { + $operand = substr($operand, 1); + } + + return $operand; + } + + protected static function reverseCellAdjustment(array $matches, int $referenceColumn, int $referenceRow): string + { + $worksheet = $matches[1]; + $column = $matches[6]; + $row = $matches[7]; + + if (strpos($column, '$') === false) { + $column = Coordinate::columnIndexFromString($column); + $column -= $referenceColumn - 1; + $column = Coordinate::stringFromColumnIndex($column); + } + + if (strpos($row, '$') === false) { + $row -= $referenceRow - 1; + } + + return "{$worksheet}{$column}{$row}"; + } + + public static function reverseAdjustCellRef(string $condition, string $cellRange): string + { + $conditionalRange = Coordinate::splitRange(str_replace('$', '', strtoupper($cellRange))); + [$referenceCell] = $conditionalRange[0]; + [$referenceColumnIndex, $referenceRow] = Coordinate::indexesFromString($referenceCell); + + $splitCondition = explode(Calculation::FORMULA_STRING_QUOTE, $condition); + $i = false; + foreach ($splitCondition as &$value) { + // Only count/replace in alternating array entries (ie. not in quoted strings) + $i = $i === false; + if ($i) { + $value = (string) preg_replace_callback( + '/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/i', + function ($matches) use ($referenceColumnIndex, $referenceRow) { + return self::reverseCellAdjustment($matches, $referenceColumnIndex, $referenceRow); + }, + $value + ); + } + } + unset($value); + + // Then rebuild the condition string to return it + return implode(Calculation::FORMULA_STRING_QUOTE, $splitCondition); + } + + protected function conditionCellAdjustment(array $matches): string + { + $worksheet = $matches[1]; + $column = $matches[6]; + $row = $matches[7]; + + if (strpos($column, '$') === false) { + $column = Coordinate::columnIndexFromString($column); + $column += $this->referenceColumn - 1; + $column = Coordinate::stringFromColumnIndex($column); + } + + if (strpos($row, '$') === false) { + $row += $this->referenceRow - 1; + } + + return "{$worksheet}{$column}{$row}"; + } + + protected function cellConditionCheck(string $condition): string + { + $splitCondition = explode(Calculation::FORMULA_STRING_QUOTE, $condition); + $i = false; + foreach ($splitCondition as &$value) { + // Only count/replace in alternating array entries (ie. not in quoted strings) + $i = $i === false; + if ($i) { + $value = (string) preg_replace_callback( + '/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/i', + [$this, 'conditionCellAdjustment'], + $value + ); + } + } + unset($value); + + // Then rebuild the condition string to return it + return implode(Calculation::FORMULA_STRING_QUOTE, $splitCondition); + } + + protected function adjustConditionsForCellReferences(array $conditions): array + { + return array_map( + [$this, 'cellConditionCheck'], + $conditions + ); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardInterface.php b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardInterface.php new file mode 100644 index 0000000..10ad57b --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/WizardInterface.php @@ -0,0 +1,25 @@ +parent->getSharedComponent()->getFill(); + /** @var Style */ + $parent = $this->parent; + + return $parent->getSharedComponent()->getFill(); } /** @@ -126,7 +130,7 @@ class Fill extends Supervisor * $spreadsheet->getActiveSheet()->getStyle('B2')->getFill()->applyFromArray( * [ * 'fillType' => Fill::FILL_GRADIENT_LINEAR, - * 'rotation' => 0, + * 'rotation' => 0.0, * 'startColor' => [ * 'rgb' => '000000' * ], @@ -137,32 +141,30 @@ class Fill extends Supervisor * ); * * - * @param array $pStyles Array containing style information + * @param array $styleArray Array containing style information * - * @throws PhpSpreadsheetException - * - * @return Fill + * @return $this */ - public function applyFromArray(array $pStyles) + public function applyFromArray(array $styleArray) { if ($this->isSupervisor) { - $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($pStyles)); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray)); } else { - if (isset($pStyles['fillType'])) { - $this->setFillType($pStyles['fillType']); + if (isset($styleArray['fillType'])) { + $this->setFillType($styleArray['fillType']); } - if (isset($pStyles['rotation'])) { - $this->setRotation($pStyles['rotation']); + if (isset($styleArray['rotation'])) { + $this->setRotation($styleArray['rotation']); } - if (isset($pStyles['startColor'])) { - $this->getStartColor()->applyFromArray($pStyles['startColor']); + if (isset($styleArray['startColor'])) { + $this->getStartColor()->applyFromArray($styleArray['startColor']); } - if (isset($pStyles['endColor'])) { - $this->getEndColor()->applyFromArray($pStyles['endColor']); + if (isset($styleArray['endColor'])) { + $this->getEndColor()->applyFromArray($styleArray['endColor']); } - if (isset($pStyles['color'])) { - $this->getStartColor()->applyFromArray($pStyles['color']); - $this->getEndColor()->applyFromArray($pStyles['color']); + if (isset($styleArray['color'])) { + $this->getStartColor()->applyFromArray($styleArray['color']); + $this->getEndColor()->applyFromArray($styleArray['color']); } } @@ -172,7 +174,7 @@ class Fill extends Supervisor /** * Get Fill Type. * - * @return string + * @return null|string */ public function getFillType() { @@ -186,17 +188,17 @@ class Fill extends Supervisor /** * Set Fill Type. * - * @param string $pValue Fill type, see self::FILL_* + * @param string $fillType Fill type, see self::FILL_* * - * @return Fill + * @return $this */ - public function setFillType($pValue) + public function setFillType($fillType) { if ($this->isSupervisor) { - $styleArray = $this->getStyleArray(['fillType' => $pValue]); + $styleArray = $this->getStyleArray(['fillType' => $fillType]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { - $this->fillType = $pValue; + $this->fillType = $fillType; } return $this; @@ -219,17 +221,17 @@ class Fill extends Supervisor /** * Set Rotation. * - * @param float $pValue + * @param float $angleInDegrees * - * @return Fill + * @return $this */ - public function setRotation($pValue) + public function setRotation($angleInDegrees) { if ($this->isSupervisor) { - $styleArray = $this->getStyleArray(['rotation' => $pValue]); + $styleArray = $this->getStyleArray(['rotation' => $angleInDegrees]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { - $this->rotation = $pValue; + $this->rotation = $angleInDegrees; } return $this; @@ -248,16 +250,13 @@ class Fill extends Supervisor /** * Set Start Color. * - * @param Color $pValue - * - * @throws PhpSpreadsheetException - * - * @return Fill + * @return $this */ - public function setStartColor(Color $pValue) + public function setStartColor(Color $color) { + $this->colorChanged = true; // make sure parameter is a real color and not a supervisor - $color = $pValue->getIsSupervisor() ? $pValue->getSharedComponent() : $pValue; + $color = $color->getIsSupervisor() ? $color->getSharedComponent() : $color; if ($this->isSupervisor) { $styleArray = $this->getStartColor()->getStyleArray(['argb' => $color->getARGB()]); @@ -282,16 +281,13 @@ class Fill extends Supervisor /** * Set End Color. * - * @param Color $pValue - * - * @throws PhpSpreadsheetException - * - * @return Fill + * @return $this */ - public function setEndColor(Color $pValue) + public function setEndColor(Color $color) { + $this->colorChanged = true; // make sure parameter is a real color and not a supervisor - $color = $pValue->getIsSupervisor() ? $pValue->getSharedComponent() : $pValue; + $color = $color->getIsSupervisor() ? $color->getSharedComponent() : $color; if ($this->isSupervisor) { $styleArray = $this->getEndColor()->getStyleArray(['argb' => $color->getARGB()]); @@ -303,6 +299,17 @@ class Fill extends Supervisor return $this; } + public function getColorsChanged(): bool + { + if ($this->isSupervisor) { + $changed = $this->getSharedComponent()->colorChanged; + } else { + $changed = $this->colorChanged; + } + + return $changed || $this->startColor->getHasChanged() || $this->endColor->getHasChanged(); + } + /** * Get hash code. * @@ -320,7 +327,21 @@ class Fill extends Supervisor $this->getRotation() . ($this->getFillType() !== self::FILL_NONE ? $this->getStartColor()->getHashCode() : '') . ($this->getFillType() !== self::FILL_NONE ? $this->getEndColor()->getHashCode() : '') . + ((string) $this->getColorsChanged()) . __CLASS__ ); } + + protected function exportArray1(): array + { + $exportedArray = []; + $this->exportArray2($exportedArray, 'fillType', $this->getFillType()); + $this->exportArray2($exportedArray, 'rotation', $this->getRotation()); + if ($this->getColorsChanged()) { + $this->exportArray2($exportedArray, 'endColor', $this->getEndColor()); + $this->exportArray2($exportedArray, 'startColor', $this->getStartColor()); + } + + return $exportedArray; + } } diff --git a/PhpOffice/PhpSpreadsheet/Style/Font.php b/PhpOffice/PhpSpreadsheet/Style/Font.php old mode 100755 new mode 100644 index 6d8e23b..3d7bc1b --- a/PhpOffice/PhpSpreadsheet/Style/Font.php +++ b/PhpOffice/PhpSpreadsheet/Style/Font.php @@ -2,7 +2,7 @@ namespace PhpOffice\PhpSpreadsheet\Style; -use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; +use PhpOffice\PhpSpreadsheet\Chart\ChartColor; class Font extends Supervisor { @@ -16,56 +16,82 @@ class Font extends Supervisor /** * Font Name. * - * @var string + * @var null|string */ protected $name = 'Calibri'; + /** + * The following 7 are used only for chart titles, I think. + * + *@var string + */ + private $latin = ''; + + /** @var string */ + private $eastAsian = ''; + + /** @var string */ + private $complexScript = ''; + + /** @var int */ + private $baseLine = 0; + + /** @var string */ + private $strikeType = ''; + + /** @var ?ChartColor */ + private $underlineColor; + + /** @var ?ChartColor */ + private $chartColor; + // end of chart title items + /** * Font Size. * - * @var float + * @var null|float */ protected $size = 11; /** * Bold. * - * @var bool + * @var null|bool */ protected $bold = false; /** * Italic. * - * @var bool + * @var null|bool */ protected $italic = false; /** * Superscript. * - * @var bool + * @var null|bool */ protected $superscript = false; /** * Subscript. * - * @var bool + * @var null|bool */ protected $subscript = false; /** * Underline. * - * @var string + * @var null|string */ protected $underline = self::UNDERLINE_NONE; /** * Strikethrough. * - * @var bool + * @var null|bool */ protected $strikethrough = false; @@ -77,7 +103,7 @@ class Font extends Supervisor protected $color; /** - * @var int + * @var null|int */ public $colorIndex; @@ -124,7 +150,10 @@ class Font extends Supervisor */ public function getSharedComponent() { - return $this->parent->getSharedComponent()->getFont(); + /** @var Style */ + $parent = $this->parent; + + return $parent->getSharedComponent()->getFont(); } /** @@ -157,43 +186,50 @@ class Font extends Supervisor * ); * * - * @param array $pStyles Array containing style information + * @param array $styleArray Array containing style information * - * @throws PhpSpreadsheetException - * - * @return Font + * @return $this */ - public function applyFromArray(array $pStyles) + public function applyFromArray(array $styleArray) { if ($this->isSupervisor) { - $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($pStyles)); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray)); } else { - if (isset($pStyles['name'])) { - $this->setName($pStyles['name']); + if (isset($styleArray['name'])) { + $this->setName($styleArray['name']); } - if (isset($pStyles['bold'])) { - $this->setBold($pStyles['bold']); + if (isset($styleArray['latin'])) { + $this->setLatin($styleArray['latin']); } - if (isset($pStyles['italic'])) { - $this->setItalic($pStyles['italic']); + if (isset($styleArray['eastAsian'])) { + $this->setEastAsian($styleArray['eastAsian']); } - if (isset($pStyles['superscript'])) { - $this->setSuperscript($pStyles['superscript']); + if (isset($styleArray['complexScript'])) { + $this->setComplexScript($styleArray['complexScript']); } - if (isset($pStyles['subscript'])) { - $this->setSubscript($pStyles['subscript']); + if (isset($styleArray['bold'])) { + $this->setBold($styleArray['bold']); } - if (isset($pStyles['underline'])) { - $this->setUnderline($pStyles['underline']); + if (isset($styleArray['italic'])) { + $this->setItalic($styleArray['italic']); } - if (isset($pStyles['strikethrough'])) { - $this->setStrikethrough($pStyles['strikethrough']); + if (isset($styleArray['superscript'])) { + $this->setSuperscript($styleArray['superscript']); } - if (isset($pStyles['color'])) { - $this->getColor()->applyFromArray($pStyles['color']); + if (isset($styleArray['subscript'])) { + $this->setSubscript($styleArray['subscript']); } - if (isset($pStyles['size'])) { - $this->setSize($pStyles['size']); + if (isset($styleArray['underline'])) { + $this->setUnderline($styleArray['underline']); + } + if (isset($styleArray['strikethrough'])) { + $this->setStrikethrough($styleArray['strikethrough']); + } + if (isset($styleArray['color'])) { + $this->getColor()->applyFromArray($styleArray['color']); + } + if (isset($styleArray['size'])) { + $this->setSize($styleArray['size']); } } @@ -203,7 +239,7 @@ class Font extends Supervisor /** * Get Name. * - * @return string + * @return null|string */ public function getName() { @@ -214,23 +250,104 @@ class Font extends Supervisor return $this->name; } + public function getLatin(): string + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getLatin(); + } + + return $this->latin; + } + + public function getEastAsian(): string + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getEastAsian(); + } + + return $this->eastAsian; + } + + public function getComplexScript(): string + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getComplexScript(); + } + + return $this->complexScript; + } + /** * Set Name. * - * @param string $pValue + * @param string $fontname * - * @return Font + * @return $this */ - public function setName($pValue) + public function setName($fontname) { - if ($pValue == '') { - $pValue = 'Calibri'; + if ($fontname == '') { + $fontname = 'Calibri'; } if ($this->isSupervisor) { - $styleArray = $this->getStyleArray(['name' => $pValue]); + $styleArray = $this->getStyleArray(['name' => $fontname]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { - $this->name = $pValue; + $this->name = $fontname; + } + + return $this; + } + + public function setLatin(string $fontname): self + { + if ($fontname == '') { + $fontname = 'Calibri'; + } + if (!$this->isSupervisor) { + $this->latin = $fontname; + } else { + // should never be true + // @codeCoverageIgnoreStart + $styleArray = $this->getStyleArray(['latin' => $fontname]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + // @codeCoverageIgnoreEnd + } + + return $this; + } + + public function setEastAsian(string $fontname): self + { + if ($fontname == '') { + $fontname = 'Calibri'; + } + if (!$this->isSupervisor) { + $this->eastAsian = $fontname; + } else { + // should never be true + // @codeCoverageIgnoreStart + $styleArray = $this->getStyleArray(['eastAsian' => $fontname]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + // @codeCoverageIgnoreEnd + } + + return $this; + } + + public function setComplexScript(string $fontname): self + { + if ($fontname == '') { + $fontname = 'Calibri'; + } + if (!$this->isSupervisor) { + $this->complexScript = $fontname; + } else { + // should never be true + // @codeCoverageIgnoreStart + $styleArray = $this->getStyleArray(['complexScript' => $fontname]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + // @codeCoverageIgnoreEnd } return $this; @@ -239,7 +356,7 @@ class Font extends Supervisor /** * Get Size. * - * @return float + * @return null|float */ public function getSize() { @@ -253,20 +370,29 @@ class Font extends Supervisor /** * Set Size. * - * @param float $pValue + * @param mixed $sizeInPoints A float representing the value of a positive measurement in points (1/72 of an inch) * - * @return Font + * @return $this */ - public function setSize($pValue) + public function setSize($sizeInPoints, bool $nullOk = false) { - if ($pValue == '') { - $pValue = 10; + if (is_string($sizeInPoints) || is_int($sizeInPoints)) { + $sizeInPoints = (float) $sizeInPoints; // $pValue = 0 if given string is not numeric } + + // Size must be a positive floating point number + // ECMA-376-1:2016, part 1, chapter 18.4.11 sz (Font Size), p. 1536 + if (!is_float($sizeInPoints) || !($sizeInPoints > 0)) { + if (!$nullOk || $sizeInPoints !== null) { + $sizeInPoints = 10.0; + } + } + if ($this->isSupervisor) { - $styleArray = $this->getStyleArray(['size' => $pValue]); + $styleArray = $this->getStyleArray(['size' => $sizeInPoints]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { - $this->size = $pValue; + $this->size = $sizeInPoints; } return $this; @@ -275,7 +401,7 @@ class Font extends Supervisor /** * Get Bold. * - * @return bool + * @return null|bool */ public function getBold() { @@ -289,20 +415,20 @@ class Font extends Supervisor /** * Set Bold. * - * @param bool $pValue + * @param bool $bold * - * @return Font + * @return $this */ - public function setBold($pValue) + public function setBold($bold) { - if ($pValue == '') { - $pValue = false; + if ($bold == '') { + $bold = false; } if ($this->isSupervisor) { - $styleArray = $this->getStyleArray(['bold' => $pValue]); + $styleArray = $this->getStyleArray(['bold' => $bold]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { - $this->bold = $pValue; + $this->bold = $bold; } return $this; @@ -311,7 +437,7 @@ class Font extends Supervisor /** * Get Italic. * - * @return bool + * @return null|bool */ public function getItalic() { @@ -325,20 +451,20 @@ class Font extends Supervisor /** * Set Italic. * - * @param bool $pValue + * @param bool $italic * - * @return Font + * @return $this */ - public function setItalic($pValue) + public function setItalic($italic) { - if ($pValue == '') { - $pValue = false; + if ($italic == '') { + $italic = false; } if ($this->isSupervisor) { - $styleArray = $this->getStyleArray(['italic' => $pValue]); + $styleArray = $this->getStyleArray(['italic' => $italic]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { - $this->italic = $pValue; + $this->italic = $italic; } return $this; @@ -347,7 +473,7 @@ class Font extends Supervisor /** * Get Superscript. * - * @return bool + * @return null|bool */ public function getSuperscript() { @@ -361,21 +487,18 @@ class Font extends Supervisor /** * Set Superscript. * - * @param bool $pValue - * - * @return Font + * @return $this */ - public function setSuperscript($pValue) + public function setSuperscript(bool $superscript) { - if ($pValue == '') { - $pValue = false; - } if ($this->isSupervisor) { - $styleArray = $this->getStyleArray(['superscript' => $pValue]); + $styleArray = $this->getStyleArray(['superscript' => $superscript]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { - $this->superscript = $pValue; - $this->subscript = !$pValue; + $this->superscript = $superscript; + if ($this->superscript) { + $this->subscript = false; + } } return $this; @@ -384,7 +507,7 @@ class Font extends Supervisor /** * Get Subscript. * - * @return bool + * @return null|bool */ public function getSubscript() { @@ -398,21 +521,114 @@ class Font extends Supervisor /** * Set Subscript. * - * @param bool $pValue - * - * @return Font + * @return $this */ - public function setSubscript($pValue) + public function setSubscript(bool $subscript) { - if ($pValue == '') { - $pValue = false; - } if ($this->isSupervisor) { - $styleArray = $this->getStyleArray(['subscript' => $pValue]); + $styleArray = $this->getStyleArray(['subscript' => $subscript]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { - $this->subscript = $pValue; - $this->superscript = !$pValue; + $this->subscript = $subscript; + if ($this->subscript) { + $this->superscript = false; + } + } + + return $this; + } + + public function getBaseLine(): int + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getBaseLine(); + } + + return $this->baseLine; + } + + public function setBaseLine(int $baseLine): self + { + if (!$this->isSupervisor) { + $this->baseLine = $baseLine; + } else { + // should never be true + // @codeCoverageIgnoreStart + $styleArray = $this->getStyleArray(['baseLine' => $baseLine]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + // @codeCoverageIgnoreEnd + } + + return $this; + } + + public function getStrikeType(): string + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getStrikeType(); + } + + return $this->strikeType; + } + + public function setStrikeType(string $strikeType): self + { + if (!$this->isSupervisor) { + $this->strikeType = $strikeType; + } else { + // should never be true + // @codeCoverageIgnoreStart + $styleArray = $this->getStyleArray(['strikeType' => $strikeType]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + // @codeCoverageIgnoreEnd + } + + return $this; + } + + public function getUnderlineColor(): ?ChartColor + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getUnderlineColor(); + } + + return $this->underlineColor; + } + + public function setUnderlineColor(array $colorArray): self + { + if (!$this->isSupervisor) { + $this->underlineColor = new ChartColor($colorArray); + } else { + // should never be true + // @codeCoverageIgnoreStart + $styleArray = $this->getStyleArray(['underlineColor' => $colorArray]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + // @codeCoverageIgnoreEnd + } + + return $this; + } + + public function getChartColor(): ?ChartColor + { + if ($this->isSupervisor) { + return $this->getSharedComponent()->getChartColor(); + } + + return $this->chartColor; + } + + public function setChartColor(array $colorArray): self + { + if (!$this->isSupervisor) { + $this->chartColor = new ChartColor($colorArray); + } else { + // should never be true + // @codeCoverageIgnoreStart + $styleArray = $this->getStyleArray(['chartColor' => $colorArray]); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); + // @codeCoverageIgnoreEnd } return $this; @@ -421,7 +637,7 @@ class Font extends Supervisor /** * Get Underline. * - * @return string + * @return null|string */ public function getUnderline() { @@ -435,24 +651,24 @@ class Font extends Supervisor /** * Set Underline. * - * @param bool|string $pValue \PhpOffice\PhpSpreadsheet\Style\Font underline type + * @param bool|string $underlineStyle \PhpOffice\PhpSpreadsheet\Style\Font underline type * If a boolean is passed, then TRUE equates to UNDERLINE_SINGLE, * false equates to UNDERLINE_NONE * - * @return Font + * @return $this */ - public function setUnderline($pValue) + public function setUnderline($underlineStyle) { - if (is_bool($pValue)) { - $pValue = ($pValue) ? self::UNDERLINE_SINGLE : self::UNDERLINE_NONE; - } elseif ($pValue == '') { - $pValue = self::UNDERLINE_NONE; + if (is_bool($underlineStyle)) { + $underlineStyle = ($underlineStyle) ? self::UNDERLINE_SINGLE : self::UNDERLINE_NONE; + } elseif ($underlineStyle == '') { + $underlineStyle = self::UNDERLINE_NONE; } if ($this->isSupervisor) { - $styleArray = $this->getStyleArray(['underline' => $pValue]); + $styleArray = $this->getStyleArray(['underline' => $underlineStyle]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { - $this->underline = $pValue; + $this->underline = $underlineStyle; } return $this; @@ -461,7 +677,7 @@ class Font extends Supervisor /** * Get Strikethrough. * - * @return bool + * @return null|bool */ public function getStrikethrough() { @@ -475,21 +691,21 @@ class Font extends Supervisor /** * Set Strikethrough. * - * @param bool $pValue + * @param bool $strikethru * - * @return Font + * @return $this */ - public function setStrikethrough($pValue) + public function setStrikethrough($strikethru) { - if ($pValue == '') { - $pValue = false; + if ($strikethru == '') { + $strikethru = false; } if ($this->isSupervisor) { - $styleArray = $this->getStyleArray(['strikethrough' => $pValue]); + $styleArray = $this->getStyleArray(['strikethrough' => $strikethru]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { - $this->strikethrough = $pValue; + $this->strikethrough = $strikethru; } return $this; @@ -508,16 +724,12 @@ class Font extends Supervisor /** * Set Color. * - * @param Color $pValue - * - * @throws PhpSpreadsheetException - * - * @return Font + * @return $this */ - public function setColor(Color $pValue) + public function setColor(Color $color) { // make sure parameter is a real color and not a supervisor - $color = $pValue->getIsSupervisor() ? $pValue->getSharedComponent() : $pValue; + $color = $color->getIsSupervisor() ? $color->getSharedComponent() : $color; if ($this->isSupervisor) { $styleArray = $this->getColor()->getStyleArray(['argb' => $color->getARGB()]); @@ -529,6 +741,18 @@ class Font extends Supervisor return $this; } + private function hashChartColor(?ChartColor $underlineColor): string + { + if ($underlineColor === null) { + return ''; + } + + return + $underlineColor->getValue() + . $underlineColor->getType() + . (string) $underlineColor->getAlpha(); + } + /** * Get hash code. * @@ -550,7 +774,42 @@ class Font extends Supervisor $this->underline . ($this->strikethrough ? 't' : 'f') . $this->color->getHashCode() . + implode( + '*', + [ + $this->latin, + $this->eastAsian, + $this->complexScript, + $this->strikeType, + $this->hashChartColor($this->chartColor), + $this->hashChartColor($this->underlineColor), + (string) $this->baseLine, + ] + ) . __CLASS__ ); } + + protected function exportArray1(): array + { + $exportedArray = []; + $this->exportArray2($exportedArray, 'baseLine', $this->getBaseLine()); + $this->exportArray2($exportedArray, 'bold', $this->getBold()); + $this->exportArray2($exportedArray, 'chartColor', $this->getChartColor()); + $this->exportArray2($exportedArray, 'color', $this->getColor()); + $this->exportArray2($exportedArray, 'complexScript', $this->getComplexScript()); + $this->exportArray2($exportedArray, 'eastAsian', $this->getEastAsian()); + $this->exportArray2($exportedArray, 'italic', $this->getItalic()); + $this->exportArray2($exportedArray, 'latin', $this->getLatin()); + $this->exportArray2($exportedArray, 'name', $this->getName()); + $this->exportArray2($exportedArray, 'size', $this->getSize()); + $this->exportArray2($exportedArray, 'strikethrough', $this->getStrikethrough()); + $this->exportArray2($exportedArray, 'strikeType', $this->getStrikeType()); + $this->exportArray2($exportedArray, 'subscript', $this->getSubscript()); + $this->exportArray2($exportedArray, 'superscript', $this->getSuperscript()); + $this->exportArray2($exportedArray, 'underline', $this->getUnderline()); + $this->exportArray2($exportedArray, 'underlineColor', $this->getUnderlineColor()); + + return $exportedArray; + } } diff --git a/PhpOffice/PhpSpreadsheet/Style/NumberFormat.php b/PhpOffice/PhpSpreadsheet/Style/NumberFormat.php old mode 100755 new mode 100644 index 6577aef..c2b11a6 --- a/PhpOffice/PhpSpreadsheet/Style/NumberFormat.php +++ b/PhpOffice/PhpSpreadsheet/Style/NumberFormat.php @@ -2,11 +2,6 @@ namespace PhpOffice\PhpSpreadsheet\Style; -use PhpOffice\PhpSpreadsheet\Calculation\MathTrig; -use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; -use PhpOffice\PhpSpreadsheet\Shared\Date; -use PhpOffice\PhpSpreadsheet\Shared\StringHelper; - class NumberFormat extends Supervisor { // Pre-defined formats @@ -15,13 +10,16 @@ class NumberFormat extends Supervisor const FORMAT_TEXT = '@'; const FORMAT_NUMBER = '0'; + const FORMAT_NUMBER_0 = '0.0'; const FORMAT_NUMBER_00 = '0.00'; const FORMAT_NUMBER_COMMA_SEPARATED1 = '#,##0.00'; const FORMAT_NUMBER_COMMA_SEPARATED2 = '#,##0.00_-'; const FORMAT_PERCENTAGE = '0%'; + const FORMAT_PERCENTAGE_0 = '0.0%'; const FORMAT_PERCENTAGE_00 = '0.00%'; + /** @deprecated 1.26 use FORMAT_DATE_YYYYMMDD instead */ const FORMAT_DATE_YYYYMMDD2 = 'yyyy-mm-dd'; const FORMAT_DATE_YYYYMMDD = 'yyyy-mm-dd'; const FORMAT_DATE_DDMMYYYY = 'dd/mm/yyyy'; @@ -45,6 +43,42 @@ class NumberFormat extends Supervisor const FORMAT_DATE_TIME8 = 'h:mm:ss;@'; const FORMAT_DATE_YYYYMMDDSLASH = 'yyyy/mm/dd;@'; + const DATE_TIME_OR_DATETIME_ARRAY = [ + self::FORMAT_DATE_YYYYMMDD, + self::FORMAT_DATE_DDMMYYYY, + self::FORMAT_DATE_DMYSLASH, + self::FORMAT_DATE_DMYMINUS, + self::FORMAT_DATE_DMMINUS, + self::FORMAT_DATE_MYMINUS, + self::FORMAT_DATE_XLSX14, + self::FORMAT_DATE_XLSX15, + self::FORMAT_DATE_XLSX16, + self::FORMAT_DATE_XLSX17, + self::FORMAT_DATE_XLSX22, + self::FORMAT_DATE_DATETIME, + self::FORMAT_DATE_TIME1, + self::FORMAT_DATE_TIME2, + self::FORMAT_DATE_TIME3, + self::FORMAT_DATE_TIME4, + self::FORMAT_DATE_TIME5, + self::FORMAT_DATE_TIME6, + self::FORMAT_DATE_TIME7, + self::FORMAT_DATE_TIME8, + self::FORMAT_DATE_YYYYMMDDSLASH, + ]; + const TIME_OR_DATETIME_ARRAY = [ + self::FORMAT_DATE_XLSX22, + self::FORMAT_DATE_DATETIME, + self::FORMAT_DATE_TIME1, + self::FORMAT_DATE_TIME2, + self::FORMAT_DATE_TIME3, + self::FORMAT_DATE_TIME4, + self::FORMAT_DATE_TIME5, + self::FORMAT_DATE_TIME6, + self::FORMAT_DATE_TIME7, + self::FORMAT_DATE_TIME8, + ]; + const FORMAT_CURRENCY_USD_SIMPLE = '"$"#,##0.00_-'; const FORMAT_CURRENCY_USD = '$#,##0_-'; const FORMAT_CURRENCY_EUR_SIMPLE = '#,##0.00_-"€"'; @@ -69,14 +103,14 @@ class NumberFormat extends Supervisor /** * Format Code. * - * @var string + * @var null|string */ protected $formatCode = self::FORMAT_GENERAL; /** * Built-in format Code. * - * @var string + * @var false|int */ protected $builtInFormatCode = 0; @@ -109,7 +143,10 @@ class NumberFormat extends Supervisor */ public function getSharedComponent() { - return $this->parent->getSharedComponent()->getNumberFormat(); + /** @var Style */ + $parent = $this->parent; + + return $parent->getSharedComponent()->getNumberFormat(); } /** @@ -135,19 +172,17 @@ class NumberFormat extends Supervisor * ); * * - * @param array $pStyles Array containing style information + * @param array $styleArray Array containing style information * - * @throws PhpSpreadsheetException - * - * @return NumberFormat + * @return $this */ - public function applyFromArray(array $pStyles) + public function applyFromArray(array $styleArray) { if ($this->isSupervisor) { - $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($pStyles)); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray)); } else { - if (isset($pStyles['formatCode'])) { - $this->setFormatCode($pStyles['formatCode']); + if (isset($styleArray['formatCode'])) { + $this->setFormatCode($styleArray['formatCode']); } } @@ -157,14 +192,14 @@ class NumberFormat extends Supervisor /** * Get Format Code. * - * @return string + * @return null|string */ public function getFormatCode() { if ($this->isSupervisor) { return $this->getSharedComponent()->getFormatCode(); } - if ($this->builtInFormatCode !== false) { + if (is_int($this->builtInFormatCode)) { return self::builtInFormatCode($this->builtInFormatCode); } @@ -174,21 +209,21 @@ class NumberFormat extends Supervisor /** * Set Format Code. * - * @param string $pValue see self::FORMAT_* + * @param string $formatCode see self::FORMAT_* * - * @return NumberFormat + * @return $this */ - public function setFormatCode($pValue) + public function setFormatCode($formatCode) { - if ($pValue == '') { - $pValue = self::FORMAT_GENERAL; + if ($formatCode == '') { + $formatCode = self::FORMAT_GENERAL; } if ($this->isSupervisor) { - $styleArray = $this->getStyleArray(['formatCode' => $pValue]); + $styleArray = $this->getStyleArray(['formatCode' => $formatCode]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { - $this->formatCode = $pValue; - $this->builtInFormatCode = self::builtInFormatCodeIndex($pValue); + $this->formatCode = $formatCode; + $this->builtInFormatCode = self::builtInFormatCodeIndex($formatCode); } return $this; @@ -197,7 +232,7 @@ class NumberFormat extends Supervisor /** * Get Built-In Format Code. * - * @return int + * @return false|int */ public function getBuiltInFormatCode() { @@ -205,24 +240,25 @@ class NumberFormat extends Supervisor return $this->getSharedComponent()->getBuiltInFormatCode(); } + // Scrutinizer says this could return true. It is wrong. return $this->builtInFormatCode; } /** * Set Built-In Format Code. * - * @param int $pValue + * @param int $formatCodeIndex * - * @return NumberFormat + * @return $this */ - public function setBuiltInFormatCode($pValue) + public function setBuiltInFormatCode($formatCodeIndex) { if ($this->isSupervisor) { - $styleArray = $this->getStyleArray(['formatCode' => self::builtInFormatCode($pValue)]); + $styleArray = $this->getStyleArray(['formatCode' => self::builtInFormatCode($formatCodeIndex)]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { - $this->builtInFormatCode = $pValue; - $this->formatCode = self::builtInFormatCode($pValue); + $this->builtInFormatCode = $formatCodeIndex; + $this->formatCode = self::builtInFormatCode($formatCodeIndex); } return $this; @@ -231,7 +267,7 @@ class NumberFormat extends Supervisor /** * Fill built-in format codes. */ - private static function fillBuiltInFormatCodes() + private static function fillBuiltInFormatCodes(): void { // [MS-OI29500: Microsoft Office Implementation Information for ISO/IEC-29500 Standard Compliance] // 18.8.30. numFmt (Number Format) @@ -256,7 +292,7 @@ class NumberFormat extends Supervisor // KOR fmt 55: "yyyy/mm/dd" // Built-in format codes - if (self::$builtInFormats === null) { + if (empty(self::$builtInFormats)) { self::$builtInFormats = []; // General @@ -334,21 +370,21 @@ class NumberFormat extends Supervisor /** * Get built-in format code. * - * @param int $pIndex + * @param int $index * * @return string */ - public static function builtInFormatCode($pIndex) + public static function builtInFormatCode($index) { // Clean parameter - $pIndex = (int) $pIndex; + $index = (int) $index; // Ensure built-in format codes are available self::fillBuiltInFormatCodes(); // Lookup format code - if (isset(self::$builtInFormats[$pIndex])) { - return self::$builtInFormats[$pIndex]; + if (isset(self::$builtInFormats[$index])) { + return self::$builtInFormats[$index]; } return ''; @@ -357,18 +393,18 @@ class NumberFormat extends Supervisor /** * Get built-in format code index. * - * @param string $formatCode + * @param string $formatCodeIndex * - * @return bool|int + * @return false|int */ - public static function builtInFormatCodeIndex($formatCode) + public static function builtInFormatCodeIndex($formatCodeIndex) { // Ensure built-in format codes are available self::fillBuiltInFormatCodes(); // Lookup format code - if (isset(self::$flippedBuiltInFormats[$formatCode])) { - return self::$flippedBuiltInFormats[$formatCode]; + if (array_key_exists($formatCodeIndex, self::$flippedBuiltInFormats)) { + return self::$flippedBuiltInFormats[$formatCodeIndex]; } return false; @@ -392,330 +428,6 @@ class NumberFormat extends Supervisor ); } - /** - * Search/replace values to convert Excel date/time format masks to PHP format masks. - * - * @var array - */ - private static $dateFormatReplacements = [ - // first remove escapes related to non-format characters - '\\' => '', - // 12-hour suffix - 'am/pm' => 'A', - // 4-digit year - 'e' => 'Y', - 'yyyy' => 'Y', - // 2-digit year - 'yy' => 'y', - // first letter of month - no php equivalent - 'mmmmm' => 'M', - // full month name - 'mmmm' => 'F', - // short month name - 'mmm' => 'M', - // mm is minutes if time, but can also be month w/leading zero - // so we try to identify times be the inclusion of a : separator in the mask - // It isn't perfect, but the best way I know how - ':mm' => ':i', - 'mm:' => 'i:', - // month leading zero - 'mm' => 'm', - // month no leading zero - 'm' => 'n', - // full day of week name - 'dddd' => 'l', - // short day of week name - 'ddd' => 'D', - // days leading zero - 'dd' => 'd', - // days no leading zero - 'd' => 'j', - // seconds - 'ss' => 's', - // fractional seconds - no php equivalent - '.s' => '', - ]; - - /** - * Search/replace values to convert Excel date/time format masks hours to PHP format masks (24 hr clock). - * - * @var array - */ - private static $dateFormatReplacements24 = [ - 'hh' => 'H', - 'h' => 'G', - ]; - - /** - * Search/replace values to convert Excel date/time format masks hours to PHP format masks (12 hr clock). - * - * @var array - */ - private static $dateFormatReplacements12 = [ - 'hh' => 'h', - 'h' => 'g', - ]; - - private static function setLowercaseCallback($matches) - { - return mb_strtolower($matches[0]); - } - - private static function escapeQuotesCallback($matches) - { - return '\\' . implode('\\', str_split($matches[1])); - } - - private static function formatAsDate(&$value, &$format) - { - // strip off first part containing e.g. [$-F800] or [$USD-409] - // general syntax: [$-] - // language info is in hexadecimal - // strip off chinese part like [DBNum1][$-804] - $format = preg_replace('/^(\[[0-9A-Za-z]*\])*(\[\$[A-Z]*-[0-9A-F]*\])/i', '', $format); - - // OpenOffice.org uses upper-case number formats, e.g. 'YYYY', convert to lower-case; - // but we don't want to change any quoted strings - $format = preg_replace_callback('/(?:^|")([^"]*)(?:$|")/', ['self', 'setLowercaseCallback'], $format); - - // Only process the non-quoted blocks for date format characters - $blocks = explode('"', $format); - foreach ($blocks as $key => &$block) { - if ($key % 2 == 0) { - $block = strtr($block, self::$dateFormatReplacements); - if (!strpos($block, 'A')) { - // 24-hour time format - // when [h]:mm format, the [h] should replace to the hours of the value * 24 - if (false !== strpos($block, '[h]')) { - $hours = (int) ($value * 24); - $block = str_replace('[h]', $hours, $block); - - continue; - } - $block = strtr($block, self::$dateFormatReplacements24); - } else { - // 12-hour time format - $block = strtr($block, self::$dateFormatReplacements12); - } - } - } - $format = implode('"', $blocks); - - // escape any quoted characters so that DateTime format() will render them correctly - $format = preg_replace_callback('/"(.*)"/U', ['self', 'escapeQuotesCallback'], $format); - - $dateObj = Date::excelToDateTimeObject($value); - $value = $dateObj->format($format); - } - - private static function formatAsPercentage(&$value, &$format) - { - if ($format === self::FORMAT_PERCENTAGE) { - $value = round((100 * $value), 0) . '%'; - } else { - if (preg_match('/\.[#0]+/', $format, $m)) { - $s = substr($m[0], 0, 1) . (strlen($m[0]) - 1); - $format = str_replace($m[0], $s, $format); - } - if (preg_match('/^[#0]+/', $format, $m)) { - $format = str_replace($m[0], strlen($m[0]), $format); - } - $format = '%' . str_replace('%', 'f%%', $format); - - $value = sprintf($format, 100 * $value); - } - } - - private static function formatAsFraction(&$value, &$format) - { - $sign = ($value < 0) ? '-' : ''; - - $integerPart = floor(abs($value)); - $decimalPart = trim(fmod(abs($value), 1), '0.'); - $decimalLength = strlen($decimalPart); - $decimalDivisor = pow(10, $decimalLength); - - $GCD = MathTrig::GCD($decimalPart, $decimalDivisor); - - $adjustedDecimalPart = $decimalPart / $GCD; - $adjustedDecimalDivisor = $decimalDivisor / $GCD; - - if ((strpos($format, '0') !== false) || (strpos($format, '#') !== false) || (substr($format, 0, 3) == '? ?')) { - if ($integerPart == 0) { - $integerPart = ''; - } - $value = "$sign$integerPart $adjustedDecimalPart/$adjustedDecimalDivisor"; - } else { - $adjustedDecimalPart += $integerPart * $adjustedDecimalDivisor; - $value = "$sign$adjustedDecimalPart/$adjustedDecimalDivisor"; - } - } - - private static function mergeComplexNumberFormatMasks($numbers, $masks) - { - $decimalCount = strlen($numbers[1]); - $postDecimalMasks = []; - - do { - $tempMask = array_pop($masks); - $postDecimalMasks[] = $tempMask; - $decimalCount -= strlen($tempMask); - } while ($decimalCount > 0); - - return [ - implode('.', $masks), - implode('.', array_reverse($postDecimalMasks)), - ]; - } - - private static function processComplexNumberFormatMask($number, $mask) - { - $result = $number; - $maskingBlockCount = preg_match_all('/0+/', $mask, $maskingBlocks, PREG_OFFSET_CAPTURE); - - if ($maskingBlockCount > 1) { - $maskingBlocks = array_reverse($maskingBlocks[0]); - - foreach ($maskingBlocks as $block) { - $divisor = 1 . $block[0]; - $size = strlen($block[0]); - $offset = $block[1]; - - $blockValue = sprintf( - '%0' . $size . 'd', - fmod($number, $divisor) - ); - $number = floor($number / $divisor); - $mask = substr_replace($mask, $blockValue, $offset, $size); - } - if ($number > 0) { - $mask = substr_replace($mask, $number, $offset, 0); - } - $result = $mask; - } - - return $result; - } - - private static function complexNumberFormatMask($number, $mask, $splitOnPoint = true) - { - $sign = ($number < 0.0); - $number = abs($number); - if ($splitOnPoint && strpos($mask, '.') !== false && strpos($number, '.') !== false) { - $numbers = explode('.', $number); - $masks = explode('.', $mask); - if (count($masks) > 2) { - $masks = self::mergeComplexNumberFormatMasks($numbers, $masks); - } - $result1 = self::complexNumberFormatMask($numbers[0], $masks[0], false); - $result2 = strrev(self::complexNumberFormatMask(strrev($numbers[1]), strrev($masks[1]), false)); - - return (($sign) ? '-' : '') . $result1 . '.' . $result2; - } - - $result = self::processComplexNumberFormatMask($number, $mask); - - return (($sign) ? '-' : '') . $result; - } - - private static function formatStraightNumericValue($value, $format, array $matches, $useThousands, $number_regex) - { - $left = $matches[1]; - $dec = $matches[2]; - $right = $matches[3]; - - // minimun width of formatted number (including dot) - $minWidth = strlen($left) + strlen($dec) + strlen($right); - if ($useThousands) { - $value = number_format( - $value, - strlen($right), - StringHelper::getDecimalSeparator(), - StringHelper::getThousandsSeparator() - ); - $value = preg_replace($number_regex, $value, $format); - } else { - if (preg_match('/[0#]E[+-]0/i', $format)) { - // Scientific format - $value = sprintf('%5.2E', $value); - } elseif (preg_match('/0([^\d\.]+)0/', $format) || substr_count($format, '.') > 1) { - $value = self::complexNumberFormatMask($value, $format); - } else { - $sprintf_pattern = "%0$minWidth." . strlen($right) . 'f'; - $value = sprintf($sprintf_pattern, $value); - $value = preg_replace($number_regex, $value, $format); - } - } - - return $value; - } - - private static function formatAsNumber($value, $format) - { - if ($format === self::FORMAT_CURRENCY_EUR_SIMPLE) { - return 'EUR ' . sprintf('%1.2f', $value); - } - - // Some non-number strings are quoted, so we'll get rid of the quotes, likewise any positional * symbols - $format = str_replace(['"', '*'], '', $format); - - // Find out if we need thousands separator - // This is indicated by a comma enclosed by a digit placeholder: - // #,# or 0,0 - $useThousands = preg_match('/(#,#|0,0)/', $format); - if ($useThousands) { - $format = preg_replace('/0,0/', '00', $format); - $format = preg_replace('/#,#/', '##', $format); - } - - // Scale thousands, millions,... - // This is indicated by a number of commas after a digit placeholder: - // #, or 0.0,, - $scale = 1; // same as no scale - $matches = []; - if (preg_match('/(#|0)(,+)/', $format, $matches)) { - $scale = pow(1000, strlen($matches[2])); - - // strip the commas - $format = preg_replace('/0,+/', '0', $format); - $format = preg_replace('/#,+/', '#', $format); - } - - if (preg_match('/#?.*\?\/\?/', $format, $m)) { - if ($value != (int) $value) { - self::formatAsFraction($value, $format); - } - } else { - // Handle the number itself - - // scale number - $value = $value / $scale; - // Strip # - $format = preg_replace('/\\#/', '0', $format); - // Remove locale code [$-###] - $format = preg_replace('/\[\$\-.*\]/', '', $format); - - $n = '/\\[[^\\]]+\\]/'; - $m = preg_replace($n, '', $format); - $number_regex = '/(0+)(\\.?)(0*)/'; - if (preg_match($number_regex, $m, $matches)) { - $value = self::formatStraightNumericValue($value, $format, $matches, $useThousands, $number_regex); - } - } - - if (preg_match('/\[\$(.*)\]/u', $format, $m)) { - // Currency or Accounting - $currencyCode = $m[1]; - [$currencyCode] = explode('-', $currencyCode); - if ($currencyCode == '') { - $currencyCode = StringHelper::getCurrencyCode(); - } - $value = preg_replace('/\[\$([^\]]*)\]/u', $currencyCode, $value); - } - - return $value; - } - /** * Convert a value in a pre-defined format to a PHP string. * @@ -727,90 +439,14 @@ class NumberFormat extends Supervisor */ public static function toFormattedString($value, $format, $callBack = null) { - // For now we do not treat strings although section 4 of a format code affects strings - if (!is_numeric($value)) { - return $value; - } + return NumberFormat\Formatter::toFormattedString($value, $format, $callBack); + } - // For 'General' format code, we just pass the value although this is not entirely the way Excel does it, - // it seems to round numbers to a total of 10 digits. - if (($format === self::FORMAT_GENERAL) || ($format === self::FORMAT_TEXT)) { - return $value; - } + protected function exportArray1(): array + { + $exportedArray = []; + $this->exportArray2($exportedArray, 'formatCode', $this->getFormatCode()); - // Convert any other escaped characters to quoted strings, e.g. (\T to "T") - $format = preg_replace('/(\\\(((.)(?!((AM\/PM)|(A\/P))))|([^ ])))(?=(?:[^"]|"[^"]*")*$)/u', '"${2}"', $format); - - // Get the sections, there can be up to four sections, separated with a semi-colon (but only if not a quoted literal) - $sections = preg_split('/(;)(?=(?:[^"]|"[^"]*")*$)/u', $format); - - // Extract the relevant section depending on whether number is positive, negative, or zero? - // Text not supported yet. - // Here is how the sections apply to various values in Excel: - // 1 section: [POSITIVE/NEGATIVE/ZERO/TEXT] - // 2 sections: [POSITIVE/ZERO/TEXT] [NEGATIVE] - // 3 sections: [POSITIVE/TEXT] [NEGATIVE] [ZERO] - // 4 sections: [POSITIVE] [NEGATIVE] [ZERO] [TEXT] - switch (count($sections)) { - case 1: - $format = $sections[0]; - - break; - case 2: - $format = ($value >= 0) ? $sections[0] : $sections[1]; - $value = abs($value); // Use the absolute value - break; - case 3: - $format = ($value > 0) ? - $sections[0] : (($value < 0) ? - $sections[1] : $sections[2]); - $value = abs($value); // Use the absolute value - break; - case 4: - $format = ($value > 0) ? - $sections[0] : (($value < 0) ? - $sections[1] : $sections[2]); - $value = abs($value); // Use the absolute value - break; - default: - // something is wrong, just use first section - $format = $sections[0]; - - break; - } - - // In Excel formats, "_" is used to add spacing, - // The following character indicates the size of the spacing, which we can't do in HTML, so we just use a standard space - $format = preg_replace('/_./', ' ', $format); - - // Save format with color information for later use below - $formatColor = $format; - // Strip colour information - $color_regex = '/\[(' . implode('|', Color::NAMED_COLORS) . ')\]/'; - $format = preg_replace($color_regex, '', $format); - // Let's begin inspecting the format and converting the value to a formatted string - - // Check for date/time characters (not inside quotes) - if (preg_match('/(\[\$[A-Z]*-[0-9A-F]*\])*[hmsdy](?=(?:[^"]|"[^"]*")*$)/miu', $format, $matches)) { - // datetime format - self::formatAsDate($value, $format); - } else { - if (substr($format, 0, 1) === '"' && substr($format, -1, 1) === '"') { - $value = substr($format, 1, -1); - } elseif (preg_match('/%$/', $format)) { - // % number format - self::formatAsPercentage($value, $format); - } else { - $value = self::formatAsNumber($value, $format); - } - } - - // Additional formatting provided by callback function - if ($callBack !== null) { - [$writerInstance, $function] = $callBack; - $value = $writerInstance->$function($value, $formatColor); - } - - return $value; + return $exportedArray; } } diff --git a/PhpOffice/PhpSpreadsheet/Style/NumberFormat/BaseFormatter.php b/PhpOffice/PhpSpreadsheet/Style/NumberFormat/BaseFormatter.php new file mode 100644 index 0000000..7988143 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Style/NumberFormat/BaseFormatter.php @@ -0,0 +1,12 @@ + '', + // 12-hour suffix + 'am/pm' => 'A', + // 4-digit year + 'e' => 'Y', + 'yyyy' => 'Y', + // 2-digit year + 'yy' => 'y', + // first letter of month - no php equivalent + 'mmmmm' => 'M', + // full month name + 'mmmm' => 'F', + // short month name + 'mmm' => 'M', + // mm is minutes if time, but can also be month w/leading zero + // so we try to identify times be the inclusion of a : separator in the mask + // It isn't perfect, but the best way I know how + ':mm' => ':i', + 'mm:' => 'i:', + // full day of week name + 'dddd' => 'l', + // short day of week name + 'ddd' => 'D', + // days leading zero + 'dd' => 'd', + // days no leading zero + 'd' => 'j', + // fractional seconds - no php equivalent + '.s' => '', + ]; + + /** + * Search/replace values to convert Excel date/time format masks hours to PHP format masks (24 hr clock). + */ + private const DATE_FORMAT_REPLACEMENTS24 = [ + 'hh' => 'H', + 'h' => 'G', + // month leading zero + 'mm' => 'm', + // month no leading zero + 'm' => 'n', + // seconds + 'ss' => 's', + ]; + + /** + * Search/replace values to convert Excel date/time format masks hours to PHP format masks (12 hr clock). + */ + private const DATE_FORMAT_REPLACEMENTS12 = [ + 'hh' => 'h', + 'h' => 'g', + // month leading zero + 'mm' => 'm', + // month no leading zero + 'm' => 'n', + // seconds + 'ss' => 's', + ]; + + private const HOURS_IN_DAY = 24; + private const MINUTES_IN_DAY = 60 * self::HOURS_IN_DAY; + private const SECONDS_IN_DAY = 60 * self::MINUTES_IN_DAY; + private const INTERVAL_PRECISION = 10; + private const INTERVAL_LEADING_ZERO = [ + '[hh]', + '[mm]', + '[ss]', + ]; + private const INTERVAL_ROUND_PRECISION = [ + // hours and minutes truncate + '[h]' => self::INTERVAL_PRECISION, + '[hh]' => self::INTERVAL_PRECISION, + '[m]' => self::INTERVAL_PRECISION, + '[mm]' => self::INTERVAL_PRECISION, + // seconds round + '[s]' => 0, + '[ss]' => 0, + ]; + private const INTERVAL_MULTIPLIER = [ + '[h]' => self::HOURS_IN_DAY, + '[hh]' => self::HOURS_IN_DAY, + '[m]' => self::MINUTES_IN_DAY, + '[mm]' => self::MINUTES_IN_DAY, + '[s]' => self::SECONDS_IN_DAY, + '[ss]' => self::SECONDS_IN_DAY, + ]; + + /** @param mixed $value */ + private static function tryInterval(bool &$seekingBracket, string &$block, $value, string $format): void + { + if ($seekingBracket) { + if (false !== strpos($block, $format)) { + $hours = (string) (int) round( + self::INTERVAL_MULTIPLIER[$format] * $value, + self::INTERVAL_ROUND_PRECISION[$format] + ); + if (strlen($hours) === 1 && in_array($format, self::INTERVAL_LEADING_ZERO, true)) { + $hours = "0$hours"; + } + $block = str_replace($format, $hours, $block); + $seekingBracket = false; + } + } + } + + /** @param mixed $value */ + public static function format($value, string $format): string + { + // strip off first part containing e.g. [$-F800] or [$USD-409] + // general syntax: [$-] + // language info is in hexadecimal + // strip off chinese part like [DBNum1][$-804] + $format = (string) preg_replace('/^(\[DBNum\d\])*(\[\$[^\]]*\])/i', '', $format); + + // OpenOffice.org uses upper-case number formats, e.g. 'YYYY', convert to lower-case; + // but we don't want to change any quoted strings + /** @var callable */ + $callable = [self::class, 'setLowercaseCallback']; + $format = (string) preg_replace_callback('/(?:^|")([^"]*)(?:$|")/', $callable, $format); + + // Only process the non-quoted blocks for date format characters + + $blocks = explode('"', $format); + foreach ($blocks as $key => &$block) { + if ($key % 2 == 0) { + $block = strtr($block, self::DATE_FORMAT_REPLACEMENTS); + if (!strpos($block, 'A')) { + // 24-hour time format + // when [h]:mm format, the [h] should replace to the hours of the value * 24 + $seekingBracket = true; + self::tryInterval($seekingBracket, $block, $value, '[h]'); + self::tryInterval($seekingBracket, $block, $value, '[hh]'); + self::tryInterval($seekingBracket, $block, $value, '[mm]'); + self::tryInterval($seekingBracket, $block, $value, '[m]'); + self::tryInterval($seekingBracket, $block, $value, '[s]'); + self::tryInterval($seekingBracket, $block, $value, '[ss]'); + $block = strtr($block, self::DATE_FORMAT_REPLACEMENTS24); + } else { + // 12-hour time format + $block = strtr($block, self::DATE_FORMAT_REPLACEMENTS12); + } + } + } + $format = implode('"', $blocks); + + // escape any quoted characters so that DateTime format() will render them correctly + /** @var callable */ + $callback = [self::class, 'escapeQuotesCallback']; + $format = (string) preg_replace_callback('/"(.*)"/U', $callback, $format); + + $dateObj = Date::excelToDateTimeObject($value); + // If the colon preceding minute had been quoted, as happens in + // Excel 2003 XML formats, m will not have been changed to i above. + // Change it now. + $format = (string) \preg_replace('/\\\\:m/', ':i', $format); + + return $dateObj->format($format); + } + + private static function setLowercaseCallback(array $matches): string + { + return mb_strtolower($matches[0]); + } + + private static function escapeQuotesCallback(array $matches): string + { + return '\\' . implode('\\', /** @scrutinizer ignore-type */ str_split($matches[1])); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Style/NumberFormat/Formatter.php b/PhpOffice/PhpSpreadsheet/Style/NumberFormat/Formatter.php new file mode 100644 index 0000000..be195a8 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Style/NumberFormat/Formatter.php @@ -0,0 +1,165 @@ +': + return $value > $val; + + case '<': + return $value < $val; + + case '<=': + return $value <= $val; + + case '<>': + return $value != $val; + + case '=': + return $value == $val; + } + + return $value >= $val; + } + + private static function splitFormat($sections, $value) + { + // Extract the relevant section depending on whether number is positive, negative, or zero? + // Text not supported yet. + // Here is how the sections apply to various values in Excel: + // 1 section: [POSITIVE/NEGATIVE/ZERO/TEXT] + // 2 sections: [POSITIVE/ZERO/TEXT] [NEGATIVE] + // 3 sections: [POSITIVE/TEXT] [NEGATIVE] [ZERO] + // 4 sections: [POSITIVE] [NEGATIVE] [ZERO] [TEXT] + $cnt = count($sections); + $color_regex = '/\\[(' . implode('|', Color::NAMED_COLORS) . ')\\]/mui'; + $cond_regex = '/\\[(>|>=|<|<=|=|<>)([+-]?\\d+([.]\\d+)?)\\]/'; + $colors = ['', '', '', '', '']; + $condops = ['', '', '', '', '']; + $condvals = [0, 0, 0, 0, 0]; + for ($idx = 0; $idx < $cnt; ++$idx) { + if (preg_match($color_regex, $sections[$idx], $matches)) { + $colors[$idx] = $matches[0]; + $sections[$idx] = (string) preg_replace($color_regex, '', $sections[$idx]); + } + if (preg_match($cond_regex, $sections[$idx], $matches)) { + $condops[$idx] = $matches[1]; + $condvals[$idx] = $matches[2]; + $sections[$idx] = (string) preg_replace($cond_regex, '', $sections[$idx]); + } + } + $color = $colors[0]; + $format = $sections[0]; + $absval = $value; + switch ($cnt) { + case 2: + $absval = abs($value); + if (!self::splitFormatCompare($value, $condops[0], $condvals[0], '>=', 0)) { + $color = $colors[1]; + $format = $sections[1]; + } + + break; + case 3: + case 4: + $absval = abs($value); + if (!self::splitFormatCompare($value, $condops[0], $condvals[0], '>', 0)) { + if (self::splitFormatCompare($value, $condops[1], $condvals[1], '<', 0)) { + $color = $colors[1]; + $format = $sections[1]; + } else { + $color = $colors[2]; + $format = $sections[2]; + } + } + + break; + } + + return [$color, $format, $absval]; + } + + /** + * Convert a value in a pre-defined format to a PHP string. + * + * @param mixed $value Value to format + * @param string $format Format code, see = NumberFormat::FORMAT_* + * @param array $callBack Callback function for additional formatting of string + * + * @return string Formatted string + */ + public static function toFormattedString($value, $format, $callBack = null) + { + // For now we do not treat strings although section 4 of a format code affects strings + if (!is_numeric($value)) { + return $value; + } + + // For 'General' format code, we just pass the value although this is not entirely the way Excel does it, + // it seems to round numbers to a total of 10 digits. + if (($format === NumberFormat::FORMAT_GENERAL) || ($format === NumberFormat::FORMAT_TEXT)) { + return $value; + } + + // Ignore square-$-brackets prefix in format string, like "[$-411]ge.m.d", "[$-010419]0%", etc + $format = (string) preg_replace('/^\[\$-[^\]]*\]/', '', $format); + + $format = (string) preg_replace_callback( + '/(["])(?:(?=(\\\\?))\\2.)*?\\1/u', + function ($matches) { + return str_replace('.', chr(0x00), $matches[0]); + }, + $format + ); + + // Convert any other escaped characters to quoted strings, e.g. (\T to "T") + $format = (string) preg_replace('/(\\\(((.)(?!((AM\/PM)|(A\/P))))|([^ ])))(?=(?:[^"]|"[^"]*")*$)/ui', '"${2}"', $format); + + // Get the sections, there can be up to four sections, separated with a semi-colon (but only if not a quoted literal) + $sections = preg_split('/(;)(?=(?:[^"]|"[^"]*")*$)/u', $format); + + [$colors, $format, $value] = self::splitFormat($sections, $value); + + // In Excel formats, "_" is used to add spacing, + // The following character indicates the size of the spacing, which we can't do in HTML, so we just use a standard space + $format = (string) preg_replace('/_.?/ui', ' ', $format); + + // Let's begin inspecting the format and converting the value to a formatted string + + // Check for date/time characters (not inside quotes) + if (preg_match('/(\[\$[A-Z]*-[0-9A-F]*\])*[hmsdy](?=(?:[^"]|"[^"]*")*$)/miu', $format, $matches)) { + // datetime format + $value = DateFormatter::format($value, $format); + } else { + if (substr($format, 0, 1) === '"' && substr($format, -1, 1) === '"' && substr_count($format, '"') === 2) { + $value = substr($format, 1, -1); + } elseif (preg_match('/[0#, ]%/', $format)) { + // % number format + $value = PercentageFormatter::format($value, $format); + } else { + $value = NumberFormatter::format($value, $format); + } + } + + // Additional formatting provided by callback function + if ($callBack !== null) { + [$writerInstance, $function] = $callBack; + $value = $writerInstance->$function($value, $colors); + } + + $value = str_replace(chr(0x00), '.', $value); + + return $value; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php b/PhpOffice/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php new file mode 100644 index 0000000..d1fc89f --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php @@ -0,0 +1,67 @@ + 0); + + return [ + implode('.', $masks), + implode('.', array_reverse($postDecimalMasks)), + ]; + } + + /** + * @param mixed $number + */ + private static function processComplexNumberFormatMask($number, string $mask): string + { + /** @var string */ + $result = $number; + $maskingBlockCount = preg_match_all('/0+/', $mask, $maskingBlocks, PREG_OFFSET_CAPTURE); + + if ($maskingBlockCount > 1) { + $maskingBlocks = array_reverse($maskingBlocks[0]); + + $offset = 0; + foreach ($maskingBlocks as $block) { + $size = strlen($block[0]); + $divisor = 10 ** $size; + $offset = $block[1]; + + /** @var float */ + $numberFloat = $number; + $blockValue = sprintf("%0{$size}d", fmod($numberFloat, $divisor)); + $number = floor($numberFloat / $divisor); + $mask = substr_replace($mask, $blockValue, $offset, $size); + } + /** @var string */ + $numberString = $number; + if ($number > 0) { + $mask = substr_replace($mask, $numberString, $offset, 0); + } + $result = $mask; + } + + return self::makeString($result); + } + + /** + * @param mixed $number + */ + private static function complexNumberFormatMask($number, string $mask, bool $splitOnPoint = true): string + { + /** @var float */ + $numberFloat = $number; + if ($splitOnPoint) { + $masks = explode('.', $mask); + if (count($masks) <= 2) { + $decmask = $masks[1] ?? ''; + $decpos = substr_count($decmask, '0'); + $numberFloat = round($numberFloat, $decpos); + } + } + $sign = ($numberFloat < 0.0) ? '-' : ''; + $number = self::f2s(abs($numberFloat)); + + if ($splitOnPoint && strpos($mask, '.') !== false && strpos($number, '.') !== false) { + $numbers = explode('.', $number); + $masks = explode('.', $mask); + if (count($masks) > 2) { + $masks = self::mergeComplexNumberFormatMasks($numbers, $masks); + } + $integerPart = self::complexNumberFormatMask($numbers[0], $masks[0], false); + $numlen = strlen($numbers[1]); + $msklen = strlen($masks[1]); + if ($numlen < $msklen) { + $numbers[1] .= str_repeat('0', $msklen - $numlen); + } + $decimalPart = strrev(self::complexNumberFormatMask(strrev($numbers[1]), strrev($masks[1]), false)); + $decimalPart = substr($decimalPart, 0, $msklen); + + return "{$sign}{$integerPart}.{$decimalPart}"; + } + + if (strlen($number) < strlen($mask)) { + $number = str_repeat('0', strlen($mask) - strlen($number)) . $number; + } + $result = self::processComplexNumberFormatMask($number, $mask); + + return "{$sign}{$result}"; + } + + public static function f2s(float $f): string + { + return self::floatStringConvertScientific((string) $f); + } + + public static function floatStringConvertScientific(string $s): string + { + // convert only normalized form of scientific notation: + // optional sign, single digit 1-9, + // decimal point and digits (allowed to be omitted), + // E (e permitted), optional sign, one or more digits + if (preg_match('/^([+-])?([1-9])([.]([0-9]+))?[eE]([+-]?[0-9]+)$/', $s, $matches) === 1) { + $exponent = (int) $matches[5]; + $sign = ($matches[1] === '-') ? '-' : ''; + if ($exponent >= 0) { + $exponentPlus1 = $exponent + 1; + $out = $matches[2] . $matches[4]; + $len = strlen($out); + if ($len < $exponentPlus1) { + $out .= str_repeat('0', $exponentPlus1 - $len); + } + $out = substr($out, 0, $exponentPlus1) . ((strlen($out) === $exponentPlus1) ? '' : ('.' . substr($out, $exponentPlus1))); + $s = "$sign$out"; + } else { + $s = $sign . '0.' . str_repeat('0', -$exponent - 1) . $matches[2] . $matches[4]; + } + } + + return $s; + } + + /** + * @param mixed $value + */ + private static function formatStraightNumericValue($value, string $format, array $matches, bool $useThousands): string + { + /** @var float */ + $valueFloat = $value; + $left = $matches[1]; + $dec = $matches[2]; + $right = $matches[3]; + + // minimun width of formatted number (including dot) + $minWidth = strlen($left) + strlen($dec) + strlen($right); + if ($useThousands) { + $value = number_format( + $valueFloat, + strlen($right), + StringHelper::getDecimalSeparator(), + StringHelper::getThousandsSeparator() + ); + + return self::pregReplace(self::NUMBER_REGEX, $value, $format); + } + + if (preg_match('/[0#]E[+-]0/i', $format)) { + // Scientific format + return sprintf('%5.2E', $valueFloat); + } elseif (preg_match('/0([^\d\.]+)0/', $format) || substr_count($format, '.') > 1) { + if ($valueFloat == floor($valueFloat) && substr_count($format, '.') === 1) { + $value *= 10 ** strlen(explode('.', $format)[1]); + } + + $result = self::complexNumberFormatMask($value, $format); + if (strpos($result, 'E') !== false) { + // This is a hack and doesn't match Excel. + // It will, at least, be an accurate representation, + // even if formatted incorrectly. + // This is needed for absolute values >=1E18. + $result = self::f2s($valueFloat); + } + + return $result; + } + + $sprintf_pattern = "%0$minWidth." . strlen($right) . 'f'; + /** @var float */ + $valueFloat = $value; + $value = sprintf($sprintf_pattern, round($valueFloat, strlen($right))); + + return self::pregReplace(self::NUMBER_REGEX, $value, $format); + } + + /** + * @param mixed $value + */ + public static function format($value, string $format): string + { + // The "_" in this string has already been stripped out, + // so this test is never true. Furthermore, testing + // on Excel shows this format uses Euro symbol, not "EUR". + //if ($format === NumberFormat::FORMAT_CURRENCY_EUR_SIMPLE) { + // return 'EUR ' . sprintf('%1.2f', $value); + //} + + // Some non-number strings are quoted, so we'll get rid of the quotes, likewise any positional * symbols + $format = self::makeString(str_replace(['"', '*'], '', $format)); + + // Find out if we need thousands separator + // This is indicated by a comma enclosed by a digit placeholder: + // #,# or 0,0 + $useThousands = (bool) preg_match('/(#,#|0,0)/', $format); + if ($useThousands) { + $format = self::pregReplace('/0,0/', '00', $format); + $format = self::pregReplace('/#,#/', '##', $format); + } + + // Scale thousands, millions,... + // This is indicated by a number of commas after a digit placeholder: + // #, or 0.0,, + $scale = 1; // same as no scale + $matches = []; + if (preg_match('/(#|0)(,+)/', $format, $matches)) { + $scale = 1000 ** strlen($matches[2]); + + // strip the commas + $format = self::pregReplace('/0,+/', '0', $format); + $format = self::pregReplace('/#,+/', '#', $format); + } + if (preg_match('/#?.*\?\/\?/', $format, $m)) { + $value = FractionFormatter::format($value, $format); + } else { + // Handle the number itself + + // scale number + $value = $value / $scale; + // Strip # + $format = self::pregReplace('/\\#/', '0', $format); + // Remove locale code [$-###] + $format = self::pregReplace('/\[\$\-.*\]/', '', $format); + + $n = '/\\[[^\\]]+\\]/'; + $m = self::pregReplace($n, '', $format); + if (preg_match(self::NUMBER_REGEX, $m, $matches)) { + // There are placeholders for digits, so inject digits from the value into the mask + $value = self::formatStraightNumericValue($value, $format, $matches, $useThousands); + } elseif ($format !== NumberFormat::FORMAT_GENERAL) { + // Yes, I know that this is basically just a hack; + // if there's no placeholders for digits, just return the format mask "as is" + $value = self::makeString(str_replace('?', '', $format)); + } + } + + if (preg_match('/\[\$(.*)\]/u', $format, $m)) { + // Currency or Accounting + $currencyCode = $m[1]; + [$currencyCode] = explode('-', $currencyCode); + if ($currencyCode == '') { + $currencyCode = StringHelper::getCurrencyCode(); + } + $value = self::pregReplace('/\[\$([^\]]*)\]/u', $currencyCode, (string) $value); + } + + return (string) $value; + } + + /** + * @param array|string $value + */ + private static function makeString($value): string + { + return is_array($value) ? '' : "$value"; + } + + private static function pregReplace(string $pattern, string $replacement, string $subject): string + { + return self::makeString(preg_replace($pattern, $replacement, $subject) ?? ''); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php b/PhpOffice/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php new file mode 100644 index 0000000..74f3877 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php @@ -0,0 +1,47 @@ + 0); + $replacement = "0{$wholePartSize}.{$decimalPartSize}"; + $mask = (string) preg_replace('/[#0,]+\.?[?#0,]*/ui', "%{$replacement}f{$placeHolders}", $format); + + /** @var float */ + $valueFloat = $value; + + return sprintf($mask, round($valueFloat, $decimalPartSize)); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Style/Protection.php b/PhpOffice/PhpSpreadsheet/Style/Protection.php old mode 100755 new mode 100644 index b5feb53..1c174e7 --- a/PhpOffice/PhpSpreadsheet/Style/Protection.php +++ b/PhpOffice/PhpSpreadsheet/Style/Protection.php @@ -2,8 +2,6 @@ namespace PhpOffice\PhpSpreadsheet\Style; -use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; - class Protection extends Supervisor { /** Protection styles */ @@ -55,7 +53,10 @@ class Protection extends Supervisor */ public function getSharedComponent() { - return $this->parent->getSharedComponent()->getProtection(); + /** @var Style */ + $parent = $this->parent; + + return $parent->getSharedComponent()->getProtection(); } /** @@ -82,22 +83,20 @@ class Protection extends Supervisor * ); * * - * @param array $pStyles Array containing style information + * @param array $styleArray Array containing style information * - * @throws PhpSpreadsheetException - * - * @return Protection + * @return $this */ - public function applyFromArray(array $pStyles) + public function applyFromArray(array $styleArray) { if ($this->isSupervisor) { - $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($pStyles)); + $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray)); } else { - if (isset($pStyles['locked'])) { - $this->setLocked($pStyles['locked']); + if (isset($styleArray['locked'])) { + $this->setLocked($styleArray['locked']); } - if (isset($pStyles['hidden'])) { - $this->setHidden($pStyles['hidden']); + if (isset($styleArray['hidden'])) { + $this->setHidden($styleArray['hidden']); } } @@ -121,17 +120,17 @@ class Protection extends Supervisor /** * Set locked. * - * @param string $pValue see self::PROTECTION_* + * @param string $lockType see self::PROTECTION_* * - * @return Protection + * @return $this */ - public function setLocked($pValue) + public function setLocked($lockType) { if ($this->isSupervisor) { - $styleArray = $this->getStyleArray(['locked' => $pValue]); + $styleArray = $this->getStyleArray(['locked' => $lockType]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { - $this->locked = $pValue; + $this->locked = $lockType; } return $this; @@ -154,17 +153,17 @@ class Protection extends Supervisor /** * Set hidden. * - * @param string $pValue see self::PROTECTION_* + * @param string $hiddenType see self::PROTECTION_* * - * @return Protection + * @return $this */ - public function setHidden($pValue) + public function setHidden($hiddenType) { if ($this->isSupervisor) { - $styleArray = $this->getStyleArray(['hidden' => $pValue]); + $styleArray = $this->getStyleArray(['hidden' => $hiddenType]); $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { - $this->hidden = $pValue; + $this->hidden = $hiddenType; } return $this; @@ -187,4 +186,13 @@ class Protection extends Supervisor __CLASS__ ); } + + protected function exportArray1(): array + { + $exportedArray = []; + $this->exportArray2($exportedArray, 'locked', $this->getLocked()); + $this->exportArray2($exportedArray, 'hidden', $this->getHidden()); + + return $exportedArray; + } } diff --git a/PhpOffice/PhpSpreadsheet/Style/Style.php b/PhpOffice/PhpSpreadsheet/Style/Style.php old mode 100755 new mode 100644 index 9cdfc1b..506159d --- a/PhpOffice/PhpSpreadsheet/Style/Style.php +++ b/PhpOffice/PhpSpreadsheet/Style/Style.php @@ -42,13 +42,6 @@ class Style extends Supervisor */ protected $numberFormat; - /** - * Conditional styles. - * - * @var Conditional[] - */ - protected $conditionalStyles; - /** * Protection. * @@ -70,6 +63,25 @@ class Style extends Supervisor */ protected $quotePrefix = false; + /** + * Internal cache for styles + * Used when applying style on range of cells (column or row) and cleared when + * all cells in range is styled. + * + * PhpSpreadsheet will always minimize the amount of styles used. So cells with + * same styles will reference the same Style instance. To check if two styles + * are similar Style::getHashCode() is used. This call is expensive. To minimize + * the need to call this method we can cache the internal PHP object id of the + * Style in the range. Style::getHashCode() will then only be called when we + * encounter a unique style. + * + * @see Style::applyFromArray() + * @see Style::getHashCode() + * + * @var null|array + */ + private static $cachedStyles; + /** * Create a new Style. * @@ -85,10 +97,9 @@ class Style extends Supervisor parent::__construct($isSupervisor); // Initialise values - $this->conditionalStyles = []; $this->font = new Font($isSupervisor, $isConditional); $this->fill = new Fill($isSupervisor, $isConditional); - $this->borders = new Borders($isSupervisor, $isConditional); + $this->borders = new Borders($isSupervisor); $this->alignment = new Alignment($isSupervisor, $isConditional); $this->numberFormat = new NumberFormat($isSupervisor, $isConditional); $this->protection = new Protection($isSupervisor, $isConditional); @@ -107,10 +118,8 @@ class Style extends Supervisor /** * Get the shared style component for the currently active cell in currently active sheet. * Only used for style supervisor. - * - * @return Style */ - public function getSharedComponent() + public function getSharedComponent(): self { $activeSheet = $this->getActiveSheet(); $selectedCell = $this->getActiveCell(); // e.g. 'A1' @@ -121,17 +130,15 @@ class Style extends Supervisor $xfIndex = 0; } - return $this->parent->getCellXfByIndex($xfIndex); + return $activeSheet->getParent()->getCellXfByIndex($xfIndex); } /** * Get parent. Only used for style supervisor. - * - * @return Spreadsheet */ - public function getParent() + public function getParent(): Spreadsheet { - return $this->parent; + return $this->getActiveSheet()->getParent(); } /** @@ -186,12 +193,12 @@ class Style extends Supervisor * ); * * - * @param array $pStyles Array containing style information - * @param bool $pAdvanced advanced mode for setting borders + * @param array $styleArray Array containing style information + * @param bool $advancedBorders advanced mode for setting borders * - * @return Style + * @return $this */ - public function applyFromArray(array $pStyles, $pAdvanced = true) + public function applyFromArray(array $styleArray, $advancedBorders = true) { if ($this->isSupervisor) { $pRange = $this->getSelectedCells(); @@ -210,64 +217,65 @@ class Style extends Supervisor // Calculate range outer borders $rangeStart = Coordinate::coordinateFromString($rangeA); $rangeEnd = Coordinate::coordinateFromString($rangeB); + $rangeStartIndexes = Coordinate::indexesFromString($rangeA); + $rangeEndIndexes = Coordinate::indexesFromString($rangeB); - // Translate column into index - $rangeStart[0] = Coordinate::columnIndexFromString($rangeStart[0]); - $rangeEnd[0] = Coordinate::columnIndexFromString($rangeEnd[0]); + $columnStart = $rangeStart[0]; + $columnEnd = $rangeEnd[0]; // Make sure we can loop upwards on rows and columns - if ($rangeStart[0] > $rangeEnd[0] && $rangeStart[1] > $rangeEnd[1]) { - $tmp = $rangeStart; - $rangeStart = $rangeEnd; - $rangeEnd = $tmp; + if ($rangeStartIndexes[0] > $rangeEndIndexes[0] && $rangeStartIndexes[1] > $rangeEndIndexes[1]) { + $tmp = $rangeStartIndexes; + $rangeStartIndexes = $rangeEndIndexes; + $rangeEndIndexes = $tmp; } // ADVANCED MODE: - if ($pAdvanced && isset($pStyles['borders'])) { + if ($advancedBorders && isset($styleArray['borders'])) { // 'allBorders' is a shorthand property for 'outline' and 'inside' and // it applies to components that have not been set explicitly - if (isset($pStyles['borders']['allBorders'])) { + if (isset($styleArray['borders']['allBorders'])) { foreach (['outline', 'inside'] as $component) { - if (!isset($pStyles['borders'][$component])) { - $pStyles['borders'][$component] = $pStyles['borders']['allBorders']; + if (!isset($styleArray['borders'][$component])) { + $styleArray['borders'][$component] = $styleArray['borders']['allBorders']; } } - unset($pStyles['borders']['allBorders']); // not needed any more + unset($styleArray['borders']['allBorders']); // not needed any more } // 'outline' is a shorthand property for 'top', 'right', 'bottom', 'left' // it applies to components that have not been set explicitly - if (isset($pStyles['borders']['outline'])) { + if (isset($styleArray['borders']['outline'])) { foreach (['top', 'right', 'bottom', 'left'] as $component) { - if (!isset($pStyles['borders'][$component])) { - $pStyles['borders'][$component] = $pStyles['borders']['outline']; + if (!isset($styleArray['borders'][$component])) { + $styleArray['borders'][$component] = $styleArray['borders']['outline']; } } - unset($pStyles['borders']['outline']); // not needed any more + unset($styleArray['borders']['outline']); // not needed any more } // 'inside' is a shorthand property for 'vertical' and 'horizontal' // it applies to components that have not been set explicitly - if (isset($pStyles['borders']['inside'])) { + if (isset($styleArray['borders']['inside'])) { foreach (['vertical', 'horizontal'] as $component) { - if (!isset($pStyles['borders'][$component])) { - $pStyles['borders'][$component] = $pStyles['borders']['inside']; + if (!isset($styleArray['borders'][$component])) { + $styleArray['borders'][$component] = $styleArray['borders']['inside']; } } - unset($pStyles['borders']['inside']); // not needed any more + unset($styleArray['borders']['inside']); // not needed any more } // width and height characteristics of selection, 1, 2, or 3 (for 3 or more) - $xMax = min($rangeEnd[0] - $rangeStart[0] + 1, 3); - $yMax = min($rangeEnd[1] - $rangeStart[1] + 1, 3); + $xMax = min($rangeEndIndexes[0] - $rangeStartIndexes[0] + 1, 3); + $yMax = min($rangeEndIndexes[1] - $rangeStartIndexes[1] + 1, 3); // loop through up to 3 x 3 = 9 regions for ($x = 1; $x <= $xMax; ++$x) { // start column index for region $colStart = ($x == 3) ? - Coordinate::stringFromColumnIndex($rangeEnd[0]) - : Coordinate::stringFromColumnIndex($rangeStart[0] + $x - 1); + Coordinate::stringFromColumnIndex($rangeEndIndexes[0]) + : Coordinate::stringFromColumnIndex($rangeStartIndexes[0] + $x - 1); // end column index for region $colEnd = ($x == 1) ? - Coordinate::stringFromColumnIndex($rangeStart[0]) - : Coordinate::stringFromColumnIndex($rangeEnd[0] - $xMax + $x); + Coordinate::stringFromColumnIndex($rangeStartIndexes[0]) + : Coordinate::stringFromColumnIndex($rangeEndIndexes[0] - $xMax + $x); for ($y = 1; $y <= $yMax; ++$y) { // which edges are touching the region @@ -291,17 +299,17 @@ class Style extends Supervisor // start row index for region $rowStart = ($y == 3) ? - $rangeEnd[1] : $rangeStart[1] + $y - 1; + $rangeEndIndexes[1] : $rangeStartIndexes[1] + $y - 1; // end row index for region $rowEnd = ($y == 1) ? - $rangeStart[1] : $rangeEnd[1] - $yMax + $y; + $rangeStartIndexes[1] : $rangeEndIndexes[1] - $yMax + $y; // build range for region $range = $colStart . $rowStart . ':' . $colEnd . $rowEnd; // retrieve relevant style array for region - $regionStyles = $pStyles; + $regionStyles = $styleArray; unset($regionStyles['borders']['inside']); // what are the inner edges of the region when looking at the selection @@ -313,8 +321,8 @@ class Style extends Supervisor case 'top': case 'bottom': // should pick up 'horizontal' border property if set - if (isset($pStyles['borders']['horizontal'])) { - $regionStyles['borders'][$innerEdge] = $pStyles['borders']['horizontal']; + if (isset($styleArray['borders']['horizontal'])) { + $regionStyles['borders'][$innerEdge] = $styleArray['borders']['horizontal']; } else { unset($regionStyles['borders'][$innerEdge]); } @@ -323,8 +331,8 @@ class Style extends Supervisor case 'left': case 'right': // should pick up 'vertical' border property if set - if (isset($pStyles['borders']['vertical'])) { - $regionStyles['borders'][$innerEdge] = $pStyles['borders']['vertical']; + if (isset($styleArray['borders']['vertical'])) { + $regionStyles['borders'][$innerEdge] = $styleArray['borders']['vertical']; } else { unset($regionStyles['borders'][$innerEdge]); } @@ -348,54 +356,77 @@ class Style extends Supervisor // Selection type, inspect if (preg_match('/^[A-Z]+1:[A-Z]+1048576$/', $pRange)) { $selectionType = 'COLUMN'; + + // Enable caching of styles + self::$cachedStyles = ['hashByObjId' => [], 'styleByHash' => []]; } elseif (preg_match('/^A\d+:XFD\d+$/', $pRange)) { $selectionType = 'ROW'; + + // Enable caching of styles + self::$cachedStyles = ['hashByObjId' => [], 'styleByHash' => []]; } else { $selectionType = 'CELL'; } // First loop through columns, rows, or cells to find out which styles are affected by this operation - switch ($selectionType) { - case 'COLUMN': - $oldXfIndexes = []; - for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { - $oldXfIndexes[$this->getActiveSheet()->getColumnDimensionByColumn($col)->getXfIndex()] = true; - } - - break; - case 'ROW': - $oldXfIndexes = []; - for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { - if ($this->getActiveSheet()->getRowDimension($row)->getXfIndex() == null) { - $oldXfIndexes[0] = true; // row without explicit style should be formatted based on default style - } else { - $oldXfIndexes[$this->getActiveSheet()->getRowDimension($row)->getXfIndex()] = true; - } - } - - break; - case 'CELL': - $oldXfIndexes = []; - for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { - for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { - $oldXfIndexes[$this->getActiveSheet()->getCellByColumnAndRow($col, $row)->getXfIndex()] = true; - } - } - - break; - } + $oldXfIndexes = $this->getOldXfIndexes($selectionType, $rangeStartIndexes, $rangeEndIndexes, $columnStart, $columnEnd, $styleArray); // clone each of the affected styles, apply the style array, and add the new styles to the workbook $workbook = $this->getActiveSheet()->getParent(); + $newXfIndexes = []; foreach ($oldXfIndexes as $oldXfIndex => $dummy) { $style = $workbook->getCellXfByIndex($oldXfIndex); - $newStyle = clone $style; - $newStyle->applyFromArray($pStyles); - if ($existingStyle = $workbook->getCellXfByHashCode($newStyle->getHashCode())) { + // $cachedStyles is set when applying style for a range of cells, either column or row + if (self::$cachedStyles === null) { + // Clone the old style and apply style-array + $newStyle = clone $style; + $newStyle->applyFromArray($styleArray); + + // Look for existing style we can use instead (reduce memory usage) + $existingStyle = $workbook->getCellXfByHashCode($newStyle->getHashCode()); + } else { + // Style cache is stored by Style::getHashCode(). But calling this method is + // expensive. So we cache the php obj id -> hash. + $objId = spl_object_id($style); + + // Look for the original HashCode + $styleHash = self::$cachedStyles['hashByObjId'][$objId] ?? null; + if ($styleHash === null) { + // This object_id is not cached, store the hashcode in case encounter again + $styleHash = self::$cachedStyles['hashByObjId'][$objId] = $style->getHashCode(); + } + + // Find existing style by hash. + $existingStyle = self::$cachedStyles['styleByHash'][$styleHash] ?? null; + + if (!$existingStyle) { + // The old style combined with the new style array is not cached, so we create it now + $newStyle = clone $style; + $newStyle->applyFromArray($styleArray); + + // Look for similar style in workbook to reduce memory usage + $existingStyle = $workbook->getCellXfByHashCode($newStyle->getHashCode()); + + // Cache the new style by original hashcode + self::$cachedStyles['styleByHash'][$styleHash] = $existingStyle instanceof self ? $existingStyle : $newStyle; + } + } + + if ($existingStyle) { // there is already such cell Xf in our collection $newXfIndexes[$oldXfIndex] = $existingStyle->getIndex(); } else { + if (!isset($newStyle)) { + // Handle bug in PHPStan, see https://github.com/phpstan/phpstan/issues/5805 + // $newStyle should always be defined. + // This block might not be needed in the future + // @codeCoverageIgnoreStart + $newStyle = clone $style; + $newStyle->applyFromArray($styleArray); + // @codeCoverageIgnoreEnd + } + // we don't have such a cell Xf, need to add $workbook->addCellXf($newStyle); $newXfIndexes[$oldXfIndex] = $newStyle->getIndex(); @@ -405,26 +436,32 @@ class Style extends Supervisor // Loop through columns, rows, or cells again and update the XF index switch ($selectionType) { case 'COLUMN': - for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { + for ($col = $rangeStartIndexes[0]; $col <= $rangeEndIndexes[0]; ++$col) { $columnDimension = $this->getActiveSheet()->getColumnDimensionByColumn($col); $oldXfIndex = $columnDimension->getXfIndex(); $columnDimension->setXfIndex($newXfIndexes[$oldXfIndex]); } + // Disable caching of styles + self::$cachedStyles = null; + break; case 'ROW': - for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { + for ($row = $rangeStartIndexes[1]; $row <= $rangeEndIndexes[1]; ++$row) { $rowDimension = $this->getActiveSheet()->getRowDimension($row); - $oldXfIndex = $rowDimension->getXfIndex() === null ? - 0 : $rowDimension->getXfIndex(); // row without explicit style should be formatted based on default style + // row without explicit style should be formatted based on default style + $oldXfIndex = $rowDimension->getXfIndex() ?? 0; $rowDimension->setXfIndex($newXfIndexes[$oldXfIndex]); } + // Disable caching of styles + self::$cachedStyles = null; + break; case 'CELL': - for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { - for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { - $cell = $this->getActiveSheet()->getCellByColumnAndRow($col, $row); + for ($col = $rangeStartIndexes[0]; $col <= $rangeEndIndexes[0]; ++$col) { + for ($row = $rangeStartIndexes[1]; $row <= $rangeEndIndexes[1]; ++$row) { + $cell = $this->getActiveSheet()->getCell([$col, $row]); $oldXfIndex = $cell->getXfIndex(); $cell->setXfIndex($newXfIndexes[$oldXfIndex]); } @@ -434,32 +471,83 @@ class Style extends Supervisor } } else { // not a supervisor, just apply the style array directly on style object - if (isset($pStyles['fill'])) { - $this->getFill()->applyFromArray($pStyles['fill']); + if (isset($styleArray['fill'])) { + $this->getFill()->applyFromArray($styleArray['fill']); } - if (isset($pStyles['font'])) { - $this->getFont()->applyFromArray($pStyles['font']); + if (isset($styleArray['font'])) { + $this->getFont()->applyFromArray($styleArray['font']); } - if (isset($pStyles['borders'])) { - $this->getBorders()->applyFromArray($pStyles['borders']); + if (isset($styleArray['borders'])) { + $this->getBorders()->applyFromArray($styleArray['borders']); } - if (isset($pStyles['alignment'])) { - $this->getAlignment()->applyFromArray($pStyles['alignment']); + if (isset($styleArray['alignment'])) { + $this->getAlignment()->applyFromArray($styleArray['alignment']); } - if (isset($pStyles['numberFormat'])) { - $this->getNumberFormat()->applyFromArray($pStyles['numberFormat']); + if (isset($styleArray['numberFormat'])) { + $this->getNumberFormat()->applyFromArray($styleArray['numberFormat']); } - if (isset($pStyles['protection'])) { - $this->getProtection()->applyFromArray($pStyles['protection']); + if (isset($styleArray['protection'])) { + $this->getProtection()->applyFromArray($styleArray['protection']); } - if (isset($pStyles['quotePrefix'])) { - $this->quotePrefix = $pStyles['quotePrefix']; + if (isset($styleArray['quotePrefix'])) { + $this->quotePrefix = $styleArray['quotePrefix']; } } return $this; } + private function getOldXfIndexes(string $selectionType, array $rangeStart, array $rangeEnd, string $columnStart, string $columnEnd, array $styleArray): array + { + $oldXfIndexes = []; + switch ($selectionType) { + case 'COLUMN': + for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { + $oldXfIndexes[$this->getActiveSheet()->getColumnDimensionByColumn($col)->getXfIndex()] = true; + } + foreach ($this->getActiveSheet()->getColumnIterator($columnStart, $columnEnd) as $columnIterator) { + $cellIterator = $columnIterator->getCellIterator(); + $cellIterator->setIterateOnlyExistingCells(true); + foreach ($cellIterator as $columnCell) { + if ($columnCell !== null) { + $columnCell->getStyle()->applyFromArray($styleArray); + } + } + } + + break; + case 'ROW': + for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { + if ($this->getActiveSheet()->getRowDimension($row)->getXfIndex() === null) { + $oldXfIndexes[0] = true; // row without explicit style should be formatted based on default style + } else { + $oldXfIndexes[$this->getActiveSheet()->getRowDimension($row)->getXfIndex()] = true; + } + } + foreach ($this->getActiveSheet()->getRowIterator((int) $rangeStart[1], (int) $rangeEnd[1]) as $rowIterator) { + $cellIterator = $rowIterator->getCellIterator(); + $cellIterator->setIterateOnlyExistingCells(true); + foreach ($cellIterator as $rowCell) { + if ($rowCell !== null) { + $rowCell->getStyle()->applyFromArray($styleArray); + } + } + } + + break; + case 'CELL': + for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { + for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { + $oldXfIndexes[$this->getActiveSheet()->getCell([$col, $row])->getXfIndex()] = true; + } + } + + break; + } + + return $oldXfIndexes; + } + /** * Get Fill. * @@ -483,9 +571,7 @@ class Style extends Supervisor /** * Set font. * - * @param Font $font - * - * @return Style + * @return $this */ public function setFont(Font $font) { @@ -537,13 +623,13 @@ class Style extends Supervisor /** * Set Conditional Styles. Only used on supervisor. * - * @param Conditional[] $pValue Array of conditional styles + * @param Conditional[] $conditionalStyleArray Array of conditional styles * - * @return Style + * @return $this */ - public function setConditionalStyles(array $pValue) + public function setConditionalStyles(array $conditionalStyleArray) { - $this->getActiveSheet()->setConditionalStyles($this->getSelectedCells(), $pValue); + $this->getActiveSheet()->setConditionalStyles($this->getSelectedCells(), $conditionalStyleArray); return $this; } @@ -575,20 +661,20 @@ class Style extends Supervisor /** * Set quote prefix. * - * @param bool $pValue + * @param bool $quotePrefix * - * @return Style + * @return $this */ - public function setQuotePrefix($pValue) + public function setQuotePrefix($quotePrefix) { - if ($pValue == '') { - $pValue = false; + if ($quotePrefix == '') { + $quotePrefix = false; } if ($this->isSupervisor) { - $styleArray = ['quotePrefix' => $pValue]; + $styleArray = ['quotePrefix' => $quotePrefix]; $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray); } else { - $this->quotePrefix = (bool) $pValue; + $this->quotePrefix = (bool) $quotePrefix; } return $this; @@ -601,18 +687,12 @@ class Style extends Supervisor */ public function getHashCode() { - $hashConditionals = ''; - foreach ($this->conditionalStyles as $conditional) { - $hashConditionals .= $conditional->getHashCode(); - } - return md5( $this->fill->getHashCode() . $this->font->getHashCode() . $this->borders->getHashCode() . $this->alignment->getHashCode() . $this->numberFormat->getHashCode() . - $hashConditionals . $this->protection->getHashCode() . ($this->quotePrefix ? 't' : 'f') . __CLASS__ @@ -632,10 +712,24 @@ class Style extends Supervisor /** * Set own index in style collection. * - * @param int $pValue + * @param int $index */ - public function setIndex($pValue) + public function setIndex($index): void { - $this->index = $pValue; + $this->index = $index; + } + + protected function exportArray1(): array + { + $exportedArray = []; + $this->exportArray2($exportedArray, 'alignment', $this->getAlignment()); + $this->exportArray2($exportedArray, 'borders', $this->getBorders()); + $this->exportArray2($exportedArray, 'fill', $this->getFill()); + $this->exportArray2($exportedArray, 'font', $this->getFont()); + $this->exportArray2($exportedArray, 'numberFormat', $this->getNumberFormat()); + $this->exportArray2($exportedArray, 'protection', $this->getProtection()); + $this->exportArray2($exportedArray, 'quotePrefx', $this->getQuotePrefix()); + + return $exportedArray; } } diff --git a/PhpOffice/PhpSpreadsheet/Style/Supervisor.php b/PhpOffice/PhpSpreadsheet/Style/Supervisor.php old mode 100755 new mode 100644 index 2d1a272..8a5c350 --- a/PhpOffice/PhpSpreadsheet/Style/Supervisor.php +++ b/PhpOffice/PhpSpreadsheet/Style/Supervisor.php @@ -18,7 +18,7 @@ abstract class Supervisor implements IComparable /** * Parent. Only used for supervisor. * - * @var Spreadsheet|Style + * @var Spreadsheet|Supervisor */ protected $parent; @@ -45,10 +45,10 @@ abstract class Supervisor implements IComparable /** * Bind parent. Only used for supervisor. * - * @param Spreadsheet|Style $parent + * @param Spreadsheet|Supervisor $parent * @param null|string $parentPropertyName * - * @return Supervisor + * @return $this */ public function bindParent($parent, $parentPropertyName = null) { @@ -114,4 +114,62 @@ abstract class Supervisor implements IComparable } } } + + /** + * Export style as array. + * + * Available to anything which extends this class: + * Alignment, Border, Borders, Color, Fill, Font, + * NumberFormat, Protection, and Style. + */ + final public function exportArray(): array + { + return $this->exportArray1(); + } + + /** + * Abstract method to be implemented in anything which + * extends this class. + * + * This method invokes exportArray2 with the names and values + * of all properties to be included in output array, + * returning that array to exportArray, then to caller. + */ + abstract protected function exportArray1(): array; + + /** + * Populate array from exportArray1. + * This method is available to anything which extends this class. + * The parameter index is the key to be added to the array. + * The parameter objOrValue is either a primitive type, + * which is the value added to the array, + * or a Style object to be recursively added via exportArray. + * + * @param mixed $objOrValue + */ + final protected function exportArray2(array &$exportedArray, string $index, $objOrValue): void + { + if ($objOrValue instanceof self) { + $exportedArray[$index] = $objOrValue->exportArray(); + } else { + $exportedArray[$index] = $objOrValue; + } + } + + /** + * Get the shared style component for the currently active cell in currently active sheet. + * Only used for style supervisor. + * + * @return mixed + */ + abstract public function getSharedComponent(); + + /** + * Build style array from subcomponents. + * + * @param array $array + * + * @return array + */ + abstract public function getStyleArray($array); } diff --git a/PhpOffice/PhpSpreadsheet/Worksheet/AutoFilter.php b/PhpOffice/PhpSpreadsheet/Worksheet/AutoFilter.php old mode 100755 new mode 100644 index b444925..d604198 --- a/PhpOffice/PhpSpreadsheet/Worksheet/AutoFilter.php +++ b/PhpOffice/PhpSpreadsheet/Worksheet/AutoFilter.php @@ -2,19 +2,23 @@ namespace PhpOffice\PhpSpreadsheet\Worksheet; +use DateTime; +use DateTimeZone; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; -use PhpOffice\PhpSpreadsheet\Calculation\DateTime; use PhpOffice\PhpSpreadsheet\Calculation\Functions; +use PhpOffice\PhpSpreadsheet\Calculation\Internal\WildcardMatch; +use PhpOffice\PhpSpreadsheet\Cell\AddressRange; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; -use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; +use PhpOffice\PhpSpreadsheet\Exception; use PhpOffice\PhpSpreadsheet\Shared\Date; +use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule; class AutoFilter { /** * Autofilter Worksheet. * - * @var Worksheet + * @var null|Worksheet */ private $workSheet; @@ -32,22 +36,41 @@ class AutoFilter */ private $columns = []; + /** @var bool */ + private $evaluated = false; + + public function getEvaluated(): bool + { + return $this->evaluated; + } + + public function setEvaluated(bool $value): void + { + $this->evaluated = $value; + } + /** * Create a new AutoFilter. * - * @param string $pRange Cell range (i.e. A1:E10) - * @param Worksheet $pSheet + * @param AddressRange|array|string $range + * A simple string containing a Cell range like 'A1:E10' is permitted + * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), + * or an AddressRange object. */ - public function __construct($pRange = '', Worksheet $pSheet = null) + public function __construct($range = '', ?Worksheet $worksheet = null) { - $this->range = $pRange; - $this->workSheet = $pSheet; + if ($range !== '') { + [, $range] = Worksheet::extractSheetTitle(Validations::validateCellRange($range), true); + } + + $this->range = $range; + $this->workSheet = $worksheet; } /** * Get AutoFilter Parent Worksheet. * - * @return Worksheet + * @return null|Worksheet */ public function getParent() { @@ -57,13 +80,12 @@ class AutoFilter /** * Set AutoFilter Parent Worksheet. * - * @param Worksheet $pSheet - * - * @return AutoFilter + * @return $this */ - public function setParent(Worksheet $pSheet = null) + public function setParent(?Worksheet $worksheet = null) { - $this->workSheet = $pSheet; + $this->evaluated = false; + $this->workSheet = $worksheet; return $this; } @@ -79,38 +101,54 @@ class AutoFilter } /** - * Set AutoFilter Range. + * Set AutoFilter Cell Range. * - * @param string $pRange Cell range (i.e. A1:E10) - * - * @throws PhpSpreadsheetException - * - * @return AutoFilter + * @param AddressRange|array|string $range + * A simple string containing a Cell range like 'A1:E10' or a Cell address like 'A1' is permitted + * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), + * or an AddressRange object. */ - public function setRange($pRange) + public function setRange($range = ''): self { + $this->evaluated = false; // extract coordinate - [$worksheet, $pRange] = Worksheet::extractSheetTitle($pRange, true); - - if (strpos($pRange, ':') !== false) { - $this->range = $pRange; - } elseif (empty($pRange)) { - $this->range = ''; - } else { - throw new PhpSpreadsheetException('Autofilter must be set on a range of cells.'); + if ($range !== '') { + [, $range] = Worksheet::extractSheetTitle(Validations::validateCellRange($range), true); } - if (empty($pRange)) { + if (empty($range)) { // Discard all column rules $this->columns = []; - } else { - // Discard any column rules that are no longer valid within this range - [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range); - foreach ($this->columns as $key => $value) { - $colIndex = Coordinate::columnIndexFromString($key); - if (($rangeStart[0] > $colIndex) || ($rangeEnd[0] < $colIndex)) { - unset($this->columns[$key]); - } + $this->range = ''; + + return $this; + } + + if (ctype_digit($range) || ctype_alpha($range)) { + throw new Exception("{$range} is an invalid range for AutoFilter"); + } + + $this->range = $range; + // Discard any column rules that are no longer valid within this range + [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range); + foreach ($this->columns as $key => $value) { + $colIndex = Coordinate::columnIndexFromString($key); + if (($rangeStart[0] > $colIndex) || ($rangeEnd[0] < $colIndex)) { + unset($this->columns[$key]); + } + } + + return $this; + } + + public function setRangeToMaxRow(): self + { + $this->evaluated = false; + if ($this->workSheet !== null) { + $thisrange = $this->range; + $range = (string) preg_replace('/\\d+$/', (string) $this->workSheet->getHighestRow(), $thisrange); + if ($range !== $thisrange) { + $this->setRange($range); } } @@ -132,20 +170,18 @@ class AutoFilter * * @param string $column Column name (e.g. A) * - * @throws PhpSpreadsheetException - * * @return int The column offset within the autofilter range */ public function testColumnInRange($column) { if (empty($this->range)) { - throw new PhpSpreadsheetException('No autofilter range is defined.'); + throw new Exception('No autofilter range is defined.'); } $columnIndex = Coordinate::columnIndexFromString($column); [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range); if (($rangeStart[0] > $columnIndex) || ($rangeEnd[0] < $columnIndex)) { - throw new PhpSpreadsheetException('Column is outside of current autofilter range.'); + throw new Exception('Column is outside of current autofilter range.'); } return $columnIndex - $rangeStart[0]; @@ -154,50 +190,44 @@ class AutoFilter /** * Get a specified AutoFilter Column Offset within the defined AutoFilter range. * - * @param string $pColumn Column name (e.g. A) - * - * @throws PhpSpreadsheetException + * @param string $column Column name (e.g. A) * * @return int The offset of the specified column within the autofilter range */ - public function getColumnOffset($pColumn) + public function getColumnOffset($column) { - return $this->testColumnInRange($pColumn); + return $this->testColumnInRange($column); } /** * Get a specified AutoFilter Column. * - * @param string $pColumn Column name (e.g. A) - * - * @throws PhpSpreadsheetException + * @param string $column Column name (e.g. A) * * @return AutoFilter\Column */ - public function getColumn($pColumn) + public function getColumn($column) { - $this->testColumnInRange($pColumn); + $this->testColumnInRange($column); - if (!isset($this->columns[$pColumn])) { - $this->columns[$pColumn] = new AutoFilter\Column($pColumn, $this); + if (!isset($this->columns[$column])) { + $this->columns[$column] = new AutoFilter\Column($column, $this); } - return $this->columns[$pColumn]; + return $this->columns[$column]; } /** * Get a specified AutoFilter Column by it's offset. * - * @param int $pColumnOffset Column offset within range (starting from 0) - * - * @throws PhpSpreadsheetException + * @param int $columnOffset Column offset within range (starting from 0) * * @return AutoFilter\Column */ - public function getColumnByOffset($pColumnOffset) + public function getColumnByOffset($columnOffset) { [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range); - $pColumn = Coordinate::stringFromColumnIndex($rangeStart[0] + $pColumnOffset); + $pColumn = Coordinate::stringFromColumnIndex($rangeStart[0] + $columnOffset); return $this->getColumn($pColumn); } @@ -205,29 +235,28 @@ class AutoFilter /** * Set AutoFilter. * - * @param AutoFilter\Column|string $pColumn + * @param AutoFilter\Column|string $columnObjectOrString * A simple string containing a Column ID like 'A' is permitted * - * @throws PhpSpreadsheetException - * - * @return AutoFilter + * @return $this */ - public function setColumn($pColumn) + public function setColumn($columnObjectOrString) { - if ((is_string($pColumn)) && (!empty($pColumn))) { - $column = $pColumn; - } elseif (is_object($pColumn) && ($pColumn instanceof AutoFilter\Column)) { - $column = $pColumn->getColumnIndex(); + $this->evaluated = false; + if ((is_string($columnObjectOrString)) && (!empty($columnObjectOrString))) { + $column = $columnObjectOrString; + } elseif (is_object($columnObjectOrString) && ($columnObjectOrString instanceof AutoFilter\Column)) { + $column = $columnObjectOrString->getColumnIndex(); } else { - throw new PhpSpreadsheetException('Column is not within the autofilter range.'); + throw new Exception('Column is not within the autofilter range.'); } $this->testColumnInRange($column); - if (is_string($pColumn)) { - $this->columns[$pColumn] = new AutoFilter\Column($pColumn, $this); - } elseif (is_object($pColumn) && ($pColumn instanceof AutoFilter\Column)) { - $pColumn->setParent($this); - $this->columns[$column] = $pColumn; + if (is_string($columnObjectOrString)) { + $this->columns[$columnObjectOrString] = new AutoFilter\Column($columnObjectOrString, $this); + } else { + $columnObjectOrString->setParent($this); + $this->columns[$column] = $columnObjectOrString; } ksort($this->columns); @@ -237,18 +266,17 @@ class AutoFilter /** * Clear a specified AutoFilter Column. * - * @param string $pColumn Column name (e.g. A) + * @param string $column Column name (e.g. A) * - * @throws PhpSpreadsheetException - * - * @return AutoFilter + * @return $this */ - public function clearColumn($pColumn) + public function clearColumn($column) { - $this->testColumnInRange($pColumn); + $this->evaluated = false; + $this->testColumnInRange($column); - if (isset($this->columns[$pColumn])) { - unset($this->columns[$pColumn]); + if (isset($this->columns[$column])) { + unset($this->columns[$column]); } return $this; @@ -264,10 +292,11 @@ class AutoFilter * @param string $fromColumn Column name (e.g. A) * @param string $toColumn Column name (e.g. B) * - * @return AutoFilter + * @return $this */ public function shiftColumn($fromColumn, $toColumn) { + $this->evaluated = false; $fromColumn = strtoupper($fromColumn); $toColumn = strtoupper($toColumn); @@ -318,20 +347,22 @@ class AutoFilter if (($cellValue == '') || ($cellValue === null)) { return $blanks; } + $timeZone = new DateTimeZone('UTC'); if (is_numeric($cellValue)) { - $dateValue = Date::excelToTimestamp($cellValue); + $dateTime = Date::excelToDateTimeObject((float) $cellValue, $timeZone); + $cellValue = (float) $cellValue; if ($cellValue < 1) { // Just the time part - $dtVal = date('His', $dateValue); + $dtVal = $dateTime->format('His'); $dateSet = $dateSet['time']; } elseif ($cellValue == floor($cellValue)) { // Just the date part - $dtVal = date('Ymd', $dateValue); + $dtVal = $dateTime->format('Ymd'); $dateSet = $dateSet['date']; } else { // date and time parts - $dtVal = date('YmdHis', $dateValue); + $dtVal = $dateTime->format('YmdHis'); $dateSet = $dateSet['dateTime']; } foreach ($dateSet as $dateValue) { @@ -355,6 +386,7 @@ class AutoFilter */ private static function filterTestInCustomDataSet($cellValue, $ruleSet) { + /** @var array[] */ $dataSet = $ruleSet['filterRules']; $join = $ruleSet['join']; $customRuleForBlanks = $ruleSet['customRuleForBlanks'] ?? false; @@ -367,43 +399,50 @@ class AutoFilter } $returnVal = ($join == AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_AND); foreach ($dataSet as $rule) { + /** @var string */ + $ruleValue = $rule['value']; + /** @var string */ + $ruleOperator = $rule['operator']; + /** @var string */ + $cellValueString = $cellValue ?? ''; $retVal = false; - if (is_numeric($rule['value'])) { + if (is_numeric($ruleValue)) { // Numeric values are tested using the appropriate operator - switch ($rule['operator']) { - case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_EQUAL: - $retVal = ($cellValue == $rule['value']); + $numericTest = is_numeric($cellValue); + switch ($ruleOperator) { + case Rule::AUTOFILTER_COLUMN_RULE_EQUAL: + $retVal = $numericTest && ($cellValue == $ruleValue); break; - case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_NOTEQUAL: - $retVal = ($cellValue != $rule['value']); + case Rule::AUTOFILTER_COLUMN_RULE_NOTEQUAL: + $retVal = !$numericTest || ($cellValue != $ruleValue); break; - case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN: - $retVal = ($cellValue > $rule['value']); + case Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN: + $retVal = $numericTest && ($cellValue > $ruleValue); break; - case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL: - $retVal = ($cellValue >= $rule['value']); + case Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL: + $retVal = $numericTest && ($cellValue >= $ruleValue); break; - case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN: - $retVal = ($cellValue < $rule['value']); + case Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN: + $retVal = $numericTest && ($cellValue < $ruleValue); break; - case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL: - $retVal = ($cellValue <= $rule['value']); + case Rule::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL: + $retVal = $numericTest && ($cellValue <= $ruleValue); break; } - } elseif ($rule['value'] == '') { - switch ($rule['operator']) { - case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_EQUAL: + } elseif ($ruleValue == '') { + switch ($ruleOperator) { + case Rule::AUTOFILTER_COLUMN_RULE_EQUAL: $retVal = (($cellValue == '') || ($cellValue === null)); break; - case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_NOTEQUAL: + case Rule::AUTOFILTER_COLUMN_RULE_NOTEQUAL: $retVal = (($cellValue != '') && ($cellValue !== null)); break; @@ -414,7 +453,32 @@ class AutoFilter } } else { // String values are always tested for equality, factoring in for wildcards (hence a regexp test) - $retVal = preg_match('/^' . $rule['value'] . '$/i', $cellValue); + switch ($ruleOperator) { + case Rule::AUTOFILTER_COLUMN_RULE_EQUAL: + $retVal = (bool) preg_match('/^' . $ruleValue . '$/i', $cellValueString); + + break; + case Rule::AUTOFILTER_COLUMN_RULE_NOTEQUAL: + $retVal = !((bool) preg_match('/^' . $ruleValue . '$/i', $cellValueString)); + + break; + case Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN: + $retVal = strcasecmp($cellValueString, $ruleValue) > 0; + + break; + case Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL: + $retVal = strcasecmp($cellValueString, $ruleValue) >= 0; + + break; + case Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN: + $retVal = strcasecmp($cellValueString, $ruleValue) < 0; + + break; + case Rule::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL: + $retVal = strcasecmp($cellValueString, $ruleValue) <= 0; + + break; + } } // If there are multiple conditions, then we need to test both using the appropriate join operator switch ($join) { @@ -453,7 +517,8 @@ class AutoFilter } if (is_numeric($cellValue)) { - $dateValue = date('m', Date::excelToTimestamp($cellValue)); + $dateObject = Date::excelToDateTimeObject((float) $cellValue, new DateTimeZone('UTC')); + $dateValue = (int) $dateObject->format('m'); if (in_array($dateValue, $monthSet)) { return true; } @@ -462,165 +527,298 @@ class AutoFilter return false; } - /** - * Search/Replace arrays to convert Excel wildcard syntax to a regexp syntax for preg_matching. - * - * @var array - */ - private static $fromReplace = ['\*', '\?', '~~', '~.*', '~.?']; + private static function makeDateObject(int $year, int $month, int $day, int $hour = 0, int $minute = 0, int $second = 0): DateTime + { + $baseDate = new DateTime(); + $baseDate->setDate($year, $month, $day); + $baseDate->setTime($hour, $minute, $second); - private static $toReplace = ['.*', '.', '~', '\*', '\?']; + return $baseDate; + } + + private const DATE_FUNCTIONS = [ + Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTMONTH => 'dynamicLastMonth', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTQUARTER => 'dynamicLastQuarter', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTWEEK => 'dynamicLastWeek', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTYEAR => 'dynamicLastYear', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTMONTH => 'dynamicNextMonth', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTQUARTER => 'dynamicNextQuarter', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTWEEK => 'dynamicNextWeek', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTYEAR => 'dynamicNextYear', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISMONTH => 'dynamicThisMonth', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISQUARTER => 'dynamicThisQuarter', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISWEEK => 'dynamicThisWeek', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISYEAR => 'dynamicThisYear', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_TODAY => 'dynamicToday', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_TOMORROW => 'dynamicTomorrow', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_YEARTODATE => 'dynamicYearToDate', + Rule::AUTOFILTER_RULETYPE_DYNAMIC_YESTERDAY => 'dynamicYesterday', + ]; + + private static function dynamicLastMonth(): array + { + $maxval = new DateTime(); + $year = (int) $maxval->format('Y'); + $month = (int) $maxval->format('m'); + $maxval->setDate($year, $month, 1); + $maxval->setTime(0, 0, 0); + $val = clone $maxval; + $val->modify('-1 month'); + + return [$val, $maxval]; + } + + private static function firstDayOfQuarter(): DateTime + { + $val = new DateTime(); + $year = (int) $val->format('Y'); + $month = (int) $val->format('m'); + $month = 3 * intdiv($month - 1, 3) + 1; + $val->setDate($year, $month, 1); + $val->setTime(0, 0, 0); + + return $val; + } + + private static function dynamicLastQuarter(): array + { + $maxval = self::firstDayOfQuarter(); + $val = clone $maxval; + $val->modify('-3 months'); + + return [$val, $maxval]; + } + + private static function dynamicLastWeek(): array + { + $val = new DateTime(); + $val->setTime(0, 0, 0); + $dayOfWeek = (int) $val->format('w'); // Sunday is 0 + $subtract = $dayOfWeek + 7; // revert to prior Sunday + $val->modify("-$subtract days"); + $maxval = clone $val; + $maxval->modify('+7 days'); + + return [$val, $maxval]; + } + + private static function dynamicLastYear(): array + { + $val = new DateTime(); + $year = (int) $val->format('Y'); + $val = self::makeDateObject($year - 1, 1, 1); + $maxval = self::makeDateObject($year, 1, 1); + + return [$val, $maxval]; + } + + private static function dynamicNextMonth(): array + { + $val = new DateTime(); + $year = (int) $val->format('Y'); + $month = (int) $val->format('m'); + $val->setDate($year, $month, 1); + $val->setTime(0, 0, 0); + $val->modify('+1 month'); + $maxval = clone $val; + $maxval->modify('+1 month'); + + return [$val, $maxval]; + } + + private static function dynamicNextQuarter(): array + { + $val = self::firstDayOfQuarter(); + $val->modify('+3 months'); + $maxval = clone $val; + $maxval->modify('+3 months'); + + return [$val, $maxval]; + } + + private static function dynamicNextWeek(): array + { + $val = new DateTime(); + $val->setTime(0, 0, 0); + $dayOfWeek = (int) $val->format('w'); // Sunday is 0 + $add = 7 - $dayOfWeek; // move to next Sunday + $val->modify("+$add days"); + $maxval = clone $val; + $maxval->modify('+7 days'); + + return [$val, $maxval]; + } + + private static function dynamicNextYear(): array + { + $val = new DateTime(); + $year = (int) $val->format('Y'); + $val = self::makeDateObject($year + 1, 1, 1); + $maxval = self::makeDateObject($year + 2, 1, 1); + + return [$val, $maxval]; + } + + private static function dynamicThisMonth(): array + { + $baseDate = new DateTime(); + $baseDate->setTime(0, 0, 0); + $year = (int) $baseDate->format('Y'); + $month = (int) $baseDate->format('m'); + $val = self::makeDateObject($year, $month, 1); + $maxval = clone $val; + $maxval->modify('+1 month'); + + return [$val, $maxval]; + } + + private static function dynamicThisQuarter(): array + { + $val = self::firstDayOfQuarter(); + $maxval = clone $val; + $maxval->modify('+3 months'); + + return [$val, $maxval]; + } + + private static function dynamicThisWeek(): array + { + $val = new DateTime(); + $val->setTime(0, 0, 0); + $dayOfWeek = (int) $val->format('w'); // Sunday is 0 + $subtract = $dayOfWeek; // revert to Sunday + $val->modify("-$subtract days"); + $maxval = clone $val; + $maxval->modify('+7 days'); + + return [$val, $maxval]; + } + + private static function dynamicThisYear(): array + { + $val = new DateTime(); + $year = (int) $val->format('Y'); + $val = self::makeDateObject($year, 1, 1); + $maxval = self::makeDateObject($year + 1, 1, 1); + + return [$val, $maxval]; + } + + private static function dynamicToday(): array + { + $val = new DateTime(); + $val->setTime(0, 0, 0); + $maxval = clone $val; + $maxval->modify('+1 day'); + + return [$val, $maxval]; + } + + private static function dynamicTomorrow(): array + { + $val = new DateTime(); + $val->setTime(0, 0, 0); + $val->modify('+1 day'); + $maxval = clone $val; + $maxval->modify('+1 day'); + + return [$val, $maxval]; + } + + private static function dynamicYearToDate(): array + { + $maxval = new DateTime(); + $maxval->setTime(0, 0, 0); + $val = self::makeDateObject((int) $maxval->format('Y'), 1, 1); + $maxval->modify('+1 day'); + + return [$val, $maxval]; + } + + private static function dynamicYesterday(): array + { + $maxval = new DateTime(); + $maxval->setTime(0, 0, 0); + $val = clone $maxval; + $val->modify('-1 day'); + + return [$val, $maxval]; + } /** * Convert a dynamic rule daterange to a custom filter range expression for ease of calculation. * * @param string $dynamicRuleType - * @param AutoFilter\Column $filterColumn * * @return mixed[] */ - private function dynamicFilterDateRange($dynamicRuleType, &$filterColumn) + private function dynamicFilterDateRange($dynamicRuleType, AutoFilter\Column &$filterColumn) { - $rDateType = Functions::getReturnDateType(); - Functions::setReturnDateType(Functions::RETURNDATE_PHP_NUMERIC); - $val = $maxVal = null; - $ruleValues = []; - $baseDate = DateTime::DATENOW(); + $callBack = [__CLASS__, self::DATE_FUNCTIONS[$dynamicRuleType]]; // What if not found? // Calculate start/end dates for the required date range based on current date - switch ($dynamicRuleType) { - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTWEEK: - $baseDate = strtotime('-7 days', $baseDate); - - break; - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTWEEK: - $baseDate = strtotime('-7 days', $baseDate); - - break; - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTMONTH: - $baseDate = strtotime('-1 month', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate))); - - break; - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTMONTH: - $baseDate = strtotime('+1 month', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate))); - - break; - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTQUARTER: - $baseDate = strtotime('-3 month', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate))); - - break; - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTQUARTER: - $baseDate = strtotime('+3 month', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate))); - - break; - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTYEAR: - $baseDate = strtotime('-1 year', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate))); - - break; - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTYEAR: - $baseDate = strtotime('+1 year', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate))); - - break; - } - - switch ($dynamicRuleType) { - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_TODAY: - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_YESTERDAY: - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_TOMORROW: - $maxVal = (int) Date::PHPtoExcel(strtotime('+1 day', $baseDate)); - $val = (int) Date::PHPToExcel($baseDate); - - break; - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_YEARTODATE: - $maxVal = (int) Date::PHPtoExcel(strtotime('+1 day', $baseDate)); - $val = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 1, 1, date('Y', $baseDate))); - - break; - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISYEAR: - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTYEAR: - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTYEAR: - $maxVal = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 31, 12, date('Y', $baseDate))); - ++$maxVal; - $val = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 1, 1, date('Y', $baseDate))); - - break; - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISQUARTER: - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTQUARTER: - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTQUARTER: - $thisMonth = date('m', $baseDate); - $thisQuarter = floor(--$thisMonth / 3); - $maxVal = (int) Date::PHPtoExcel(gmmktime(0, 0, 0, date('t', $baseDate), (1 + $thisQuarter) * 3, date('Y', $baseDate))); - ++$maxVal; - $val = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 1, 1 + $thisQuarter * 3, date('Y', $baseDate))); - - break; - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISMONTH: - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTMONTH: - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTMONTH: - $maxVal = (int) Date::PHPtoExcel(gmmktime(0, 0, 0, date('t', $baseDate), date('m', $baseDate), date('Y', $baseDate))); - ++$maxVal; - $val = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate))); - - break; - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISWEEK: - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTWEEK: - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTWEEK: - $dayOfWeek = date('w', $baseDate); - $val = (int) Date::PHPToExcel($baseDate) - $dayOfWeek; - $maxVal = $val + 7; - - break; - } - - switch ($dynamicRuleType) { - // Adjust Today dates for Yesterday and Tomorrow - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_YESTERDAY: - --$maxVal; - --$val; - - break; - case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_TOMORROW: - ++$maxVal; - ++$val; - - break; + // Val is lowest permitted value. + // Maxval is greater than highest permitted value + $val = $maxval = 0; + if (is_callable($callBack)) { + [$val, $maxval] = $callBack(); } + $val = Date::dateTimeToExcel($val); + $maxval = Date::dateTimeToExcel($maxval); // Set the filter column rule attributes ready for writing - $filterColumn->setAttributes(['val' => $val, 'maxVal' => $maxVal]); + $filterColumn->setAttributes(['val' => $val, 'maxVal' => $maxval]); // Set the rules for identifying rows for hide/show - $ruleValues[] = ['operator' => AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL, 'value' => $val]; - $ruleValues[] = ['operator' => AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN, 'value' => $maxVal]; - Functions::setReturnDateType($rDateType); + $ruleValues[] = ['operator' => Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL, 'value' => $val]; + $ruleValues[] = ['operator' => Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN, 'value' => $maxval]; return ['method' => 'filterTestInCustomDataSet', 'arguments' => ['filterRules' => $ruleValues, 'join' => AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_AND]]; } - private function calculateTopTenValue($columnID, $startRow, $endRow, $ruleType, $ruleValue) - { - $range = $columnID . $startRow . ':' . $columnID . $endRow; - $dataValues = Functions::flattenArray($this->workSheet->rangeToArray($range, null, true, false)); - - $dataValues = array_filter($dataValues); - if ($ruleType == AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP) { - rsort($dataValues); - } else { - sort($dataValues); - } - - return array_pop(array_slice($dataValues, 0, $ruleValue)); - } - /** * Apply the AutoFilter rules to the AutoFilter Range. * - * @throws PhpSpreadsheetException + * @param string $columnID + * @param int $startRow + * @param int $endRow + * @param ?string $ruleType + * @param mixed $ruleValue * - * @return AutoFilter + * @return mixed + */ + private function calculateTopTenValue($columnID, $startRow, $endRow, $ruleType, $ruleValue) + { + $range = $columnID . $startRow . ':' . $columnID . $endRow; + $retVal = null; + if ($this->workSheet !== null) { + $dataValues = Functions::flattenArray($this->workSheet->rangeToArray($range, null, true, false)); + $dataValues = array_filter($dataValues); + + if ($ruleType == Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP) { + rsort($dataValues); + } else { + sort($dataValues); + } + + $slice = array_slice($dataValues, 0, $ruleValue); + + $retVal = array_pop($slice); + } + + return $retVal; + } + + /** + * Apply the AutoFilter rules to the AutoFilter Range. + * + * @return $this */ public function showHideRows() { + if ($this->workSheet === null) { + return $this; + } [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range); // The heading row should always be visible @@ -644,7 +842,7 @@ class AutoFilter if (count($ruleValues) != count($ruleDataSet)) { $blanks = true; } - if ($ruleType == AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_FILTER) { + if ($ruleType == Rule::AUTOFILTER_RULETYPE_FILTER) { // Filter on absolute values $columnFilterTests[$columnID] = [ 'method' => 'filterTestInSimpleDataSet', @@ -658,30 +856,45 @@ class AutoFilter 'dateTime' => [], ]; foreach ($ruleDataSet as $ruleValue) { + if (!is_array($ruleValue)) { + continue; + } $date = $time = ''; - if ((isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR])) && - ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR] !== '')) { - $date .= sprintf('%04d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR]); + if ( + (isset($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR])) && + ($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR] !== '') + ) { + $date .= sprintf('%04d', $ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR]); } - if ((isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH])) && - ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH] != '')) { - $date .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH]); + if ( + (isset($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH])) && + ($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH] != '') + ) { + $date .= sprintf('%02d', $ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH]); } - if ((isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY])) && - ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY] !== '')) { - $date .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY]); + if ( + (isset($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY])) && + ($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY] !== '') + ) { + $date .= sprintf('%02d', $ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY]); } - if ((isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR])) && - ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR] !== '')) { - $time .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR]); + if ( + (isset($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR])) && + ($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR] !== '') + ) { + $time .= sprintf('%02d', $ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR]); } - if ((isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE])) && - ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE] !== '')) { - $time .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE]); + if ( + (isset($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE])) && + ($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE] !== '') + ) { + $time .= sprintf('%02d', $ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE]); } - if ((isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND])) && - ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND] !== '')) { - $time .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND]); + if ( + (isset($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND])) && + ($ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND] !== '') + ) { + $time .= sprintf('%02d', $ruleValue[Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND]); } $dateTime = $date . $time; $arguments['date'][] = $date; @@ -700,15 +913,14 @@ class AutoFilter break; case AutoFilter\Column::AUTOFILTER_FILTERTYPE_CUSTOMFILTER: - $customRuleForBlanks = false; + $customRuleForBlanks = true; $ruleValues = []; // Build a list of the filter value selections foreach ($rules as $rule) { $ruleValue = $rule->getValue(); - if (!is_numeric($ruleValue)) { + if (!is_array($ruleValue) && !is_numeric($ruleValue)) { // Convert to a regexp allowing for regexp reserved characters, wildcards and escaped wildcards - $ruleValue = preg_quote($ruleValue); - $ruleValue = str_replace(self::$fromReplace, self::$toReplace, $ruleValue); + $ruleValue = WildcardMatch::wildcard($ruleValue); if (trim($ruleValue) == '') { $customRuleForBlanks = true; $ruleValue = trim($ruleValue); @@ -728,16 +940,22 @@ class AutoFilter foreach ($rules as $rule) { // We should only ever have one Dynamic Filter Rule anyway $dynamicRuleType = $rule->getGrouping(); - if (($dynamicRuleType == AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_ABOVEAVERAGE) || - ($dynamicRuleType == AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_BELOWAVERAGE)) { + if ( + ($dynamicRuleType == Rule::AUTOFILTER_RULETYPE_DYNAMIC_ABOVEAVERAGE) || + ($dynamicRuleType == Rule::AUTOFILTER_RULETYPE_DYNAMIC_BELOWAVERAGE) + ) { // Number (Average) based // Calculate the average $averageFormula = '=AVERAGE(' . $columnID . ($rangeStart[1] + 1) . ':' . $columnID . $rangeEnd[1] . ')'; - $average = Calculation::getInstance()->calculateFormula($averageFormula, null, $this->workSheet->getCell('A1')); + $spreadsheet = ($this->workSheet === null) ? null : $this->workSheet->getParent(); + $average = Calculation::getInstance($spreadsheet)->calculateFormula($averageFormula, null, $this->workSheet->getCell('A1')); + while (is_array($average)) { + $average = array_pop($average); + } // Set above/below rule based on greaterThan or LessTan - $operator = ($dynamicRuleType === AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_ABOVEAVERAGE) - ? AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN - : AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN; + $operator = ($dynamicRuleType === Rule::AUTOFILTER_RULETYPE_DYNAMIC_ABOVEAVERAGE) + ? Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN + : Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN; $ruleValues[] = [ 'operator' => $operator, 'value' => $average, @@ -779,27 +997,30 @@ class AutoFilter case AutoFilter\Column::AUTOFILTER_FILTERTYPE_TOPTENFILTER: $ruleValues = []; $dataRowCount = $rangeEnd[1] - $rangeStart[1]; + $toptenRuleType = null; + $ruleValue = 0; + $ruleOperator = null; foreach ($rules as $rule) { // We should only ever have one Dynamic Filter Rule anyway $toptenRuleType = $rule->getGrouping(); $ruleValue = $rule->getValue(); $ruleOperator = $rule->getOperator(); } - if ($ruleOperator === AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT) { - $ruleValue = floor($ruleValue * ($dataRowCount / 100)); + if (is_numeric($ruleValue) && $ruleOperator === Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT) { + $ruleValue = floor((float) $ruleValue * ($dataRowCount / 100)); } - if ($ruleValue < 1) { + if (!is_array($ruleValue) && $ruleValue < 1) { $ruleValue = 1; } - if ($ruleValue > 500) { + if (!is_array($ruleValue) && $ruleValue > 500) { $ruleValue = 500; } - $maxVal = $this->calculateTopTenValue($columnID, $rangeStart[1] + 1, $rangeEnd[1], $toptenRuleType, $ruleValue); + $maxVal = $this->calculateTopTenValue($columnID, $rangeStart[1] + 1, (int) $rangeEnd[1], $toptenRuleType, $ruleValue); - $operator = ($toptenRuleType == AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP) - ? AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL - : AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL; + $operator = ($toptenRuleType == Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP) + ? Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL + : Rule::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL; $ruleValues[] = ['operator' => $operator, 'value' => $maxVal]; $columnFilterTests[$columnID] = [ 'method' => 'filterTestInCustomDataSet', @@ -811,29 +1032,53 @@ class AutoFilter } } + $rangeEnd[1] = $this->autoExtendRange($rangeStart[1], $rangeEnd[1]); + // Execute the column tests for each row in the autoFilter range to determine show/hide, for ($row = $rangeStart[1] + 1; $row <= $rangeEnd[1]; ++$row) { $result = true; foreach ($columnFilterTests as $columnID => $columnFilterTest) { $cellValue = $this->workSheet->getCell($columnID . $row)->getCalculatedValue(); // Execute the filter test - $result = $result && - call_user_func_array( - [self::class, $columnFilterTest['method']], - [$cellValue, $columnFilterTest['arguments']] - ); + $result = // $result && // phpstan says $result is always true here + // @phpstan-ignore-next-line + call_user_func_array([self::class, $columnFilterTest['method']], [$cellValue, $columnFilterTest['arguments']]); // If filter test has resulted in FALSE, exit the loop straightaway rather than running any more tests if (!$result) { break; } } // Set show/hide for the row based on the result of the autoFilter result - $this->workSheet->getRowDimension($row)->setVisible($result); + $this->workSheet->getRowDimension((int) $row)->setVisible($result); } + $this->evaluated = true; return $this; } + /** + * Magic Range Auto-sizing. + * For a single row rangeSet, we follow MS Excel rules, and search for the first empty row to determine our range. + */ + public function autoExtendRange(int $startRow, int $endRow): int + { + if ($startRow === $endRow && $this->workSheet !== null) { + try { + $rowIterator = $this->workSheet->getRowIterator($startRow + 1); + } catch (Exception $e) { + // If there are no rows below $startRow + return $startRow; + } + foreach ($rowIterator as $row) { + if ($row->isEmpty(CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL | CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL) === true) { + return $row->getRowIndex() - 1; + } + } + } + + return $endRow; + } + /** * Implement PHP __clone to create a deep clone, not just a shallow copy. */ diff --git a/PhpOffice/PhpSpreadsheet/Worksheet/AutoFilter/Column.php b/PhpOffice/PhpSpreadsheet/Worksheet/AutoFilter/Column.php old mode 100755 new mode 100644 index b5ab61e..076292f --- a/PhpOffice/PhpSpreadsheet/Worksheet/AutoFilter/Column.php +++ b/PhpOffice/PhpSpreadsheet/Worksheet/AutoFilter/Column.php @@ -49,7 +49,7 @@ class Column /** * Autofilter. * - * @var AutoFilter + * @var null|AutoFilter */ private $parent; @@ -77,31 +77,38 @@ class Column /** * Autofilter Column Rules. * - * @var array of Column\Rule + * @var Column\Rule[] */ private $ruleset = []; /** * Autofilter Column Dynamic Attributes. * - * @var array of mixed + * @var mixed[] */ private $attributes = []; /** * Create a new Column. * - * @param string $pColumn Column (e.g. A) - * @param AutoFilter $pParent Autofilter for this column + * @param string $column Column (e.g. A) + * @param AutoFilter $parent Autofilter for this column */ - public function __construct($pColumn, AutoFilter $pParent = null) + public function __construct($column, ?AutoFilter $parent = null) { - $this->columnIndex = $pColumn; - $this->parent = $pParent; + $this->columnIndex = $column; + $this->parent = $parent; + } + + public function setEvaluatedFalse(): void + { + if ($this->parent !== null) { + $this->parent->setEvaluated(false); + } } /** - * Get AutoFilter Column Index. + * Get AutoFilter column index as string eg: 'A'. * * @return string */ @@ -111,23 +118,22 @@ class Column } /** - * Set AutoFilter Column Index. + * Set AutoFilter column index as string eg: 'A'. * - * @param string $pColumn Column (e.g. A) + * @param string $column Column (e.g. A) * - * @throws PhpSpreadsheetException - * - * @return Column + * @return $this */ - public function setColumnIndex($pColumn) + public function setColumnIndex($column) { + $this->setEvaluatedFalse(); // Uppercase coordinate - $pColumn = strtoupper($pColumn); + $column = strtoupper($column); if ($this->parent !== null) { - $this->parent->testColumnInRange($pColumn); + $this->parent->testColumnInRange($column); } - $this->columnIndex = $pColumn; + $this->columnIndex = $column; return $this; } @@ -135,7 +141,7 @@ class Column /** * Get this Column's AutoFilter Parent. * - * @return AutoFilter + * @return null|AutoFilter */ public function getParent() { @@ -145,13 +151,12 @@ class Column /** * Set this Column's AutoFilter Parent. * - * @param AutoFilter $pParent - * - * @return Column + * @return $this */ - public function setParent(AutoFilter $pParent = null) + public function setParent(?AutoFilter $parent = null) { - $this->parent = $pParent; + $this->setEvaluatedFalse(); + $this->parent = $parent; return $this; } @@ -169,19 +174,21 @@ class Column /** * Set AutoFilter Type. * - * @param string $pFilterType + * @param string $filterType * - * @throws PhpSpreadsheetException - * - * @return Column + * @return $this */ - public function setFilterType($pFilterType) + public function setFilterType($filterType) { - if (!in_array($pFilterType, self::$filterTypes)) { + $this->setEvaluatedFalse(); + if (!in_array($filterType, self::$filterTypes)) { throw new PhpSpreadsheetException('Invalid filter type for column AutoFilter.'); } + if ($filterType === self::AUTOFILTER_FILTERTYPE_CUSTOMFILTER && count($this->ruleset) > 2) { + throw new PhpSpreadsheetException('No more than 2 rules are allowed in a Custom Filter'); + } - $this->filterType = $pFilterType; + $this->filterType = $filterType; return $this; } @@ -199,21 +206,20 @@ class Column /** * Set AutoFilter Multiple Rules And/Or. * - * @param string $pJoin And/Or + * @param string $join And/Or * - * @throws PhpSpreadsheetException - * - * @return Column + * @return $this */ - public function setJoin($pJoin) + public function setJoin($join) { + $this->setEvaluatedFalse(); // Lowercase And/Or - $pJoin = strtolower($pJoin); - if (!in_array($pJoin, self::$ruleJoins)) { + $join = strtolower($join); + if (!in_array($join, self::$ruleJoins)) { throw new PhpSpreadsheetException('Invalid rule connection for column AutoFilter.'); } - $this->join = $pJoin; + $this->join = $join; return $this; } @@ -221,12 +227,13 @@ class Column /** * Set AutoFilter Attributes. * - * @param string[] $attributes + * @param mixed[] $attributes * - * @return Column + * @return $this */ - public function setAttributes(array $attributes) + public function setAttributes($attributes) { + $this->setEvaluatedFalse(); $this->attributes = $attributes; return $this; @@ -235,14 +242,15 @@ class Column /** * Set An AutoFilter Attribute. * - * @param string $pName Attribute Name - * @param string $pValue Attribute Value + * @param string $name Attribute Name + * @param int|string $value Attribute Value * - * @return Column + * @return $this */ - public function setAttribute($pName, $pValue) + public function setAttribute($name, $value) { - $this->attributes[$pName] = $pValue; + $this->setEvaluatedFalse(); + $this->attributes[$name] = $value; return $this; } @@ -250,7 +258,7 @@ class Column /** * Get AutoFilter Column Attributes. * - * @return string[] + * @return int[]|string[] */ public function getAttributes() { @@ -260,19 +268,24 @@ class Column /** * Get specific AutoFilter Column Attribute. * - * @param string $pName Attribute Name + * @param string $name Attribute Name * - * @return string + * @return null|int|string */ - public function getAttribute($pName) + public function getAttribute($name) { - if (isset($this->attributes[$pName])) { - return $this->attributes[$pName]; + if (isset($this->attributes[$name])) { + return $this->attributes[$name]; } return null; } + public function ruleCount(): int + { + return count($this->ruleset); + } + /** * Get all AutoFilter Column Rules. * @@ -286,17 +299,17 @@ class Column /** * Get a specified AutoFilter Column Rule. * - * @param int $pIndex Rule index in the ruleset array + * @param int $index Rule index in the ruleset array * * @return Column\Rule */ - public function getRule($pIndex) + public function getRule($index) { - if (!isset($this->ruleset[$pIndex])) { - $this->ruleset[$pIndex] = new Column\Rule($this); + if (!isset($this->ruleset[$index])) { + $this->ruleset[$index] = new Column\Rule($this); } - return $this->ruleset[$pIndex]; + return $this->ruleset[$index]; } /** @@ -306,6 +319,10 @@ class Column */ public function createRule() { + $this->setEvaluatedFalse(); + if ($this->filterType === self::AUTOFILTER_FILTERTYPE_CUSTOMFILTER && count($this->ruleset) >= 2) { + throw new PhpSpreadsheetException('No more than 2 rules are allowed in a Custom Filter'); + } $this->ruleset[] = new Column\Rule($this); return end($this->ruleset); @@ -314,14 +331,13 @@ class Column /** * Add a new AutoFilter Column Rule to the ruleset. * - * @param Column\Rule $pRule - * - * @return Column + * @return $this */ - public function addRule(Column\Rule $pRule) + public function addRule(Column\Rule $rule) { - $pRule->setParent($this); - $this->ruleset[] = $pRule; + $this->setEvaluatedFalse(); + $rule->setParent($this); + $this->ruleset[] = $rule; return $this; } @@ -330,14 +346,15 @@ class Column * Delete a specified AutoFilter Column Rule * If the number of rules is reduced to 1, then we reset And/Or logic to Or. * - * @param int $pIndex Rule index in the ruleset array + * @param int $index Rule index in the ruleset array * - * @return Column + * @return $this */ - public function deleteRule($pIndex) + public function deleteRule($index) { - if (isset($this->ruleset[$pIndex])) { - unset($this->ruleset[$pIndex]); + $this->setEvaluatedFalse(); + if (isset($this->ruleset[$index])) { + unset($this->ruleset[$index]); // If we've just deleted down to a single rule, then reset And/Or joining to Or if (count($this->ruleset) <= 1) { $this->setJoin(self::AUTOFILTER_COLUMN_JOIN_OR); @@ -350,10 +367,11 @@ class Column /** * Delete all AutoFilter Column Rules. * - * @return Column + * @return $this */ public function clearRules() { + $this->setEvaluatedFalse(); $this->ruleset = []; $this->setJoin(self::AUTOFILTER_COLUMN_JOIN_OR); @@ -378,8 +396,6 @@ class Column $cloned->setParent($this); // attach the new cloned Rule to this new cloned Autofilter Cloned object $this->ruleset[$k] = $cloned; } - } elseif (is_object($value)) { - $this->$key = clone $value; } else { $this->$key = $value; } diff --git a/PhpOffice/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php b/PhpOffice/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php old mode 100755 new mode 100644 index 450bccd..408dfb3 --- a/PhpOffice/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php +++ b/PhpOffice/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php @@ -13,7 +13,7 @@ class Rule const AUTOFILTER_RULETYPE_DYNAMICFILTER = 'dynamicFilter'; const AUTOFILTER_RULETYPE_TOPTENFILTER = 'top10Filter'; - private static $ruleTypes = [ + private const RULE_TYPES = [ // Currently we're not handling // colorFilter // extLst @@ -32,7 +32,7 @@ class Rule const AUTOFILTER_RULETYPE_DATEGROUP_MINUTE = 'minute'; const AUTOFILTER_RULETYPE_DATEGROUP_SECOND = 'second'; - private static $dateTimeGroups = [ + private const DATE_TIME_GROUPS = [ self::AUTOFILTER_RULETYPE_DATEGROUP_YEAR, self::AUTOFILTER_RULETYPE_DATEGROUP_MONTH, self::AUTOFILTER_RULETYPE_DATEGROUP_DAY, @@ -88,7 +88,7 @@ class Rule const AUTOFILTER_RULETYPE_DYNAMIC_ABOVEAVERAGE = 'aboveAverage'; const AUTOFILTER_RULETYPE_DYNAMIC_BELOWAVERAGE = 'belowAverage'; - private static $dynamicTypes = [ + private const DYNAMIC_TYPES = [ self::AUTOFILTER_RULETYPE_DYNAMIC_YESTERDAY, self::AUTOFILTER_RULETYPE_DYNAMIC_TODAY, self::AUTOFILTER_RULETYPE_DYNAMIC_TOMORROW, @@ -125,15 +125,7 @@ class Rule self::AUTOFILTER_RULETYPE_DYNAMIC_BELOWAVERAGE, ]; - /* - * The only valid filter rule operators for filter and customFilter types are: - * - * - * - * - * - * - */ + // Filter rule operators for filter and customFilter types. const AUTOFILTER_COLUMN_RULE_EQUAL = 'equal'; const AUTOFILTER_COLUMN_RULE_NOTEQUAL = 'notEqual'; const AUTOFILTER_COLUMN_RULE_GREATERTHAN = 'greaterThan'; @@ -141,7 +133,7 @@ class Rule const AUTOFILTER_COLUMN_RULE_LESSTHAN = 'lessThan'; const AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL = 'lessThanOrEqual'; - private static $operators = [ + private const OPERATORS = [ self::AUTOFILTER_COLUMN_RULE_EQUAL, self::AUTOFILTER_COLUMN_RULE_NOTEQUAL, self::AUTOFILTER_COLUMN_RULE_GREATERTHAN, @@ -153,7 +145,7 @@ class Rule const AUTOFILTER_COLUMN_RULE_TOPTEN_BY_VALUE = 'byValue'; const AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT = 'byPercent'; - private static $topTenValue = [ + private const TOP_TEN_VALUE = [ self::AUTOFILTER_COLUMN_RULE_TOPTEN_BY_VALUE, self::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT, ]; @@ -161,49 +153,27 @@ class Rule const AUTOFILTER_COLUMN_RULE_TOPTEN_TOP = 'top'; const AUTOFILTER_COLUMN_RULE_TOPTEN_BOTTOM = 'bottom'; - private static $topTenType = [ + private const TOP_TEN_TYPE = [ self::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP, self::AUTOFILTER_COLUMN_RULE_TOPTEN_BOTTOM, ]; - // Rule Operators (Numeric, Boolean etc) -// const AUTOFILTER_COLUMN_RULE_BETWEEN = 'between'; // greaterThanOrEqual 1 && lessThanOrEqual 2 + // Unimplented Rule Operators (Numeric, Boolean etc) + // const AUTOFILTER_COLUMN_RULE_BETWEEN = 'between'; // greaterThanOrEqual 1 && lessThanOrEqual 2 // Rule Operators (Numeric Special) which are translated to standard numeric operators with calculated values -// const AUTOFILTER_COLUMN_RULE_TOPTEN = 'topTen'; // greaterThan calculated value -// const AUTOFILTER_COLUMN_RULE_TOPTENPERCENT = 'topTenPercent'; // greaterThan calculated value -// const AUTOFILTER_COLUMN_RULE_ABOVEAVERAGE = 'aboveAverage'; // Value is calculated as the average -// const AUTOFILTER_COLUMN_RULE_BELOWAVERAGE = 'belowAverage'; // Value is calculated as the average // Rule Operators (String) which are set as wild-carded values -// const AUTOFILTER_COLUMN_RULE_BEGINSWITH = 'beginsWith'; // A* -// const AUTOFILTER_COLUMN_RULE_ENDSWITH = 'endsWith'; // *Z -// const AUTOFILTER_COLUMN_RULE_CONTAINS = 'contains'; // *B* -// const AUTOFILTER_COLUMN_RULE_DOESNTCONTAIN = 'notEqual'; // notEqual *B* + // const AUTOFILTER_COLUMN_RULE_BEGINSWITH = 'beginsWith'; // A* + // const AUTOFILTER_COLUMN_RULE_ENDSWITH = 'endsWith'; // *Z + // const AUTOFILTER_COLUMN_RULE_CONTAINS = 'contains'; // *B* + // const AUTOFILTER_COLUMN_RULE_DOESNTCONTAIN = 'notEqual'; // notEqual *B* // Rule Operators (Date Special) which are translated to standard numeric operators with calculated values -// const AUTOFILTER_COLUMN_RULE_BEFORE = 'lessThan'; -// const AUTOFILTER_COLUMN_RULE_AFTER = 'greaterThan'; -// const AUTOFILTER_COLUMN_RULE_YESTERDAY = 'yesterday'; -// const AUTOFILTER_COLUMN_RULE_TODAY = 'today'; -// const AUTOFILTER_COLUMN_RULE_TOMORROW = 'tomorrow'; -// const AUTOFILTER_COLUMN_RULE_LASTWEEK = 'lastWeek'; -// const AUTOFILTER_COLUMN_RULE_THISWEEK = 'thisWeek'; -// const AUTOFILTER_COLUMN_RULE_NEXTWEEK = 'nextWeek'; -// const AUTOFILTER_COLUMN_RULE_LASTMONTH = 'lastMonth'; -// const AUTOFILTER_COLUMN_RULE_THISMONTH = 'thisMonth'; -// const AUTOFILTER_COLUMN_RULE_NEXTMONTH = 'nextMonth'; -// const AUTOFILTER_COLUMN_RULE_LASTQUARTER = 'lastQuarter'; -// const AUTOFILTER_COLUMN_RULE_THISQUARTER = 'thisQuarter'; -// const AUTOFILTER_COLUMN_RULE_NEXTQUARTER = 'nextQuarter'; -// const AUTOFILTER_COLUMN_RULE_LASTYEAR = 'lastYear'; -// const AUTOFILTER_COLUMN_RULE_THISYEAR = 'thisYear'; -// const AUTOFILTER_COLUMN_RULE_NEXTYEAR = 'nextYear'; -// const AUTOFILTER_COLUMN_RULE_YEARTODATE = 'yearToDate'; // -// const AUTOFILTER_COLUMN_RULE_ALLDATESINMONTH = 'allDatesInMonth'; // for Month/February -// const AUTOFILTER_COLUMN_RULE_ALLDATESINQUARTER = 'allDatesInQuarter'; // for Quarter 2 + // const AUTOFILTER_COLUMN_RULE_BEFORE = 'lessThan'; + // const AUTOFILTER_COLUMN_RULE_AFTER = 'greaterThan'; /** * Autofilter Column. * - * @var Column + * @var ?Column */ private $parent; @@ -217,7 +187,7 @@ class Rule /** * Autofilter Rule Value. * - * @var string + * @var int|int[]|string|string[] */ private $value = ''; @@ -237,12 +207,17 @@ class Rule /** * Create a new Rule. - * - * @param Column $pParent */ - public function __construct(Column $pParent = null) + public function __construct(?Column $parent = null) { - $this->parent = $pParent; + $this->parent = $parent; + } + + private function setEvaluatedFalse(): void + { + if ($this->parent !== null) { + $this->parent->setEvaluatedFalse(); + } } /** @@ -258,19 +233,18 @@ class Rule /** * Set AutoFilter Rule Type. * - * @param string $pRuleType see self::AUTOFILTER_RULETYPE_* + * @param string $ruleType see self::AUTOFILTER_RULETYPE_* * - * @throws PhpSpreadsheetException - * - * @return Rule + * @return $this */ - public function setRuleType($pRuleType) + public function setRuleType($ruleType) { - if (!in_array($pRuleType, self::$ruleTypes)) { + $this->setEvaluatedFalse(); + if (!in_array($ruleType, self::RULE_TYPES)) { throw new PhpSpreadsheetException('Invalid rule type for column AutoFilter Rule.'); } - $this->ruleType = $pRuleType; + $this->ruleType = $ruleType; return $this; } @@ -278,7 +252,7 @@ class Rule /** * Get AutoFilter Rule Value. * - * @return string + * @return int|int[]|string|string[] */ public function getValue() { @@ -288,33 +262,32 @@ class Rule /** * Set AutoFilter Rule Value. * - * @param string|string[] $pValue + * @param int|int[]|string|string[] $value * - * @throws PhpSpreadsheetException - * - * @return Rule + * @return $this */ - public function setValue($pValue) + public function setValue($value) { - if (is_array($pValue)) { + $this->setEvaluatedFalse(); + if (is_array($value)) { $grouping = -1; - foreach ($pValue as $key => $value) { + foreach ($value as $key => $v) { // Validate array entries - if (!in_array($key, self::$dateTimeGroups)) { + if (!in_array($key, self::DATE_TIME_GROUPS)) { // Remove any invalid entries from the value array - unset($pValue[$key]); + unset($value[$key]); } else { // Work out what the dateTime grouping will be - $grouping = max($grouping, array_search($key, self::$dateTimeGroups)); + $grouping = max($grouping, array_search($key, self::DATE_TIME_GROUPS)); } } - if (count($pValue) == 0) { + if (count($value) == 0) { throw new PhpSpreadsheetException('Invalid rule value for column AutoFilter Rule.'); } // Set the dateTime grouping that we've anticipated - $this->setGrouping(self::$dateTimeGroups[$grouping]); + $this->setGrouping(self::DATE_TIME_GROUPS[$grouping]); } - $this->value = $pValue; + $this->value = $value; return $this; } @@ -332,22 +305,23 @@ class Rule /** * Set AutoFilter Rule Operator. * - * @param string $pOperator see self::AUTOFILTER_COLUMN_RULE_* + * @param string $operator see self::AUTOFILTER_COLUMN_RULE_* * - * @throws PhpSpreadsheetException - * - * @return Rule + * @return $this */ - public function setOperator($pOperator) + public function setOperator($operator) { - if (empty($pOperator)) { - $pOperator = self::AUTOFILTER_COLUMN_RULE_EQUAL; + $this->setEvaluatedFalse(); + if (empty($operator)) { + $operator = self::AUTOFILTER_COLUMN_RULE_EQUAL; } - if ((!in_array($pOperator, self::$operators)) && - (!in_array($pOperator, self::$topTenValue))) { + if ( + (!in_array($operator, self::OPERATORS)) && + (!in_array($operator, self::TOP_TEN_VALUE)) + ) { throw new PhpSpreadsheetException('Invalid operator for column AutoFilter Rule.'); } - $this->operator = $pOperator; + $this->operator = $operator; return $this; } @@ -365,21 +339,22 @@ class Rule /** * Set AutoFilter Rule Grouping. * - * @param string $pGrouping + * @param string $grouping * - * @throws PhpSpreadsheetException - * - * @return Rule + * @return $this */ - public function setGrouping($pGrouping) + public function setGrouping($grouping) { - if (($pGrouping !== null) && - (!in_array($pGrouping, self::$dateTimeGroups)) && - (!in_array($pGrouping, self::$dynamicTypes)) && - (!in_array($pGrouping, self::$topTenType))) { - throw new PhpSpreadsheetException('Invalid rule type for column AutoFilter Rule.'); + $this->setEvaluatedFalse(); + if ( + ($grouping !== null) && + (!in_array($grouping, self::DATE_TIME_GROUPS)) && + (!in_array($grouping, self::DYNAMIC_TYPES)) && + (!in_array($grouping, self::TOP_TEN_TYPE)) + ) { + throw new PhpSpreadsheetException('Invalid grouping for column AutoFilter Rule.'); } - $this->grouping = $pGrouping; + $this->grouping = $grouping; return $this; } @@ -387,23 +362,22 @@ class Rule /** * Set AutoFilter Rule. * - * @param string $pOperator see self::AUTOFILTER_COLUMN_RULE_* - * @param string|string[] $pValue - * @param string $pGrouping + * @param string $operator see self::AUTOFILTER_COLUMN_RULE_* + * @param int|int[]|string|string[] $value + * @param string $grouping * - * @throws PhpSpreadsheetException - * - * @return Rule + * @return $this */ - public function setRule($pOperator, $pValue, $pGrouping = null) + public function setRule($operator, $value, $grouping = null) { - $this->setOperator($pOperator); - $this->setValue($pValue); + $this->setEvaluatedFalse(); + $this->setOperator($operator); + $this->setValue($value); // Only set grouping if it's been passed in as a user-supplied argument, // otherwise we're calculating it when we setValue() and don't want to overwrite that // If the user supplies an argumnet for grouping, then on their own head be it - if ($pGrouping !== null) { - $this->setGrouping($pGrouping); + if ($grouping !== null) { + $this->setGrouping($grouping); } return $this; @@ -412,7 +386,7 @@ class Rule /** * Get this Rule's AutoFilter Column Parent. * - * @return Column + * @return ?Column */ public function getParent() { @@ -422,13 +396,12 @@ class Rule /** * Set this Rule's AutoFilter Column Parent. * - * @param Column $pParent - * - * @return Rule + * @return $this */ - public function setParent(Column $pParent = null) + public function setParent(?Column $parent = null) { - $this->parent = $pParent; + $this->setEvaluatedFalse(); + $this->parent = $parent; return $this; } @@ -441,11 +414,9 @@ class Rule $vars = get_object_vars($this); foreach ($vars as $key => $value) { if (is_object($value)) { - if ($key == 'parent') { + if ($key == 'parent') { // this is only object // Detach from autofilter column parent $this->$key = null; - } else { - $this->$key = clone $value; } } else { $this->$key = $value; diff --git a/PhpOffice/PhpSpreadsheet/Worksheet/BaseDrawing.php b/PhpOffice/PhpSpreadsheet/Worksheet/BaseDrawing.php old mode 100755 new mode 100644 index 98b6897..5001346 --- a/PhpOffice/PhpSpreadsheet/Worksheet/BaseDrawing.php +++ b/PhpOffice/PhpSpreadsheet/Worksheet/BaseDrawing.php @@ -8,6 +8,22 @@ use PhpOffice\PhpSpreadsheet\IComparable; class BaseDrawing implements IComparable { + const EDIT_AS_ABSOLUTE = 'absolute'; + const EDIT_AS_ONECELL = 'oneCell'; + const EDIT_AS_TWOCELL = 'twoCell'; + private const VALID_EDIT_AS = [ + self::EDIT_AS_ABSOLUTE, + self::EDIT_AS_ONECELL, + self::EDIT_AS_TWOCELL, + ]; + + /** + * The editAs attribute, used only with two cell anchor. + * + * @var string + */ + protected $editAs = ''; + /** * Image counter. * @@ -27,19 +43,19 @@ class BaseDrawing implements IComparable * * @var string */ - protected $name; + protected $name = ''; /** * Description. * * @var string */ - protected $description; + protected $description = ''; /** * Worksheet. * - * @var Worksheet + * @var null|Worksheet */ protected $worksheet; @@ -48,49 +64,84 @@ class BaseDrawing implements IComparable * * @var string */ - protected $coordinates; + protected $coordinates = 'A1'; /** * Offset X. * * @var int */ - protected $offsetX; + protected $offsetX = 0; /** * Offset Y. * * @var int */ - protected $offsetY; + protected $offsetY = 0; + + /** + * Coordinates2. + * + * @var string + */ + protected $coordinates2 = ''; + + /** + * Offset X2. + * + * @var int + */ + protected $offsetX2 = 0; + + /** + * Offset Y2. + * + * @var int + */ + protected $offsetY2 = 0; /** * Width. * * @var int */ - protected $width; + protected $width = 0; /** * Height. * * @var int */ - protected $height; + protected $height = 0; + + /** + * Pixel width of image. See $width for the size the Drawing will be in the sheet. + * + * @var int + */ + protected $imageWidth = 0; + + /** + * Pixel width of image. See $height for the size the Drawing will be in the sheet. + * + * @var int + */ + protected $imageHeight = 0; /** * Proportional resize. * * @var bool */ - protected $resizeProportional; + protected $resizeProportional = true; /** * Rotation. * * @var int */ - protected $rotation; + protected $rotation = 0; /** * Shadow. @@ -106,93 +157,56 @@ class BaseDrawing implements IComparable */ private $hyperlink; + /** + * Image type. + * + * @var int + */ + protected $type = IMAGETYPE_UNKNOWN; + /** * Create a new BaseDrawing. */ public function __construct() { // Initialise values - $this->name = ''; - $this->description = ''; - $this->worksheet = null; - $this->coordinates = 'A1'; - $this->offsetX = 0; - $this->offsetY = 0; - $this->width = 0; - $this->height = 0; - $this->resizeProportional = true; - $this->rotation = 0; - $this->shadow = new Drawing\Shadow(); + $this->setShadow(); // Set image index ++self::$imageCounter; $this->imageIndex = self::$imageCounter; } - /** - * Get image index. - * - * @return int - */ - public function getImageIndex() + public function getImageIndex(): int { return $this->imageIndex; } - /** - * Get Name. - * - * @return string - */ - public function getName() + public function getName(): string { return $this->name; } - /** - * Set Name. - * - * @param string $pValue - * - * @return BaseDrawing - */ - public function setName($pValue) + public function setName(string $name): self { - $this->name = $pValue; + $this->name = $name; return $this; } - /** - * Get Description. - * - * @return string - */ - public function getDescription() + public function getDescription(): string { return $this->description; } - /** - * Set Description. - * - * @param string $description - * - * @return BaseDrawing - */ - public function setDescription($description) + public function setDescription(string $description): self { $this->description = $description; return $this; } - /** - * Get Worksheet. - * - * @return Worksheet - */ - public function getWorksheet() + public function getWorksheet(): ?Worksheet { return $this->worksheet; } @@ -200,28 +214,25 @@ class BaseDrawing implements IComparable /** * Set Worksheet. * - * @param Worksheet $pValue - * @param bool $pOverrideOld If a Worksheet has already been assigned, overwrite it and remove image from old Worksheet? - * - * @throws PhpSpreadsheetException - * - * @return BaseDrawing + * @param bool $overrideOld If a Worksheet has already been assigned, overwrite it and remove image from old Worksheet? */ - public function setWorksheet(Worksheet $pValue = null, $pOverrideOld = false) + public function setWorksheet(?Worksheet $worksheet = null, bool $overrideOld = false): self { if ($this->worksheet === null) { // Add drawing to \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet - $this->worksheet = $pValue; - $this->worksheet->getCell($this->coordinates); - $this->worksheet->getDrawingCollection()->append($this); + if ($worksheet !== null) { + $this->worksheet = $worksheet; + $this->worksheet->getCell($this->coordinates); + $this->worksheet->getDrawingCollection()->append($this); + } } else { - if ($pOverrideOld) { + if ($overrideOld) { // Remove drawing from old \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $iterator = $this->worksheet->getDrawingCollection()->getIterator(); while ($iterator->valid()) { - if ($iterator->current()->getHashCode() == $this->getHashCode()) { - $this->worksheet->getDrawingCollection()->offsetUnset($iterator->key()); + if ($iterator->current()->getHashCode() === $this->getHashCode()) { + $this->worksheet->getDrawingCollection()->offsetUnset(/** @scrutinizer ignore-type */ $iterator->key()); $this->worksheet = null; break; @@ -229,7 +240,7 @@ class BaseDrawing implements IComparable } // Set new \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet - $this->setWorksheet($pValue); + $this->setWorksheet($worksheet); } else { throw new PhpSpreadsheetException('A Worksheet has already been assigned. Drawings can only exist on one \\PhpOffice\\PhpSpreadsheet\\Worksheet.'); } @@ -238,136 +249,112 @@ class BaseDrawing implements IComparable return $this; } - /** - * Get Coordinates. - * - * @return string - */ - public function getCoordinates() + public function getCoordinates(): string { return $this->coordinates; } - /** - * Set Coordinates. - * - * @param string $pValue eg: 'A1' - * - * @return BaseDrawing - */ - public function setCoordinates($pValue) + public function setCoordinates(string $coordinates): self { - $this->coordinates = $pValue; + $this->coordinates = $coordinates; return $this; } - /** - * Get OffsetX. - * - * @return int - */ - public function getOffsetX() + public function getOffsetX(): int { return $this->offsetX; } - /** - * Set OffsetX. - * - * @param int $pValue - * - * @return BaseDrawing - */ - public function setOffsetX($pValue) + public function setOffsetX(int $offsetX): self { - $this->offsetX = $pValue; + $this->offsetX = $offsetX; return $this; } - /** - * Get OffsetY. - * - * @return int - */ - public function getOffsetY() + public function getOffsetY(): int { return $this->offsetY; } - /** - * Set OffsetY. - * - * @param int $pValue - * - * @return BaseDrawing - */ - public function setOffsetY($pValue) + public function setOffsetY(int $offsetY): self { - $this->offsetY = $pValue; + $this->offsetY = $offsetY; return $this; } - /** - * Get Width. - * - * @return int - */ - public function getWidth() + public function getCoordinates2(): string + { + return $this->coordinates2; + } + + public function setCoordinates2(string $coordinates2): self + { + $this->coordinates2 = $coordinates2; + + return $this; + } + + public function getOffsetX2(): int + { + return $this->offsetX2; + } + + public function setOffsetX2(int $offsetX2): self + { + $this->offsetX2 = $offsetX2; + + return $this; + } + + public function getOffsetY2(): int + { + return $this->offsetY2; + } + + public function setOffsetY2(int $offsetY2): self + { + $this->offsetY2 = $offsetY2; + + return $this; + } + + public function getWidth(): int { return $this->width; } - /** - * Set Width. - * - * @param int $pValue - * - * @return BaseDrawing - */ - public function setWidth($pValue) + public function setWidth(int $width): self { // Resize proportional? - if ($this->resizeProportional && $pValue != 0) { + if ($this->resizeProportional && $width != 0) { $ratio = $this->height / ($this->width != 0 ? $this->width : 1); - $this->height = round($ratio * $pValue); + $this->height = (int) round($ratio * $width); } // Set width - $this->width = $pValue; + $this->width = $width; return $this; } - /** - * Get Height. - * - * @return int - */ - public function getHeight() + public function getHeight(): int { return $this->height; } - /** - * Set Height. - * - * @param int $pValue - * - * @return BaseDrawing - */ - public function setHeight($pValue) + public function setHeight(int $height): self { // Resize proportional? - if ($this->resizeProportional && $pValue != 0) { + if ($this->resizeProportional && $height != 0) { $ratio = $this->width / ($this->height != 0 ? $this->height : 1); - $this->width = round($ratio * $pValue); + $this->width = (int) round($ratio * $height); } // Set height - $this->height = $pValue; + $this->height = $height; return $this; } @@ -382,22 +369,17 @@ class BaseDrawing implements IComparable * * * @author Vincent@luo MSN:kele_100@hotmail.com - * - * @param int $width - * @param int $height - * - * @return BaseDrawing */ - public function setWidthAndHeight($width, $height) + public function setWidthAndHeight(int $width, int $height): self { $xratio = $width / ($this->width != 0 ? $this->width : 1); $yratio = $height / ($this->height != 0 ? $this->height : 1); if ($this->resizeProportional && !($width == 0 || $height == 0)) { if (($xratio * $this->height) < $height) { - $this->height = ceil($xratio * $this->height); + $this->height = (int) ceil($xratio * $this->height); $this->width = $width; } else { - $this->width = ceil($yratio * $this->width); + $this->width = (int) ceil($yratio * $this->width); $this->height = $height; } } else { @@ -408,74 +390,38 @@ class BaseDrawing implements IComparable return $this; } - /** - * Get ResizeProportional. - * - * @return bool - */ - public function getResizeProportional() + public function getResizeProportional(): bool { return $this->resizeProportional; } - /** - * Set ResizeProportional. - * - * @param bool $pValue - * - * @return BaseDrawing - */ - public function setResizeProportional($pValue) + public function setResizeProportional(bool $resizeProportional): self { - $this->resizeProportional = $pValue; + $this->resizeProportional = $resizeProportional; return $this; } - /** - * Get Rotation. - * - * @return int - */ - public function getRotation() + public function getRotation(): int { return $this->rotation; } - /** - * Set Rotation. - * - * @param int $pValue - * - * @return BaseDrawing - */ - public function setRotation($pValue) + public function setRotation(int $rotation): self { - $this->rotation = $pValue; + $this->rotation = $rotation; return $this; } - /** - * Get Shadow. - * - * @return Drawing\Shadow - */ - public function getShadow() + public function getShadow(): Drawing\Shadow { return $this->shadow; } - /** - * Set Shadow. - * - * @param Drawing\Shadow $pValue - * - * @return BaseDrawing - */ - public function setShadow(Drawing\Shadow $pValue = null) + public function setShadow(?Drawing\Shadow $shadow = null): self { - $this->shadow = $pValue; + $this->shadow = $shadow ?? new Drawing\Shadow(); return $this; } @@ -490,10 +436,13 @@ class BaseDrawing implements IComparable return md5( $this->name . $this->description . - $this->worksheet->getHashCode() . + (($this->worksheet === null) ? '' : $this->worksheet->getHashCode()) . $this->coordinates . $this->offsetX . $this->offsetY . + $this->coordinates2 . + $this->offsetX2 . + $this->offsetY2 . $this->width . $this->height . $this->rotation . @@ -519,19 +468,68 @@ class BaseDrawing implements IComparable } } - /** - * @param null|Hyperlink $pHyperlink - */ - public function setHyperlink(Hyperlink $pHyperlink = null) + public function setHyperlink(?Hyperlink $hyperlink = null): void { - $this->hyperlink = $pHyperlink; + $this->hyperlink = $hyperlink; } - /** - * @return null|Hyperlink - */ - public function getHyperlink() + public function getHyperlink(): ?Hyperlink { return $this->hyperlink; } + + /** + * Set Fact Sizes and Type of Image. + */ + protected function setSizesAndType(string $path): void + { + if ($this->imageWidth === 0 && $this->imageHeight === 0 && $this->type === IMAGETYPE_UNKNOWN) { + $imageData = getimagesize($path); + + if (!empty($imageData)) { + $this->imageWidth = $imageData[0]; + $this->imageHeight = $imageData[1]; + $this->type = $imageData[2]; + } + } + if ($this->width === 0 && $this->height === 0) { + $this->width = $this->imageWidth; + $this->height = $this->imageHeight; + } + } + + /** + * Get Image Type. + */ + public function getType(): int + { + return $this->type; + } + + public function getImageWidth(): int + { + return $this->imageWidth; + } + + public function getImageHeight(): int + { + return $this->imageHeight; + } + + public function getEditAs(): string + { + return $this->editAs; + } + + public function setEditAs(string $editAs): self + { + $this->editAs = $editAs; + + return $this; + } + + public function validEditAs(): bool + { + return in_array($this->editAs, self::VALID_EDIT_AS, true); + } } diff --git a/PhpOffice/PhpSpreadsheet/Worksheet/CellIterator.php b/PhpOffice/PhpSpreadsheet/Worksheet/CellIterator.php old mode 100755 new mode 100644 index d97e33f..b0d9d05 --- a/PhpOffice/PhpSpreadsheet/Worksheet/CellIterator.php +++ b/PhpOffice/PhpSpreadsheet/Worksheet/CellIterator.php @@ -2,10 +2,21 @@ namespace PhpOffice\PhpSpreadsheet\Worksheet; -use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; +use Iterator as NativeIterator; +use PhpOffice\PhpSpreadsheet\Cell\Cell; +use PhpOffice\PhpSpreadsheet\Collection\Cells; -abstract class CellIterator implements \Iterator +/** + * @template TKey + * + * @implements NativeIterator + */ +abstract class CellIterator implements NativeIterator { + public const TREAT_NULL_VALUE_AS_EMPTY_CELL = 1; + + public const TREAT_EMPTY_STRING_AS_EMPTY_CELL = 2; + /** * Worksheet to iterate. * @@ -13,6 +24,13 @@ abstract class CellIterator implements \Iterator */ protected $worksheet; + /** + * Cell Collection to iterate. + * + * @var Cells + */ + protected $cellCollection; + /** * Iterate only existing cells. * @@ -25,34 +43,27 @@ abstract class CellIterator implements \Iterator */ public function __destruct() { - unset($this->worksheet); + // @phpstan-ignore-next-line + $this->worksheet = $this->cellCollection = null; } /** * Get loop only existing cells. - * - * @return bool */ - public function getIterateOnlyExistingCells() + public function getIterateOnlyExistingCells(): bool { return $this->onlyExistingCells; } /** * Validate start/end values for "IterateOnlyExistingCells" mode, and adjust if necessary. - * - * @throws PhpSpreadsheetException */ abstract protected function adjustForExistingOnlyRange(); /** * Set the iterator to loop only existing cells. - * - * @param bool $value - * - * @throws PhpSpreadsheetException */ - public function setIterateOnlyExistingCells($value) + public function setIterateOnlyExistingCells(bool $value): void { $this->onlyExistingCells = (bool) $value; diff --git a/PhpOffice/PhpSpreadsheet/Worksheet/Column.php b/PhpOffice/PhpSpreadsheet/Worksheet/Column.php old mode 100755 new mode 100644 index 4baaae1..390a159 --- a/PhpOffice/PhpSpreadsheet/Worksheet/Column.php +++ b/PhpOffice/PhpSpreadsheet/Worksheet/Column.php @@ -9,7 +9,7 @@ class Column * * @var Worksheet */ - private $parent; + private $worksheet; /** * Column index. @@ -21,13 +21,12 @@ class Column /** * Create a new column. * - * @param Worksheet $parent * @param string $columnIndex */ - public function __construct(Worksheet $parent = null, $columnIndex = 'A') + public function __construct(Worksheet $worksheet, $columnIndex = 'A') { // Set parent and column index - $this->parent = $parent; + $this->worksheet = $worksheet; $this->columnIndex = $columnIndex; } @@ -36,15 +35,14 @@ class Column */ public function __destruct() { - unset($this->parent); + // @phpstan-ignore-next-line + $this->worksheet = null; } /** - * Get column index. - * - * @return string + * Get column index as string eg: 'A'. */ - public function getColumnIndex() + public function getColumnIndex(): string { return $this->columnIndex; } @@ -59,6 +57,54 @@ class Column */ public function getCellIterator($startRow = 1, $endRow = null) { - return new ColumnCellIterator($this->parent, $this->columnIndex, $startRow, $endRow); + return new ColumnCellIterator($this->worksheet, $this->columnIndex, $startRow, $endRow); + } + + /** + * Returns a boolean true if the column contains no cells. By default, this means that no cell records exist in the + * collection for this column. false will be returned otherwise. + * This rule can be modified by passing a $definitionOfEmptyFlags value: + * 1 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL If the only cells in the collection are null value + * cells, then the column will be considered empty. + * 2 - CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL If the only cells in the collection are empty + * string value cells, then the column will be considered empty. + * 3 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL | CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL + * If the only cells in the collection are null value or empty string value cells, then the column + * will be considered empty. + * + * @param int $definitionOfEmptyFlags + * Possible Flag Values are: + * CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL + * CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL + */ + public function isEmpty(int $definitionOfEmptyFlags = 0): bool + { + $nullValueCellIsEmpty = (bool) ($definitionOfEmptyFlags & CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL); + $emptyStringCellIsEmpty = (bool) ($definitionOfEmptyFlags & CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL); + + $cellIterator = $this->getCellIterator(); + $cellIterator->setIterateOnlyExistingCells(true); + foreach ($cellIterator as $cell) { + /** @scrutinizer ignore-call */ + $value = $cell->getValue(); + if ($value === null && $nullValueCellIsEmpty === true) { + continue; + } + if ($value === '' && $emptyStringCellIsEmpty === true) { + continue; + } + + return false; + } + + return true; + } + + /** + * Returns bound worksheet. + */ + public function getWorksheet(): Worksheet + { + return $this->worksheet; } } diff --git a/PhpOffice/PhpSpreadsheet/Worksheet/ColumnCellIterator.php b/PhpOffice/PhpSpreadsheet/Worksheet/ColumnCellIterator.php old mode 100755 new mode 100644 index 7e8f040..a518f59 --- a/PhpOffice/PhpSpreadsheet/Worksheet/ColumnCellIterator.php +++ b/PhpOffice/PhpSpreadsheet/Worksheet/ColumnCellIterator.php @@ -2,9 +2,13 @@ namespace PhpOffice\PhpSpreadsheet\Worksheet; +use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; +/** + * @extends CellIterator + */ class ColumnCellIterator extends CellIterator { /** @@ -17,7 +21,7 @@ class ColumnCellIterator extends CellIterator /** * Column index. * - * @var string + * @var int */ private $columnIndex; @@ -38,15 +42,16 @@ class ColumnCellIterator extends CellIterator /** * Create a new row iterator. * - * @param Worksheet $subject The worksheet to iterate over + * @param Worksheet $worksheet The worksheet to iterate over * @param string $columnIndex The column that we want to iterate * @param int $startRow The row number at which to start iterating * @param int $endRow Optionally, the row number at which to stop iterating */ - public function __construct(Worksheet $subject = null, $columnIndex = 'A', $startRow = 1, $endRow = null) + public function __construct(Worksheet $worksheet, $columnIndex = 'A', $startRow = 1, $endRow = null) { // Set subject - $this->worksheet = $subject; + $this->worksheet = $worksheet; + $this->cellCollection = $worksheet->getCellCollection(); $this->columnIndex = Coordinate::columnIndexFromString($columnIndex); $this->resetEnd($endRow); $this->resetStart($startRow); @@ -57,11 +62,9 @@ class ColumnCellIterator extends CellIterator * * @param int $startRow The row number at which to start iterating * - * @throws PhpSpreadsheetException - * - * @return ColumnCellIterator + * @return $this */ - public function resetStart($startRow = 1) + public function resetStart(int $startRow = 1) { $this->startRow = $startRow; $this->adjustForExistingOnlyRange(); @@ -75,13 +78,11 @@ class ColumnCellIterator extends CellIterator * * @param int $endRow The row number at which to stop iterating * - * @throws PhpSpreadsheetException - * - * @return ColumnCellIterator + * @return $this */ public function resetEnd($endRow = null) { - $this->endRow = ($endRow) ? $endRow : $this->worksheet->getHighestRow(); + $this->endRow = $endRow ?: $this->worksheet->getHighestRow(); $this->adjustForExistingOnlyRange(); return $this; @@ -92,16 +93,18 @@ class ColumnCellIterator extends CellIterator * * @param int $row The row number to set the current pointer at * - * @throws PhpSpreadsheetException - * - * @return ColumnCellIterator + * @return $this */ - public function seek($row = 1) + public function seek(int $row = 1) { + if ( + $this->onlyExistingCells && + (!$this->cellCollection->has(Coordinate::stringFromColumnIndex($this->columnIndex) . $row)) + ) { + throw new PhpSpreadsheetException('In "IterateOnlyExistingCells" mode and Cell does not exist'); + } if (($row < $this->startRow) || ($row > $this->endRow)) { throw new PhpSpreadsheetException("Row $row is out of range ({$this->startRow} - {$this->endRow})"); - } elseif ($this->onlyExistingCells && !($this->worksheet->cellExistsByColumnAndRow($this->columnIndex, $row))) { - throw new PhpSpreadsheetException('In "IterateOnlyExistingCells" mode and Cell does not exist'); } $this->currentRow = $row; @@ -111,27 +114,27 @@ class ColumnCellIterator extends CellIterator /** * Rewind the iterator to the starting row. */ - public function rewind() + public function rewind(): void { $this->currentRow = $this->startRow; } /** * Return the current cell in this worksheet column. - * - * @return null|\PhpOffice\PhpSpreadsheet\Cell\Cell */ - public function current() + public function current(): ?Cell { - return $this->worksheet->getCellByColumnAndRow($this->columnIndex, $this->currentRow); + $cellAddress = Coordinate::stringFromColumnIndex($this->columnIndex) . $this->currentRow; + + return $this->cellCollection->has($cellAddress) + ? $this->cellCollection->get($cellAddress) + : $this->worksheet->createNewCell($cellAddress); } /** * Return the current iterator key. - * - * @return int */ - public function key() + public function key(): int { return $this->currentRow; } @@ -139,59 +142,60 @@ class ColumnCellIterator extends CellIterator /** * Set the iterator to its next value. */ - public function next() + public function next(): void { + $columnAddress = Coordinate::stringFromColumnIndex($this->columnIndex); do { ++$this->currentRow; - } while (($this->onlyExistingCells) && - (!$this->worksheet->cellExistsByColumnAndRow($this->columnIndex, $this->currentRow)) && - ($this->currentRow <= $this->endRow)); + } while ( + ($this->onlyExistingCells) && + ($this->currentRow <= $this->endRow) && + (!$this->cellCollection->has($columnAddress . $this->currentRow)) + ); } /** * Set the iterator to its previous value. */ - public function prev() + public function prev(): void { + $columnAddress = Coordinate::stringFromColumnIndex($this->columnIndex); do { --$this->currentRow; - } while (($this->onlyExistingCells) && - (!$this->worksheet->cellExistsByColumnAndRow($this->columnIndex, $this->currentRow)) && - ($this->currentRow >= $this->startRow)); + } while ( + ($this->onlyExistingCells) && + ($this->currentRow >= $this->startRow) && + (!$this->cellCollection->has($columnAddress . $this->currentRow)) + ); } /** * Indicate if more rows exist in the worksheet range of rows that we're iterating. - * - * @return bool */ - public function valid() + public function valid(): bool { return $this->currentRow <= $this->endRow && $this->currentRow >= $this->startRow; } /** * Validate start/end values for "IterateOnlyExistingCells" mode, and adjust if necessary. - * - * @throws PhpSpreadsheetException */ - protected function adjustForExistingOnlyRange() + protected function adjustForExistingOnlyRange(): void { if ($this->onlyExistingCells) { - while ((!$this->worksheet->cellExistsByColumnAndRow($this->columnIndex, $this->startRow)) && - ($this->startRow <= $this->endRow)) { + $columnAddress = Coordinate::stringFromColumnIndex($this->columnIndex); + while ( + (!$this->cellCollection->has($columnAddress . $this->startRow)) && + ($this->startRow <= $this->endRow) + ) { ++$this->startRow; } - if ($this->startRow > $this->endRow) { - throw new PhpSpreadsheetException('No cells exist within the specified range'); - } - while ((!$this->worksheet->cellExistsByColumnAndRow($this->columnIndex, $this->endRow)) && - ($this->endRow >= $this->startRow)) { + while ( + (!$this->cellCollection->has($columnAddress . $this->endRow)) && + ($this->endRow >= $this->startRow) + ) { --$this->endRow; } - if ($this->endRow < $this->startRow) { - throw new PhpSpreadsheetException('No cells exist within the specified range'); - } } } } diff --git a/PhpOffice/PhpSpreadsheet/Worksheet/ColumnDimension.php b/PhpOffice/PhpSpreadsheet/Worksheet/ColumnDimension.php old mode 100755 new mode 100644 index e2ea8af..b64ecec --- a/PhpOffice/PhpSpreadsheet/Worksheet/ColumnDimension.php +++ b/PhpOffice/PhpSpreadsheet/Worksheet/ColumnDimension.php @@ -2,6 +2,9 @@ namespace PhpOffice\PhpSpreadsheet\Worksheet; +use PhpOffice\PhpSpreadsheet\Cell\Coordinate; +use PhpOffice\PhpSpreadsheet\Helper\Dimension as CssDimension; + class ColumnDimension extends Dimension { /** @@ -30,37 +33,49 @@ class ColumnDimension extends Dimension /** * Create a new ColumnDimension. * - * @param string $pIndex Character column index + * @param string $index Character column index */ - public function __construct($pIndex = 'A') + public function __construct($index = 'A') { // Initialise values - $this->columnIndex = $pIndex; + $this->columnIndex = $index; // set dimension as unformatted by default parent::__construct(0); } /** - * Get ColumnIndex. - * - * @return string + * Get column index as string eg: 'A'. */ - public function getColumnIndex() + public function getColumnIndex(): string { return $this->columnIndex; } /** - * Set ColumnIndex. - * - * @param string $pValue - * - * @return ColumnDimension + * Set column index as string eg: 'A'. */ - public function setColumnIndex($pValue) + public function setColumnIndex(string $index): self { - $this->columnIndex = $pValue; + $this->columnIndex = $index; + + return $this; + } + + /** + * Get column index as numeric. + */ + public function getColumnNumeric(): int + { + return Coordinate::columnIndexFromString($this->columnIndex); + } + + /** + * Set column index as numeric. + */ + public function setColumnNumeric(int $index): self + { + $this->columnIndex = Coordinate::stringFromColumnIndex($index); return $this; } @@ -68,33 +83,42 @@ class ColumnDimension extends Dimension /** * Get Width. * - * @return float + * Each unit of column width is equal to the width of one character in the default font size. A value of -1 + * tells Excel to display this column in its default width. + * By default, this will be the return value; but this method also accepts an optional unit of measure argument + * and will convert the returned value to the specified UoM.. */ - public function getWidth() + public function getWidth(?string $unitOfMeasure = null): float { - return $this->width; + return ($unitOfMeasure === null || $this->width < 0) + ? $this->width + : (new CssDimension((string) $this->width))->toUnit($unitOfMeasure); } /** * Set Width. * - * @param float $pValue + * Each unit of column width is equal to the width of one character in the default font size. A value of -1 + * tells Excel to display this column in its default width. + * By default, this will be the unit of measure for the passed value; but this method also accepts an + * optional unit of measure argument, and will convert the value from the specified UoM using an + * approximation method. * - * @return ColumnDimension + * @return $this */ - public function setWidth($pValue) + public function setWidth(float $width, ?string $unitOfMeasure = null) { - $this->width = $pValue; + $this->width = ($unitOfMeasure === null || $width < 0) + ? $width + : (new CssDimension("{$width}{$unitOfMeasure}"))->width(); return $this; } /** * Get Auto Size. - * - * @return bool */ - public function getAutoSize() + public function getAutoSize(): bool { return $this->autoSize; } @@ -102,13 +126,11 @@ class ColumnDimension extends Dimension /** * Set Auto Size. * - * @param bool $pValue - * - * @return ColumnDimension + * @return $this */ - public function setAutoSize($pValue) + public function setAutoSize(bool $autosizeEnabled) { - $this->autoSize = $pValue; + $this->autoSize = $autosizeEnabled; return $this; } diff --git a/PhpOffice/PhpSpreadsheet/Worksheet/ColumnIterator.php b/PhpOffice/PhpSpreadsheet/Worksheet/ColumnIterator.php old mode 100755 new mode 100644 index d2b57aa..fffef12 --- a/PhpOffice/PhpSpreadsheet/Worksheet/ColumnIterator.php +++ b/PhpOffice/PhpSpreadsheet/Worksheet/ColumnIterator.php @@ -2,11 +2,15 @@ namespace PhpOffice\PhpSpreadsheet\Worksheet; +use Iterator as NativeIterator; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Exception; use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; -class ColumnIterator implements \Iterator +/** + * @implements NativeIterator + */ +class ColumnIterator implements NativeIterator { /** * Worksheet to iterate. @@ -56,7 +60,8 @@ class ColumnIterator implements \Iterator */ public function __destruct() { - unset($this->worksheet); + // @phpstan-ignore-next-line + $this->worksheet = null; } /** @@ -64,15 +69,15 @@ class ColumnIterator implements \Iterator * * @param string $startColumn The column address at which to start iterating * - * @throws Exception - * - * @return ColumnIterator + * @return $this */ - public function resetStart($startColumn = 'A') + public function resetStart(string $startColumn = 'A') { $startColumnIndex = Coordinate::columnIndexFromString($startColumn); if ($startColumnIndex > Coordinate::columnIndexFromString($this->worksheet->getHighestColumn())) { - throw new Exception("Start column ({$startColumn}) is beyond highest column ({$this->worksheet->getHighestColumn()})"); + throw new Exception( + "Start column ({$startColumn}) is beyond highest column ({$this->worksheet->getHighestColumn()})" + ); } $this->startColumnIndex = $startColumnIndex; @@ -89,11 +94,11 @@ class ColumnIterator implements \Iterator * * @param string $endColumn The column address at which to stop iterating * - * @return ColumnIterator + * @return $this */ public function resetEnd($endColumn = null) { - $endColumn = $endColumn ? $endColumn : $this->worksheet->getHighestColumn(); + $endColumn = $endColumn ?: $this->worksheet->getHighestColumn(); $this->endColumnIndex = Coordinate::columnIndexFromString($endColumn); return $this; @@ -104,15 +109,15 @@ class ColumnIterator implements \Iterator * * @param string $column The column address to set the current pointer at * - * @throws PhpSpreadsheetException - * - * @return ColumnIterator + * @return $this */ - public function seek($column = 'A') + public function seek(string $column = 'A') { $column = Coordinate::columnIndexFromString($column); if (($column < $this->startColumnIndex) || ($column > $this->endColumnIndex)) { - throw new PhpSpreadsheetException("Column $column is out of range ({$this->startColumnIndex} - {$this->endColumnIndex})"); + throw new PhpSpreadsheetException( + "Column $column is out of range ({$this->startColumnIndex} - {$this->endColumnIndex})" + ); } $this->currentColumnIndex = $column; @@ -122,27 +127,23 @@ class ColumnIterator implements \Iterator /** * Rewind the iterator to the starting column. */ - public function rewind() + public function rewind(): void { $this->currentColumnIndex = $this->startColumnIndex; } /** * Return the current column in this worksheet. - * - * @return Column */ - public function current() + public function current(): Column { return new Column($this->worksheet, Coordinate::stringFromColumnIndex($this->currentColumnIndex)); } /** * Return the current iterator key. - * - * @return string */ - public function key() + public function key(): string { return Coordinate::stringFromColumnIndex($this->currentColumnIndex); } @@ -150,7 +151,7 @@ class ColumnIterator implements \Iterator /** * Set the iterator to its next value. */ - public function next() + public function next(): void { ++$this->currentColumnIndex; } @@ -158,17 +159,15 @@ class ColumnIterator implements \Iterator /** * Set the iterator to its previous value. */ - public function prev() + public function prev(): void { --$this->currentColumnIndex; } /** * Indicate if more columns exist in the worksheet range of columns that we're iterating. - * - * @return bool */ - public function valid() + public function valid(): bool { return $this->currentColumnIndex <= $this->endColumnIndex && $this->currentColumnIndex >= $this->startColumnIndex; } diff --git a/PhpOffice/PhpSpreadsheet/Worksheet/Dimension.php b/PhpOffice/PhpSpreadsheet/Worksheet/Dimension.php old mode 100755 new mode 100644 index 697fc97..ba03b5b --- a/PhpOffice/PhpSpreadsheet/Worksheet/Dimension.php +++ b/PhpOffice/PhpSpreadsheet/Worksheet/Dimension.php @@ -47,10 +47,8 @@ abstract class Dimension /** * Get Visible. - * - * @return bool */ - public function getVisible() + public function getVisible(): bool { return $this->visible; } @@ -58,23 +56,19 @@ abstract class Dimension /** * Set Visible. * - * @param bool $pValue - * - * @return Dimension + * @return $this */ - public function setVisible($pValue) + public function setVisible(bool $visible) { - $this->visible = (bool) $pValue; + $this->visible = $visible; return $this; } /** * Get Outline Level. - * - * @return int */ - public function getOutlineLevel() + public function getOutlineLevel(): int { return $this->outlineLevel; } @@ -83,29 +77,23 @@ abstract class Dimension * Set Outline Level. * Value must be between 0 and 7. * - * @param int $pValue - * - * @throws PhpSpreadsheetException - * - * @return Dimension + * @return $this */ - public function setOutlineLevel($pValue) + public function setOutlineLevel(int $level) { - if ($pValue < 0 || $pValue > 7) { + if ($level < 0 || $level > 7) { throw new PhpSpreadsheetException('Outline level must range between 0 and 7.'); } - $this->outlineLevel = $pValue; + $this->outlineLevel = $level; return $this; } /** * Get Collapsed. - * - * @return bool */ - public function getCollapsed() + public function getCollapsed(): bool { return $this->collapsed; } @@ -113,13 +101,11 @@ abstract class Dimension /** * Set Collapsed. * - * @param bool $pValue - * - * @return Dimension + * @return $this */ - public function setCollapsed($pValue) + public function setCollapsed(bool $collapsed) { - $this->collapsed = (bool) $pValue; + $this->collapsed = $collapsed; return $this; } @@ -129,7 +115,7 @@ abstract class Dimension * * @return int */ - public function getXfIndex() + public function getXfIndex(): ?int { return $this->xfIndex; } @@ -137,29 +123,12 @@ abstract class Dimension /** * Set index to cellXf. * - * @param int $pValue - * - * @return Dimension + * @return $this */ - public function setXfIndex($pValue) + public function setXfIndex(int $XfIndex) { - $this->xfIndex = $pValue; + $this->xfIndex = $XfIndex; return $this; } - - /** - * Implement PHP __clone to create a deep clone, not just a shallow copy. - */ - public function __clone() - { - $vars = get_object_vars($this); - foreach ($vars as $key => $value) { - if (is_object($value)) { - $this->$key = clone $value; - } else { - $this->$key = $value; - } - } - } } diff --git a/PhpOffice/PhpSpreadsheet/Worksheet/Drawing.php b/PhpOffice/PhpSpreadsheet/Worksheet/Drawing.php old mode 100755 new mode 100644 index 8194da3..7d95753 --- a/PhpOffice/PhpSpreadsheet/Worksheet/Drawing.php +++ b/PhpOffice/PhpSpreadsheet/Worksheet/Drawing.php @@ -3,9 +3,17 @@ namespace PhpOffice\PhpSpreadsheet\Worksheet; use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; +use ZipArchive; class Drawing extends BaseDrawing { + const IMAGE_TYPES_CONVERTION_MAP = [ + IMAGETYPE_GIF => IMAGETYPE_PNG, + IMAGETYPE_JPEG => IMAGETYPE_JPEG, + IMAGETYPE_PNG => IMAGETYPE_PNG, + IMAGETYPE_BMP => IMAGETYPE_PNG, + ]; + /** * Path. * @@ -13,6 +21,13 @@ class Drawing extends BaseDrawing */ private $path; + /** + * Whether or not we are dealing with a URL. + * + * @var bool + */ + private $isUrl; + /** * Create a new Drawing. */ @@ -20,6 +35,7 @@ class Drawing extends BaseDrawing { // Initialise values $this->path = ''; + $this->isUrl = false; // Initialize parent parent::__construct(); @@ -37,15 +53,10 @@ class Drawing extends BaseDrawing /** * Get indexed filename (using image index). - * - * @return string */ - public function getIndexedFilename() + public function getIndexedFilename(): string { - $fileName = $this->getFilename(); - $fileName = str_replace(' ', '_', $fileName); - - return str_replace('.' . $this->getExtension(), '', $fileName) . $this->getImageIndex() . '.' . $this->getExtension(); + return md5($this->path) . '.' . $this->getExtension(); } /** @@ -60,6 +71,20 @@ class Drawing extends BaseDrawing return $exploded[count($exploded) - 1]; } + /** + * Get full filepath to store drawing in zip archive. + * + * @return string + */ + public function getMediaFilename() + { + if (!array_key_exists($this->type, self::IMAGE_TYPES_CONVERTION_MAP)) { + throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.'); + } + + return sprintf('image%d%s', $this->getImageIndex(), $this->getImageFileExtensionForSave()); + } + /** * Get Path. * @@ -73,33 +98,68 @@ class Drawing extends BaseDrawing /** * Set Path. * - * @param string $pValue File path - * @param bool $pVerifyFile Verify file + * @param string $path File path + * @param bool $verifyFile Verify file + * @param ZipArchive $zip Zip archive instance * - * @throws PhpSpreadsheetException - * - * @return Drawing + * @return $this */ - public function setPath($pValue, $pVerifyFile = true) + public function setPath($path, $verifyFile = true, $zip = null) { - if ($pVerifyFile) { - if (file_exists($pValue)) { - $this->path = $pValue; - - if ($this->width == 0 && $this->height == 0) { - // Get width/height - [$this->width, $this->height] = getimagesize($pValue); + if ($verifyFile && preg_match('~^data:image/[a-z]+;base64,~', $path) !== 1) { + // Check if a URL has been passed. https://stackoverflow.com/a/2058596/1252979 + if (filter_var($path, FILTER_VALIDATE_URL)) { + $this->path = $path; + // Implicit that it is a URL, rather store info than running check above on value in other places. + $this->isUrl = true; + $imageContents = file_get_contents($path); + $filePath = tempnam(sys_get_temp_dir(), 'Drawing'); + if ($filePath) { + file_put_contents($filePath, $imageContents); + if (file_exists($filePath)) { + $this->setSizesAndType($filePath); + unlink($filePath); + } + } + } elseif (file_exists($path)) { + $this->path = $path; + $this->setSizesAndType($path); + } elseif ($zip instanceof ZipArchive) { + $zipPath = explode('#', $path)[1]; + if ($zip->locateName($zipPath) !== false) { + $this->path = $path; + $this->setSizesAndType($path); } } else { - throw new PhpSpreadsheetException("File $pValue not found!"); + throw new PhpSpreadsheetException("File $path not found!"); } } else { - $this->path = $pValue; + $this->path = $path; } return $this; } + /** + * Get isURL. + */ + public function getIsURL(): bool + { + return $this->isUrl; + } + + /** + * Set isURL. + * + * @return $this + */ + public function setIsURL(bool $isUrl): self + { + $this->isUrl = $isUrl; + + return $this; + } + /** * Get hash code. * @@ -113,4 +173,42 @@ class Drawing extends BaseDrawing __CLASS__ ); } + + /** + * Get Image Type for Save. + */ + public function getImageTypeForSave(): int + { + if (!array_key_exists($this->type, self::IMAGE_TYPES_CONVERTION_MAP)) { + throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.'); + } + + return self::IMAGE_TYPES_CONVERTION_MAP[$this->type]; + } + + /** + * Get Image file extention for Save. + */ + public function getImageFileExtensionForSave(bool $includeDot = true): string + { + if (!array_key_exists($this->type, self::IMAGE_TYPES_CONVERTION_MAP)) { + throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.'); + } + + $result = image_type_to_extension(self::IMAGE_TYPES_CONVERTION_MAP[$this->type], $includeDot); + + return "$result"; + } + + /** + * Get Image mime type. + */ + public function getImageMimeType(): string + { + if (!array_key_exists($this->type, self::IMAGE_TYPES_CONVERTION_MAP)) { + throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.'); + } + + return image_type_to_mime_type(self::IMAGE_TYPES_CONVERTION_MAP[$this->type]); + } } diff --git a/PhpOffice/PhpSpreadsheet/Worksheet/Drawing/Shadow.php b/PhpOffice/PhpSpreadsheet/Worksheet/Drawing/Shadow.php old mode 100755 new mode 100644 index a1e05d6..a461a51 --- a/PhpOffice/PhpSpreadsheet/Worksheet/Drawing/Shadow.php +++ b/PhpOffice/PhpSpreadsheet/Worksheet/Drawing/Shadow.php @@ -52,7 +52,7 @@ class Shadow implements IComparable /** * Shadow alignment. * - * @var int + * @var string */ private $alignment; @@ -98,13 +98,13 @@ class Shadow implements IComparable /** * Set Visible. * - * @param bool $pValue + * @param bool $visible * - * @return Shadow + * @return $this */ - public function setVisible($pValue) + public function setVisible($visible) { - $this->visible = $pValue; + $this->visible = $visible; return $this; } @@ -122,13 +122,13 @@ class Shadow implements IComparable /** * Set Blur radius. * - * @param int $pValue + * @param int $blurRadius * - * @return Shadow + * @return $this */ - public function setBlurRadius($pValue) + public function setBlurRadius($blurRadius) { - $this->blurRadius = $pValue; + $this->blurRadius = $blurRadius; return $this; } @@ -146,13 +146,13 @@ class Shadow implements IComparable /** * Set Shadow distance. * - * @param int $pValue + * @param int $distance * - * @return Shadow + * @return $this */ - public function setDistance($pValue) + public function setDistance($distance) { - $this->distance = $pValue; + $this->distance = $distance; return $this; } @@ -170,13 +170,13 @@ class Shadow implements IComparable /** * Set Shadow direction (in degrees). * - * @param int $pValue + * @param int $direction * - * @return Shadow + * @return $this */ - public function setDirection($pValue) + public function setDirection($direction) { - $this->direction = $pValue; + $this->direction = $direction; return $this; } @@ -184,7 +184,7 @@ class Shadow implements IComparable /** * Get Shadow alignment. * - * @return int + * @return string */ public function getAlignment() { @@ -194,13 +194,13 @@ class Shadow implements IComparable /** * Set Shadow alignment. * - * @param int $pValue + * @param string $alignment * - * @return Shadow + * @return $this */ - public function setAlignment($pValue) + public function setAlignment($alignment) { - $this->alignment = $pValue; + $this->alignment = $alignment; return $this; } @@ -218,13 +218,11 @@ class Shadow implements IComparable /** * Set Color. * - * @param Color $pValue - * - * @return Shadow + * @return $this */ - public function setColor(Color $pValue = null) + public function setColor(?Color $color = null) { - $this->color = $pValue; + $this->color = $color; return $this; } @@ -242,13 +240,13 @@ class Shadow implements IComparable /** * Set Alpha. * - * @param int $pValue + * @param int $alpha * - * @return Shadow + * @return $this */ - public function setAlpha($pValue) + public function setAlpha($alpha) { - $this->alpha = $pValue; + $this->alpha = $alpha; return $this; } diff --git a/PhpOffice/PhpSpreadsheet/Worksheet/HeaderFooter.php b/PhpOffice/PhpSpreadsheet/Worksheet/HeaderFooter.php old mode 100755 new mode 100644 index a78f4fc..c3504d8 --- a/PhpOffice/PhpSpreadsheet/Worksheet/HeaderFooter.php +++ b/PhpOffice/PhpSpreadsheet/Worksheet/HeaderFooter.php @@ -170,13 +170,13 @@ class HeaderFooter /** * Set OddHeader. * - * @param string $pValue + * @param string $oddHeader * - * @return HeaderFooter + * @return $this */ - public function setOddHeader($pValue) + public function setOddHeader($oddHeader) { - $this->oddHeader = $pValue; + $this->oddHeader = $oddHeader; return $this; } @@ -194,13 +194,13 @@ class HeaderFooter /** * Set OddFooter. * - * @param string $pValue + * @param string $oddFooter * - * @return HeaderFooter + * @return $this */ - public function setOddFooter($pValue) + public function setOddFooter($oddFooter) { - $this->oddFooter = $pValue; + $this->oddFooter = $oddFooter; return $this; } @@ -218,13 +218,13 @@ class HeaderFooter /** * Set EvenHeader. * - * @param string $pValue + * @param string $eventHeader * - * @return HeaderFooter + * @return $this */ - public function setEvenHeader($pValue) + public function setEvenHeader($eventHeader) { - $this->evenHeader = $pValue; + $this->evenHeader = $eventHeader; return $this; } @@ -242,13 +242,13 @@ class HeaderFooter /** * Set EvenFooter. * - * @param string $pValue + * @param string $evenFooter * - * @return HeaderFooter + * @return $this */ - public function setEvenFooter($pValue) + public function setEvenFooter($evenFooter) { - $this->evenFooter = $pValue; + $this->evenFooter = $evenFooter; return $this; } @@ -266,13 +266,13 @@ class HeaderFooter /** * Set FirstHeader. * - * @param string $pValue + * @param string $firstHeader * - * @return HeaderFooter + * @return $this */ - public function setFirstHeader($pValue) + public function setFirstHeader($firstHeader) { - $this->firstHeader = $pValue; + $this->firstHeader = $firstHeader; return $this; } @@ -290,13 +290,13 @@ class HeaderFooter /** * Set FirstFooter. * - * @param string $pValue + * @param string $firstFooter * - * @return HeaderFooter + * @return $this */ - public function setFirstFooter($pValue) + public function setFirstFooter($firstFooter) { - $this->firstFooter = $pValue; + $this->firstFooter = $firstFooter; return $this; } @@ -314,13 +314,13 @@ class HeaderFooter /** * Set DifferentOddEven. * - * @param bool $pValue + * @param bool $differentOddEvent * - * @return HeaderFooter + * @return $this */ - public function setDifferentOddEven($pValue) + public function setDifferentOddEven($differentOddEvent) { - $this->differentOddEven = $pValue; + $this->differentOddEven = $differentOddEvent; return $this; } @@ -338,13 +338,13 @@ class HeaderFooter /** * Set DifferentFirst. * - * @param bool $pValue + * @param bool $differentFirst * - * @return HeaderFooter + * @return $this */ - public function setDifferentFirst($pValue) + public function setDifferentFirst($differentFirst) { - $this->differentFirst = $pValue; + $this->differentFirst = $differentFirst; return $this; } @@ -362,13 +362,13 @@ class HeaderFooter /** * Set ScaleWithDocument. * - * @param bool $pValue + * @param bool $scaleWithDocument * - * @return HeaderFooter + * @return $this */ - public function setScaleWithDocument($pValue) + public function setScaleWithDocument($scaleWithDocument) { - $this->scaleWithDocument = $pValue; + $this->scaleWithDocument = $scaleWithDocument; return $this; } @@ -386,13 +386,13 @@ class HeaderFooter /** * Set AlignWithMargins. * - * @param bool $pValue + * @param bool $alignWithMargins * - * @return HeaderFooter + * @return $this */ - public function setAlignWithMargins($pValue) + public function setAlignWithMargins($alignWithMargins) { - $this->alignWithMargins = $pValue; + $this->alignWithMargins = $alignWithMargins; return $this; } @@ -400,10 +400,9 @@ class HeaderFooter /** * Add header/footer image. * - * @param HeaderFooterDrawing $image * @param string $location * - * @return HeaderFooter + * @return $this */ public function addImage(HeaderFooterDrawing $image, $location = self::IMAGE_HEADER_LEFT) { @@ -417,7 +416,7 @@ class HeaderFooter * * @param string $location * - * @return HeaderFooter + * @return $this */ public function removeImage($location = self::IMAGE_HEADER_LEFT) { @@ -433,7 +432,7 @@ class HeaderFooter * * @param HeaderFooterDrawing[] $images * - * @return HeaderFooter + * @return $this */ public function setImages(array $images) { diff --git a/PhpOffice/PhpSpreadsheet/Worksheet/HeaderFooterDrawing.php b/PhpOffice/PhpSpreadsheet/Worksheet/HeaderFooterDrawing.php old mode 100755 new mode 100644 diff --git a/PhpOffice/PhpSpreadsheet/Worksheet/Iterator.php b/PhpOffice/PhpSpreadsheet/Worksheet/Iterator.php old mode 100755 new mode 100644 index 59aa98d..dc08204 --- a/PhpOffice/PhpSpreadsheet/Worksheet/Iterator.php +++ b/PhpOffice/PhpSpreadsheet/Worksheet/Iterator.php @@ -4,6 +4,9 @@ namespace PhpOffice\PhpSpreadsheet\Worksheet; use PhpOffice\PhpSpreadsheet\Spreadsheet; +/** + * @implements \Iterator + */ class Iterator implements \Iterator { /** @@ -22,8 +25,6 @@ class Iterator implements \Iterator /** * Create a new worksheet iterator. - * - * @param Spreadsheet $subject */ public function __construct(Spreadsheet $subject) { @@ -31,38 +32,26 @@ class Iterator implements \Iterator $this->subject = $subject; } - /** - * Destructor. - */ - public function __destruct() - { - unset($this->subject); - } - /** * Rewind iterator. */ - public function rewind() : void + public function rewind(): void { $this->position = 0; } /** * Current Worksheet. - * - * @return Worksheet */ - public function current() : mixed + public function current(): Worksheet { return $this->subject->getSheet($this->position); } /** * Current key. - * - * @return int */ - public function key() : mixed + public function key(): int { return $this->position; } @@ -70,17 +59,15 @@ class Iterator implements \Iterator /** * Next value. */ - public function next() : void + public function next(): void { ++$this->position; } /** * Are there more Worksheet instances available? - * - * @return bool */ - public function valid() : bool + public function valid(): bool { return $this->position < $this->subject->getSheetCount() && $this->position >= 0; } diff --git a/PhpOffice/PhpSpreadsheet/Worksheet/MemoryDrawing.php b/PhpOffice/PhpSpreadsheet/Worksheet/MemoryDrawing.php old mode 100755 new mode 100644 index 6012e93..c1c4160 --- a/PhpOffice/PhpSpreadsheet/Worksheet/MemoryDrawing.php +++ b/PhpOffice/PhpSpreadsheet/Worksheet/MemoryDrawing.php @@ -2,6 +2,10 @@ namespace PhpOffice\PhpSpreadsheet\Worksheet; +use GdImage; +use PhpOffice\PhpSpreadsheet\Exception; +use PhpOffice\PhpSpreadsheet\Shared\File; + class MemoryDrawing extends BaseDrawing { // Rendering functions @@ -16,10 +20,16 @@ class MemoryDrawing extends BaseDrawing const MIMETYPE_GIF = 'image/gif'; const MIMETYPE_JPEG = 'image/jpeg'; + const SUPPORTED_MIME_TYPES = [ + self::MIMETYPE_GIF, + self::MIMETYPE_JPEG, + self::MIMETYPE_PNG, + ]; + /** * Image resource. * - * @var resource + * @var null|GdImage|resource */ private $imageResource; @@ -44,25 +54,204 @@ class MemoryDrawing extends BaseDrawing */ private $uniqueName; + /** @var null|resource */ + private $alwaysNull; + /** * Create a new MemoryDrawing. */ public function __construct() { // Initialise values - $this->imageResource = null; $this->renderingFunction = self::RENDERING_DEFAULT; $this->mimeType = self::MIMETYPE_DEFAULT; - $this->uniqueName = md5(rand(0, 9999) . time() . rand(0, 9999)); + $this->uniqueName = md5(mt_rand(0, 9999) . time() . mt_rand(0, 9999)); + $this->alwaysNull = null; // Initialize parent parent::__construct(); } + public function __destruct() + { + if ($this->imageResource) { + $rslt = @imagedestroy($this->imageResource); + // "Fix" for Scrutinizer + $this->imageResource = $rslt ? null : $this->alwaysNull; + } + } + + public function __clone() + { + parent::__clone(); + $this->cloneResource(); + } + + private function cloneResource(): void + { + if (!$this->imageResource) { + return; + } + + $width = (int) imagesx($this->imageResource); + $height = (int) imagesy($this->imageResource); + + if (imageistruecolor($this->imageResource)) { + $clone = imagecreatetruecolor($width, $height); + if (!$clone) { + throw new Exception('Could not clone image resource'); + } + + imagealphablending($clone, false); + imagesavealpha($clone, true); + } else { + $clone = imagecreate($width, $height); + if (!$clone) { + throw new Exception('Could not clone image resource'); + } + + // If the image has transparency... + $transparent = imagecolortransparent($this->imageResource); + if ($transparent >= 0) { + $rgb = imagecolorsforindex($this->imageResource, $transparent); + if (empty($rgb)) { + throw new Exception('Could not get image colors'); + } + + imagesavealpha($clone, true); + $color = imagecolorallocatealpha($clone, $rgb['red'], $rgb['green'], $rgb['blue'], $rgb['alpha']); + if ($color === false) { + throw new Exception('Could not get image alpha color'); + } + + imagefill($clone, 0, 0, $color); + } + } + + //Create the Clone!! + imagecopy($clone, $this->imageResource, 0, 0, 0, 0, $width, $height); + + $this->imageResource = $clone; + } + + /** + * @param resource $imageStream Stream data to be converted to a Memory Drawing + * + * @throws Exception + */ + public static function fromStream($imageStream): self + { + $streamValue = stream_get_contents($imageStream); + if ($streamValue === false) { + throw new Exception('Unable to read data from stream'); + } + + return self::fromString($streamValue); + } + + /** + * @param string $imageString String data to be converted to a Memory Drawing + * + * @throws Exception + */ + public static function fromString(string $imageString): self + { + $gdImage = @imagecreatefromstring($imageString); + if ($gdImage === false) { + throw new Exception('Value cannot be converted to an image'); + } + + $mimeType = self::identifyMimeType($imageString); + $renderingFunction = self::identifyRenderingFunction($mimeType); + + $drawing = new self(); + $drawing->setImageResource($gdImage); + $drawing->setRenderingFunction($renderingFunction); + $drawing->setMimeType($mimeType); + + return $drawing; + } + + private static function identifyRenderingFunction(string $mimeType): string + { + switch ($mimeType) { + case self::MIMETYPE_PNG: + return self::RENDERING_PNG; + case self::MIMETYPE_JPEG: + return self::RENDERING_JPEG; + case self::MIMETYPE_GIF: + return self::RENDERING_GIF; + } + + return self::RENDERING_DEFAULT; + } + + /** + * @throws Exception + */ + private static function identifyMimeType(string $imageString): string + { + $temporaryFileName = File::temporaryFilename(); + file_put_contents($temporaryFileName, $imageString); + + $mimeType = self::identifyMimeTypeUsingExif($temporaryFileName); + if ($mimeType !== null) { + unlink($temporaryFileName); + + return $mimeType; + } + + $mimeType = self::identifyMimeTypeUsingGd($temporaryFileName); + if ($mimeType !== null) { + unlink($temporaryFileName); + + return $mimeType; + } + + unlink($temporaryFileName); + + return self::MIMETYPE_DEFAULT; + } + + private static function identifyMimeTypeUsingExif(string $temporaryFileName): ?string + { + if (function_exists('exif_imagetype')) { + $imageType = @exif_imagetype($temporaryFileName); + $mimeType = ($imageType) ? image_type_to_mime_type($imageType) : null; + + return self::supportedMimeTypes($mimeType); + } + + return null; + } + + private static function identifyMimeTypeUsingGd(string $temporaryFileName): ?string + { + if (function_exists('getimagesize')) { + $imageSize = @getimagesize($temporaryFileName); + if (is_array($imageSize)) { + $mimeType = $imageSize['mime'] ?? null; + + return self::supportedMimeTypes($mimeType); + } + } + + return null; + } + + private static function supportedMimeTypes(?string $mimeType = null): ?string + { + if (in_array($mimeType, self::SUPPORTED_MIME_TYPES, true)) { + return $mimeType; + } + + return null; + } + /** * Get image resource. * - * @return resource + * @return null|GdImage|resource */ public function getImageResource() { @@ -72,9 +261,9 @@ class MemoryDrawing extends BaseDrawing /** * Set image resource. * - * @param resource $value + * @param GdImage|resource $value * - * @return MemoryDrawing + * @return $this */ public function setImageResource($value) { @@ -82,8 +271,8 @@ class MemoryDrawing extends BaseDrawing if ($this->imageResource !== null) { // Get width/height - $this->width = imagesx($this->imageResource); - $this->height = imagesy($this->imageResource); + $this->width = (int) imagesx($this->imageResource); + $this->height = (int) imagesy($this->imageResource); } return $this; @@ -104,7 +293,7 @@ class MemoryDrawing extends BaseDrawing * * @param string $value see self::RENDERING_* * - * @return MemoryDrawing + * @return $this */ public function setRenderingFunction($value) { @@ -128,7 +317,7 @@ class MemoryDrawing extends BaseDrawing * * @param string $value see self::MIMETYPE_* * - * @return MemoryDrawing + * @return $this */ public function setMimeType($value) { @@ -139,10 +328,8 @@ class MemoryDrawing extends BaseDrawing /** * Get indexed filename (using image index). - * - * @return string */ - public function getIndexedFilename() + public function getIndexedFilename(): string { $extension = strtolower($this->getMimeType()); $extension = explode('/', $extension); diff --git a/PhpOffice/PhpSpreadsheet/Worksheet/PageMargins.php b/PhpOffice/PhpSpreadsheet/Worksheet/PageMargins.php old mode 100755 new mode 100644 index eb44a10..34e1145 --- a/PhpOffice/PhpSpreadsheet/Worksheet/PageMargins.php +++ b/PhpOffice/PhpSpreadsheet/Worksheet/PageMargins.php @@ -66,13 +66,13 @@ class PageMargins /** * Set Left. * - * @param float $pValue + * @param float $left * - * @return PageMargins + * @return $this */ - public function setLeft($pValue) + public function setLeft($left) { - $this->left = $pValue; + $this->left = $left; return $this; } @@ -90,13 +90,13 @@ class PageMargins /** * Set Right. * - * @param float $pValue + * @param float $right * - * @return PageMargins + * @return $this */ - public function setRight($pValue) + public function setRight($right) { - $this->right = $pValue; + $this->right = $right; return $this; } @@ -114,13 +114,13 @@ class PageMargins /** * Set Top. * - * @param float $pValue + * @param float $top * - * @return PageMargins + * @return $this */ - public function setTop($pValue) + public function setTop($top) { - $this->top = $pValue; + $this->top = $top; return $this; } @@ -138,13 +138,13 @@ class PageMargins /** * Set Bottom. * - * @param float $pValue + * @param float $bottom * - * @return PageMargins + * @return $this */ - public function setBottom($pValue) + public function setBottom($bottom) { - $this->bottom = $pValue; + $this->bottom = $bottom; return $this; } @@ -162,13 +162,13 @@ class PageMargins /** * Set Header. * - * @param float $pValue + * @param float $header * - * @return PageMargins + * @return $this */ - public function setHeader($pValue) + public function setHeader($header) { - $this->header = $pValue; + $this->header = $header; return $this; } @@ -186,13 +186,13 @@ class PageMargins /** * Set Footer. * - * @param float $pValue + * @param float $footer * - * @return PageMargins + * @return $this */ - public function setFooter($pValue) + public function setFooter($footer) { - $this->footer = $pValue; + $this->footer = $footer; return $this; } @@ -211,4 +211,34 @@ class PageMargins } } } + + public static function fromCentimeters(float $value): float + { + return $value / 2.54; + } + + public static function toCentimeters(float $value): float + { + return $value * 2.54; + } + + public static function fromMillimeters(float $value): float + { + return $value / 25.4; + } + + public static function toMillimeters(float $value): float + { + return $value * 25.4; + } + + public static function fromPoints(float $value): float + { + return $value / 72; + } + + public static function toPoints(float $value): float + { + return $value * 72; + } } diff --git a/PhpOffice/PhpSpreadsheet/Worksheet/PageSetup.php b/PhpOffice/PhpSpreadsheet/Worksheet/PageSetup.php old mode 100755 new mode 100644 index ab007f6..93030db --- a/PhpOffice/PhpSpreadsheet/Worksheet/PageSetup.php +++ b/PhpOffice/PhpSpreadsheet/Worksheet/PageSetup.php @@ -76,10 +76,6 @@ use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; * 67 = A3 transverse paper (297 mm by 420 mm) * 68 = A3 extra transverse paper (322 mm by 445 mm) * - * - * @category PhpSpreadsheet - * - * @copyright Copyright (c) 2006 - 2016 PhpSpreadsheet (https://github.com/PHPOffice/PhpSpreadsheet) */ class PageSetup { @@ -160,19 +156,36 @@ class PageSetup const SETPRINTRANGE_OVERWRITE = 'O'; const SETPRINTRANGE_INSERT = 'I'; + const PAGEORDER_OVER_THEN_DOWN = 'overThenDown'; + const PAGEORDER_DOWN_THEN_OVER = 'downThenOver'; + /** - * Paper size. + * Paper size default. * * @var int */ - private $paperSize = self::PAPERSIZE_LETTER; + private static $paperSizeDefault = self::PAPERSIZE_LETTER; + + /** + * Paper size. + * + * @var ?int + */ + private $paperSize; + + /** + * Orientation default. + * + * @var string + */ + private static $orientationDefault = self::ORIENTATION_DEFAULT; /** * Orientation. * * @var string */ - private $orientation = self::ORIENTATION_DEFAULT; + private $orientation; /** * Scale (Print Scale). @@ -180,7 +193,7 @@ class PageSetup * Print scaling. Valid values range from 10 to 400 * This setting is overridden when fitToWidth and/or fitToHeight are in use * - * @var int? + * @var null|int */ private $scale = 100; @@ -196,7 +209,7 @@ class PageSetup * Fit To Height * Number of vertical pages to fit on. * - * @var int? + * @var null|int */ private $fitToHeight = 1; @@ -204,7 +217,7 @@ class PageSetup * Fit To Width * Number of horizontal pages to fit on. * - * @var int? + * @var null|int */ private $fitToWidth = 1; @@ -239,22 +252,26 @@ class PageSetup /** * Print area. * - * @var string + * @var null|string */ private $printArea; /** * First page number. * - * @var int + * @var ?int */ private $firstPageNumber; + /** @var string */ + private $pageOrder = self::PAGEORDER_DOWN_THEN_OVER; + /** * Create a new PageSetup. */ public function __construct() { + $this->orientation = self::$orientationDefault; } /** @@ -264,23 +281,39 @@ class PageSetup */ public function getPaperSize() { - return $this->paperSize; + return $this->paperSize ?? self::$paperSizeDefault; } /** * Set Paper Size. * - * @param int $pValue see self::PAPERSIZE_* + * @param int $paperSize see self::PAPERSIZE_* * - * @return PageSetup + * @return $this */ - public function setPaperSize($pValue) + public function setPaperSize($paperSize) { - $this->paperSize = $pValue; + $this->paperSize = $paperSize; return $this; } + /** + * Get Paper Size default. + */ + public static function getPaperSizeDefault(): int + { + return self::$paperSizeDefault; + } + + /** + * Set Paper Size Default. + */ + public static function setPaperSizeDefault(int $paperSize): void + { + self::$paperSizeDefault = $paperSize; + } + /** * Get Orientation. * @@ -294,21 +327,35 @@ class PageSetup /** * Set Orientation. * - * @param string $pValue see self::ORIENTATION_* + * @param string $orientation see self::ORIENTATION_* * - * @return PageSetup + * @return $this */ - public function setOrientation($pValue) + public function setOrientation($orientation) { - $this->orientation = $pValue; + if ($orientation === self::ORIENTATION_LANDSCAPE || $orientation === self::ORIENTATION_PORTRAIT || $orientation === self::ORIENTATION_DEFAULT) { + $this->orientation = $orientation; + } return $this; } + public static function getOrientationDefault(): string + { + return self::$orientationDefault; + } + + public static function setOrientationDefault(string $orientation): void + { + if ($orientation === self::ORIENTATION_LANDSCAPE || $orientation === self::ORIENTATION_PORTRAIT || $orientation === self::ORIENTATION_DEFAULT) { + self::$orientationDefault = $orientation; + } + } + /** * Get Scale. * - * @return int? + * @return null|int */ public function getScale() { @@ -320,20 +367,18 @@ class PageSetup * Print scaling. Valid values range from 10 to 400 * This setting is overridden when fitToWidth and/or fitToHeight are in use. * - * @param null|int $pValue - * @param bool $pUpdate Update fitToPage so scaling applies rather than fitToHeight / fitToWidth + * @param null|int $scale + * @param bool $update Update fitToPage so scaling applies rather than fitToHeight / fitToWidth * - * @throws PhpSpreadsheetException - * - * @return PageSetup + * @return $this */ - public function setScale($pValue, $pUpdate = true) + public function setScale($scale, $update = true) { // Microsoft Office Excel 2007 only allows setting a scale between 10 and 400 via the user interface, // but it is apparently still able to handle any scale >= 0, where 0 results in 100 - if (($pValue >= 0) || $pValue === null) { - $this->scale = $pValue; - if ($pUpdate) { + if ($scale === null || $scale >= 0) { + $this->scale = $scale; + if ($update) { $this->fitToPage = false; } } else { @@ -356,13 +401,13 @@ class PageSetup /** * Set Fit To Page. * - * @param bool $pValue + * @param bool $fitToPage * - * @return PageSetup + * @return $this */ - public function setFitToPage($pValue) + public function setFitToPage($fitToPage) { - $this->fitToPage = $pValue; + $this->fitToPage = $fitToPage; return $this; } @@ -370,7 +415,7 @@ class PageSetup /** * Get Fit To Height. * - * @return int? + * @return null|int */ public function getFitToHeight() { @@ -380,15 +425,15 @@ class PageSetup /** * Set Fit To Height. * - * @param null|int $pValue - * @param bool $pUpdate Update fitToPage so it applies rather than scaling + * @param null|int $fitToHeight + * @param bool $update Update fitToPage so it applies rather than scaling * - * @return PageSetup + * @return $this */ - public function setFitToHeight($pValue, $pUpdate = true) + public function setFitToHeight($fitToHeight, $update = true) { - $this->fitToHeight = $pValue; - if ($pUpdate) { + $this->fitToHeight = $fitToHeight; + if ($update) { $this->fitToPage = true; } @@ -398,7 +443,7 @@ class PageSetup /** * Get Fit To Width. * - * @return int? + * @return null|int */ public function getFitToWidth() { @@ -408,15 +453,15 @@ class PageSetup /** * Set Fit To Width. * - * @param null|int $pValue - * @param bool $pUpdate Update fitToPage so it applies rather than scaling + * @param null|int $value + * @param bool $update Update fitToPage so it applies rather than scaling * - * @return PageSetup + * @return $this */ - public function setFitToWidth($pValue, $pUpdate = true) + public function setFitToWidth($value, $update = true) { - $this->fitToWidth = $pValue; - if ($pUpdate) { + $this->fitToWidth = $value; + if ($update) { $this->fitToPage = true; } @@ -430,7 +475,7 @@ class PageSetup */ public function isColumnsToRepeatAtLeftSet() { - if (is_array($this->columnsToRepeatAtLeft)) { + if (!empty($this->columnsToRepeatAtLeft)) { if ($this->columnsToRepeatAtLeft[0] != '' && $this->columnsToRepeatAtLeft[1] != '') { return true; } @@ -452,13 +497,13 @@ class PageSetup /** * Set Columns to repeat at left. * - * @param array $pValue Containing start column and end column, empty array if option unset + * @param array $columnsToRepeatAtLeft Containing start column and end column, empty array if option unset * - * @return PageSetup + * @return $this */ - public function setColumnsToRepeatAtLeft(array $pValue) + public function setColumnsToRepeatAtLeft(array $columnsToRepeatAtLeft) { - $this->columnsToRepeatAtLeft = $pValue; + $this->columnsToRepeatAtLeft = $columnsToRepeatAtLeft; return $this; } @@ -466,14 +511,14 @@ class PageSetup /** * Set Columns to repeat at left by start and end. * - * @param string $pStart eg: 'A' - * @param string $pEnd eg: 'B' + * @param string $start eg: 'A' + * @param string $end eg: 'B' * - * @return PageSetup + * @return $this */ - public function setColumnsToRepeatAtLeftByStartAndEnd($pStart, $pEnd) + public function setColumnsToRepeatAtLeftByStartAndEnd($start, $end) { - $this->columnsToRepeatAtLeft = [$pStart, $pEnd]; + $this->columnsToRepeatAtLeft = [$start, $end]; return $this; } @@ -485,7 +530,7 @@ class PageSetup */ public function isRowsToRepeatAtTopSet() { - if (is_array($this->rowsToRepeatAtTop)) { + if (!empty($this->rowsToRepeatAtTop)) { if ($this->rowsToRepeatAtTop[0] != 0 && $this->rowsToRepeatAtTop[1] != 0) { return true; } @@ -507,13 +552,13 @@ class PageSetup /** * Set Rows to repeat at top. * - * @param array $pValue Containing start column and end column, empty array if option unset + * @param array $rowsToRepeatAtTop Containing start column and end column, empty array if option unset * - * @return PageSetup + * @return $this */ - public function setRowsToRepeatAtTop(array $pValue) + public function setRowsToRepeatAtTop(array $rowsToRepeatAtTop) { - $this->rowsToRepeatAtTop = $pValue; + $this->rowsToRepeatAtTop = $rowsToRepeatAtTop; return $this; } @@ -521,14 +566,14 @@ class PageSetup /** * Set Rows to repeat at top by start and end. * - * @param int $pStart eg: 1 - * @param int $pEnd eg: 1 + * @param int $start eg: 1 + * @param int $end eg: 1 * - * @return PageSetup + * @return $this */ - public function setRowsToRepeatAtTopByStartAndEnd($pStart, $pEnd) + public function setRowsToRepeatAtTopByStartAndEnd($start, $end) { - $this->rowsToRepeatAtTop = [$pStart, $pEnd]; + $this->rowsToRepeatAtTop = [$start, $end]; return $this; } @@ -548,7 +593,7 @@ class PageSetup * * @param bool $value * - * @return PageSetup + * @return $this */ public function setHorizontalCentered($value) { @@ -572,7 +617,7 @@ class PageSetup * * @param bool $value * - * @return PageSetup + * @return $this */ public function setVerticalCentered($value) { @@ -589,8 +634,6 @@ class PageSetup * Otherwise, the specific range identified by the value of $index will be returned * Print areas are numbered from 1 * - * @throws PhpSpreadsheetException - * * @return string */ public function getPrintArea($index = 0) @@ -598,7 +641,7 @@ class PageSetup if ($index == 0) { return $this->printArea; } - $printAreas = explode(',', $this->printArea); + $printAreas = explode(',', (string) $this->printArea); if (isset($printAreas[$index - 1])) { return $printAreas[$index - 1]; } @@ -621,7 +664,7 @@ class PageSetup if ($index == 0) { return $this->printArea !== null; } - $printAreas = explode(',', $this->printArea); + $printAreas = explode(',', (string) $this->printArea); return isset($printAreas[$index - 1]); } @@ -634,14 +677,14 @@ class PageSetup * Otherwise, the range identified by the value of $index will be removed from the series * Print areas are numbered from 1 * - * @return PageSetup + * @return $this */ public function clearPrintArea($index = 0) { if ($index == 0) { $this->printArea = null; } else { - $printAreas = explode(',', $this->printArea); + $printAreas = explode(',', (string) $this->printArea); if (isset($printAreas[$index - 1])) { unset($printAreas[$index - 1]); $this->printArea = implode(',', $printAreas); @@ -669,9 +712,7 @@ class PageSetup * Default behaviour, or the "O" method, overwrites existing print area * The "I" method, inserts the new print area before any specified index, or at the end of the list * - * @throws PhpSpreadsheetException - * - * @return PageSetup + * @return $this */ public function setPrintArea($value, $index = 0, $method = self::SETPRINTRANGE_OVERWRITE) { @@ -683,12 +724,15 @@ class PageSetup throw new PhpSpreadsheetException('Cell coordinate must not be absolute.'); } $value = strtoupper($value); + if (!$this->printArea) { + $index = 0; + } if ($method == self::SETPRINTRANGE_OVERWRITE) { if ($index == 0) { $this->printArea = $value; } else { - $printAreas = explode(',', $this->printArea); + $printAreas = explode(',', (string) $this->printArea); if ($index < 0) { $index = count($printAreas) - abs($index) + 1; } @@ -700,11 +744,11 @@ class PageSetup } } elseif ($method == self::SETPRINTRANGE_INSERT) { if ($index == 0) { - $this->printArea .= ($this->printArea == '') ? $value : ',' . $value; + $this->printArea = $this->printArea ? ($this->printArea . ',' . $value) : $value; } else { - $printAreas = explode(',', $this->printArea); + $printAreas = explode(',', (string) $this->printArea); if ($index < 0) { - $index = abs($index) - 1; + $index = (int) abs($index) - 1; } if ($index > count($printAreas)) { throw new PhpSpreadsheetException('Invalid index for setting print range.'); @@ -730,9 +774,7 @@ class PageSetup * list. * Print areas are numbered from 1 * - * @throws PhpSpreadsheetException - * - * @return PageSetup + * @return $this */ public function addPrintArea($value, $index = -1) { @@ -760,9 +802,7 @@ class PageSetup * Default behaviour, or the "O" method, overwrites existing print area * The "I" method, inserts the new print area before any specified index, or at the end of the list * - * @throws PhpSpreadsheetException - * - * @return PageSetup + * @return $this */ public function setPrintAreaByColumnAndRow($column1, $row1, $column2, $row2, $index = 0, $method = self::SETPRINTRANGE_OVERWRITE) { @@ -787,9 +827,7 @@ class PageSetup * list. * Print areas are numbered from 1 * - * @throws PhpSpreadsheetException - * - * @return PageSetup + * @return $this */ public function addPrintAreaByColumnAndRow($column1, $row1, $column2, $row2, $index = -1) { @@ -803,7 +841,7 @@ class PageSetup /** * Get first page number. * - * @return int + * @return ?int */ public function getFirstPageNumber() { @@ -813,9 +851,9 @@ class PageSetup /** * Set first page number. * - * @param int $value + * @param ?int $value * - * @return PageSetup + * @return $this */ public function setFirstPageNumber($value) { @@ -827,13 +865,27 @@ class PageSetup /** * Reset first page number. * - * @return PageSetup + * @return $this */ public function resetFirstPageNumber() { return $this->setFirstPageNumber(null); } + public function getPageOrder(): string + { + return $this->pageOrder; + } + + public function setPageOrder(?string $pageOrder): self + { + if ($pageOrder === null || $pageOrder === self::PAGEORDER_DOWN_THEN_OVER || $pageOrder === self::PAGEORDER_OVER_THEN_DOWN) { + $this->pageOrder = $pageOrder ?? self::PAGEORDER_DOWN_THEN_OVER; + } + + return $this; + } + /** * Implement PHP __clone to create a deep clone, not just a shallow copy. */ diff --git a/PhpOffice/PhpSpreadsheet/Worksheet/Protection.php b/PhpOffice/PhpSpreadsheet/Worksheet/Protection.php old mode 100755 new mode 100644 index 1815f45..063a5cc --- a/PhpOffice/PhpSpreadsheet/Worksheet/Protection.php +++ b/PhpOffice/PhpSpreadsheet/Worksheet/Protection.php @@ -6,125 +6,157 @@ use PhpOffice\PhpSpreadsheet\Shared\PasswordHasher; class Protection { - /** - * Sheet. - * - * @var bool - */ - private $sheet = false; + const ALGORITHM_MD2 = 'MD2'; + const ALGORITHM_MD4 = 'MD4'; + const ALGORITHM_MD5 = 'MD5'; + const ALGORITHM_SHA_1 = 'SHA-1'; + const ALGORITHM_SHA_256 = 'SHA-256'; + const ALGORITHM_SHA_384 = 'SHA-384'; + const ALGORITHM_SHA_512 = 'SHA-512'; + const ALGORITHM_RIPEMD_128 = 'RIPEMD-128'; + const ALGORITHM_RIPEMD_160 = 'RIPEMD-160'; + const ALGORITHM_WHIRLPOOL = 'WHIRLPOOL'; /** - * Objects. + * Autofilters are locked when sheet is protected, default true. * - * @var bool + * @var ?bool */ - private $objects = false; + private $autoFilter; /** - * Scenarios. + * Deleting columns is locked when sheet is protected, default true. * - * @var bool + * @var ?bool */ - private $scenarios = false; + private $deleteColumns; /** - * Format cells. + * Deleting rows is locked when sheet is protected, default true. * - * @var bool + * @var ?bool */ - private $formatCells = false; + private $deleteRows; /** - * Format columns. + * Formatting cells is locked when sheet is protected, default true. * - * @var bool + * @var ?bool */ - private $formatColumns = false; + private $formatCells; /** - * Format rows. + * Formatting columns is locked when sheet is protected, default true. * - * @var bool + * @var ?bool */ - private $formatRows = false; + private $formatColumns; /** - * Insert columns. + * Formatting rows is locked when sheet is protected, default true. * - * @var bool + * @var ?bool */ - private $insertColumns = false; + private $formatRows; /** - * Insert rows. + * Inserting columns is locked when sheet is protected, default true. * - * @var bool + * @var ?bool */ - private $insertRows = false; + private $insertColumns; /** - * Insert hyperlinks. + * Inserting hyperlinks is locked when sheet is protected, default true. * - * @var bool + * @var ?bool */ - private $insertHyperlinks = false; + private $insertHyperlinks; /** - * Delete columns. + * Inserting rows is locked when sheet is protected, default true. * - * @var bool + * @var ?bool */ - private $deleteColumns = false; + private $insertRows; /** - * Delete rows. + * Objects are locked when sheet is protected, default false. * - * @var bool + * @var ?bool */ - private $deleteRows = false; + private $objects; /** - * Select locked cells. + * Pivot tables are locked when the sheet is protected, default true. * - * @var bool + * @var ?bool */ - private $selectLockedCells = false; + private $pivotTables; /** - * Sort. + * Scenarios are locked when sheet is protected, default false. * - * @var bool + * @var ?bool */ - private $sort = false; + private $scenarios; /** - * AutoFilter. + * Selection of locked cells is locked when sheet is protected, default false. * - * @var bool + * @var ?bool */ - private $autoFilter = false; + private $selectLockedCells; /** - * Pivot tables. + * Selection of unlocked cells is locked when sheet is protected, default false. * - * @var bool + * @var ?bool */ - private $pivotTables = false; + private $selectUnlockedCells; /** - * Select unlocked cells. + * Sheet is locked when sheet is protected, default false. * - * @var bool + * @var ?bool */ - private $selectUnlockedCells = false; + private $sheet; /** - * Password. + * Sorting is locked when sheet is protected, default true. + * + * @var ?bool + */ + private $sort; + + /** + * Hashed password. * * @var string */ private $password = ''; + /** + * Algorithm name. + * + * @var string + */ + private $algorithm = ''; + + /** + * Salt value. + * + * @var string + */ + private $salt = ''; + + /** + * Spin count. + * + * @var int + */ + private $spinCount = 10000; + /** * Create a new Protection. */ @@ -134,415 +166,223 @@ class Protection /** * Is some sort of protection enabled? - * - * @return bool */ - public function isProtectionEnabled() + public function isProtectionEnabled(): bool { - return $this->sheet || - $this->objects || - $this->scenarios || - $this->formatCells || - $this->formatColumns || - $this->formatRows || - $this->insertColumns || - $this->insertRows || - $this->insertHyperlinks || - $this->deleteColumns || - $this->deleteRows || - $this->selectLockedCells || - $this->sort || - $this->autoFilter || - $this->pivotTables || - $this->selectUnlockedCells; + return + $this->password !== '' || + isset($this->sheet) || + isset($this->objects) || + isset($this->scenarios) || + isset($this->formatCells) || + isset($this->formatColumns) || + isset($this->formatRows) || + isset($this->insertColumns) || + isset($this->insertRows) || + isset($this->insertHyperlinks) || + isset($this->deleteColumns) || + isset($this->deleteRows) || + isset($this->selectLockedCells) || + isset($this->sort) || + isset($this->autoFilter) || + isset($this->pivotTables) || + isset($this->selectUnlockedCells); } - /** - * Get Sheet. - * - * @return bool - */ - public function getSheet() + public function getSheet(): ?bool { return $this->sheet; } - /** - * Set Sheet. - * - * @param bool $pValue - * - * @return Protection - */ - public function setSheet($pValue) + public function setSheet(?bool $sheet): self { - $this->sheet = $pValue; + $this->sheet = $sheet; return $this; } - /** - * Get Objects. - * - * @return bool - */ - public function getObjects() + public function getObjects(): ?bool { return $this->objects; } - /** - * Set Objects. - * - * @param bool $pValue - * - * @return Protection - */ - public function setObjects($pValue) + public function setObjects(?bool $objects): self { - $this->objects = $pValue; + $this->objects = $objects; return $this; } - /** - * Get Scenarios. - * - * @return bool - */ - public function getScenarios() + public function getScenarios(): ?bool { return $this->scenarios; } - /** - * Set Scenarios. - * - * @param bool $pValue - * - * @return Protection - */ - public function setScenarios($pValue) + public function setScenarios(?bool $scenarios): self { - $this->scenarios = $pValue; + $this->scenarios = $scenarios; return $this; } - /** - * Get FormatCells. - * - * @return bool - */ - public function getFormatCells() + public function getFormatCells(): ?bool { return $this->formatCells; } - /** - * Set FormatCells. - * - * @param bool $pValue - * - * @return Protection - */ - public function setFormatCells($pValue) + public function setFormatCells(?bool $formatCells): self { - $this->formatCells = $pValue; + $this->formatCells = $formatCells; return $this; } - /** - * Get FormatColumns. - * - * @return bool - */ - public function getFormatColumns() + public function getFormatColumns(): ?bool { return $this->formatColumns; } - /** - * Set FormatColumns. - * - * @param bool $pValue - * - * @return Protection - */ - public function setFormatColumns($pValue) + public function setFormatColumns(?bool $formatColumns): self { - $this->formatColumns = $pValue; + $this->formatColumns = $formatColumns; return $this; } - /** - * Get FormatRows. - * - * @return bool - */ - public function getFormatRows() + public function getFormatRows(): ?bool { return $this->formatRows; } - /** - * Set FormatRows. - * - * @param bool $pValue - * - * @return Protection - */ - public function setFormatRows($pValue) + public function setFormatRows(?bool $formatRows): self { - $this->formatRows = $pValue; + $this->formatRows = $formatRows; return $this; } - /** - * Get InsertColumns. - * - * @return bool - */ - public function getInsertColumns() + public function getInsertColumns(): ?bool { return $this->insertColumns; } - /** - * Set InsertColumns. - * - * @param bool $pValue - * - * @return Protection - */ - public function setInsertColumns($pValue) + public function setInsertColumns(?bool $insertColumns): self { - $this->insertColumns = $pValue; + $this->insertColumns = $insertColumns; return $this; } - /** - * Get InsertRows. - * - * @return bool - */ - public function getInsertRows() + public function getInsertRows(): ?bool { return $this->insertRows; } - /** - * Set InsertRows. - * - * @param bool $pValue - * - * @return Protection - */ - public function setInsertRows($pValue) + public function setInsertRows(?bool $insertRows): self { - $this->insertRows = $pValue; + $this->insertRows = $insertRows; return $this; } - /** - * Get InsertHyperlinks. - * - * @return bool - */ - public function getInsertHyperlinks() + public function getInsertHyperlinks(): ?bool { return $this->insertHyperlinks; } - /** - * Set InsertHyperlinks. - * - * @param bool $pValue - * - * @return Protection - */ - public function setInsertHyperlinks($pValue) + public function setInsertHyperlinks(?bool $insertHyperLinks): self { - $this->insertHyperlinks = $pValue; + $this->insertHyperlinks = $insertHyperLinks; return $this; } - /** - * Get DeleteColumns. - * - * @return bool - */ - public function getDeleteColumns() + public function getDeleteColumns(): ?bool { return $this->deleteColumns; } - /** - * Set DeleteColumns. - * - * @param bool $pValue - * - * @return Protection - */ - public function setDeleteColumns($pValue) + public function setDeleteColumns(?bool $deleteColumns): self { - $this->deleteColumns = $pValue; + $this->deleteColumns = $deleteColumns; return $this; } - /** - * Get DeleteRows. - * - * @return bool - */ - public function getDeleteRows() + public function getDeleteRows(): ?bool { return $this->deleteRows; } - /** - * Set DeleteRows. - * - * @param bool $pValue - * - * @return Protection - */ - public function setDeleteRows($pValue) + public function setDeleteRows(?bool $deleteRows): self { - $this->deleteRows = $pValue; + $this->deleteRows = $deleteRows; return $this; } - /** - * Get SelectLockedCells. - * - * @return bool - */ - public function getSelectLockedCells() + public function getSelectLockedCells(): ?bool { return $this->selectLockedCells; } - /** - * Set SelectLockedCells. - * - * @param bool $pValue - * - * @return Protection - */ - public function setSelectLockedCells($pValue) + public function setSelectLockedCells(?bool $selectLockedCells): self { - $this->selectLockedCells = $pValue; + $this->selectLockedCells = $selectLockedCells; return $this; } - /** - * Get Sort. - * - * @return bool - */ - public function getSort() + public function getSort(): ?bool { return $this->sort; } - /** - * Set Sort. - * - * @param bool $pValue - * - * @return Protection - */ - public function setSort($pValue) + public function setSort(?bool $sort): self { - $this->sort = $pValue; + $this->sort = $sort; return $this; } - /** - * Get AutoFilter. - * - * @return bool - */ - public function getAutoFilter() + public function getAutoFilter(): ?bool { return $this->autoFilter; } - /** - * Set AutoFilter. - * - * @param bool $pValue - * - * @return Protection - */ - public function setAutoFilter($pValue) + public function setAutoFilter(?bool $autoFilter): self { - $this->autoFilter = $pValue; + $this->autoFilter = $autoFilter; return $this; } - /** - * Get PivotTables. - * - * @return bool - */ - public function getPivotTables() + public function getPivotTables(): ?bool { return $this->pivotTables; } - /** - * Set PivotTables. - * - * @param bool $pValue - * - * @return Protection - */ - public function setPivotTables($pValue) + public function setPivotTables(?bool $pivotTables): self { - $this->pivotTables = $pValue; + $this->pivotTables = $pivotTables; return $this; } - /** - * Get SelectUnlockedCells. - * - * @return bool - */ - public function getSelectUnlockedCells() + public function getSelectUnlockedCells(): ?bool { return $this->selectUnlockedCells; } - /** - * Set SelectUnlockedCells. - * - * @param bool $pValue - * - * @return Protection - */ - public function setSelectUnlockedCells($pValue) + public function setSelectUnlockedCells(?bool $selectUnlockedCells): self { - $this->selectUnlockedCells = $pValue; + $this->selectUnlockedCells = $selectUnlockedCells; return $this; } /** - * Get Password (hashed). + * Get hashed password. * * @return string */ @@ -554,21 +394,112 @@ class Protection /** * Set Password. * - * @param string $pValue - * @param bool $pAlreadyHashed If the password has already been hashed, set this to true + * @param string $password + * @param bool $alreadyHashed If the password has already been hashed, set this to true * - * @return Protection + * @return $this */ - public function setPassword($pValue, $pAlreadyHashed = false) + public function setPassword($password, $alreadyHashed = false) { - if (!$pAlreadyHashed) { - $pValue = PasswordHasher::hashPassword($pValue); + if (!$alreadyHashed) { + $salt = $this->generateSalt(); + $this->setSalt($salt); + $password = PasswordHasher::hashPassword($password, $this->getAlgorithm(), $this->getSalt(), $this->getSpinCount()); } - $this->password = $pValue; + + $this->password = $password; return $this; } + public function setHashValue(string $password): self + { + return $this->setPassword($password, true); + } + + /** + * Create a pseudorandom string. + */ + private function generateSalt(): string + { + return base64_encode(random_bytes(16)); + } + + /** + * Get algorithm name. + */ + public function getAlgorithm(): string + { + return $this->algorithm; + } + + /** + * Set algorithm name. + */ + public function setAlgorithm(string $algorithm): self + { + return $this->setAlgorithmName($algorithm); + } + + /** + * Set algorithm name. + */ + public function setAlgorithmName(string $algorithm): self + { + $this->algorithm = $algorithm; + + return $this; + } + + public function getSalt(): string + { + return $this->salt; + } + + public function setSalt(string $salt): self + { + return $this->setSaltValue($salt); + } + + public function setSaltValue(string $salt): self + { + $this->salt = $salt; + + return $this; + } + + /** + * Get spin count. + */ + public function getSpinCount(): int + { + return $this->spinCount; + } + + /** + * Set spin count. + */ + public function setSpinCount(int $spinCount): self + { + $this->spinCount = $spinCount; + + return $this; + } + + /** + * Verify that the given non-hashed password can "unlock" the protection. + */ + public function verify(string $password): bool + { + if ($this->password === '') { + return true; + } + + $hash = PasswordHasher::hashPassword($password, $this->getAlgorithm(), $this->getSalt(), $this->getSpinCount()); + + return $this->getPassword() === $hash; + } + /** * Implement PHP __clone to create a deep clone, not just a shallow copy. */ diff --git a/PhpOffice/PhpSpreadsheet/Worksheet/Row.php b/PhpOffice/PhpSpreadsheet/Worksheet/Row.php old mode 100755 new mode 100644 index 2a379d2..223a07c --- a/PhpOffice/PhpSpreadsheet/Worksheet/Row.php +++ b/PhpOffice/PhpSpreadsheet/Worksheet/Row.php @@ -21,10 +21,9 @@ class Row /** * Create a new row. * - * @param Worksheet $worksheet * @param int $rowIndex */ - public function __construct(Worksheet $worksheet = null, $rowIndex = 1) + public function __construct(Worksheet $worksheet, $rowIndex = 1) { // Set parent and row index $this->worksheet = $worksheet; @@ -36,15 +35,13 @@ class Row */ public function __destruct() { - unset($this->worksheet); + $this->worksheet = null; // @phpstan-ignore-line } /** * Get row index. - * - * @return int */ - public function getRowIndex() + public function getRowIndex(): int { return $this->rowIndex; } @@ -63,11 +60,49 @@ class Row } /** - * Returns bound worksheet. + * Returns a boolean true if the row contains no cells. By default, this means that no cell records exist in the + * collection for this row. false will be returned otherwise. + * This rule can be modified by passing a $definitionOfEmptyFlags value: + * 1 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL If the only cells in the collection are null value + * cells, then the row will be considered empty. + * 2 - CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL If the only cells in the collection are empty + * string value cells, then the row will be considered empty. + * 3 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL | CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL + * If the only cells in the collection are null value or empty string value cells, then the row + * will be considered empty. * - * @return Worksheet + * @param int $definitionOfEmptyFlags + * Possible Flag Values are: + * CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL + * CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL */ - public function getWorksheet() + public function isEmpty(int $definitionOfEmptyFlags = 0): bool + { + $nullValueCellIsEmpty = (bool) ($definitionOfEmptyFlags & CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL); + $emptyStringCellIsEmpty = (bool) ($definitionOfEmptyFlags & CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL); + + $cellIterator = $this->getCellIterator(); + $cellIterator->setIterateOnlyExistingCells(true); + foreach ($cellIterator as $cell) { + /** @scrutinizer ignore-call */ + $value = $cell->getValue(); + if ($value === null && $nullValueCellIsEmpty === true) { + continue; + } + if ($value === '' && $emptyStringCellIsEmpty === true) { + continue; + } + + return false; + } + + return true; + } + + /** + * Returns bound worksheet. + */ + public function getWorksheet(): Worksheet { return $this->worksheet; } diff --git a/PhpOffice/PhpSpreadsheet/Worksheet/RowCellIterator.php b/PhpOffice/PhpSpreadsheet/Worksheet/RowCellIterator.php old mode 100755 new mode 100644 index ba2a016..5af0507 --- a/PhpOffice/PhpSpreadsheet/Worksheet/RowCellIterator.php +++ b/PhpOffice/PhpSpreadsheet/Worksheet/RowCellIterator.php @@ -2,9 +2,13 @@ namespace PhpOffice\PhpSpreadsheet\Worksheet; +use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; +/** + * @extends CellIterator + */ class RowCellIterator extends CellIterator { /** @@ -43,10 +47,11 @@ class RowCellIterator extends CellIterator * @param string $startColumn The column address at which to start iterating * @param string $endColumn Optionally, the column address at which to stop iterating */ - public function __construct(Worksheet $worksheet = null, $rowIndex = 1, $startColumn = 'A', $endColumn = null) + public function __construct(Worksheet $worksheet, $rowIndex = 1, $startColumn = 'A', $endColumn = null) { // Set subject and row index $this->worksheet = $worksheet; + $this->cellCollection = $worksheet->getCellCollection(); $this->rowIndex = $rowIndex; $this->resetEnd($endColumn); $this->resetStart($startColumn); @@ -57,11 +62,9 @@ class RowCellIterator extends CellIterator * * @param string $startColumn The column address at which to start iterating * - * @throws PhpSpreadsheetException - * - * @return RowCellIterator + * @return $this */ - public function resetStart($startColumn = 'A') + public function resetStart(string $startColumn = 'A') { $this->startColumnIndex = Coordinate::columnIndexFromString($startColumn); $this->adjustForExistingOnlyRange(); @@ -75,13 +78,11 @@ class RowCellIterator extends CellIterator * * @param string $endColumn The column address at which to stop iterating * - * @throws PhpSpreadsheetException - * - * @return RowCellIterator + * @return $this */ public function resetEnd($endColumn = null) { - $endColumn = $endColumn ? $endColumn : $this->worksheet->getHighestColumn(); + $endColumn = $endColumn ?: $this->worksheet->getHighestColumn(); $this->endColumnIndex = Coordinate::columnIndexFromString($endColumn); $this->adjustForExistingOnlyRange(); @@ -93,19 +94,18 @@ class RowCellIterator extends CellIterator * * @param string $column The column address to set the current pointer at * - * @throws PhpSpreadsheetException - * - * @return RowCellIterator + * @return $this */ - public function seek($column = 'A') + public function seek(string $column = 'A') { - $column = Coordinate::columnIndexFromString($column); - if (($column < $this->startColumnIndex) || ($column > $this->endColumnIndex)) { - throw new PhpSpreadsheetException("Column $column is out of range ({$this->startColumnIndex} - {$this->endColumnIndex})"); - } elseif ($this->onlyExistingCells && !($this->worksheet->cellExistsByColumnAndRow($column, $this->rowIndex))) { + $columnId = Coordinate::columnIndexFromString($column); + if ($this->onlyExistingCells && !($this->cellCollection->has($column . $this->rowIndex))) { throw new PhpSpreadsheetException('In "IterateOnlyExistingCells" mode and Cell does not exist'); } - $this->currentColumnIndex = $column; + if (($columnId < $this->startColumnIndex) || ($columnId > $this->endColumnIndex)) { + throw new PhpSpreadsheetException("Column $column is out of range ({$this->startColumnIndex} - {$this->endColumnIndex})"); + } + $this->currentColumnIndex = $columnId; return $this; } @@ -113,27 +113,27 @@ class RowCellIterator extends CellIterator /** * Rewind the iterator to the starting column. */ - public function rewind() : void + public function rewind(): void { $this->currentColumnIndex = $this->startColumnIndex; } /** * Return the current cell in this worksheet row. - * - * @return \PhpOffice\PhpSpreadsheet\Cell\Cell */ - public function current() : mixed + public function current(): ?Cell { - return $this->worksheet->getCellByColumnAndRow($this->currentColumnIndex, $this->rowIndex); + $cellAddress = Coordinate::stringFromColumnIndex($this->currentColumnIndex) . $this->rowIndex; + + return $this->cellCollection->has($cellAddress) + ? $this->cellCollection->get($cellAddress) + : $this->worksheet->createNewCell($cellAddress); } /** * Return the current iterator key. - * - * @return string */ - public function key() : mixed + public function key(): string { return Coordinate::stringFromColumnIndex($this->currentColumnIndex); } @@ -141,65 +141,51 @@ class RowCellIterator extends CellIterator /** * Set the iterator to its next value. */ - public function next() : void + public function next(): void { do { ++$this->currentColumnIndex; - } while (($this->onlyExistingCells) && (!$this->worksheet->cellExistsByColumnAndRow($this->currentColumnIndex, $this->rowIndex)) && ($this->currentColumnIndex <= $this->endColumnIndex)); + } while (($this->onlyExistingCells) && (!$this->cellCollection->has(Coordinate::stringFromColumnIndex($this->currentColumnIndex) . $this->rowIndex)) && ($this->currentColumnIndex <= $this->endColumnIndex)); } /** * Set the iterator to its previous value. - * - * @throws PhpSpreadsheetException */ - public function prev() : void + public function prev(): void { do { --$this->currentColumnIndex; - } while (($this->onlyExistingCells) && (!$this->worksheet->cellExistsByColumnAndRow($this->currentColumnIndex, $this->rowIndex)) && ($this->currentColumnIndex >= $this->startColumnIndex)); + } while (($this->onlyExistingCells) && (!$this->cellCollection->has(Coordinate::stringFromColumnIndex($this->currentColumnIndex) . $this->rowIndex)) && ($this->currentColumnIndex >= $this->startColumnIndex)); } /** * Indicate if more columns exist in the worksheet range of columns that we're iterating. - * - * @return bool */ - public function valid() : bool + public function valid(): bool { return $this->currentColumnIndex <= $this->endColumnIndex && $this->currentColumnIndex >= $this->startColumnIndex; } /** * Return the current iterator position. - * - * @return int */ - public function getCurrentColumnIndex() + public function getCurrentColumnIndex(): int { return $this->currentColumnIndex; } /** * Validate start/end values for "IterateOnlyExistingCells" mode, and adjust if necessary. - * - * @throws PhpSpreadsheetException */ - protected function adjustForExistingOnlyRange() + protected function adjustForExistingOnlyRange(): void { if ($this->onlyExistingCells) { - while ((!$this->worksheet->cellExistsByColumnAndRow($this->startColumnIndex, $this->rowIndex)) && ($this->startColumnIndex <= $this->endColumnIndex)) { + while ((!$this->cellCollection->has(Coordinate::stringFromColumnIndex($this->startColumnIndex) . $this->rowIndex)) && ($this->startColumnIndex <= $this->endColumnIndex)) { ++$this->startColumnIndex; } - if ($this->startColumnIndex > $this->endColumnIndex) { - throw new PhpSpreadsheetException('No cells exist within the specified range'); - } - while ((!$this->worksheet->cellExistsByColumnAndRow($this->endColumnIndex, $this->rowIndex)) && ($this->endColumnIndex >= $this->startColumnIndex)) { + while ((!$this->cellCollection->has(Coordinate::stringFromColumnIndex($this->endColumnIndex) . $this->rowIndex)) && ($this->endColumnIndex >= $this->startColumnIndex)) { --$this->endColumnIndex; } - if ($this->endColumnIndex < $this->startColumnIndex) { - throw new PhpSpreadsheetException('No cells exist within the specified range'); - } } } } diff --git a/PhpOffice/PhpSpreadsheet/Worksheet/RowDimension.php b/PhpOffice/PhpSpreadsheet/Worksheet/RowDimension.php old mode 100755 new mode 100644 index e434640..acaafac --- a/PhpOffice/PhpSpreadsheet/Worksheet/RowDimension.php +++ b/PhpOffice/PhpSpreadsheet/Worksheet/RowDimension.php @@ -2,6 +2,8 @@ namespace PhpOffice\PhpSpreadsheet\Worksheet; +use PhpOffice\PhpSpreadsheet\Helper\Dimension as CssDimension; + class RowDimension extends Dimension { /** @@ -30,12 +32,12 @@ class RowDimension extends Dimension /** * Create a new RowDimension. * - * @param int $pIndex Numeric row index + * @param int $index Numeric row index */ - public function __construct($pIndex = 0) + public function __construct($index = 0) { // Initialise values - $this->rowIndex = $pIndex; + $this->rowIndex = $index; // set dimension as unformatted by default parent::__construct(null); @@ -43,10 +45,8 @@ class RowDimension extends Dimension /** * Get Row Index. - * - * @return int */ - public function getRowIndex() + public function getRowIndex(): int { return $this->rowIndex; } @@ -54,47 +54,52 @@ class RowDimension extends Dimension /** * Set Row Index. * - * @param int $pValue - * - * @return RowDimension + * @return $this */ - public function setRowIndex($pValue) + public function setRowIndex(int $index) { - $this->rowIndex = $pValue; + $this->rowIndex = $index; return $this; } /** * Get Row Height. + * By default, this will be in points; but this method also accepts an optional unit of measure + * argument, and will convert the value from points to the specified UoM. + * A value of -1 tells Excel to display this column in its default height. * * @return float */ - public function getRowHeight() + public function getRowHeight(?string $unitOfMeasure = null) { - return $this->height; + return ($unitOfMeasure === null || $this->height < 0) + ? $this->height + : (new CssDimension($this->height . CssDimension::UOM_POINTS))->toUnit($unitOfMeasure); } /** * Set Row Height. * - * @param float $pValue + * @param float $height in points. A value of -1 tells Excel to display this column in its default height. + * By default, this will be the passed argument value; but this method also accepts an optional unit of measure + * argument, and will convert the passed argument value to points from the specified UoM * - * @return RowDimension + * @return $this */ - public function setRowHeight($pValue) + public function setRowHeight($height, ?string $unitOfMeasure = null) { - $this->height = $pValue; + $this->height = ($unitOfMeasure === null || $height < 0) + ? $height + : (new CssDimension("{$height}{$unitOfMeasure}"))->height(); return $this; } /** * Get ZeroHeight. - * - * @return bool */ - public function getZeroHeight() + public function getZeroHeight(): bool { return $this->zeroHeight; } @@ -102,13 +107,11 @@ class RowDimension extends Dimension /** * Set ZeroHeight. * - * @param bool $pValue - * - * @return RowDimension + * @return $this */ - public function setZeroHeight($pValue) + public function setZeroHeight(bool $zeroHeight) { - $this->zeroHeight = $pValue; + $this->zeroHeight = $zeroHeight; return $this; } diff --git a/PhpOffice/PhpSpreadsheet/Worksheet/RowIterator.php b/PhpOffice/PhpSpreadsheet/Worksheet/RowIterator.php old mode 100755 new mode 100644 index b828306..84f376c --- a/PhpOffice/PhpSpreadsheet/Worksheet/RowIterator.php +++ b/PhpOffice/PhpSpreadsheet/Worksheet/RowIterator.php @@ -2,9 +2,13 @@ namespace PhpOffice\PhpSpreadsheet\Worksheet; +use Iterator as NativeIterator; use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; -class RowIterator implements \Iterator +/** + * @implements NativeIterator + */ +class RowIterator implements NativeIterator { /** * Worksheet to iterate. @@ -49,27 +53,19 @@ class RowIterator implements \Iterator $this->resetStart($startRow); } - /** - * Destructor. - */ - public function __destruct() - { - unset($this->subject); - } - /** * (Re)Set the start row and the current row pointer. * * @param int $startRow The row number at which to start iterating * - * @throws PhpSpreadsheetException - * - * @return RowIterator + * @return $this */ - public function resetStart($startRow = 1) + public function resetStart(int $startRow = 1) { if ($startRow > $this->subject->getHighestRow()) { - throw new PhpSpreadsheetException("Start row ({$startRow}) is beyond highest row ({$this->subject->getHighestRow()})"); + throw new PhpSpreadsheetException( + "Start row ({$startRow}) is beyond highest row ({$this->subject->getHighestRow()})" + ); } $this->startRow = $startRow; @@ -86,11 +82,11 @@ class RowIterator implements \Iterator * * @param int $endRow The row number at which to stop iterating * - * @return RowIterator + * @return $this */ public function resetEnd($endRow = null) { - $this->endRow = ($endRow) ? $endRow : $this->subject->getHighestRow(); + $this->endRow = $endRow ?: $this->subject->getHighestRow(); return $this; } @@ -100,11 +96,9 @@ class RowIterator implements \Iterator * * @param int $row The row number to set the current pointer at * - * @throws PhpSpreadsheetException - * - * @return RowIterator + * @return $this */ - public function seek($row = 1) + public function seek(int $row = 1) { if (($row < $this->startRow) || ($row > $this->endRow)) { throw new PhpSpreadsheetException("Row $row is out of range ({$this->startRow} - {$this->endRow})"); @@ -117,27 +111,23 @@ class RowIterator implements \Iterator /** * Rewind the iterator to the starting row. */ - public function rewind() : void + public function rewind(): void { $this->position = $this->startRow; } /** * Return the current row in this worksheet. - * - * @return Row */ - public function current() : mixed + public function current(): Row { return new Row($this->subject, $this->position); } /** * Return the current iterator key. - * - * @return int */ - public function key() : mixed + public function key(): int { return $this->position; } @@ -145,7 +135,7 @@ class RowIterator implements \Iterator /** * Set the iterator to its next value. */ - public function next() : void + public function next(): void { ++$this->position; } @@ -153,17 +143,15 @@ class RowIterator implements \Iterator /** * Set the iterator to its previous value. */ - public function prev() : void + public function prev(): void { --$this->position; } /** * Indicate if more rows exist in the worksheet range of rows that we're iterating. - * - * @return bool */ - public function valid() : bool + public function valid(): bool { return $this->position <= $this->endRow && $this->position >= $this->startRow; } diff --git a/PhpOffice/PhpSpreadsheet/Worksheet/SheetView.php b/PhpOffice/PhpSpreadsheet/Worksheet/SheetView.php old mode 100755 new mode 100644 index 1728232..13464c9 --- a/PhpOffice/PhpSpreadsheet/Worksheet/SheetView.php +++ b/PhpOffice/PhpSpreadsheet/Worksheet/SheetView.php @@ -11,7 +11,7 @@ class SheetView const SHEETVIEW_PAGE_LAYOUT = 'pageLayout'; const SHEETVIEW_PAGE_BREAK_PREVIEW = 'pageBreakPreview'; - private static $sheetViewTypes = [ + private const SHEET_VIEW_TYPES = [ self::SHEETVIEW_NORMAL, self::SHEETVIEW_PAGE_LAYOUT, self::SHEETVIEW_PAGE_BREAK_PREVIEW, @@ -22,7 +22,7 @@ class SheetView * * Valid values range from 10 to 400. * - * @var int + * @var ?int */ private $zoomScale = 100; @@ -31,10 +31,20 @@ class SheetView * * Valid values range from 10 to 400. * - * @var int + * @var ?int */ private $zoomScaleNormal = 100; + /** + * ShowZeros. + * + * If true, "null" values from a calculation will be shown as "0". This is the default Excel behaviour and can be changed + * with the advanced worksheet option "Show a zero in cells that have zero value" + * + * @var bool + */ + private $showZeros = true; + /** * View. * @@ -54,7 +64,7 @@ class SheetView /** * Get ZoomScale. * - * @return int + * @return ?int */ public function getZoomScale() { @@ -65,18 +75,16 @@ class SheetView * Set ZoomScale. * Valid values range from 10 to 400. * - * @param int $pValue + * @param ?int $zoomScale * - * @throws PhpSpreadsheetException - * - * @return SheetView + * @return $this */ - public function setZoomScale($pValue) + public function setZoomScale($zoomScale) { // Microsoft Office Excel 2007 only allows setting a scale between 10 and 400 via the user interface, // but it is apparently still able to handle any scale >= 1 - if (($pValue >= 1) || $pValue === null) { - $this->zoomScale = $pValue; + if ($zoomScale === null || $zoomScale >= 1) { + $this->zoomScale = $zoomScale; } else { throw new PhpSpreadsheetException('Scale must be greater than or equal to 1.'); } @@ -87,7 +95,7 @@ class SheetView /** * Get ZoomScaleNormal. * - * @return int + * @return ?int */ public function getZoomScaleNormal() { @@ -98,16 +106,14 @@ class SheetView * Set ZoomScale. * Valid values range from 10 to 400. * - * @param int $pValue + * @param ?int $zoomScaleNormal * - * @throws PhpSpreadsheetException - * - * @return SheetView + * @return $this */ - public function setZoomScaleNormal($pValue) + public function setZoomScaleNormal($zoomScaleNormal) { - if (($pValue >= 1) || $pValue === null) { - $this->zoomScaleNormal = $pValue; + if ($zoomScaleNormal === null || $zoomScaleNormal >= 1) { + $this->zoomScaleNormal = $zoomScaleNormal; } else { throw new PhpSpreadsheetException('Scale must be greater than or equal to 1.'); } @@ -115,6 +121,24 @@ class SheetView return $this; } + /** + * Set ShowZeroes setting. + * + * @param bool $showZeros + */ + public function setShowZeros($showZeros): void + { + $this->showZeros = $showZeros; + } + + /** + * @return bool + */ + public function getShowZeros() + { + return $this->showZeros; + } + /** * Get View. * @@ -133,20 +157,18 @@ class SheetView * 'pageLayout' self::SHEETVIEW_PAGE_LAYOUT * 'pageBreakPreview' self::SHEETVIEW_PAGE_BREAK_PREVIEW * - * @param string $pValue + * @param ?string $sheetViewType * - * @throws PhpSpreadsheetException - * - * @return SheetView + * @return $this */ - public function setView($pValue) + public function setView($sheetViewType) { // MS Excel 2007 allows setting the view to 'normal', 'pageLayout' or 'pageBreakPreview' via the user interface - if ($pValue === null) { - $pValue = self::SHEETVIEW_NORMAL; + if ($sheetViewType === null) { + $sheetViewType = self::SHEETVIEW_NORMAL; } - if (in_array($pValue, self::$sheetViewTypes)) { - $this->sheetviewType = $pValue; + if (in_array($sheetViewType, self::SHEET_VIEW_TYPES)) { + $this->sheetviewType = $sheetViewType; } else { throw new PhpSpreadsheetException('Invalid sheetview layout type.'); } diff --git a/PhpOffice/PhpSpreadsheet/Worksheet/Table.php b/PhpOffice/PhpSpreadsheet/Worksheet/Table.php new file mode 100644 index 0000000..4c25259 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Worksheet/Table.php @@ -0,0 +1,585 @@ +|string $range + * A simple string containing a Cell range like 'A1:E10' is permitted + * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), + * or an AddressRange object. + * @param string $name (e.g. Table1) + */ + public function __construct($range = '', string $name = '') + { + $this->style = new TableStyle(); + $this->autoFilter = new AutoFilter($range); + $this->setRange($range); + $this->setName($name); + } + + /** + * Get Table name. + */ + public function getName(): string + { + return $this->name; + } + + /** + * Set Table name. + * + * @throws PhpSpreadsheetException + */ + public function setName(string $name): self + { + $name = trim($name); + + if (!empty($name)) { + if (strlen($name) === 1 && in_array($name, ['C', 'c', 'R', 'r'])) { + throw new PhpSpreadsheetException('The table name is invalid'); + } + if (StringHelper::countCharacters($name) > 255) { + throw new PhpSpreadsheetException('The table name cannot be longer than 255 characters'); + } + // Check for A1 or R1C1 cell reference notation + if ( + preg_match(Coordinate::A1_COORDINATE_REGEX, $name) || + preg_match('/^R\[?\-?[0-9]*\]?C\[?\-?[0-9]*\]?$/i', $name) + ) { + throw new PhpSpreadsheetException('The table name can\'t be the same as a cell reference'); + } + if (!preg_match('/^[\p{L}_\\\\]/iu', $name)) { + throw new PhpSpreadsheetException('The table name must begin a name with a letter, an underscore character (_), or a backslash (\)'); + } + if (!preg_match('/^[\p{L}_\\\\][\p{L}\p{M}0-9\._]+$/iu', $name)) { + throw new PhpSpreadsheetException('The table name contains invalid characters'); + } + + $this->checkForDuplicateTableNames($name, $this->workSheet); + $this->updateStructuredReferences($name); + } + + $this->name = $name; + + return $this; + } + + /** + * @throws PhpSpreadsheetException + */ + private function checkForDuplicateTableNames(string $name, ?Worksheet $worksheet): void + { + // Remember that table names are case-insensitive + $tableName = StringHelper::strToLower($name); + + if ($worksheet !== null && StringHelper::strToLower($this->name) !== $name) { + $spreadsheet = $worksheet->getParent(); + + foreach ($spreadsheet->getWorksheetIterator() as $sheet) { + foreach ($sheet->getTableCollection() as $table) { + if (StringHelper::strToLower($table->getName()) === $tableName && $table != $this) { + throw new PhpSpreadsheetException("Spreadsheet already contains a table named '{$this->name}'"); + } + } + } + } + } + + private function updateStructuredReferences(string $name): void + { + if ($this->workSheet === null || $this->name === null || $this->name === '') { + return; + } + + // Remember that table names are case-insensitive + if (StringHelper::strToLower($this->name) !== StringHelper::strToLower($name)) { + // We need to check all formula cells that might contain fully-qualified Structured References + // that refer to this table, and update those formulae to reference the new table name + $spreadsheet = $this->workSheet->getParent(); + foreach ($spreadsheet->getWorksheetIterator() as $sheet) { + $this->updateStructuredReferencesInCells($sheet, $name); + } + $this->updateStructuredReferencesInNamedFormulae($spreadsheet, $name); + } + } + + private function updateStructuredReferencesInCells(Worksheet $worksheet, string $newName): void + { + $pattern = '/' . preg_quote($this->name) . '\[/mui'; + + foreach ($worksheet->getCoordinates(false) as $coordinate) { + $cell = $worksheet->getCell($coordinate); + if ($cell->getDataType() === DataType::TYPE_FORMULA) { + $formula = $cell->getValue(); + if (preg_match($pattern, $formula) === 1) { + $formula = preg_replace($pattern, "{$newName}[", $formula); + $cell->setValueExplicit($formula, DataType::TYPE_FORMULA); + } + } + } + } + + private function updateStructuredReferencesInNamedFormulae(Spreadsheet $spreadsheet, string $newName): void + { + $pattern = '/' . preg_quote($this->name) . '\[/mui'; + + foreach ($spreadsheet->getNamedFormulae() as $namedFormula) { + $formula = $namedFormula->getValue(); + if (preg_match($pattern, $formula) === 1) { + $formula = preg_replace($pattern, "{$newName}[", $formula); + $namedFormula->setValue($formula); // @phpstan-ignore-line + } + } + } + + /** + * Get show Header Row. + */ + public function getShowHeaderRow(): bool + { + return $this->showHeaderRow; + } + + /** + * Set show Header Row. + */ + public function setShowHeaderRow(bool $showHeaderRow): self + { + $this->showHeaderRow = $showHeaderRow; + + return $this; + } + + /** + * Get show Totals Row. + */ + public function getShowTotalsRow(): bool + { + return $this->showTotalsRow; + } + + /** + * Set show Totals Row. + */ + public function setShowTotalsRow(bool $showTotalsRow): self + { + $this->showTotalsRow = $showTotalsRow; + + return $this; + } + + /** + * Get allow filter. + * If false, autofiltering is disabled for the table, if true it is enabled. + */ + public function getAllowFilter(): bool + { + return $this->allowFilter; + } + + /** + * Set show Autofiltering. + * Disabling autofiltering has the same effect as hiding the filter button on all the columns in the table. + */ + public function setAllowFilter(bool $allowFilter): self + { + $this->allowFilter = $allowFilter; + + return $this; + } + + /** + * Get Table Range. + */ + public function getRange(): string + { + return $this->range; + } + + /** + * Set Table Cell Range. + * + * @param AddressRange|array|string $range + * A simple string containing a Cell range like 'A1:E10' is permitted + * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), + * or an AddressRange object. + */ + public function setRange($range = ''): self + { + // extract coordinate + if ($range !== '') { + [, $range] = Worksheet::extractSheetTitle(Validations::validateCellRange($range), true); + } + if (empty($range)) { + // Discard all column rules + $this->columns = []; + $this->range = ''; + + return $this; + } + + if (strpos($range, ':') === false) { + throw new PhpSpreadsheetException('Table must be set on a range of cells.'); + } + + [$width, $height] = Coordinate::rangeDimension($range); + if ($width < 1 || $height < 2) { + throw new PhpSpreadsheetException('The table range must be at least 1 column and 2 rows'); + } + + $this->range = $range; + $this->autoFilter->setRange($range); + + // Discard any column rules that are no longer valid within this range + [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range); + foreach ($this->columns as $key => $value) { + $colIndex = Coordinate::columnIndexFromString($key); + if (($rangeStart[0] > $colIndex) || ($rangeEnd[0] < $colIndex)) { + unset($this->columns[$key]); + } + } + + return $this; + } + + /** + * Set Table Cell Range to max row. + */ + public function setRangeToMaxRow(): self + { + if ($this->workSheet !== null) { + $thisrange = $this->range; + $range = (string) preg_replace('/\\d+$/', (string) $this->workSheet->getHighestRow(), $thisrange); + if ($range !== $thisrange) { + $this->setRange($range); + } + } + + return $this; + } + + /** + * Get Table's Worksheet. + */ + public function getWorksheet(): ?Worksheet + { + return $this->workSheet; + } + + /** + * Set Table's Worksheet. + */ + public function setWorksheet(?Worksheet $worksheet = null): self + { + if ($this->name !== '' && $worksheet !== null) { + $spreadsheet = $worksheet->getParent(); + $tableName = StringHelper::strToUpper($this->name); + + foreach ($spreadsheet->getWorksheetIterator() as $sheet) { + foreach ($sheet->getTableCollection() as $table) { + if (StringHelper::strToUpper($table->getName()) === $tableName) { + throw new PhpSpreadsheetException("Workbook already contains a table named '{$this->name}'"); + } + } + } + } + + $this->workSheet = $worksheet; + $this->autoFilter->setParent($worksheet); + + return $this; + } + + /** + * Get all Table Columns. + * + * @return Table\Column[] + */ + public function getColumns(): array + { + return $this->columns; + } + + /** + * Validate that the specified column is in the Table range. + * + * @param string $column Column name (e.g. A) + * + * @return int The column offset within the table range + */ + public function isColumnInRange(string $column): int + { + if (empty($this->range)) { + throw new PhpSpreadsheetException('No table range is defined.'); + } + + $columnIndex = Coordinate::columnIndexFromString($column); + [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range); + if (($rangeStart[0] > $columnIndex) || ($rangeEnd[0] < $columnIndex)) { + throw new PhpSpreadsheetException('Column is outside of current table range.'); + } + + return $columnIndex - $rangeStart[0]; + } + + /** + * Get a specified Table Column Offset within the defined Table range. + * + * @param string $column Column name (e.g. A) + * + * @return int The offset of the specified column within the table range + */ + public function getColumnOffset($column): int + { + return $this->isColumnInRange($column); + } + + /** + * Get a specified Table Column. + * + * @param string $column Column name (e.g. A) + */ + public function getColumn($column): Table\Column + { + $this->isColumnInRange($column); + + if (!isset($this->columns[$column])) { + $this->columns[$column] = new Table\Column($column, $this); + } + + return $this->columns[$column]; + } + + /** + * Get a specified Table Column by it's offset. + * + * @param int $columnOffset Column offset within range (starting from 0) + */ + public function getColumnByOffset($columnOffset): Table\Column + { + [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range); + $pColumn = Coordinate::stringFromColumnIndex($rangeStart[0] + $columnOffset); + + return $this->getColumn($pColumn); + } + + /** + * Set Table. + * + * @param string|Table\Column $columnObjectOrString + * A simple string containing a Column ID like 'A' is permitted + */ + public function setColumn($columnObjectOrString): self + { + if ((is_string($columnObjectOrString)) && (!empty($columnObjectOrString))) { + $column = $columnObjectOrString; + } elseif (is_object($columnObjectOrString) && ($columnObjectOrString instanceof Table\Column)) { + $column = $columnObjectOrString->getColumnIndex(); + } else { + throw new PhpSpreadsheetException('Column is not within the table range.'); + } + $this->isColumnInRange($column); + + if (is_string($columnObjectOrString)) { + $this->columns[$columnObjectOrString] = new Table\Column($columnObjectOrString, $this); + } else { + $columnObjectOrString->setTable($this); + $this->columns[$column] = $columnObjectOrString; + } + ksort($this->columns); + + return $this; + } + + /** + * Clear a specified Table Column. + * + * @param string $column Column name (e.g. A) + */ + public function clearColumn($column): self + { + $this->isColumnInRange($column); + + if (isset($this->columns[$column])) { + unset($this->columns[$column]); + } + + return $this; + } + + /** + * Shift an Table Column Rule to a different column. + * + * Note: This method bypasses validation of the destination column to ensure it is within this Table range. + * Nor does it verify whether any column rule already exists at $toColumn, but will simply override any existing value. + * Use with caution. + * + * @param string $fromColumn Column name (e.g. A) + * @param string $toColumn Column name (e.g. B) + */ + public function shiftColumn($fromColumn, $toColumn): self + { + $fromColumn = strtoupper($fromColumn); + $toColumn = strtoupper($toColumn); + + if (($fromColumn !== null) && (isset($this->columns[$fromColumn])) && ($toColumn !== null)) { + $this->columns[$fromColumn]->setTable(); + $this->columns[$fromColumn]->setColumnIndex($toColumn); + $this->columns[$toColumn] = $this->columns[$fromColumn]; + $this->columns[$toColumn]->setTable($this); + unset($this->columns[$fromColumn]); + + ksort($this->columns); + } + + return $this; + } + + /** + * Get table Style. + */ + public function getStyle(): Table\TableStyle + { + return $this->style; + } + + /** + * Set table Style. + */ + public function setStyle(TableStyle $style): self + { + $this->style = $style; + + return $this; + } + + /** + * Get AutoFilter. + */ + public function getAutoFilter(): AutoFilter + { + return $this->autoFilter; + } + + /** + * Set AutoFilter. + */ + public function setAutoFilter(AutoFilter $autoFilter): self + { + $this->autoFilter = $autoFilter; + + return $this; + } + + /** + * Implement PHP __clone to create a deep clone, not just a shallow copy. + */ + public function __clone() + { + $vars = get_object_vars($this); + foreach ($vars as $key => $value) { + if (is_object($value)) { + if ($key === 'workSheet') { + // Detach from worksheet + $this->{$key} = null; + } else { + $this->{$key} = clone $value; + } + } elseif ((is_array($value)) && ($key === 'columns')) { + // The columns array of \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\Table objects + $this->{$key} = []; + foreach ($value as $k => $v) { + $this->{$key}[$k] = clone $v; + // attach the new cloned Column to this new cloned Table object + $this->{$key}[$k]->setTable($this); + } + } else { + $this->{$key} = $value; + } + } + } + + /** + * toString method replicates previous behavior by returning the range if object is + * referenced as a property of its worksheet. + */ + public function __toString() + { + return (string) $this->range; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Worksheet/Table/Column.php b/PhpOffice/PhpSpreadsheet/Worksheet/Table/Column.php new file mode 100644 index 0000000..9fe19ec --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Worksheet/Table/Column.php @@ -0,0 +1,254 @@ +columnIndex = $column; + $this->table = $table; + } + + /** + * Get Table column index as string eg: 'A'. + */ + public function getColumnIndex(): string + { + return $this->columnIndex; + } + + /** + * Set Table column index as string eg: 'A'. + * + * @param string $column Column (e.g. A) + */ + public function setColumnIndex($column): self + { + // Uppercase coordinate + $column = strtoupper($column); + if ($this->table !== null) { + $this->table->isColumnInRange($column); + } + + $this->columnIndex = $column; + + return $this; + } + + /** + * Get show Filter Button. + */ + public function getShowFilterButton(): bool + { + return $this->showFilterButton; + } + + /** + * Set show Filter Button. + */ + public function setShowFilterButton(bool $showFilterButton): self + { + $this->showFilterButton = $showFilterButton; + + return $this; + } + + /** + * Get total Row Label. + */ + public function getTotalsRowLabel(): ?string + { + return $this->totalsRowLabel; + } + + /** + * Set total Row Label. + */ + public function setTotalsRowLabel(string $totalsRowLabel): self + { + $this->totalsRowLabel = $totalsRowLabel; + + return $this; + } + + /** + * Get total Row Function. + */ + public function getTotalsRowFunction(): ?string + { + return $this->totalsRowFunction; + } + + /** + * Set total Row Function. + */ + public function setTotalsRowFunction(string $totalsRowFunction): self + { + $this->totalsRowFunction = $totalsRowFunction; + + return $this; + } + + /** + * Get total Row Formula. + */ + public function getTotalsRowFormula(): ?string + { + return $this->totalsRowFormula; + } + + /** + * Set total Row Formula. + */ + public function setTotalsRowFormula(string $totalsRowFormula): self + { + $this->totalsRowFormula = $totalsRowFormula; + + return $this; + } + + /** + * Get column Formula. + */ + public function getColumnFormula(): ?string + { + return $this->columnFormula; + } + + /** + * Set column Formula. + */ + public function setColumnFormula(string $columnFormula): self + { + $this->columnFormula = $columnFormula; + + return $this; + } + + /** + * Get this Column's Table. + */ + public function getTable(): ?Table + { + return $this->table; + } + + /** + * Set this Column's Table. + */ + public function setTable(?Table $table = null): self + { + $this->table = $table; + + return $this; + } + + public static function updateStructuredReferences(?Worksheet $workSheet, ?string $oldTitle, string $newTitle): void + { + if ($workSheet === null || $oldTitle === null || $oldTitle === '') { + return; + } + + // Remember that table headings are case-insensitive + if (StringHelper::strToLower($oldTitle) !== StringHelper::strToLower($newTitle)) { + // We need to check all formula cells that might contain Structured References that refer + // to this column, and update those formulae to reference the new column text + $spreadsheet = $workSheet->getParent(); + foreach ($spreadsheet->getWorksheetIterator() as $sheet) { + self::updateStructuredReferencesInCells($sheet, $oldTitle, $newTitle); + } + self::updateStructuredReferencesInNamedFormulae($spreadsheet, $oldTitle, $newTitle); + } + } + + private static function updateStructuredReferencesInCells(Worksheet $worksheet, string $oldTitle, string $newTitle): void + { + $pattern = '/\[(@?)' . preg_quote($oldTitle) . '\]/mui'; + + foreach ($worksheet->getCoordinates(false) as $coordinate) { + $cell = $worksheet->getCell($coordinate); + if ($cell->getDataType() === DataType::TYPE_FORMULA) { + $formula = $cell->getValue(); + if (preg_match($pattern, $formula) === 1) { + $formula = preg_replace($pattern, "[$1{$newTitle}]", $formula); + $cell->setValueExplicit($formula, DataType::TYPE_FORMULA); + } + } + } + } + + private static function updateStructuredReferencesInNamedFormulae(Spreadsheet $spreadsheet, string $oldTitle, string $newTitle): void + { + $pattern = '/\[(@?)' . preg_quote($oldTitle) . '\]/mui'; + + foreach ($spreadsheet->getNamedFormulae() as $namedFormula) { + $formula = $namedFormula->getValue(); + if (preg_match($pattern, $formula) === 1) { + $formula = preg_replace($pattern, "[$1{$newTitle}]", $formula); + $namedFormula->setValue($formula); // @phpstan-ignore-line + } + } + } +} diff --git a/PhpOffice/PhpSpreadsheet/Worksheet/Table/TableStyle.php b/PhpOffice/PhpSpreadsheet/Worksheet/Table/TableStyle.php new file mode 100644 index 0000000..78643c7 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Worksheet/Table/TableStyle.php @@ -0,0 +1,230 @@ +theme = $theme; + } + + /** + * Get theme. + */ + public function getTheme(): string + { + return $this->theme; + } + + /** + * Set theme. + */ + public function setTheme(string $theme): self + { + $this->theme = $theme; + + return $this; + } + + /** + * Get show First Column. + */ + public function getShowFirstColumn(): bool + { + return $this->showFirstColumn; + } + + /** + * Set show First Column. + */ + public function setShowFirstColumn(bool $showFirstColumn): self + { + $this->showFirstColumn = $showFirstColumn; + + return $this; + } + + /** + * Get show Last Column. + */ + public function getShowLastColumn(): bool + { + return $this->showLastColumn; + } + + /** + * Set show Last Column. + */ + public function setShowLastColumn(bool $showLastColumn): self + { + $this->showLastColumn = $showLastColumn; + + return $this; + } + + /** + * Get show Row Stripes. + */ + public function getShowRowStripes(): bool + { + return $this->showRowStripes; + } + + /** + * Set show Row Stripes. + */ + public function setShowRowStripes(bool $showRowStripes): self + { + $this->showRowStripes = $showRowStripes; + + return $this; + } + + /** + * Get show Column Stripes. + */ + public function getShowColumnStripes(): bool + { + return $this->showColumnStripes; + } + + /** + * Set show Column Stripes. + */ + public function setShowColumnStripes(bool $showColumnStripes): self + { + $this->showColumnStripes = $showColumnStripes; + + return $this; + } + + /** + * Get this Style's Table. + */ + public function getTable(): ?Table + { + return $this->table; + } + + /** + * Set this Style's Table. + */ + public function setTable(?Table $table = null): self + { + $this->table = $table; + + return $this; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Worksheet/Validations.php b/PhpOffice/PhpSpreadsheet/Worksheet/Validations.php new file mode 100644 index 0000000..5e5cdf7 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Worksheet/Validations.php @@ -0,0 +1,115 @@ +|CellAddress|string $cellAddress Coordinate of the cell as a string, eg: 'C5'; + * or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. + */ + public static function validateCellAddress($cellAddress): string + { + if (is_string($cellAddress)) { + [$worksheet, $address] = Worksheet::extractSheetTitle($cellAddress, true); +// if (!empty($worksheet) && $worksheet !== $this->getTitle()) { +// throw new Exception('Reference is not for this worksheet'); +// } + + return empty($worksheet) ? strtoupper("$address") : $worksheet . '!' . strtoupper("$address"); + } + + if (is_array($cellAddress)) { + $cellAddress = CellAddress::fromColumnRowArray($cellAddress); + } + + return (string) $cellAddress; + } + + /** + * Validate a cell address or cell range. + * + * @param AddressRange|array|CellAddress|int|string $cellRange Coordinate of the cells as a string, eg: 'C5:F12'; + * or as an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 12]), + * or as a CellAddress or AddressRange object. + */ + public static function validateCellOrCellRange($cellRange): string + { + if (is_string($cellRange) || is_numeric($cellRange)) { + // Convert a single column reference like 'A' to 'A:A', + // a single row reference like '1' to '1:1' + $cellRange = (string) preg_replace('/^([A-Z]+|\d+)$/', '${1}:${1}', (string) $cellRange); + } elseif (is_object($cellRange) && $cellRange instanceof CellAddress) { + $cellRange = new CellRange($cellRange, $cellRange); + } + + return self::validateCellRange($cellRange); + } + + /** + * Validate a cell range. + * + * @param AddressRange|array|string $cellRange Coordinate of the cells as a string, eg: 'C5:F12'; + * or as an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 12]), + * or as an AddressRange object. + */ + public static function validateCellRange($cellRange): string + { + if (is_string($cellRange)) { + [$worksheet, $addressRange] = Worksheet::extractSheetTitle($cellRange, true); + + // Convert Column ranges like 'A:C' to 'A1:C1048576' + // or Row ranges like '1:3' to 'A1:XFD3' + $addressRange = (string) preg_replace( + ['/^([A-Z]+):([A-Z]+)$/i', '/^(\\d+):(\\d+)$/'], + ['${1}1:${2}1048576', 'A${1}:XFD${2}'], + $addressRange + ); + + return empty($worksheet) ? strtoupper($addressRange) : $worksheet . '!' . strtoupper($addressRange); + } + + if (is_array($cellRange)) { + switch (count($cellRange)) { + case 2: + $from = [$cellRange[0], $cellRange[1]]; + $to = [$cellRange[0], $cellRange[1]]; + + break; + case 4: + $from = [$cellRange[0], $cellRange[1]]; + $to = [$cellRange[2], $cellRange[3]]; + + break; + default: + throw new SpreadsheetException('CellRange array length must be 2 or 4'); + } + $cellRange = new CellRange(CellAddress::fromColumnRowArray($from), CellAddress::fromColumnRowArray($to)); + } + + return (string) $cellRange; + } + + public static function definedNameToCoordinate(string $coordinate, Worksheet $worksheet): string + { + // Uppercase coordinate + $coordinate = strtoupper($coordinate); + // Eliminate leading equal sign + $testCoordinate = (string) preg_replace('/^=/', '', $coordinate); + $defined = $worksheet->getParent()->getDefinedName($testCoordinate, $worksheet); + if ($defined !== null) { + if ($defined->getWorksheet() === $worksheet && !$defined->isFormula()) { + $coordinate = (string) preg_replace('/^=/', '', $defined->getValue()); + } + } + + return $coordinate; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Worksheet/Worksheet.php b/PhpOffice/PhpSpreadsheet/Worksheet/Worksheet.php old mode 100755 new mode 100644 index 7da5f69..d71bc38 --- a/PhpOffice/PhpSpreadsheet/Worksheet/Worksheet.php +++ b/PhpOffice/PhpSpreadsheet/Worksheet/Worksheet.php @@ -4,7 +4,11 @@ namespace PhpOffice\PhpSpreadsheet\Worksheet; use ArrayObject; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; +use PhpOffice\PhpSpreadsheet\Calculation\Functions; +use PhpOffice\PhpSpreadsheet\Cell\AddressRange; use PhpOffice\PhpSpreadsheet\Cell\Cell; +use PhpOffice\PhpSpreadsheet\Cell\CellAddress; +use PhpOffice\PhpSpreadsheet\Cell\CellRange; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Cell\DataType; use PhpOffice\PhpSpreadsheet\Cell\DataValidation; @@ -13,9 +17,9 @@ use PhpOffice\PhpSpreadsheet\Chart\Chart; use PhpOffice\PhpSpreadsheet\Collection\Cells; use PhpOffice\PhpSpreadsheet\Collection\CellsFactory; use PhpOffice\PhpSpreadsheet\Comment; +use PhpOffice\PhpSpreadsheet\DefinedName; use PhpOffice\PhpSpreadsheet\Exception; use PhpOffice\PhpSpreadsheet\IComparable; -use PhpOffice\PhpSpreadsheet\NamedRange; use PhpOffice\PhpSpreadsheet\ReferenceHelper; use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\Shared; @@ -28,14 +32,20 @@ use PhpOffice\PhpSpreadsheet\Style\Style; class Worksheet implements IComparable { // Break types - const BREAK_NONE = 0; - const BREAK_ROW = 1; - const BREAK_COLUMN = 2; + public const BREAK_NONE = 0; + public const BREAK_ROW = 1; + public const BREAK_COLUMN = 2; // Sheet state - const SHEETSTATE_VISIBLE = 'visible'; - const SHEETSTATE_HIDDEN = 'hidden'; - const SHEETSTATE_VERYHIDDEN = 'veryHidden'; + public const SHEETSTATE_VISIBLE = 'visible'; + public const SHEETSTATE_HIDDEN = 'hidden'; + public const SHEETSTATE_VERYHIDDEN = 'veryHidden'; + + public const MERGE_CELL_CONTENT_EMPTY = 'empty'; + public const MERGE_CELL_CONTENT_HIDE = 'hide'; + public const MERGE_CELL_CONTENT_MERGE = 'merge'; + + protected const SHEET_NAME_REQUIRES_NO_QUOTES = '/^[_\p{L}][_\p{L}\p{N}]*$/mui'; /** * Maximum 31 characters allowed for sheet title. @@ -96,16 +106,23 @@ class Worksheet implements IComparable /** * Collection of drawings. * - * @var BaseDrawing[] + * @var ArrayObject */ private $drawingCollection; /** * Collection of Chart objects. * - * @var Chart[] + * @var ArrayObject */ - private $chartCollection = []; + private $chartCollection; + + /** + * Collection of Table objects. + * + * @var ArrayObject + */ + private $tableCollection; /** * Worksheet title. @@ -170,31 +187,24 @@ class Worksheet implements IComparable */ private $conditionalStylesCollection = []; - /** - * Is the current cell collection sorted already? - * - * @var bool - */ - private $cellCollectionIsSorted = false; - /** * Collection of breaks. * - * @var array + * @var int[] */ private $breaks = []; /** * Collection of merged cell ranges. * - * @var array + * @var string[] */ private $mergeCells = []; /** * Collection of protected cell ranges. * - * @var array + * @var string[] */ private $protectedCells = []; @@ -278,9 +288,9 @@ class Worksheet implements IComparable /** * Cached highest column. * - * @var string + * @var int */ - private $cachedHighestColumn = 'A'; + private $cachedHighestColumn = 1; /** * Cached highest row. @@ -313,7 +323,7 @@ class Worksheet implements IComparable /** * Tab color. * - * @var Color + * @var null|Color */ private $tabColor; @@ -341,14 +351,13 @@ class Worksheet implements IComparable /** * Create a new worksheet. * - * @param Spreadsheet $parent - * @param string $pTitle + * @param string $title */ - public function __construct(Spreadsheet $parent = null, $pTitle = 'Worksheet') + public function __construct(?Spreadsheet $parent = null, $title = 'Worksheet') { // Set parent and title $this->parent = $parent; - $this->setTitle($pTitle, false); + $this->setTitle($title, false); // setTitle can change $pTitle $this->setCodeName($this->getTitle()); $this->setSheetState(self::SHEETSTATE_VISIBLE); @@ -363,29 +372,34 @@ class Worksheet implements IComparable // Set sheet view $this->sheetView = new SheetView(); // Drawing collection - $this->drawingCollection = new \ArrayObject(); + $this->drawingCollection = new ArrayObject(); // Chart collection - $this->chartCollection = new \ArrayObject(); + $this->chartCollection = new ArrayObject(); // Protection $this->protection = new Protection(); // Default row dimension $this->defaultRowDimension = new RowDimension(null); // Default column dimension $this->defaultColumnDimension = new ColumnDimension(null); - $this->autoFilter = new AutoFilter(null, $this); + // AutoFilter + $this->autoFilter = new AutoFilter('', $this); + // Table collection + $this->tableCollection = new ArrayObject(); } /** * Disconnect all cells from this Worksheet object, * typically so that the worksheet object can be unset. */ - public function disconnectCells() + public function disconnectCells(): void { if ($this->cellCollection !== null) { $this->cellCollection->unsetWorksheetCells(); + // @phpstan-ignore-next-line $this->cellCollection = null; } // detach ourself from the workbook, so that it can then delete this worksheet successfully + // @phpstan-ignore-next-line $this->parent = null; } @@ -397,6 +411,7 @@ class Worksheet implements IComparable Calculation::getInstance($this->parent)->clearCalculationCacheForWorksheet($this->title); $this->disconnectCells(); + $this->rowDimensions = []; } /** @@ -422,55 +437,53 @@ class Worksheet implements IComparable /** * Check sheet code name for valid Excel syntax. * - * @param string $pValue The string to check - * - * @throws Exception + * @param string $sheetCodeName The string to check * * @return string The valid string */ - private static function checkSheetCodeName($pValue) + private static function checkSheetCodeName($sheetCodeName) { - $CharCount = Shared\StringHelper::countCharacters($pValue); - if ($CharCount == 0) { + $charCount = Shared\StringHelper::countCharacters($sheetCodeName); + if ($charCount == 0) { throw new Exception('Sheet code name cannot be empty.'); } // Some of the printable ASCII characters are invalid: * : / \ ? [ ] and first and last characters cannot be a "'" - if ((str_replace(self::$invalidCharacters, '', $pValue) !== $pValue) || - (Shared\StringHelper::substring($pValue, -1, 1) == '\'') || - (Shared\StringHelper::substring($pValue, 0, 1) == '\'')) { + if ( + (str_replace(self::$invalidCharacters, '', $sheetCodeName) !== $sheetCodeName) || + (Shared\StringHelper::substring($sheetCodeName, -1, 1) == '\'') || + (Shared\StringHelper::substring($sheetCodeName, 0, 1) == '\'') + ) { throw new Exception('Invalid character found in sheet code name'); } // Enforce maximum characters allowed for sheet title - if ($CharCount > self::SHEET_TITLE_MAXIMUM_LENGTH) { + if ($charCount > self::SHEET_TITLE_MAXIMUM_LENGTH) { throw new Exception('Maximum ' . self::SHEET_TITLE_MAXIMUM_LENGTH . ' characters allowed in sheet code name.'); } - return $pValue; + return $sheetCodeName; } /** * Check sheet title for valid Excel syntax. * - * @param string $pValue The string to check - * - * @throws Exception + * @param string $sheetTitle The string to check * * @return string The valid string */ - private static function checkSheetTitle($pValue) + private static function checkSheetTitle($sheetTitle) { // Some of the printable ASCII characters are invalid: * : / \ ? [ ] - if (str_replace(self::$invalidCharacters, '', $pValue) !== $pValue) { + if (str_replace(self::$invalidCharacters, '', $sheetTitle) !== $sheetTitle) { throw new Exception('Invalid character found in sheet title'); } // Enforce maximum characters allowed for sheet title - if (Shared\StringHelper::countCharacters($pValue) > self::SHEET_TITLE_MAXIMUM_LENGTH) { + if (Shared\StringHelper::countCharacters($sheetTitle) > self::SHEET_TITLE_MAXIMUM_LENGTH) { throw new Exception('Maximum ' . self::SHEET_TITLE_MAXIMUM_LENGTH . ' characters allowed in sheet title.'); } - return $pValue; + return $sheetTitle; } /** @@ -536,7 +549,7 @@ class Worksheet implements IComparable /** * Get collection of drawings. * - * @return BaseDrawing[] + * @return ArrayObject */ public function getDrawingCollection() { @@ -546,7 +559,7 @@ class Worksheet implements IComparable /** * Get collection of charts. * - * @return Chart[] + * @return ArrayObject */ public function getChartCollection() { @@ -556,22 +569,22 @@ class Worksheet implements IComparable /** * Add chart. * - * @param Chart $pChart - * @param null|int $iChartIndex Index where chart should go (0,1,..., or null for last) + * @param null|int $chartIndex Index where chart should go (0,1,..., or null for last) * * @return Chart */ - public function addChart(Chart $pChart, $iChartIndex = null) + public function addChart(Chart $chart, $chartIndex = null) { - $pChart->setWorksheet($this); - if ($iChartIndex === null) { - $this->chartCollection[] = $pChart; + $chart->setWorksheet($this); + if ($chartIndex === null) { + $this->chartCollection[] = $chart; } else { // Insert the chart at the requested index - array_splice($this->chartCollection, $iChartIndex, 0, [$pChart]); + // @phpstan-ignore-next-line + array_splice(/** @scrutinizer ignore-type */ $this->chartCollection, $chartIndex, 0, [$chart]); } - return $pChart; + return $chart; } /** @@ -587,7 +600,7 @@ class Worksheet implements IComparable /** * Get a chart by its index position. * - * @param string $index Chart index position + * @param ?string $index Chart index position * * @return Chart|false */ @@ -647,14 +660,12 @@ class Worksheet implements IComparable /** * Refresh column dimensions. * - * @return Worksheet + * @return $this */ public function refreshColumnDimensions() { - $currentColumnDimensions = $this->getColumnDimensions(); $newColumnDimensions = []; - - foreach ($currentColumnDimensions as $objColumnDimension) { + foreach ($this->getColumnDimensions() as $objColumnDimension) { $newColumnDimensions[$objColumnDimension->getColumnIndex()] = $objColumnDimension; } @@ -666,14 +677,12 @@ class Worksheet implements IComparable /** * Refresh row dimensions. * - * @return Worksheet + * @return $this */ public function refreshRowDimensions() { - $currentRowDimensions = $this->getRowDimensions(); $newRowDimensions = []; - - foreach ($currentRowDimensions as $objRowDimension) { + foreach ($this->getRowDimensions() as $objRowDimension) { $newRowDimensions[$objRowDimension->getRowIndex()] = $objRowDimension; } @@ -690,7 +699,7 @@ class Worksheet implements IComparable public function calculateWorksheetDimension() { // Return - return 'A1' . ':' . $this->getHighestColumn() . $this->getHighestRow(); + return 'A1:' . $this->getHighestColumn() . $this->getHighestRow(); } /** @@ -701,13 +710,13 @@ class Worksheet implements IComparable public function calculateWorksheetDataDimension() { // Return - return 'A1' . ':' . $this->getHighestDataColumn() . $this->getHighestDataRow(); + return 'A1:' . $this->getHighestDataColumn() . $this->getHighestDataRow(); } /** * Calculate widths for auto-size columns. * - * @return Worksheet; + * @return $this */ public function calculateColumnWidths() { @@ -729,9 +738,19 @@ class Worksheet implements IComparable } } + $autoFilterRange = $autoFilterFirstRowRange = $this->autoFilter->getRange(); + if (!empty($autoFilterRange)) { + $autoFilterRangeBoundaries = Coordinate::rangeBoundaries($autoFilterRange); + $autoFilterFirstRowRange = (string) new CellRange( + CellAddress::fromColumnAndRow($autoFilterRangeBoundaries[0][0], $autoFilterRangeBoundaries[0][1]), + CellAddress::fromColumnAndRow($autoFilterRangeBoundaries[1][0], $autoFilterRangeBoundaries[0][1]) + ); + } + // loop through all cells in the worksheet foreach ($this->getCoordinates(false) as $coordinate) { - $cell = $this->getCell($coordinate, false); + $cell = $this->getCellOrNull($coordinate); + if ($cell !== null && isset($autoSizes[$this->cellCollection->getCurrentColumn()])) { //Determine if cell is in merge range $isMerged = isset($isMergeCell[$this->cellCollection->getCurrentCoordinate()]); @@ -739,33 +758,48 @@ class Worksheet implements IComparable //By default merged cells should be ignored $isMergedButProceed = false; - //The only exception is if it's a merge range value cell of a 'vertical' randge (1 column wide) + //The only exception is if it's a merge range value cell of a 'vertical' range (1 column wide) if ($isMerged && $cell->isMergeRangeValueCell()) { $range = $cell->getMergeRange(); $rangeBoundaries = Coordinate::rangeDimension($range); - if ($rangeBoundaries[0] == 1) { + if ($rangeBoundaries[0] === 1) { $isMergedButProceed = true; } } - // Determine width if cell does not participate in a merge or does and is a value cell of 1-column wide range + // Determine width if cell is not part of a merge or does and is a value cell of 1-column wide range if (!$isMerged || $isMergedButProceed) { + // Determine if we need to make an adjustment for the first row in an AutoFilter range that + // has a column filter dropdown + $filterAdjustment = false; + if (!empty($autoFilterRange) && $cell->isInRange($autoFilterFirstRowRange)) { + $filterAdjustment = true; + } + + $indentAdjustment = $cell->getStyle()->getAlignment()->getIndent(); + // Calculated value // To formatted string $cellValue = NumberFormat::toFormattedString( $cell->getCalculatedValue(), - $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode() + $this->getParent()->getCellXfByIndex($cell->getXfIndex()) + ->getNumberFormat()->getFormatCode() ); - $autoSizes[$this->cellCollection->getCurrentColumn()] = max( - (float) $autoSizes[$this->cellCollection->getCurrentColumn()], - (float) Shared\Font::calculateColumnWidth( - $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont(), - $cellValue, - $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getAlignment()->getTextRotation(), - $this->getParent()->getDefaultStyle()->getFont() - ) - ); + if ($cellValue !== null && $cellValue !== '') { + $autoSizes[$this->cellCollection->getCurrentColumn()] = max( + (float) $autoSizes[$this->cellCollection->getCurrentColumn()], + (float) Shared\Font::calculateColumnWidth( + $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont(), + $cellValue, + $this->getParent()->getCellXfByIndex($cell->getXfIndex()) + ->getAlignment()->getTextRotation(), + $this->getParent()->getDefaultStyle()->getFont(), + $filterAdjustment, + $indentAdjustment + ) + ); + } } } } @@ -795,16 +829,14 @@ class Worksheet implements IComparable /** * Re-bind parent. * - * @param Spreadsheet $parent - * - * @return Worksheet + * @return $this */ public function rebindParent(Spreadsheet $parent) { if ($this->parent !== null) { - $namedRanges = $this->parent->getNamedRanges(); - foreach ($namedRanges as $namedRange) { - $parent->addNamedRange($namedRange); + $definedNames = $this->parent->getDefinedNames(); + foreach ($definedNames as $definedName) { + $parent->addDefinedName($definedName); } $this->parent->removeSheetByIndex( @@ -829,7 +861,7 @@ class Worksheet implements IComparable /** * Set title. * - * @param string $pValue String containing the dimension of this worksheet + * @param string $title String containing the dimension of this worksheet * @param bool $updateFormulaCellReferences Flag indicating whether cell references in formulae should * be updated to reflect the new sheet name. * This should be left as the default true, unless you are @@ -838,12 +870,12 @@ class Worksheet implements IComparable * @param bool $validate False to skip validation of new title. WARNING: This should only be set * at parse time (by Readers), where titles can be assumed to be valid. * - * @return Worksheet + * @return $this */ - public function setTitle($pValue, $updateFormulaCellReferences = true, $validate = true) + public function setTitle($title, $updateFormulaCellReferences = true, $validate = true) { // Is this a 'rename' or not? - if ($this->getTitle() == $pValue) { + if ($this->getTitle() == $title) { return $this; } @@ -852,37 +884,37 @@ class Worksheet implements IComparable if ($validate) { // Syntax check - self::checkSheetTitle($pValue); + self::checkSheetTitle($title); if ($this->parent) { // Is there already such sheet name? - if ($this->parent->sheetNameExists($pValue)) { + if ($this->parent->sheetNameExists($title)) { // Use name, but append with lowest possible integer - if (Shared\StringHelper::countCharacters($pValue) > 29) { - $pValue = Shared\StringHelper::substring($pValue, 0, 29); + if (Shared\StringHelper::countCharacters($title) > 29) { + $title = Shared\StringHelper::substring($title, 0, 29); } $i = 1; - while ($this->parent->sheetNameExists($pValue . ' ' . $i)) { + while ($this->parent->sheetNameExists($title . ' ' . $i)) { ++$i; if ($i == 10) { - if (Shared\StringHelper::countCharacters($pValue) > 28) { - $pValue = Shared\StringHelper::substring($pValue, 0, 28); + if (Shared\StringHelper::countCharacters($title) > 28) { + $title = Shared\StringHelper::substring($title, 0, 28); } } elseif ($i == 100) { - if (Shared\StringHelper::countCharacters($pValue) > 27) { - $pValue = Shared\StringHelper::substring($pValue, 0, 27); + if (Shared\StringHelper::countCharacters($title) > 27) { + $title = Shared\StringHelper::substring($title, 0, 27); } } } - $pValue .= " $i"; + $title .= " $i"; } } } // Set title - $this->title = $pValue; + $this->title = $title; $this->dirty = true; if ($this->parent && $this->parent->getCalculationEngine()) { @@ -891,7 +923,7 @@ class Worksheet implements IComparable $this->parent->getCalculationEngine() ->renameCalculationCacheForWorksheet($oldTitle, $newTitle); if ($updateFormulaCellReferences) { - ReferenceHelper::getInstance()->updateNamedFormulas($this->parent, $oldTitle, $newTitle); + ReferenceHelper::getInstance()->updateNamedFormulae($this->parent, $oldTitle, $newTitle); } } @@ -913,7 +945,7 @@ class Worksheet implements IComparable * * @param string $value Sheet state (visible, hidden, veryHidden) * - * @return Worksheet + * @return $this */ public function setSheetState($value) { @@ -935,13 +967,11 @@ class Worksheet implements IComparable /** * Set page setup. * - * @param PageSetup $pValue - * - * @return Worksheet + * @return $this */ - public function setPageSetup(PageSetup $pValue) + public function setPageSetup(PageSetup $pageSetup) { - $this->pageSetup = $pValue; + $this->pageSetup = $pageSetup; return $this; } @@ -959,13 +989,11 @@ class Worksheet implements IComparable /** * Set page margins. * - * @param PageMargins $pValue - * - * @return Worksheet + * @return $this */ - public function setPageMargins(PageMargins $pValue) + public function setPageMargins(PageMargins $pageMargins) { - $this->pageMargins = $pValue; + $this->pageMargins = $pageMargins; return $this; } @@ -983,13 +1011,11 @@ class Worksheet implements IComparable /** * Set page header/footer. * - * @param HeaderFooter $pValue - * - * @return Worksheet + * @return $this */ - public function setHeaderFooter(HeaderFooter $pValue) + public function setHeaderFooter(HeaderFooter $headerFooter) { - $this->headerFooter = $pValue; + $this->headerFooter = $headerFooter; return $this; } @@ -1007,13 +1033,11 @@ class Worksheet implements IComparable /** * Set sheet view. * - * @param SheetView $pValue - * - * @return Worksheet + * @return $this */ - public function setSheetView(SheetView $pValue) + public function setSheetView(SheetView $sheetView) { - $this->sheetView = $pValue; + $this->sheetView = $sheetView; return $this; } @@ -1031,13 +1055,11 @@ class Worksheet implements IComparable /** * Set Protection. * - * @param Protection $pValue - * - * @return Worksheet + * @return $this */ - public function setProtection(Protection $pValue) + public function setProtection(Protection $protection) { - $this->protection = $pValue; + $this->protection = $protection; $this->dirty = true; return $this; @@ -1046,15 +1068,15 @@ class Worksheet implements IComparable /** * Get highest worksheet column. * - * @param string $row Return the data highest column for the specified row, + * @param null|int|string $row Return the data highest column for the specified row, * or the highest column of any row if no row number is passed * * @return string Highest column name */ public function getHighestColumn($row = null) { - if ($row == null) { - return $this->cachedHighestColumn; + if ($row === null) { + return Coordinate::stringFromColumnIndex($this->cachedHighestColumn); } return $this->getHighestDataColumn($row); @@ -1063,7 +1085,7 @@ class Worksheet implements IComparable /** * Get highest worksheet column that contains data. * - * @param string $row Return the highest data column for the specified row, + * @param null|int|string $row Return the highest data column for the specified row, * or the highest data column of any row if no row number is passed * * @return string Highest column name that contains data @@ -1076,14 +1098,14 @@ class Worksheet implements IComparable /** * Get highest worksheet row. * - * @param string $column Return the highest data row for the specified column, + * @param null|string $column Return the highest data row for the specified column, * or the highest row of any column if no column letter is passed * * @return int Highest row number */ public function getHighestRow($column = null) { - if ($column == null) { + if ($column === null) { return $this->cachedHighestRow; } @@ -1093,7 +1115,7 @@ class Worksheet implements IComparable /** * Get highest worksheet row that contains data. * - * @param string $column Return the highest data row for the specified column, + * @param null|string $column Return the highest data row for the specified column, * or the highest data row of any column if no column letter is passed * * @return int Highest row number that contains data @@ -1116,14 +1138,16 @@ class Worksheet implements IComparable /** * Set a cell value. * - * @param string $pCoordinate Coordinate of the cell, eg: 'A1' - * @param mixed $pValue Value of the cell + * @param array|CellAddress|string $coordinate Coordinate of the cell as a string, eg: 'C5'; + * or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. + * @param mixed $value Value for the cell * - * @return Worksheet + * @return $this */ - public function setCellValue($pCoordinate, $pValue) + public function setCellValue($coordinate, $value) { - $this->getCell($pCoordinate)->setValue($pValue); + $cellAddress = Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate)); + $this->getCell($cellAddress)->setValue($value); return $this; } @@ -1131,15 +1155,20 @@ class Worksheet implements IComparable /** * Set a cell value by using numeric cell coordinates. * + * @deprecated 1.23.0 + * Use the setCellValue() method with a cell address such as 'C5' instead;, + * or passing in an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. + * @see Worksheet::setCellValue() + * * @param int $columnIndex Numeric column coordinate of the cell * @param int $row Numeric row coordinate of the cell * @param mixed $value Value of the cell * - * @return Worksheet + * @return $this */ public function setCellValueByColumnAndRow($columnIndex, $row, $value) { - $this->getCellByColumnAndRow($columnIndex, $row)->setValue($value); + $this->getCell(Coordinate::stringFromColumnIndex($columnIndex) . $row)->setValue($value); return $this; } @@ -1147,16 +1176,24 @@ class Worksheet implements IComparable /** * Set a cell value. * - * @param string $pCoordinate Coordinate of the cell, eg: 'A1' - * @param mixed $pValue Value of the cell - * @param string $pDataType Explicit data type, see DataType::TYPE_* + * @param array|CellAddress|string $coordinate Coordinate of the cell as a string, eg: 'C5'; + * or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. + * @param mixed $value Value of the cell + * @param string $dataType Explicit data type, see DataType::TYPE_* + * Note that PhpSpreadsheet does not validate that the value and datatype are consistent, in using this + * method, then it is your responsibility as an end-user developer to validate that the value and + * the datatype match. + * If you do mismatch value and datatpe, then the value you enter may be changed to match the datatype + * that you specify. * - * @return Worksheet + * @see DataType + * + * @return $this */ - public function setCellValueExplicit($pCoordinate, $pValue, $pDataType) + public function setCellValueExplicit($coordinate, $value, $dataType) { - // Set value - $this->getCell($pCoordinate)->setValueExplicit($pValue, $pDataType); + $cellAddress = Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate)); + $this->getCell($cellAddress)->setValueExplicit($value, $dataType); return $this; } @@ -1164,16 +1201,28 @@ class Worksheet implements IComparable /** * Set a cell value by using numeric cell coordinates. * + * @deprecated 1.23.0 + * Use the setCellValueExplicit() method with a cell address such as 'C5' instead;, + * or passing in an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. + * @see Worksheet::setCellValueExplicit() + * * @param int $columnIndex Numeric column coordinate of the cell * @param int $row Numeric row coordinate of the cell * @param mixed $value Value of the cell * @param string $dataType Explicit data type, see DataType::TYPE_* + * Note that PhpSpreadsheet does not validate that the value and datatype are consistent, in using this + * method, then it is your responsibility as an end-user developer to validate that the value and + * the datatype match. + * If you do mismatch value and datatpe, then the value you enter may be changed to match the datatype + * that you specify. * - * @return Worksheet + * @see DataType + * + * @return $this */ public function setCellValueExplicitByColumnAndRow($columnIndex, $row, $value, $dataType) { - $this->getCellByColumnAndRow($columnIndex, $row)->setValueExplicit($value, $dataType); + $this->getCell(Coordinate::stringFromColumnIndex($columnIndex) . $row)->setValueExplicit($value, $dataType); return $this; } @@ -1181,108 +1230,171 @@ class Worksheet implements IComparable /** * Get cell at a specific coordinate. * - * @param string $pCoordinate Coordinate of the cell, eg: 'A1' - * @param bool $createIfNotExists Flag indicating whether a new cell should be created if it doesn't - * already exist, or a null should be returned instead + * @param array|CellAddress|string $coordinate Coordinate of the cell as a string, eg: 'C5'; + * or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. * - * @throws Exception - * - * @return null|Cell Cell that was found/created or null + * @return Cell Cell that was found or created + * WARNING: Because the cell collection can be cached to reduce memory, it only allows one + * "active" cell at a time in memory. If you assign that cell to a variable, then select + * another cell using getCell() or any of its variants, the newly selected cell becomes + * the "active" cell, and any previous assignment becomes a disconnected reference because + * the active cell has changed. */ - public function getCell($pCoordinate, $createIfNotExists = true) + public function getCell($coordinate): Cell { - // Uppercase coordinate - $pCoordinateUpper = strtoupper($pCoordinate); + $cellAddress = Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate)); - // Check cell collection - if ($this->cellCollection->has($pCoordinateUpper)) { - return $this->cellCollection->get($pCoordinateUpper); + // Shortcut for increased performance for the vast majority of simple cases + if ($this->cellCollection->has($cellAddress)) { + /** @var Cell $cell */ + $cell = $this->cellCollection->get($cellAddress); + + return $cell; } + /** @var Worksheet $sheet */ + [$sheet, $finalCoordinate] = $this->getWorksheetAndCoordinate($cellAddress); + $cell = $sheet->cellCollection->get($finalCoordinate); + + return $cell ?? $sheet->createNewCell($finalCoordinate); + } + + /** + * Get the correct Worksheet and coordinate from a coordinate that may + * contains reference to another sheet or a named range. + * + * @return array{0: Worksheet, 1: string} + */ + private function getWorksheetAndCoordinate(string $coordinate): array + { + $sheet = null; + $finalCoordinate = null; + // Worksheet reference? - if (strpos($pCoordinate, '!') !== false) { - $worksheetReference = self::extractSheetTitle($pCoordinate, true); + if (strpos($coordinate, '!') !== false) { + $worksheetReference = self::extractSheetTitle($coordinate, true); - return $this->parent->getSheetByName($worksheetReference[0])->getCell(strtoupper($worksheetReference[1]), $createIfNotExists); - } + $sheet = $this->parent->getSheetByName($worksheetReference[0]); + $finalCoordinate = strtoupper($worksheetReference[1]); - // Named range? - if ((!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $pCoordinate, $matches)) && - (preg_match('/^' . Calculation::CALCULATION_REGEXP_NAMEDRANGE . '$/i', $pCoordinate, $matches))) { - $namedRange = NamedRange::resolveRange($pCoordinate, $this); + if ($sheet === null) { + throw new Exception('Sheet not found for name: ' . $worksheetReference[0]); + } + } elseif ( + !preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $coordinate) && + preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/iu', $coordinate) + ) { + // Named range? + $namedRange = $this->validateNamedRange($coordinate, true); if ($namedRange !== null) { - $pCoordinate = $namedRange->getRange(); + $sheet = $namedRange->getWorksheet(); + if ($sheet === null) { + throw new Exception('Sheet not found for named range: ' . $namedRange->getName()); + } - return $namedRange->getWorksheet()->getCell($pCoordinate, $createIfNotExists); + /** @phpstan-ignore-next-line */ + $cellCoordinate = ltrim(substr($namedRange->getValue(), strrpos($namedRange->getValue(), '!')), '!'); + $finalCoordinate = str_replace('$', '', $cellCoordinate); } } - if (Coordinate::coordinateIsRange($pCoordinate)) { - throw new Exception('Cell coordinate can not be a range of cells.'); - } elseif (strpos($pCoordinate, '$') !== false) { + if ($sheet === null || $finalCoordinate === null) { + $sheet = $this; + $finalCoordinate = strtoupper($coordinate); + } + + if (Coordinate::coordinateIsRange($finalCoordinate)) { + throw new Exception('Cell coordinate string can not be a range of cells.'); + } elseif (strpos($finalCoordinate, '$') !== false) { throw new Exception('Cell coordinate must not be absolute.'); } - // Create new cell object, if required - return $createIfNotExists ? $this->createNewCell($pCoordinateUpper) : null; + return [$sheet, $finalCoordinate]; + } + + /** + * Get an existing cell at a specific coordinate, or null. + * + * @param string $coordinate Coordinate of the cell, eg: 'A1' + * + * @return null|Cell Cell that was found or null + */ + private function getCellOrNull($coordinate): ?Cell + { + // Check cell collection + if ($this->cellCollection->has($coordinate)) { + return $this->cellCollection->get($coordinate); + } + + return null; } /** * Get cell at a specific coordinate by using numeric cell coordinates. * + * @deprecated 1.23.0 + * Use the getCell() method with a cell address such as 'C5' instead;, + * or passing in an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. + * @see Worksheet::getCell() + * * @param int $columnIndex Numeric column coordinate of the cell * @param int $row Numeric row coordinate of the cell - * @param bool $createIfNotExists Flag indicating whether a new cell should be created if it doesn't - * already exist, or a null should be returned instead * - * @return null|Cell Cell that was found/created or null + * @return Cell Cell that was found/created or null + * WARNING: Because the cell collection can be cached to reduce memory, it only allows one + * "active" cell at a time in memory. If you assign that cell to a variable, then select + * another cell using getCell() or any of its variants, the newly selected cell becomes + * the "active" cell, and any previous assignment becomes a disconnected reference because + * the active cell has changed. */ - public function getCellByColumnAndRow($columnIndex, $row, $createIfNotExists = true) + public function getCellByColumnAndRow($columnIndex, $row): Cell { - $columnLetter = Coordinate::stringFromColumnIndex($columnIndex); - $coordinate = $columnLetter . $row; - - if ($this->cellCollection->has($coordinate)) { - return $this->cellCollection->get($coordinate); - } - - // Create new cell object, if required - return $createIfNotExists ? $this->createNewCell($coordinate) : null; + return $this->getCell(Coordinate::stringFromColumnIndex($columnIndex) . $row); } /** * Create a new cell at the specified coordinate. * - * @param string $pCoordinate Coordinate of the cell + * @param string $coordinate Coordinate of the cell * * @return Cell Cell that was created + * WARNING: Because the cell collection can be cached to reduce memory, it only allows one + * "active" cell at a time in memory. If you assign that cell to a variable, then select + * another cell using getCell() or any of its variants, the newly selected cell becomes + * the "active" cell, and any previous assignment becomes a disconnected reference because + * the active cell has changed. */ - private function createNewCell($pCoordinate) + public function createNewCell($coordinate): Cell { + [$column, $row, $columnString] = Coordinate::indexesFromString($coordinate); $cell = new Cell(null, DataType::TYPE_NULL, $this); - $this->cellCollection->add($pCoordinate, $cell); - $this->cellCollectionIsSorted = false; + $this->cellCollection->add($coordinate, $cell); // Coordinates - $aCoordinates = Coordinate::coordinateFromString($pCoordinate); - if (Coordinate::columnIndexFromString($this->cachedHighestColumn) < Coordinate::columnIndexFromString($aCoordinates[0])) { - $this->cachedHighestColumn = $aCoordinates[0]; + if ($column > $this->cachedHighestColumn) { + $this->cachedHighestColumn = $column; } - if ($aCoordinates[1] > $this->cachedHighestRow) { - $this->cachedHighestRow = $aCoordinates[1]; + if ($row > $this->cachedHighestRow) { + $this->cachedHighestRow = $row; } // Cell needs appropriate xfIndex from dimensions records // but don't create dimension records if they don't already exist - $rowDimension = $this->getRowDimension($aCoordinates[1], false); - $columnDimension = $this->getColumnDimension($aCoordinates[0], false); + $rowDimension = $this->rowDimensions[$row] ?? null; + $columnDimension = $this->columnDimensions[$columnString] ?? null; - if ($rowDimension !== null && $rowDimension->getXfIndex() > 0) { - // then there is a row dimension with explicit style, assign it to the cell - $cell->setXfIndex($rowDimension->getXfIndex()); - } elseif ($columnDimension !== null && $columnDimension->getXfIndex() > 0) { - // then there is a column dimension, assign it to the cell - $cell->setXfIndex($columnDimension->getXfIndex()); + if ($rowDimension !== null) { + $rowXf = (int) $rowDimension->getXfIndex(); + if ($rowXf > 0) { + // then there is a row dimension with explicit style, assign it to the cell + $cell->setXfIndex($rowXf); + } + } elseif ($columnDimension !== null) { + $colXf = (int) $columnDimension->getXfIndex(); + if ($colXf > 0) { + // then there is a column dimension, assign it to the cell + $cell->setXfIndex($colXf); + } } return $cell; @@ -1291,61 +1403,30 @@ class Worksheet implements IComparable /** * Does the cell at a specific coordinate exist? * - * @param string $pCoordinate Coordinate of the cell eg: 'A1' - * - * @throws Exception - * - * @return bool + * @param array|CellAddress|string $coordinate Coordinate of the cell as a string, eg: 'C5'; + * or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. */ - public function cellExists($pCoordinate) + public function cellExists($coordinate): bool { - // Worksheet reference? - if (strpos($pCoordinate, '!') !== false) { - $worksheetReference = self::extractSheetTitle($pCoordinate, true); + $cellAddress = Validations::validateCellAddress($coordinate); + /** @var Worksheet $sheet */ + [$sheet, $finalCoordinate] = $this->getWorksheetAndCoordinate($cellAddress); - return $this->parent->getSheetByName($worksheetReference[0])->cellExists(strtoupper($worksheetReference[1])); - } - - // Named range? - if ((!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $pCoordinate, $matches)) && - (preg_match('/^' . Calculation::CALCULATION_REGEXP_NAMEDRANGE . '$/i', $pCoordinate, $matches))) { - $namedRange = NamedRange::resolveRange($pCoordinate, $this); - if ($namedRange !== null) { - $pCoordinate = $namedRange->getRange(); - if ($this->getHashCode() != $namedRange->getWorksheet()->getHashCode()) { - if (!$namedRange->getLocalOnly()) { - return $namedRange->getWorksheet()->cellExists($pCoordinate); - } - - throw new Exception('Named range ' . $namedRange->getName() . ' is not accessible from within sheet ' . $this->getTitle()); - } - } else { - return false; - } - } - - // Uppercase coordinate - $pCoordinate = strtoupper($pCoordinate); - - if (Coordinate::coordinateIsRange($pCoordinate)) { - throw new Exception('Cell coordinate can not be a range of cells.'); - } elseif (strpos($pCoordinate, '$') !== false) { - throw new Exception('Cell coordinate must not be absolute.'); - } - - // Cell exists? - return $this->cellCollection->has($pCoordinate); + return $sheet->cellCollection->has($finalCoordinate); } /** * Cell at a specific coordinate by using numeric cell coordinates exists? * + * @deprecated 1.23.0 + * Use the cellExists() method with a cell address such as 'C5' instead;, + * or passing in an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. + * @see Worksheet::cellExists() + * * @param int $columnIndex Numeric column coordinate of the cell * @param int $row Numeric row coordinate of the cell - * - * @return bool */ - public function cellExistsByColumnAndRow($columnIndex, $row) + public function cellExistsByColumnAndRow($columnIndex, $row): bool { return $this->cellExists(Coordinate::stringFromColumnIndex($columnIndex) . $row); } @@ -1353,65 +1434,54 @@ class Worksheet implements IComparable /** * Get row dimension at a specific row. * - * @param int $pRow Numeric index of the row - * @param bool $create - * - * @return RowDimension + * @param int $row Numeric index of the row */ - public function getRowDimension($pRow, $create = true) + public function getRowDimension(int $row): RowDimension { - // Found - $found = null; - // Get row dimension - if (!isset($this->rowDimensions[$pRow])) { - if (!$create) { - return null; - } - $this->rowDimensions[$pRow] = new RowDimension($pRow); + if (!isset($this->rowDimensions[$row])) { + $this->rowDimensions[$row] = new RowDimension($row); - $this->cachedHighestRow = max($this->cachedHighestRow, $pRow); + $this->cachedHighestRow = max($this->cachedHighestRow, $row); } - return $this->rowDimensions[$pRow]; + return $this->rowDimensions[$row]; + } + + public function rowDimensionExists(int $row): bool + { + return isset($this->rowDimensions[$row]); } /** * Get column dimension at a specific column. * - * @param string $pColumn String index of the column eg: 'A' - * @param bool $create - * - * @return ColumnDimension + * @param string $column String index of the column eg: 'A' */ - public function getColumnDimension($pColumn, $create = true) + public function getColumnDimension(string $column): ColumnDimension { // Uppercase coordinate - $pColumn = strtoupper($pColumn); + $column = strtoupper($column); // Fetch dimensions - if (!isset($this->columnDimensions[$pColumn])) { - if (!$create) { - return null; - } - $this->columnDimensions[$pColumn] = new ColumnDimension($pColumn); + if (!isset($this->columnDimensions[$column])) { + $this->columnDimensions[$column] = new ColumnDimension($column); - if (Coordinate::columnIndexFromString($this->cachedHighestColumn) < Coordinate::columnIndexFromString($pColumn)) { - $this->cachedHighestColumn = $pColumn; + $columnIndex = Coordinate::columnIndexFromString($column); + if ($this->cachedHighestColumn < $columnIndex) { + $this->cachedHighestColumn = $columnIndex; } } - return $this->columnDimensions[$pColumn]; + return $this->columnDimensions[$column]; } /** * Get column dimension at a specific column by using numeric cell coordinates. * * @param int $columnIndex Numeric column coordinate of the cell - * - * @return ColumnDimension */ - public function getColumnDimensionByColumn($columnIndex) + public function getColumnDimensionByColumn(int $columnIndex): ColumnDimension { return $this->getColumnDimension(Coordinate::stringFromColumnIndex($columnIndex)); } @@ -1429,62 +1499,131 @@ class Worksheet implements IComparable /** * Get style for cell. * - * @param string $pCellCoordinate Cell coordinate (or range) to get style for, eg: 'A1' - * - * @throws Exception - * - * @return Style + * @param AddressRange|array|CellAddress|int|string $cellCoordinate + * A simple string containing a cell address like 'A1' or a cell range like 'A1:E10' + * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), + * or a CellAddress or AddressRange object. */ - public function getStyle($pCellCoordinate) + public function getStyle($cellCoordinate): Style { + $cellCoordinate = Validations::validateCellOrCellRange($cellCoordinate); + // set this sheet as active $this->parent->setActiveSheetIndex($this->parent->getIndex($this)); // set cell coordinate as active - $this->setSelectedCells($pCellCoordinate); + $this->setSelectedCells($cellCoordinate); return $this->parent->getCellXfSupervisor(); } + /** + * Get style for cell by using numeric cell coordinates. + * + * @deprecated 1.23.0 + * Use the getStyle() method with a cell address range such as 'C5:F8' instead;, + * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), + * or an AddressRange object. + * @see Worksheet::getStyle() + * + * @param int $columnIndex1 Numeric column coordinate of the cell + * @param int $row1 Numeric row coordinate of the cell + * @param null|int $columnIndex2 Numeric column coordinate of the range cell + * @param null|int $row2 Numeric row coordinate of the range cell + * + * @return Style + */ + public function getStyleByColumnAndRow($columnIndex1, $row1, $columnIndex2 = null, $row2 = null) + { + if ($columnIndex2 !== null && $row2 !== null) { + $cellRange = new CellRange( + CellAddress::fromColumnAndRow($columnIndex1, $row1), + CellAddress::fromColumnAndRow($columnIndex2, $row2) + ); + + return $this->getStyle($cellRange); + } + + return $this->getStyle(CellAddress::fromColumnAndRow($columnIndex1, $row1)); + } + /** * Get conditional styles for a cell. * - * @param string $pCoordinate eg: 'A1' + * @param string $coordinate eg: 'A1' or 'A1:A3'. + * If a single cell is referenced, then the array of conditional styles will be returned if the cell is + * included in a conditional style range. + * If a range of cells is specified, then the styles will only be returned if the range matches the entire + * range of the conditional. * * @return Conditional[] */ - public function getConditionalStyles($pCoordinate) + public function getConditionalStyles(string $coordinate): array { - $pCoordinate = strtoupper($pCoordinate); - if (!isset($this->conditionalStylesCollection[$pCoordinate])) { - $this->conditionalStylesCollection[$pCoordinate] = []; + $coordinate = strtoupper($coordinate); + if (strpos($coordinate, ':') !== false) { + return $this->conditionalStylesCollection[$coordinate] ?? []; } - return $this->conditionalStylesCollection[$pCoordinate]; + $cell = $this->getCell($coordinate); + foreach (array_keys($this->conditionalStylesCollection) as $conditionalRange) { + if ($cell->isInRange($conditionalRange)) { + return $this->conditionalStylesCollection[$conditionalRange]; + } + } + + return []; + } + + public function getConditionalRange(string $coordinate): ?string + { + $coordinate = strtoupper($coordinate); + $cell = $this->getCell($coordinate); + foreach (array_keys($this->conditionalStylesCollection) as $conditionalRange) { + if ($cell->isInRange($conditionalRange)) { + return $conditionalRange; + } + } + + return null; } /** * Do conditional styles exist for this cell? * - * @param string $pCoordinate eg: 'A1' - * - * @return bool + * @param string $coordinate eg: 'A1' or 'A1:A3'. + * If a single cell is specified, then this method will return true if that cell is included in a + * conditional style range. + * If a range of cells is specified, then true will only be returned if the range matches the entire + * range of the conditional. */ - public function conditionalStylesExists($pCoordinate) + public function conditionalStylesExists($coordinate): bool { - return isset($this->conditionalStylesCollection[strtoupper($pCoordinate)]); + $coordinate = strtoupper($coordinate); + if (strpos($coordinate, ':') !== false) { + return isset($this->conditionalStylesCollection[$coordinate]); + } + + $cell = $this->getCell($coordinate); + foreach (array_keys($this->conditionalStylesCollection) as $conditionalRange) { + if ($cell->isInRange($conditionalRange)) { + return true; + } + } + + return false; } /** * Removes conditional styles for a cell. * - * @param string $pCoordinate eg: 'A1' + * @param string $coordinate eg: 'A1' * - * @return Worksheet + * @return $this */ - public function removeConditionalStyles($pCoordinate) + public function removeConditionalStyles($coordinate) { - unset($this->conditionalStylesCollection[strtoupper($pCoordinate)]); + unset($this->conditionalStylesCollection[strtoupper($coordinate)]); return $this; } @@ -1502,66 +1641,43 @@ class Worksheet implements IComparable /** * Set conditional styles. * - * @param string $pCoordinate eg: 'A1' - * @param $pValue Conditional[] + * @param string $coordinate eg: 'A1' + * @param Conditional[] $styles * - * @return Worksheet + * @return $this */ - public function setConditionalStyles($pCoordinate, $pValue) + public function setConditionalStyles($coordinate, $styles) { - $this->conditionalStylesCollection[strtoupper($pCoordinate)] = $pValue; + $this->conditionalStylesCollection[strtoupper($coordinate)] = $styles; return $this; } - /** - * Get style for cell by using numeric cell coordinates. - * - * @param int $columnIndex1 Numeric column coordinate of the cell - * @param int $row1 Numeric row coordinate of the cell - * @param null|int $columnIndex2 Numeric column coordinate of the range cell - * @param null|int $row2 Numeric row coordinate of the range cell - * - * @return Style - */ - public function getStyleByColumnAndRow($columnIndex1, $row1, $columnIndex2 = null, $row2 = null) - { - if ($columnIndex2 !== null && $row2 !== null) { - $cellRange = Coordinate::stringFromColumnIndex($columnIndex1) . $row1 . ':' . Coordinate::stringFromColumnIndex($columnIndex2) . $row2; - - return $this->getStyle($cellRange); - } - - return $this->getStyle(Coordinate::stringFromColumnIndex($columnIndex1) . $row1); - } - /** * Duplicate cell style to a range of cells. * * Please note that this will overwrite existing cell styles for cells in range! * - * @param Style $pCellStyle Cell style to duplicate - * @param string $pRange Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1") + * @param Style $style Cell style to duplicate + * @param string $range Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1") * - * @throws Exception - * - * @return Worksheet + * @return $this */ - public function duplicateStyle(Style $pCellStyle, $pRange) + public function duplicateStyle(Style $style, $range) { // Add the style to the workbook if necessary $workbook = $this->parent; - if ($existingStyle = $this->parent->getCellXfByHashCode($pCellStyle->getHashCode())) { + if ($existingStyle = $this->parent->getCellXfByHashCode($style->getHashCode())) { // there is already such cell Xf in our collection $xfIndex = $existingStyle->getIndex(); } else { // we don't have such a cell Xf, need to add - $workbook->addCellXf($pCellStyle); - $xfIndex = $pCellStyle->getIndex(); + $workbook->addCellXf($style); + $xfIndex = $style->getIndex(); } // Calculate range outer borders - [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($pRange . ':' . $pRange); + [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($range . ':' . $range); // Make sure we can loop upwards on rows and columns if ($rangeStart[0] > $rangeEnd[0] && $rangeStart[1] > $rangeEnd[1]) { @@ -1585,23 +1701,21 @@ class Worksheet implements IComparable * * Please note that this will overwrite existing cell styles for cells in range! * - * @param Conditional[] $pCellStyle Cell style to duplicate - * @param string $pRange Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1") + * @param Conditional[] $styles Cell style to duplicate + * @param string $range Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1") * - * @throws Exception - * - * @return Worksheet + * @return $this */ - public function duplicateConditionalStyle(array $pCellStyle, $pRange = '') + public function duplicateConditionalStyle(array $styles, $range = '') { - foreach ($pCellStyle as $cellStyle) { + foreach ($styles as $cellStyle) { if (!($cellStyle instanceof Conditional)) { throw new Exception('Style is not a conditional style'); } } // Calculate range outer borders - [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($pRange . ':' . $pRange); + [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($range . ':' . $range); // Make sure we can loop upwards on rows and columns if ($rangeStart[0] > $rangeEnd[0] && $rangeStart[1] > $rangeEnd[1]) { @@ -1613,7 +1727,7 @@ class Worksheet implements IComparable // Loop through cells and apply styles for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) { for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) { - $this->setConditionalStyles(Coordinate::stringFromColumnIndex($col) . $row, $pCellStyle); + $this->setConditionalStyles(Coordinate::stringFromColumnIndex($col) . $row, $styles); } } @@ -1623,28 +1737,22 @@ class Worksheet implements IComparable /** * Set break on a cell. * - * @param string $pCoordinate Cell coordinate (e.g. A1) - * @param int $pBreak Break type (type of Worksheet::BREAK_*) + * @param array|CellAddress|string $coordinate Coordinate of the cell as a string, eg: 'C5'; + * or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. + * @param int $break Break type (type of Worksheet::BREAK_*) * - * @throws Exception - * - * @return Worksheet + * @return $this */ - public function setBreak($pCoordinate, $pBreak) + public function setBreak($coordinate, $break) { - // Uppercase coordinate - $pCoordinate = strtoupper($pCoordinate); + $cellAddress = Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate)); - if ($pCoordinate != '') { - if ($pBreak == self::BREAK_NONE) { - if (isset($this->breaks[$pCoordinate])) { - unset($this->breaks[$pCoordinate]); - } - } else { - $this->breaks[$pCoordinate] = $pBreak; + if ($break === self::BREAK_NONE) { + if (isset($this->breaks[$cellAddress])) { + unset($this->breaks[$cellAddress]); } } else { - throw new Exception('No cell coordinate specified.'); + $this->breaks[$cellAddress] = $break; } return $this; @@ -1653,11 +1761,16 @@ class Worksheet implements IComparable /** * Set break on a cell by using numeric cell coordinates. * + * @deprecated 1.23.0 + * Use the setBreak() method with a cell address such as 'C5' instead;, + * or passing in an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. + * @see Worksheet::setBreak() + * * @param int $columnIndex Numeric column coordinate of the cell * @param int $row Numeric row coordinate of the cell * @param int $break Break type (type of Worksheet::BREAK_*) * - * @return Worksheet + * @return $this */ public function setBreakByColumnAndRow($columnIndex, $row, $break) { @@ -1667,7 +1780,7 @@ class Worksheet implements IComparable /** * Get breaks. * - * @return array[] + * @return int[] */ public function getBreaks() { @@ -1677,83 +1790,177 @@ class Worksheet implements IComparable /** * Set merge on a cell range. * - * @param string $pRange Cell range (e.g. A1:E1) + * @param AddressRange|array|string $range A simple string containing a Cell range like 'A1:E10' + * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), + * or an AddressRange. + * @param string $behaviour How the merged cells should behave. + * Possible values are: + * MERGE_CELL_CONTENT_EMPTY - Empty the content of the hidden cells + * MERGE_CELL_CONTENT_HIDE - Keep the content of the hidden cells + * MERGE_CELL_CONTENT_MERGE - Move the content of the hidden cells into the first cell * - * @throws Exception - * - * @return Worksheet + * @return $this */ - public function mergeCells($pRange) + public function mergeCells($range, $behaviour = self::MERGE_CELL_CONTENT_EMPTY) { - // Uppercase coordinate - $pRange = strtoupper($pRange); + $range = Functions::trimSheetFromCellReference(Validations::validateCellRange($range)); - if (strpos($pRange, ':') !== false) { - $this->mergeCells[$pRange] = $pRange; + if (strpos($range, ':') === false) { + $range .= ":{$range}"; + } - // make sure cells are created + if (preg_match('/^([A-Z]+)(\\d+):([A-Z]+)(\\d+)$/', $range, $matches) !== 1) { + throw new Exception('Merge must be on a valid range of cells.'); + } - // get the cells in the range - $aReferences = Coordinate::extractAllCellReferencesInRange($pRange); + $this->mergeCells[$range] = $range; + $firstRow = (int) $matches[2]; + $lastRow = (int) $matches[4]; + $firstColumn = $matches[1]; + $lastColumn = $matches[3]; + $firstColumnIndex = Coordinate::columnIndexFromString($firstColumn); + $lastColumnIndex = Coordinate::columnIndexFromString($lastColumn); + $numberRows = $lastRow - $firstRow; + $numberColumns = $lastColumnIndex - $firstColumnIndex; - // create upper left cell if it does not already exist - $upperLeft = $aReferences[0]; - if (!$this->cellExists($upperLeft)) { - $this->getCell($upperLeft)->setValueExplicit(null, DataType::TYPE_NULL); - } + if ($numberRows === 1 && $numberColumns === 1) { + return $this; + } + // create upper left cell if it does not already exist + $upperLeft = "{$firstColumn}{$firstRow}"; + if (!$this->cellExists($upperLeft)) { + $this->getCell($upperLeft)->setValueExplicit(null, DataType::TYPE_NULL); + } + + if ($behaviour !== self::MERGE_CELL_CONTENT_HIDE) { // Blank out the rest of the cells in the range (if they exist) - $count = count($aReferences); - for ($i = 1; $i < $count; ++$i) { - if ($this->cellExists($aReferences[$i])) { - $this->getCell($aReferences[$i])->setValueExplicit(null, DataType::TYPE_NULL); - } + if ($numberRows > $numberColumns) { + $this->clearMergeCellsByColumn($firstColumn, $lastColumn, $firstRow, $lastRow, $upperLeft, $behaviour); + } else { + $this->clearMergeCellsByRow($firstColumn, $lastColumnIndex, $firstRow, $lastRow, $upperLeft, $behaviour); } - } else { - throw new Exception('Merge must be set on a range of cells.'); } return $this; } + private function clearMergeCellsByColumn(string $firstColumn, string $lastColumn, int $firstRow, int $lastRow, string $upperLeft, string $behaviour): void + { + $leftCellValue = ($behaviour === self::MERGE_CELL_CONTENT_MERGE) + ? [$this->getCell($upperLeft)->getFormattedValue()] + : []; + + foreach ($this->getColumnIterator($firstColumn, $lastColumn) as $column) { + $iterator = $column->getCellIterator($firstRow); + $iterator->setIterateOnlyExistingCells(true); + foreach ($iterator as $cell) { + if ($cell !== null) { + $row = $cell->getRow(); + if ($row > $lastRow) { + break; + } + $leftCellValue = $this->mergeCellBehaviour($cell, $upperLeft, $behaviour, $leftCellValue); + } + } + } + + if ($behaviour === self::MERGE_CELL_CONTENT_MERGE) { + $this->getCell($upperLeft)->setValueExplicit(implode(' ', $leftCellValue), DataType::TYPE_STRING); + } + } + + private function clearMergeCellsByRow(string $firstColumn, int $lastColumnIndex, int $firstRow, int $lastRow, string $upperLeft, string $behaviour): void + { + $leftCellValue = ($behaviour === self::MERGE_CELL_CONTENT_MERGE) + ? [$this->getCell($upperLeft)->getFormattedValue()] + : []; + + foreach ($this->getRowIterator($firstRow, $lastRow) as $row) { + $iterator = $row->getCellIterator($firstColumn); + $iterator->setIterateOnlyExistingCells(true); + foreach ($iterator as $cell) { + if ($cell !== null) { + $column = $cell->getColumn(); + $columnIndex = Coordinate::columnIndexFromString($column); + if ($columnIndex > $lastColumnIndex) { + break; + } + $leftCellValue = $this->mergeCellBehaviour($cell, $upperLeft, $behaviour, $leftCellValue); + } + } + } + + if ($behaviour === self::MERGE_CELL_CONTENT_MERGE) { + $this->getCell($upperLeft)->setValueExplicit(implode(' ', $leftCellValue), DataType::TYPE_STRING); + } + } + + public function mergeCellBehaviour(Cell $cell, string $upperLeft, string $behaviour, array $leftCellValue): array + { + if ($cell->getCoordinate() !== $upperLeft) { + Calculation::getInstance($cell->getWorksheet()->getParent())->flushInstance(); + if ($behaviour === self::MERGE_CELL_CONTENT_MERGE) { + $cellValue = $cell->getFormattedValue(); + if ($cellValue !== '') { + $leftCellValue[] = $cellValue; + } + } + $cell->setValueExplicit(null, DataType::TYPE_NULL); + } + + return $leftCellValue; + } + /** * Set merge on a cell range by using numeric cell coordinates. * + * @deprecated 1.23.0 + * Use the mergeCells() method with a cell address range such as 'C5:F8' instead;, + * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), + * or an AddressRange object. + * @see Worksheet::mergeCells() + * * @param int $columnIndex1 Numeric column coordinate of the first cell * @param int $row1 Numeric row coordinate of the first cell * @param int $columnIndex2 Numeric column coordinate of the last cell * @param int $row2 Numeric row coordinate of the last cell + * @param string $behaviour How the merged cells should behave. + * Possible values are: + * MERGE_CELL_CONTENT_EMPTY - Empty the content of the hidden cells + * MERGE_CELL_CONTENT_HIDE - Keep the content of the hidden cells + * MERGE_CELL_CONTENT_MERGE - Move the content of the hidden cells into the first cell * - * @throws Exception - * - * @return Worksheet + * @return $this */ - public function mergeCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2) + public function mergeCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2, $behaviour = self::MERGE_CELL_CONTENT_EMPTY) { - $cellRange = Coordinate::stringFromColumnIndex($columnIndex1) . $row1 . ':' . Coordinate::stringFromColumnIndex($columnIndex2) . $row2; + $cellRange = new CellRange( + CellAddress::fromColumnAndRow($columnIndex1, $row1), + CellAddress::fromColumnAndRow($columnIndex2, $row2) + ); - return $this->mergeCells($cellRange); + return $this->mergeCells($cellRange, $behaviour); } /** * Remove merge on a cell range. * - * @param string $pRange Cell range (e.g. A1:E1) + * @param AddressRange|array|string $range A simple string containing a Cell range like 'A1:E10' + * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), + * or an AddressRange. * - * @throws Exception - * - * @return Worksheet + * @return $this */ - public function unmergeCells($pRange) + public function unmergeCells($range) { - // Uppercase coordinate - $pRange = strtoupper($pRange); + $range = Functions::trimSheetFromCellReference(Validations::validateCellRange($range)); - if (strpos($pRange, ':') !== false) { - if (isset($this->mergeCells[$pRange])) { - unset($this->mergeCells[$pRange]); + if (strpos($range, ':') !== false) { + if (isset($this->mergeCells[$range])) { + unset($this->mergeCells[$range]); } else { - throw new Exception('Cell range ' . $pRange . ' not known as merged.'); + throw new Exception('Cell range ' . $range . ' not known as merged.'); } } else { throw new Exception('Merge can only be removed from a range of cells.'); @@ -1765,18 +1972,25 @@ class Worksheet implements IComparable /** * Remove merge on a cell range by using numeric cell coordinates. * + * @deprecated 1.23.0 + * Use the unmergeCells() method with a cell address range such as 'C5:F8' instead;, + * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), + * or an AddressRange object. + * @see Worksheet::unmergeCells() + * * @param int $columnIndex1 Numeric column coordinate of the first cell * @param int $row1 Numeric row coordinate of the first cell * @param int $columnIndex2 Numeric column coordinate of the last cell * @param int $row2 Numeric row coordinate of the last cell * - * @throws Exception - * - * @return Worksheet + * @return $this */ public function unmergeCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2) { - $cellRange = Coordinate::stringFromColumnIndex($columnIndex1) . $row1 . ':' . Coordinate::stringFromColumnIndex($columnIndex2) . $row2; + $cellRange = new CellRange( + CellAddress::fromColumnAndRow($columnIndex1, $row1), + CellAddress::fromColumnAndRow($columnIndex2, $row2) + ); return $this->unmergeCells($cellRange); } @@ -1784,7 +1998,7 @@ class Worksheet implements IComparable /** * Get merge cells array. * - * @return array[] + * @return string[] */ public function getMergeCells() { @@ -1795,35 +2009,36 @@ class Worksheet implements IComparable * Set merge cells array for the entire sheet. Use instead mergeCells() to merge * a single cell range. * - * @param array $pValue + * @param string[] $mergeCells * - * @return Worksheet + * @return $this */ - public function setMergeCells(array $pValue) + public function setMergeCells(array $mergeCells) { - $this->mergeCells = $pValue; + $this->mergeCells = $mergeCells; return $this; } /** - * Set protection on a cell range. + * Set protection on a cell or cell range. * - * @param string $pRange Cell (e.g. A1) or cell range (e.g. A1:E1) - * @param string $pPassword Password to unlock the protection - * @param bool $pAlreadyHashed If the password has already been hashed, set this to true + * @param AddressRange|array|CellAddress|int|string $range A simple string containing a Cell range like 'A1:E10' + * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), + * or a CellAddress or AddressRange object. + * @param string $password Password to unlock the protection + * @param bool $alreadyHashed If the password has already been hashed, set this to true * - * @return Worksheet + * @return $this */ - public function protectCells($pRange, $pPassword, $pAlreadyHashed = false) + public function protectCells($range, $password, $alreadyHashed = false) { - // Uppercase coordinate - $pRange = strtoupper($pRange); + $range = Functions::trimSheetFromCellReference(Validations::validateCellOrCellRange($range)); - if (!$pAlreadyHashed) { - $pPassword = Shared\PasswordHasher::hashPassword($pPassword); + if (!$alreadyHashed) { + $password = Shared\PasswordHasher::hashPassword($password); } - $this->protectedCells[$pRange] = $pPassword; + $this->protectedCells[$range] = $password; return $this; } @@ -1831,6 +2046,12 @@ class Worksheet implements IComparable /** * Set protection on a cell range by using numeric cell coordinates. * + * @deprecated 1.23.0 + * Use the protectCells() method with a cell address range such as 'C5:F8' instead;, + * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), + * or an AddressRange object. + * @see Worksheet::protectCells() + * * @param int $columnIndex1 Numeric column coordinate of the first cell * @param int $row1 Numeric row coordinate of the first cell * @param int $columnIndex2 Numeric column coordinate of the last cell @@ -1838,33 +2059,35 @@ class Worksheet implements IComparable * @param string $password Password to unlock the protection * @param bool $alreadyHashed If the password has already been hashed, set this to true * - * @return Worksheet + * @return $this */ public function protectCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2, $password, $alreadyHashed = false) { - $cellRange = Coordinate::stringFromColumnIndex($columnIndex1) . $row1 . ':' . Coordinate::stringFromColumnIndex($columnIndex2) . $row2; + $cellRange = new CellRange( + CellAddress::fromColumnAndRow($columnIndex1, $row1), + CellAddress::fromColumnAndRow($columnIndex2, $row2) + ); return $this->protectCells($cellRange, $password, $alreadyHashed); } /** - * Remove protection on a cell range. + * Remove protection on a cell or cell range. * - * @param string $pRange Cell (e.g. A1) or cell range (e.g. A1:E1) + * @param AddressRange|array|CellAddress|int|string $range A simple string containing a Cell range like 'A1:E10' + * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), + * or a CellAddress or AddressRange object. * - * @throws Exception - * - * @return Worksheet + * @return $this */ - public function unprotectCells($pRange) + public function unprotectCells($range) { - // Uppercase coordinate - $pRange = strtoupper($pRange); + $range = Functions::trimSheetFromCellReference(Validations::validateCellOrCellRange($range)); - if (isset($this->protectedCells[$pRange])) { - unset($this->protectedCells[$pRange]); + if (isset($this->protectedCells[$range])) { + unset($this->protectedCells[$range]); } else { - throw new Exception('Cell range ' . $pRange . ' not known as protected.'); + throw new Exception('Cell range ' . $range . ' not known as protected.'); } return $this; @@ -1873,18 +2096,25 @@ class Worksheet implements IComparable /** * Remove protection on a cell range by using numeric cell coordinates. * + * @deprecated 1.23.0 + * Use the unprotectCells() method with a cell address range such as 'C5:F8' instead;, + * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), + * or an AddressRange object. + * @see Worksheet::unprotectCells() + * * @param int $columnIndex1 Numeric column coordinate of the first cell * @param int $row1 Numeric row coordinate of the first cell * @param int $columnIndex2 Numeric column coordinate of the last cell * @param int $row2 Numeric row coordinate of the last cell * - * @throws Exception - * - * @return Worksheet + * @return $this */ public function unprotectCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2) { - $cellRange = Coordinate::stringFromColumnIndex($columnIndex1) . $row1 . ':' . Coordinate::stringFromColumnIndex($columnIndex2) . $row2; + $cellRange = new CellRange( + CellAddress::fromColumnAndRow($columnIndex1, $row1), + CellAddress::fromColumnAndRow($columnIndex2, $row2) + ); return $this->unprotectCells($cellRange); } @@ -1892,7 +2122,7 @@ class Worksheet implements IComparable /** * Get protected cells. * - * @return array[] + * @return string[] */ public function getProtectedCells() { @@ -1912,19 +2142,21 @@ class Worksheet implements IComparable /** * Set AutoFilter. * - * @param AutoFilter|string $pValue + * @param AddressRange|array|AutoFilter|string $autoFilterOrRange * A simple string containing a Cell range like 'A1:E10' is permitted for backward compatibility + * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), + * or an AddressRange. * - * @throws Exception - * - * @return Worksheet + * @return $this */ - public function setAutoFilter($pValue) + public function setAutoFilter($autoFilterOrRange) { - if (is_string($pValue)) { - $this->autoFilter->setRange($pValue); - } elseif (is_object($pValue) && ($pValue instanceof AutoFilter)) { - $this->autoFilter = $pValue; + if (is_object($autoFilterOrRange) && ($autoFilterOrRange instanceof AutoFilter)) { + $this->autoFilter = $autoFilterOrRange; + } else { + $cellRange = Functions::trimSheetFromCellReference(Validations::validateCellRange($autoFilterOrRange)); + + $this->autoFilter->setRange($cellRange); } return $this; @@ -1933,32 +2165,131 @@ class Worksheet implements IComparable /** * Set Autofilter Range by using numeric cell coordinates. * + * @deprecated 1.23.0 + * Use the setAutoFilter() method with a cell address range such as 'C5:F8' instead;, + * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), + * or an AddressRange object or AutoFilter object. + * @see Worksheet::setAutoFilter() + * * @param int $columnIndex1 Numeric column coordinate of the first cell * @param int $row1 Numeric row coordinate of the first cell * @param int $columnIndex2 Numeric column coordinate of the second cell * @param int $row2 Numeric row coordinate of the second cell * - * @throws Exception - * - * @return Worksheet + * @return $this */ public function setAutoFilterByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2) { - return $this->setAutoFilter( - Coordinate::stringFromColumnIndex($columnIndex1) . $row1 - . ':' . - Coordinate::stringFromColumnIndex($columnIndex2) . $row2 + $cellRange = new CellRange( + CellAddress::fromColumnAndRow($columnIndex1, $row1), + CellAddress::fromColumnAndRow($columnIndex2, $row2) ); + + return $this->setAutoFilter($cellRange); } /** * Remove autofilter. - * - * @return Worksheet */ - public function removeAutoFilter() + public function removeAutoFilter(): self { - $this->autoFilter->setRange(null); + $this->autoFilter->setRange(''); + + return $this; + } + + /** + * Get collection of Tables. + * + * @return ArrayObject + */ + public function getTableCollection() + { + return $this->tableCollection; + } + + /** + * Add Table. + * + * @return $this + */ + public function addTable(Table $table): self + { + $table->setWorksheet($this); + $this->tableCollection[] = $table; + + return $this; + } + + /** + * @return string[] array of Table names + */ + public function getTableNames(): array + { + $tableNames = []; + + foreach ($this->tableCollection as $table) { + /** @var Table $table */ + $tableNames[] = $table->getName(); + } + + return $tableNames; + } + + /** + * @param string $name the table name to search + * + * @return null|Table The table from the tables collection, or null if not found + */ + public function getTableByName(string $name): ?Table + { + $tableIndex = $this->getTableIndexByName($name); + + return ($tableIndex === null) ? null : $this->tableCollection[$tableIndex]; + } + + /** + * @param string $name the table name to search + * + * @return null|int The index of the located table in the tables collection, or null if not found + */ + protected function getTableIndexByName(string $name): ?int + { + $name = Shared\StringHelper::strToUpper($name); + foreach ($this->tableCollection as $index => $table) { + /** @var Table $table */ + if (Shared\StringHelper::strToUpper($table->getName()) === $name) { + return $index; + } + } + + return null; + } + + /** + * Remove Table by name. + * + * @param string $name Table name + * + * @return $this + */ + public function removeTableByName(string $name): self + { + $tableIndex = $this->getTableIndexByName($name); + + if ($tableIndex !== null) { + unset($this->tableCollection[$tableIndex]); + } + + return $this; + } + + /** + * Remove collection of Tables. + */ + public function removeTableCollection(): self + { + $this->tableCollection = new ArrayObject(); return $this; } @@ -1966,7 +2297,7 @@ class Worksheet implements IComparable /** * Get Freeze Pane. * - * @return string + * @return null|string */ public function getFreezePane() { @@ -1982,25 +2313,40 @@ class Worksheet implements IComparable * - B1 will freeze the columns to the left of cell B1 (i.e column A) * - B2 will freeze the rows above and to the left of cell B2 (i.e row 1 and column A) * - * @param null|string $cell Position of the split - * @param null|string $topLeftCell default position of the right bottom pane + * @param null|array|CellAddress|string $coordinate Coordinate of the cell as a string, eg: 'C5'; + * or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. + * Passing a null value for this argument will clear any existing freeze pane for this worksheet. + * @param null|array|CellAddress|string $topLeftCell default position of the right bottom pane + * Coordinate of the cell as a string, eg: 'C5'; or as an array of [$columnIndex, $row] (e.g. [3, 5]), + * or a CellAddress object. * - * @throws Exception - * - * @return Worksheet + * @return $this */ - public function freezePane($cell, $topLeftCell = null) + public function freezePane($coordinate, $topLeftCell = null) { - if (is_string($cell) && Coordinate::coordinateIsRange($cell)) { + $cellAddress = ($coordinate !== null) + ? Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate)) + : null; + if ($cellAddress !== null && Coordinate::coordinateIsRange($cellAddress)) { throw new Exception('Freeze pane can not be set on a range of cells.'); } + $topLeftCell = ($topLeftCell !== null) + ? Functions::trimSheetFromCellReference(Validations::validateCellAddress($topLeftCell)) + : null; - if ($cell !== null && $topLeftCell === null) { - $coordinate = Coordinate::coordinateFromString($cell); + if ($cellAddress !== null && $topLeftCell === null) { + $coordinate = Coordinate::coordinateFromString($cellAddress); $topLeftCell = $coordinate[0] . $coordinate[1]; } - $this->freezePane = $cell; + $this->freezePane = $cellAddress; + $this->topLeftCell = $topLeftCell; + + return $this; + } + + public function setTopLeftCell(string $topLeftCell): self + { $this->topLeftCell = $topLeftCell; return $this; @@ -2009,10 +2355,15 @@ class Worksheet implements IComparable /** * Freeze Pane by using numeric cell coordinates. * + * @deprecated 1.23.0 + * Use the freezePane() method with a cell address such as 'C5' instead;, + * or passing in an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. + * @see Worksheet::freezePane() + * * @param int $columnIndex Numeric column coordinate of the cell * @param int $row Numeric row coordinate of the cell * - * @return Worksheet + * @return $this */ public function freezePaneByColumnAndRow($columnIndex, $row) { @@ -2022,7 +2373,7 @@ class Worksheet implements IComparable /** * Unfreeze Pane. * - * @return Worksheet + * @return $this */ public function unfreezePane() { @@ -2032,7 +2383,7 @@ class Worksheet implements IComparable /** * Get the default position of the right bottom pane. * - * @return int + * @return null|string */ public function getTopLeftCell() { @@ -2042,18 +2393,16 @@ class Worksheet implements IComparable /** * Insert a new row, updating all possible related data. * - * @param int $pBefore Insert before this one - * @param int $pNumRows Number of rows to insert + * @param int $before Insert before this one + * @param int $numberOfRows Number of rows to insert * - * @throws Exception - * - * @return Worksheet + * @return $this */ - public function insertNewRowBefore($pBefore, $pNumRows = 1) + public function insertNewRowBefore($before, $numberOfRows = 1) { - if ($pBefore >= 1) { + if ($before >= 1) { $objReferenceHelper = ReferenceHelper::getInstance(); - $objReferenceHelper->insertNewBefore('A' . $pBefore, 0, $pNumRows, $this); + $objReferenceHelper->insertNewBefore('A' . $before, 0, $numberOfRows, $this); } else { throw new Exception('Rows can only be inserted before at least row 1.'); } @@ -2064,18 +2413,16 @@ class Worksheet implements IComparable /** * Insert a new column, updating all possible related data. * - * @param string $pBefore Insert before this one, eg: 'A' - * @param int $pNumCols Number of columns to insert + * @param string $before Insert before this one, eg: 'A' + * @param int $numberOfColumns Number of columns to insert * - * @throws Exception - * - * @return Worksheet + * @return $this */ - public function insertNewColumnBefore($pBefore, $pNumCols = 1) + public function insertNewColumnBefore($before, $numberOfColumns = 1) { - if (!is_numeric($pBefore)) { + if (!is_numeric($before)) { $objReferenceHelper = ReferenceHelper::getInstance(); - $objReferenceHelper->insertNewBefore($pBefore . '1', $pNumCols, 0, $this); + $objReferenceHelper->insertNewBefore($before . '1', $numberOfColumns, 0, $this); } else { throw new Exception('Column references should not be numeric.'); } @@ -2087,16 +2434,14 @@ class Worksheet implements IComparable * Insert a new column, updating all possible related data. * * @param int $beforeColumnIndex Insert before this one (numeric column coordinate of the cell) - * @param int $pNumCols Number of columns to insert + * @param int $numberOfColumns Number of columns to insert * - * @throws Exception - * - * @return Worksheet + * @return $this */ - public function insertNewColumnBeforeByIndex($beforeColumnIndex, $pNumCols = 1) + public function insertNewColumnBeforeByIndex($beforeColumnIndex, $numberOfColumns = 1) { if ($beforeColumnIndex >= 1) { - return $this->insertNewColumnBefore(Coordinate::stringFromColumnIndex($beforeColumnIndex), $pNumCols); + return $this->insertNewColumnBefore(Coordinate::stringFromColumnIndex($beforeColumnIndex), $numberOfColumns); } throw new Exception('Columns can only be inserted before at least column A (1).'); @@ -2105,71 +2450,128 @@ class Worksheet implements IComparable /** * Delete a row, updating all possible related data. * - * @param int $pRow Remove starting with this one - * @param int $pNumRows Number of rows to remove + * @param int $row Remove starting with this one + * @param int $numberOfRows Number of rows to remove * - * @throws Exception - * - * @return Worksheet + * @return $this */ - public function removeRow($pRow, $pNumRows = 1) + public function removeRow($row, $numberOfRows = 1) { - if ($pRow >= 1) { - for ($r = 0; $r < $pNumRows; ++$r) { - $this->getCellCollection()->removeRow($pRow + $r); - } - - $highestRow = $this->getHighestDataRow(); - $objReferenceHelper = ReferenceHelper::getInstance(); - $objReferenceHelper->insertNewBefore('A' . ($pRow + $pNumRows), 0, -$pNumRows, $this); - for ($r = 0; $r < $pNumRows; ++$r) { - $this->getCellCollection()->removeRow($highestRow); - --$highestRow; - } - } else { + if ($row < 1) { throw new Exception('Rows to be deleted should at least start from row 1.'); } + $holdRowDimensions = $this->removeRowDimensions($row, $numberOfRows); + $highestRow = $this->getHighestDataRow(); + $removedRowsCounter = 0; + + for ($r = 0; $r < $numberOfRows; ++$r) { + if ($row + $r <= $highestRow) { + $this->getCellCollection()->removeRow($row + $r); + ++$removedRowsCounter; + } + } + + $objReferenceHelper = ReferenceHelper::getInstance(); + $objReferenceHelper->insertNewBefore('A' . ($row + $numberOfRows), 0, -$numberOfRows, $this); + for ($r = 0; $r < $removedRowsCounter; ++$r) { + $this->getCellCollection()->removeRow($highestRow); + --$highestRow; + } + + $this->rowDimensions = $holdRowDimensions; + return $this; } + private function removeRowDimensions(int $row, int $numberOfRows): array + { + $highRow = $row + $numberOfRows - 1; + $holdRowDimensions = []; + foreach ($this->rowDimensions as $rowDimension) { + $num = $rowDimension->getRowIndex(); + if ($num < $row) { + $holdRowDimensions[$num] = $rowDimension; + } elseif ($num > $highRow) { + $num -= $numberOfRows; + $cloneDimension = clone $rowDimension; + $cloneDimension->setRowIndex($num); + $holdRowDimensions[$num] = $cloneDimension; + } + } + + return $holdRowDimensions; + } + /** * Remove a column, updating all possible related data. * - * @param string $pColumn Remove starting with this one, eg: 'A' - * @param int $pNumCols Number of columns to remove + * @param string $column Remove starting with this one, eg: 'A' + * @param int $numberOfColumns Number of columns to remove * - * @throws Exception - * - * @return Worksheet + * @return $this */ - public function removeColumn($pColumn, $pNumCols = 1) + public function removeColumn($column, $numberOfColumns = 1) { - if (!is_numeric($pColumn)) { - $highestColumn = $this->getHighestDataColumn(); - $pColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($pColumn) + $pNumCols); - $objReferenceHelper = ReferenceHelper::getInstance(); - $objReferenceHelper->insertNewBefore($pColumn . '1', -$pNumCols, 0, $this); - for ($c = 0; $c < $pNumCols; ++$c) { - $this->getCellCollection()->removeColumn($highestColumn); - $highestColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($highestColumn) - 1); - } - } else { + if (is_numeric($column)) { throw new Exception('Column references should not be numeric.'); } + $highestColumn = $this->getHighestDataColumn(); + $highestColumnIndex = Coordinate::columnIndexFromString($highestColumn); + $pColumnIndex = Coordinate::columnIndexFromString($column); + + $holdColumnDimensions = $this->removeColumnDimensions($pColumnIndex, $numberOfColumns); + + $column = Coordinate::stringFromColumnIndex($pColumnIndex + $numberOfColumns); + $objReferenceHelper = ReferenceHelper::getInstance(); + $objReferenceHelper->insertNewBefore($column . '1', -$numberOfColumns, 0, $this); + + $this->columnDimensions = $holdColumnDimensions; + + if ($pColumnIndex > $highestColumnIndex) { + return $this; + } + + $maxPossibleColumnsToBeRemoved = $highestColumnIndex - $pColumnIndex + 1; + + for ($c = 0, $n = min($maxPossibleColumnsToBeRemoved, $numberOfColumns); $c < $n; ++$c) { + $this->getCellCollection()->removeColumn($highestColumn); + $highestColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($highestColumn) - 1); + } + + $this->garbageCollect(); + return $this; } + private function removeColumnDimensions(int $pColumnIndex, int $numberOfColumns): array + { + $highCol = $pColumnIndex + $numberOfColumns - 1; + $holdColumnDimensions = []; + foreach ($this->columnDimensions as $columnDimension) { + $num = $columnDimension->getColumnNumeric(); + if ($num < $pColumnIndex) { + $str = $columnDimension->getColumnIndex(); + $holdColumnDimensions[$str] = $columnDimension; + } elseif ($num > $highCol) { + $cloneDimension = clone $columnDimension; + $cloneDimension->setColumnNumeric($num - $numberOfColumns); + $str = $cloneDimension->getColumnIndex(); + $holdColumnDimensions[$str] = $cloneDimension; + } + } + + return $holdColumnDimensions; + } + /** * Remove a column, updating all possible related data. * * @param int $columnIndex Remove starting with this one (numeric column coordinate of the cell) * @param int $numColumns Number of columns to remove * - * @throws Exception - * - * @return Worksheet + * @return $this */ public function removeColumnByIndex($columnIndex, $numColumns = 1) { @@ -2182,10 +2584,8 @@ class Worksheet implements IComparable /** * Show gridlines? - * - * @return bool */ - public function getShowGridlines() + public function getShowGridlines(): bool { return $this->showGridlines; } @@ -2193,23 +2593,21 @@ class Worksheet implements IComparable /** * Set show gridlines. * - * @param bool $pValue Show gridlines (true/false) + * @param bool $showGridLines Show gridlines (true/false) * - * @return Worksheet + * @return $this */ - public function setShowGridlines($pValue) + public function setShowGridlines(bool $showGridLines): self { - $this->showGridlines = $pValue; + $this->showGridlines = $showGridLines; return $this; } /** * Print gridlines? - * - * @return bool */ - public function getPrintGridlines() + public function getPrintGridlines(): bool { return $this->printGridlines; } @@ -2217,23 +2615,21 @@ class Worksheet implements IComparable /** * Set print gridlines. * - * @param bool $pValue Print gridlines (true/false) + * @param bool $printGridLines Print gridlines (true/false) * - * @return Worksheet + * @return $this */ - public function setPrintGridlines($pValue) + public function setPrintGridlines(bool $printGridLines): self { - $this->printGridlines = $pValue; + $this->printGridlines = $printGridLines; return $this; } /** * Show row and column headers? - * - * @return bool */ - public function getShowRowColHeaders() + public function getShowRowColHeaders(): bool { return $this->showRowColHeaders; } @@ -2241,23 +2637,21 @@ class Worksheet implements IComparable /** * Set show row and column headers. * - * @param bool $pValue Show row and column headers (true/false) + * @param bool $showRowColHeaders Show row and column headers (true/false) * - * @return Worksheet + * @return $this */ - public function setShowRowColHeaders($pValue) + public function setShowRowColHeaders(bool $showRowColHeaders): self { - $this->showRowColHeaders = $pValue; + $this->showRowColHeaders = $showRowColHeaders; return $this; } /** * Show summary below? (Row/Column outlining). - * - * @return bool */ - public function getShowSummaryBelow() + public function getShowSummaryBelow(): bool { return $this->showSummaryBelow; } @@ -2265,23 +2659,21 @@ class Worksheet implements IComparable /** * Set show summary below. * - * @param bool $pValue Show summary below (true/false) + * @param bool $showSummaryBelow Show summary below (true/false) * - * @return Worksheet + * @return $this */ - public function setShowSummaryBelow($pValue) + public function setShowSummaryBelow(bool $showSummaryBelow): self { - $this->showSummaryBelow = $pValue; + $this->showSummaryBelow = $showSummaryBelow; return $this; } /** * Show summary right? (Row/Column outlining). - * - * @return bool */ - public function getShowSummaryRight() + public function getShowSummaryRight(): bool { return $this->showSummaryRight; } @@ -2289,13 +2681,13 @@ class Worksheet implements IComparable /** * Set show summary right. * - * @param bool $pValue Show summary right (true/false) + * @param bool $showSummaryRight Show summary right (true/false) * - * @return Worksheet + * @return $this */ - public function setShowSummaryRight($pValue) + public function setShowSummaryRight(bool $showSummaryRight): self { - $this->showSummaryRight = $pValue; + $this->showSummaryRight = $showSummaryRight; return $this; } @@ -2313,13 +2705,40 @@ class Worksheet implements IComparable /** * Set comments array for the entire sheet. * - * @param Comment[] $pValue + * @param Comment[] $comments * - * @return Worksheet + * @return $this */ - public function setComments(array $pValue) + public function setComments(array $comments): self { - $this->comments = $pValue; + $this->comments = $comments; + + return $this; + } + + /** + * Remove comment from cell. + * + * @param array|CellAddress|string $cellCoordinate Coordinate of the cell as a string, eg: 'C5'; + * or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. + * + * @return $this + */ + public function removeComment($cellCoordinate): self + { + $cellAddress = Functions::trimSheetFromCellReference(Validations::validateCellAddress($cellCoordinate)); + + if (Coordinate::coordinateIsRange($cellAddress)) { + throw new Exception('Cell coordinate string can not be a range of cells.'); + } elseif (strpos($cellAddress, '$') !== false) { + throw new Exception('Cell coordinate string must not be absolute.'); + } elseif ($cellAddress == '') { + throw new Exception('Cell coordinate can not be zero-length string.'); + } + // Check if we have a comment for this cell and delete it + if (isset($this->comments[$cellAddress])) { + unset($this->comments[$cellAddress]); + } return $this; } @@ -2327,33 +2746,29 @@ class Worksheet implements IComparable /** * Get comment for cell. * - * @param string $pCellCoordinate Cell coordinate to get comment for, eg: 'A1' - * - * @throws Exception - * - * @return Comment + * @param array|CellAddress|string $cellCoordinate Coordinate of the cell as a string, eg: 'C5'; + * or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. */ - public function getComment($pCellCoordinate) + public function getComment($cellCoordinate): Comment { - // Uppercase coordinate - $pCellCoordinate = strtoupper($pCellCoordinate); + $cellAddress = Functions::trimSheetFromCellReference(Validations::validateCellAddress($cellCoordinate)); - if (Coordinate::coordinateIsRange($pCellCoordinate)) { + if (Coordinate::coordinateIsRange($cellAddress)) { throw new Exception('Cell coordinate string can not be a range of cells.'); - } elseif (strpos($pCellCoordinate, '$') !== false) { + } elseif (strpos($cellAddress, '$') !== false) { throw new Exception('Cell coordinate string must not be absolute.'); - } elseif ($pCellCoordinate == '') { + } elseif ($cellAddress == '') { throw new Exception('Cell coordinate can not be zero-length string.'); } // Check if we already have a comment for this cell. - if (isset($this->comments[$pCellCoordinate])) { - return $this->comments[$pCellCoordinate]; + if (isset($this->comments[$cellAddress])) { + return $this->comments[$cellAddress]; } // If not, create a new comment. $newComment = new Comment(); - $this->comments[$pCellCoordinate] = $newComment; + $this->comments[$cellAddress] = $newComment; return $newComment; } @@ -2361,12 +2776,15 @@ class Worksheet implements IComparable /** * Get comment for cell by using numeric cell coordinates. * + * @deprecated 1.23.0 + * Use the getComment() method with a cell address such as 'C5' instead;, + * or passing in an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. + * @see Worksheet::getComment() + * * @param int $columnIndex Numeric column coordinate of the cell * @param int $row Numeric row coordinate of the cell - * - * @return Comment */ - public function getCommentByColumnAndRow($columnIndex, $row) + public function getCommentByColumnAndRow($columnIndex, $row): Comment { return $this->getComment(Coordinate::stringFromColumnIndex($columnIndex) . $row); } @@ -2394,46 +2812,38 @@ class Worksheet implements IComparable /** * Selected cell. * - * @param string $pCoordinate Cell (i.e. A1) + * @param string $coordinate Cell (i.e. A1) * - * @return Worksheet + * @return $this */ - public function setSelectedCell($pCoordinate) + public function setSelectedCell($coordinate) { - return $this->setSelectedCells($pCoordinate); + return $this->setSelectedCells($coordinate); } /** * Select a range of cells. * - * @param string $pCoordinate Cell range, examples: 'A1', 'B2:G5', 'A:C', '3:6' + * @param AddressRange|array|CellAddress|int|string $coordinate A simple string containing a Cell range like 'A1:E10' + * or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]), + * or a CellAddress or AddressRange object. * - * @return Worksheet + * @return $this */ - public function setSelectedCells($pCoordinate) + public function setSelectedCells($coordinate) { - // Uppercase coordinate - $pCoordinate = strtoupper($pCoordinate); + if (is_string($coordinate)) { + $coordinate = Validations::definedNameToCoordinate($coordinate, $this); + } + $coordinate = Validations::validateCellOrCellRange($coordinate); - // Convert 'A' to 'A:A' - $pCoordinate = preg_replace('/^([A-Z]+)$/', '${1}:${1}', $pCoordinate); - - // Convert '1' to '1:1' - $pCoordinate = preg_replace('/^(\d+)$/', '${1}:${1}', $pCoordinate); - - // Convert 'A:C' to 'A1:C1048576' - $pCoordinate = preg_replace('/^([A-Z]+):([A-Z]+)$/', '${1}1:${2}1048576', $pCoordinate); - - // Convert '1:3' to 'A1:XFD3' - $pCoordinate = preg_replace('/^(\d+):(\d+)$/', 'A${1}:XFD${2}', $pCoordinate); - - if (Coordinate::coordinateIsRange($pCoordinate)) { - [$first] = Coordinate::splitRange($pCoordinate); + if (Coordinate::coordinateIsRange($coordinate)) { + [$first] = Coordinate::splitRange($coordinate); $this->activeCell = $first[0]; } else { - $this->activeCell = $pCoordinate; + $this->activeCell = $coordinate; } - $this->selectedCells = $pCoordinate; + $this->selectedCells = $coordinate; return $this; } @@ -2441,12 +2851,15 @@ class Worksheet implements IComparable /** * Selected cell by using numeric cell coordinates. * + * @deprecated 1.23.0 + * Use the setSelectedCells() method with a cell address such as 'C5' instead;, + * or passing in an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object. + * @see Worksheet::setSelectedCells() + * * @param int $columnIndex Numeric column coordinate of the cell * @param int $row Numeric row coordinate of the cell * - * @throws Exception - * - * @return Worksheet + * @return $this */ public function setSelectedCellByColumnAndRow($columnIndex, $row) { @@ -2468,7 +2881,7 @@ class Worksheet implements IComparable * * @param bool $value Right-to-left true/false * - * @return Worksheet + * @return $this */ public function setRightToLeft($value) { @@ -2485,9 +2898,7 @@ class Worksheet implements IComparable * @param string $startCell Insert array starting from this cell address as the top left coordinate * @param bool $strictNullComparison Apply strict comparison when testing for null values in the array * - * @throws Exception - * - * @return Worksheet + * @return $this */ public function fromArray(array $source, $nullValue = null, $startCell = 'A1', $strictNullComparison = false) { @@ -2525,7 +2936,7 @@ class Worksheet implements IComparable /** * Create array from a range of cells. * - * @param string $pRange Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1") + * @param string $range Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1") * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist * @param bool $calculateFormulas Should formulas be calculated? * @param bool $formatData Should formatting be applied to cell values? @@ -2534,12 +2945,12 @@ class Worksheet implements IComparable * * @return array */ - public function rangeToArray($pRange, $nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false) + public function rangeToArray($range, $nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false) { // Returnvalue $returnValue = []; // Identify the range that we need to extract from the worksheet - [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($pRange); + [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($range); $minCol = Coordinate::stringFromColumnIndex($rangeStart[0]); $minRow = $rangeStart[1]; $maxCol = Coordinate::stringFromColumnIndex($rangeEnd[0]); @@ -2549,11 +2960,11 @@ class Worksheet implements IComparable // Loop through rows $r = -1; for ($row = $minRow; $row <= $maxRow; ++$row) { - $rRef = ($returnCellRef) ? $row : ++$r; + $rRef = $returnCellRef ? $row : ++$r; $c = -1; // Loop through columns in the current row for ($col = $minCol; $col != $maxCol; ++$col) { - $cRef = ($returnCellRef) ? $col : ++$c; + $cRef = $returnCellRef ? $col : ++$c; // Using getCell() will create a new cell if it doesn't already exist. We don't want that to happen // so we test and retrieve directly against cellCollection if ($this->cellCollection->has($col . $row)) { @@ -2592,31 +3003,59 @@ class Worksheet implements IComparable return $returnValue; } + private function validateNamedRange(string $definedName, bool $returnNullIfInvalid = false): ?DefinedName + { + $namedRange = DefinedName::resolveName($definedName, $this); + if ($namedRange === null) { + if ($returnNullIfInvalid) { + return null; + } + + throw new Exception('Named Range ' . $definedName . ' does not exist.'); + } + + if ($namedRange->isFormula()) { + if ($returnNullIfInvalid) { + return null; + } + + throw new Exception('Defined Named ' . $definedName . ' is a formula, not a range or cell.'); + } + + if ($namedRange->getLocalOnly() && $this->getHashCode() !== $namedRange->getWorksheet()->getHashCode()) { + if ($returnNullIfInvalid) { + return null; + } + + throw new Exception( + 'Named range ' . $definedName . ' is not accessible from within sheet ' . $this->getTitle() + ); + } + + return $namedRange; + } + /** * Create array from a range of cells. * - * @param string $pNamedRange Name of the Named Range + * @param string $definedName The Named Range that should be returned * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist * @param bool $calculateFormulas Should formulas be calculated? * @param bool $formatData Should formatting be applied to cell values? * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero * True - Return rows and columns indexed by their actual row and column IDs * - * @throws Exception - * * @return array */ - public function namedRangeToArray($pNamedRange, $nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false) + public function namedRangeToArray(string $definedName, $nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false) { - $namedRange = NamedRange::resolveRange($pNamedRange, $this); - if ($namedRange !== null) { - $pWorkSheet = $namedRange->getWorksheet(); - $pCellRange = $namedRange->getRange(); + $namedRange = $this->validateNamedRange($definedName); + $workSheet = $namedRange->getWorksheet(); + /** @phpstan-ignore-next-line */ + $cellRange = ltrim(substr($namedRange->getValue(), strrpos($namedRange->getValue(), '!')), '!'); + $cellRange = str_replace('$', '', $cellRange); - return $pWorkSheet->rangeToArray($pCellRange, $nullValue, $calculateFormulas, $formatData, $returnCellRef); - } - - throw new Exception('Named Range ' . $pNamedRange . ' does not exist.'); + return $workSheet->rangeToArray($cellRange, $nullValue, $calculateFormulas, $formatData, $returnCellRef); } /** @@ -2672,7 +3111,7 @@ class Worksheet implements IComparable /** * Run PhpSpreadsheet garbage collector. * - * @return Worksheet + * @return $this */ public function garbageCollect() { @@ -2696,9 +3135,9 @@ class Worksheet implements IComparable // Cache values if ($highestColumn < 1) { - $this->cachedHighestColumn = 'A'; + $this->cachedHighestColumn = 1; } else { - $this->cachedHighestColumn = Coordinate::stringFromColumnIndex($highestColumn); + $this->cachedHighestColumn = $highestColumn; } $this->cachedHighestRow = $highestRow; @@ -2725,61 +3164,68 @@ class Worksheet implements IComparable * Extract worksheet title from range. * * Example: extractSheetTitle("testSheet!A1") ==> 'A1' + * Example: extractSheetTitle("testSheet!A1:C3") ==> 'A1:C3' * Example: extractSheetTitle("'testSheet 1'!A1", true) ==> ['testSheet 1', 'A1']; + * Example: extractSheetTitle("'testSheet 1'!A1:C3", true) ==> ['testSheet 1', 'A1:C3']; + * Example: extractSheetTitle("A1", true) ==> ['', 'A1']; + * Example: extractSheetTitle("A1:C3", true) ==> ['', 'A1:C3'] * - * @param string $pRange Range to extract title from + * @param string $range Range to extract title from * @param bool $returnRange Return range? (see example) * * @return mixed */ - public static function extractSheetTitle($pRange, $returnRange = false) + public static function extractSheetTitle($range, $returnRange = false) { + if (empty($range)) { + return $returnRange ? [null, null] : null; + } + // Sheet title included? - if (($sep = strrpos($pRange, '!')) === false) { - return $returnRange ? ['', $pRange] : ''; + if (($sep = strrpos($range, '!')) === false) { + return $returnRange ? ['', $range] : ''; } if ($returnRange) { - return [substr($pRange, 0, $sep), substr($pRange, $sep + 1)]; + return [substr($range, 0, $sep), substr($range, $sep + 1)]; } - return substr($pRange, $sep + 1); + return substr($range, $sep + 1); } /** * Get hyperlink. * - * @param string $pCellCoordinate Cell coordinate to get hyperlink for, eg: 'A1' + * @param string $cellCoordinate Cell coordinate to get hyperlink for, eg: 'A1' * * @return Hyperlink */ - public function getHyperlink($pCellCoordinate) + public function getHyperlink($cellCoordinate) { // return hyperlink if we already have one - if (isset($this->hyperlinkCollection[$pCellCoordinate])) { - return $this->hyperlinkCollection[$pCellCoordinate]; + if (isset($this->hyperlinkCollection[$cellCoordinate])) { + return $this->hyperlinkCollection[$cellCoordinate]; } // else create hyperlink - $this->hyperlinkCollection[$pCellCoordinate] = new Hyperlink(); + $this->hyperlinkCollection[$cellCoordinate] = new Hyperlink(); - return $this->hyperlinkCollection[$pCellCoordinate]; + return $this->hyperlinkCollection[$cellCoordinate]; } /** * Set hyperlink. * - * @param string $pCellCoordinate Cell coordinate to insert hyperlink, eg: 'A1' - * @param null|Hyperlink $pHyperlink + * @param string $cellCoordinate Cell coordinate to insert hyperlink, eg: 'A1' * - * @return Worksheet + * @return $this */ - public function setHyperlink($pCellCoordinate, Hyperlink $pHyperlink = null) + public function setHyperlink($cellCoordinate, ?Hyperlink $hyperlink = null) { - if ($pHyperlink === null) { - unset($this->hyperlinkCollection[$pCellCoordinate]); + if ($hyperlink === null) { + unset($this->hyperlinkCollection[$cellCoordinate]); } else { - $this->hyperlinkCollection[$pCellCoordinate] = $pHyperlink; + $this->hyperlinkCollection[$cellCoordinate] = $hyperlink; } return $this; @@ -2788,13 +3234,13 @@ class Worksheet implements IComparable /** * Hyperlink at a specific coordinate exists? * - * @param string $pCoordinate eg: 'A1' + * @param string $coordinate eg: 'A1' * * @return bool */ - public function hyperlinkExists($pCoordinate) + public function hyperlinkExists($coordinate) { - return isset($this->hyperlinkCollection[$pCoordinate]); + return isset($this->hyperlinkCollection[$coordinate]); } /** @@ -2810,37 +3256,36 @@ class Worksheet implements IComparable /** * Get data validation. * - * @param string $pCellCoordinate Cell coordinate to get data validation for, eg: 'A1' + * @param string $cellCoordinate Cell coordinate to get data validation for, eg: 'A1' * * @return DataValidation */ - public function getDataValidation($pCellCoordinate) + public function getDataValidation($cellCoordinate) { // return data validation if we already have one - if (isset($this->dataValidationCollection[$pCellCoordinate])) { - return $this->dataValidationCollection[$pCellCoordinate]; + if (isset($this->dataValidationCollection[$cellCoordinate])) { + return $this->dataValidationCollection[$cellCoordinate]; } // else create data validation - $this->dataValidationCollection[$pCellCoordinate] = new DataValidation(); + $this->dataValidationCollection[$cellCoordinate] = new DataValidation(); - return $this->dataValidationCollection[$pCellCoordinate]; + return $this->dataValidationCollection[$cellCoordinate]; } /** * Set data validation. * - * @param string $pCellCoordinate Cell coordinate to insert data validation, eg: 'A1' - * @param null|DataValidation $pDataValidation + * @param string $cellCoordinate Cell coordinate to insert data validation, eg: 'A1' * - * @return Worksheet + * @return $this */ - public function setDataValidation($pCellCoordinate, DataValidation $pDataValidation = null) + public function setDataValidation($cellCoordinate, ?DataValidation $dataValidation = null) { - if ($pDataValidation === null) { - unset($this->dataValidationCollection[$pCellCoordinate]); + if ($dataValidation === null) { + unset($this->dataValidationCollection[$cellCoordinate]); } else { - $this->dataValidationCollection[$pCellCoordinate] = $pDataValidation; + $this->dataValidationCollection[$cellCoordinate] = $dataValidation; } return $this; @@ -2849,13 +3294,13 @@ class Worksheet implements IComparable /** * Data validation at a specific coordinate exists? * - * @param string $pCoordinate eg: 'A1' + * @param string $coordinate eg: 'A1' * * @return bool */ - public function dataValidationExists($pCoordinate) + public function dataValidationExists($coordinate) { - return isset($this->dataValidationCollection[$pCoordinate]); + return isset($this->dataValidationCollection[$coordinate]); } /** @@ -2900,9 +3345,8 @@ class Worksheet implements IComparable $rangeSet = $rangeBoundaries[0][0] . $rangeBoundaries[0][1] . ':' . $rangeBoundaries[1][0] . $rangeBoundaries[1][1]; } unset($rangeSet); - $stRange = implode(' ', $rangeBlocks); - return $stRange; + return implode(' ', $rangeBlocks); } /** @@ -2922,12 +3366,11 @@ class Worksheet implements IComparable /** * Reset tab color. * - * @return Worksheet + * @return $this */ public function resetTabColor() { $this->tabColor = null; - unset($this->tabColor); return $this; } @@ -2945,13 +3388,71 @@ class Worksheet implements IComparable /** * Copy worksheet (!= clone!). * - * @return Worksheet + * @return static */ public function copy() { - $copied = clone $this; + return clone $this; + } - return $copied; + /** + * Returns a boolean true if the specified row contains no cells. By default, this means that no cell records + * exist in the collection for this row. false will be returned otherwise. + * This rule can be modified by passing a $definitionOfEmptyFlags value: + * 1 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL If the only cells in the collection are null value + * cells, then the row will be considered empty. + * 2 - CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL If the only cells in the collection are empty + * string value cells, then the row will be considered empty. + * 3 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL | CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL + * If the only cells in the collection are null value or empty string value cells, then the row + * will be considered empty. + * + * @param int $definitionOfEmptyFlags + * Possible Flag Values are: + * CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL + * CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL + */ + public function isEmptyRow(int $rowId, int $definitionOfEmptyFlags = 0): bool + { + try { + $iterator = new RowIterator($this, $rowId, $rowId); + $iterator->seek($rowId); + $row = $iterator->current(); + } catch (Exception $e) { + return true; + } + + return $row->isEmpty($definitionOfEmptyFlags); + } + + /** + * Returns a boolean true if the specified column contains no cells. By default, this means that no cell records + * exist in the collection for this column. false will be returned otherwise. + * This rule can be modified by passing a $definitionOfEmptyFlags value: + * 1 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL If the only cells in the collection are null value + * cells, then the column will be considered empty. + * 2 - CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL If the only cells in the collection are empty + * string value cells, then the column will be considered empty. + * 3 - CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL | CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL + * If the only cells in the collection are null value or empty string value cells, then the column + * will be considered empty. + * + * @param int $definitionOfEmptyFlags + * Possible Flag Values are: + * CellIterator::TREAT_NULL_VALUE_AS_EMPTY_CELL + * CellIterator::TREAT_EMPTY_STRING_AS_EMPTY_CELL + */ + public function isEmptyColumn(string $columnId, int $definitionOfEmptyFlags = 0): bool + { + try { + $iterator = new ColumnIterator($this, $columnId, $columnId); + $iterator->seek($columnId); + $column = $iterator->current(); + } catch (Exception $e) { + return true; + } + + return $column->isEmpty($definitionOfEmptyFlags); } /** @@ -2959,6 +3460,7 @@ class Worksheet implements IComparable */ public function __clone() { + // @phpstan-ignore-next-line foreach ($this as $key => $val) { if ($key == 'parent') { continue; @@ -2991,59 +3493,57 @@ class Worksheet implements IComparable /** * Define the code name of the sheet. * - * @param string $pValue Same rule as Title minus space not allowed (but, like Excel, change + * @param string $codeName Same rule as Title minus space not allowed (but, like Excel, change * silently space to underscore) * @param bool $validate False to skip validation of new title. WARNING: This should only be set * at parse time (by Readers), where titles can be assumed to be valid. * - * @throws Exception - * - * @return Worksheet + * @return $this */ - public function setCodeName($pValue, $validate = true) + public function setCodeName($codeName, $validate = true) { // Is this a 'rename' or not? - if ($this->getCodeName() == $pValue) { + if ($this->getCodeName() == $codeName) { return $this; } if ($validate) { - $pValue = str_replace(' ', '_', $pValue); //Excel does this automatically without flinching, we are doing the same + $codeName = str_replace(' ', '_', $codeName); //Excel does this automatically without flinching, we are doing the same // Syntax check // throw an exception if not valid - self::checkSheetCodeName($pValue); + self::checkSheetCodeName($codeName); // We use the same code that setTitle to find a valid codeName else not using a space (Excel don't like) but a '_' if ($this->getParent()) { // Is there already such sheet name? - if ($this->getParent()->sheetCodeNameExists($pValue)) { + if ($this->getParent()->sheetCodeNameExists($codeName)) { // Use name, but append with lowest possible integer - if (Shared\StringHelper::countCharacters($pValue) > 29) { - $pValue = Shared\StringHelper::substring($pValue, 0, 29); + if (Shared\StringHelper::countCharacters($codeName) > 29) { + $codeName = Shared\StringHelper::substring($codeName, 0, 29); } $i = 1; - while ($this->getParent()->sheetCodeNameExists($pValue . '_' . $i)) { + while ($this->getParent()->sheetCodeNameExists($codeName . '_' . $i)) { ++$i; if ($i == 10) { - if (Shared\StringHelper::countCharacters($pValue) > 28) { - $pValue = Shared\StringHelper::substring($pValue, 0, 28); + if (Shared\StringHelper::countCharacters($codeName) > 28) { + $codeName = Shared\StringHelper::substring($codeName, 0, 28); } } elseif ($i == 100) { - if (Shared\StringHelper::countCharacters($pValue) > 27) { - $pValue = Shared\StringHelper::substring($pValue, 0, 27); + if (Shared\StringHelper::countCharacters($codeName) > 27) { + $codeName = Shared\StringHelper::substring($codeName, 0, 27); } } } - $pValue = $pValue . '_' . $i; // ok, we have a valid name + $codeName .= '_' . $i; // ok, we have a valid name } } } - $this->codeName = $pValue; + $this->codeName = $codeName; return $this; } @@ -3067,4 +3567,9 @@ class Worksheet implements IComparable { return $this->codeName !== null; } + + public static function nameRequiresQuotes(string $sheetName): bool + { + return preg_match(self::SHEET_NAME_REQUIRES_NO_QUOTES, $sheetName) !== 1; + } } diff --git a/PhpOffice/PhpSpreadsheet/Writer/BaseWriter.php b/PhpOffice/PhpSpreadsheet/Writer/BaseWriter.php old mode 100755 new mode 100644 index f13150d..f903e93 --- a/PhpOffice/PhpSpreadsheet/Writer/BaseWriter.php +++ b/PhpOffice/PhpSpreadsheet/Writer/BaseWriter.php @@ -6,7 +6,7 @@ abstract class BaseWriter implements IWriter { /** * Write charts that are defined in the workbook? - * Identifies whether the Writer should write definitions for any charts that exist in the PhpSpreadsheet object;. + * Identifies whether the Writer should write definitions for any charts that exist in the PhpSpreadsheet object. * * @var bool */ @@ -35,14 +35,24 @@ abstract class BaseWriter implements IWriter */ private $diskCachingDirectory = './'; + /** + * @var resource + */ + protected $fileHandle; + + /** + * @var bool + */ + private $shouldCloseFile; + public function getIncludeCharts() { return $this->includeCharts; } - public function setIncludeCharts($pValue) + public function setIncludeCharts($includeCharts) { - $this->includeCharts = (bool) $pValue; + $this->includeCharts = (bool) $includeCharts; return $this; } @@ -52,9 +62,9 @@ abstract class BaseWriter implements IWriter return $this->preCalculateFormulas; } - public function setPreCalculateFormulas($pValue) + public function setPreCalculateFormulas($precalculateFormulas) { - $this->preCalculateFormulas = (bool) $pValue; + $this->preCalculateFormulas = (bool) $precalculateFormulas; return $this; } @@ -64,15 +74,15 @@ abstract class BaseWriter implements IWriter return $this->useDiskCaching; } - public function setUseDiskCaching($pValue, $pDirectory = null) + public function setUseDiskCaching($useDiskCache, $cacheDirectory = null) { - $this->useDiskCaching = $pValue; + $this->useDiskCaching = $useDiskCache; - if ($pDirectory !== null) { - if (is_dir($pDirectory)) { - $this->diskCachingDirectory = $pDirectory; + if ($cacheDirectory !== null) { + if (is_dir($cacheDirectory)) { + $this->diskCachingDirectory = $cacheDirectory; } else { - throw new Exception("Directory does not exist: $pDirectory"); + throw new Exception("Directory does not exist: $cacheDirectory"); } } @@ -83,4 +93,56 @@ abstract class BaseWriter implements IWriter { return $this->diskCachingDirectory; } + + protected function processFlags(int $flags): void + { + if (((bool) ($flags & self::SAVE_WITH_CHARTS)) === true) { + $this->setIncludeCharts(true); + } + if (((bool) ($flags & self::DISABLE_PRECALCULATE_FORMULAE)) === true) { + $this->setPreCalculateFormulas(false); + } + } + + /** + * Open file handle. + * + * @param resource|string $filename + */ + public function openFileHandle($filename): void + { + if (is_resource($filename)) { + $this->fileHandle = $filename; + $this->shouldCloseFile = false; + + return; + } + + $mode = 'wb'; + $scheme = parse_url($filename, PHP_URL_SCHEME); + if ($scheme === 's3') { + // @codeCoverageIgnoreStart + $mode = 'w'; + // @codeCoverageIgnoreEnd + } + $fileHandle = $filename ? fopen($filename, $mode) : false; + if ($fileHandle === false) { + throw new Exception('Could not open file "' . $filename . '" for writing.'); + } + + $this->fileHandle = $fileHandle; + $this->shouldCloseFile = true; + } + + /** + * Close file handle only if we opened it ourselves. + */ + protected function maybeCloseFileHandle(): void + { + if ($this->shouldCloseFile) { + if (!fclose($this->fileHandle)) { + throw new Exception('Could not close file after writing.'); + } + } + } } diff --git a/PhpOffice/PhpSpreadsheet/Writer/Csv.php b/PhpOffice/PhpSpreadsheet/Writer/Csv.php old mode 100755 new mode 100644 index ae38ab7..8f0ceda --- a/PhpOffice/PhpSpreadsheet/Writer/Csv.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Csv.php @@ -43,7 +43,7 @@ class Csv extends BaseWriter private $sheetIndex = 0; /** - * Whether to write a BOM (for UTF8). + * Whether to write a UTF8 BOM. * * @var bool */ @@ -65,9 +65,14 @@ class Csv extends BaseWriter private $excelCompatibility = false; /** - * Create a new CSV. + * Output encoding. * - * @param Spreadsheet $spreadsheet Spreadsheet object + * @var string + */ + private $outputEncoding = ''; + + /** + * Create a new CSV. */ public function __construct(Spreadsheet $spreadsheet) { @@ -77,12 +82,12 @@ class Csv extends BaseWriter /** * Save PhpSpreadsheet to file. * - * @param string $pFilename - * - * @throws Exception + * @param resource|string $filename */ - public function save($pFilename) + public function save($filename, int $flags = 0): void { + $this->processFlags($flags); + // Fetch sheet $sheet = $this->spreadsheet->getSheet($this->sheetIndex); @@ -92,10 +97,7 @@ class Csv extends BaseWriter Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_VALUE); // Open file - $fileHandle = fopen($pFilename, 'wb+'); - if ($fileHandle === false) { - throw new Exception("Could not open file $pFilename for writing."); - } + $this->openFileHandle($filename); if ($this->excelCompatibility) { $this->setUseBOM(true); // Enforce UTF-8 BOM Header @@ -104,13 +106,15 @@ class Csv extends BaseWriter $this->setDelimiter(';'); // Set delimiter to a semi-colon $this->setLineEnding("\r\n"); } + if ($this->useBOM) { // Write the UTF-8 BOM code if required - fwrite($fileHandle, "\xEF\xBB\xBF"); + fwrite($this->fileHandle, "\xEF\xBB\xBF"); } + if ($this->includeSeparatorLine) { // Write the separator line if required - fwrite($fileHandle, 'sep=' . $this->getDelimiter() . $this->lineEnding); + fwrite($this->fileHandle, 'sep=' . $this->getDelimiter() . $this->lineEnding); } // Identify the range that we need to extract from the worksheet @@ -122,145 +126,90 @@ class Csv extends BaseWriter // Convert the row to an array... $cellsArray = $sheet->rangeToArray('A' . $row . ':' . $maxCol . $row, '', $this->preCalculateFormulas); // ... and write to the file - $this->writeLine($fileHandle, $cellsArray[0]); + $this->writeLine($this->fileHandle, $cellsArray[0]); } - // Close file - fclose($fileHandle); - + $this->maybeCloseFileHandle(); Calculation::setArrayReturnType($saveArrayReturnType); Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog($saveDebugLog); } - /** - * Get delimiter. - * - * @return string - */ - public function getDelimiter() + public function getDelimiter(): string { return $this->delimiter; } - /** - * Set delimiter. - * - * @param string $pValue Delimiter, defaults to ',' - * - * @return CSV - */ - public function setDelimiter($pValue) + public function setDelimiter(string $delimiter): self { - $this->delimiter = $pValue; + $this->delimiter = $delimiter; return $this; } - /** - * Get enclosure. - * - * @return string - */ - public function getEnclosure() + public function getEnclosure(): string { return $this->enclosure; } - /** - * Set enclosure. - * - * @param string $pValue Enclosure, defaults to " - * - * @return CSV - */ - public function setEnclosure($pValue) + public function setEnclosure(string $enclosure = '"'): self { - if ($pValue == '') { - $pValue = null; - } - $this->enclosure = $pValue; + $this->enclosure = $enclosure; return $this; } - /** - * Get line ending. - * - * @return string - */ - public function getLineEnding() + public function getLineEnding(): string { return $this->lineEnding; } - /** - * Set line ending. - * - * @param string $pValue Line ending, defaults to OS line ending (PHP_EOL) - * - * @return CSV - */ - public function setLineEnding($pValue) + public function setLineEnding(string $lineEnding): self { - $this->lineEnding = $pValue; + $this->lineEnding = $lineEnding; return $this; } /** * Get whether BOM should be used. - * - * @return bool */ - public function getUseBOM() + public function getUseBOM(): bool { return $this->useBOM; } /** - * Set whether BOM should be used. - * - * @param bool $pValue Use UTF-8 byte-order mark? Defaults to false - * - * @return CSV + * Set whether BOM should be used, typically when non-ASCII characters are used. */ - public function setUseBOM($pValue) + public function setUseBOM(bool $useBOM): self { - $this->useBOM = $pValue; + $this->useBOM = $useBOM; return $this; } /** * Get whether a separator line should be included. - * - * @return bool */ - public function getIncludeSeparatorLine() + public function getIncludeSeparatorLine(): bool { return $this->includeSeparatorLine; } /** * Set whether a separator line should be included as the first line of the file. - * - * @param bool $pValue Use separator line? Defaults to false - * - * @return CSV */ - public function setIncludeSeparatorLine($pValue) + public function setIncludeSeparatorLine(bool $includeSeparatorLine): self { - $this->includeSeparatorLine = $pValue; + $this->includeSeparatorLine = $includeSeparatorLine; return $this; } /** * Get whether the file should be saved with full Excel Compatibility. - * - * @return bool */ - public function getExcelCompatibility() + public function getExcelCompatibility(): bool { return $this->excelCompatibility; } @@ -268,75 +217,110 @@ class Csv extends BaseWriter /** * Set whether the file should be saved with full Excel Compatibility. * - * @param bool $pValue Set the file to be written as a fully Excel compatible csv file + * @param bool $excelCompatibility Set the file to be written as a fully Excel compatible csv file * Note that this overrides other settings such as useBOM, enclosure and delimiter - * - * @return CSV */ - public function setExcelCompatibility($pValue) + public function setExcelCompatibility(bool $excelCompatibility): self { - $this->excelCompatibility = $pValue; + $this->excelCompatibility = $excelCompatibility; return $this; } - /** - * Get sheet index. - * - * @return int - */ - public function getSheetIndex() + public function getSheetIndex(): int { return $this->sheetIndex; } - /** - * Set sheet index. - * - * @param int $pValue Sheet index - * - * @return CSV - */ - public function setSheetIndex($pValue) + public function setSheetIndex(int $sheetIndex): self { - $this->sheetIndex = $pValue; + $this->sheetIndex = $sheetIndex; return $this; } + public function getOutputEncoding(): string + { + return $this->outputEncoding; + } + + public function setOutputEncoding(string $outputEnconding): self + { + $this->outputEncoding = $outputEnconding; + + return $this; + } + + /** @var bool */ + private $enclosureRequired = true; + + public function setEnclosureRequired(bool $value): self + { + $this->enclosureRequired = $value; + + return $this; + } + + public function getEnclosureRequired(): bool + { + return $this->enclosureRequired; + } + + /** + * Convert boolean to TRUE/FALSE; otherwise return element cast to string. + * + * @param mixed $element + */ + private static function elementToString($element): string + { + if (is_bool($element)) { + return $element ? 'TRUE' : 'FALSE'; + } + + return (string) $element; + } + /** * Write line to CSV file. * - * @param resource $pFileHandle PHP filehandle - * @param array $pValues Array containing values in a row + * @param resource $fileHandle PHP filehandle + * @param array $values Array containing values in a row */ - private function writeLine($pFileHandle, array $pValues) + private function writeLine($fileHandle, array $values): void { // No leading delimiter - $writeDelimiter = false; + $delimiter = ''; // Build the line $line = ''; - foreach ($pValues as $element) { - // Escape enclosures - $element = str_replace($this->enclosure, $this->enclosure . $this->enclosure, $element); - + foreach ($values as $element) { + $element = self::elementToString($element); // Add delimiter - if ($writeDelimiter) { - $line .= $this->delimiter; - } else { - $writeDelimiter = true; + $line .= $delimiter; + $delimiter = $this->delimiter; + // Escape enclosures + $enclosure = $this->enclosure; + if ($enclosure) { + // If enclosure is not required, use enclosure only if + // element contains newline, delimiter, or enclosure. + if (!$this->enclosureRequired && strpbrk($element, "$delimiter$enclosure\n") === false) { + $enclosure = ''; + } else { + $element = str_replace($enclosure, $enclosure . $enclosure, $element); + } } - // Add enclosed string - $line .= $this->enclosure . $element . $this->enclosure; + $line .= $enclosure . $element . $enclosure; } // Add line ending $line .= $this->lineEnding; // Write to file - fwrite($pFileHandle, $line); + if ($this->outputEncoding != '') { + $line = mb_convert_encoding($line, $this->outputEncoding); + } + fwrite($fileHandle, /** @scrutinizer ignore-type */ $line); } } diff --git a/PhpOffice/PhpSpreadsheet/Writer/Exception.php b/PhpOffice/PhpSpreadsheet/Writer/Exception.php old mode 100755 new mode 100644 diff --git a/PhpOffice/PhpSpreadsheet/Writer/Html.php b/PhpOffice/PhpSpreadsheet/Writer/Html.php old mode 100755 new mode 100644 index d626e9c..0fef0f6 --- a/PhpOffice/PhpSpreadsheet/Writer/Html.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Html.php @@ -2,12 +2,14 @@ namespace PhpOffice\PhpSpreadsheet\Writer; +use HTMLPurifier; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Chart\Chart; use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\RichText\Run; +use PhpOffice\PhpSpreadsheet\Settings; use PhpOffice\PhpSpreadsheet\Shared\Drawing as SharedDrawing; use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Shared\Font as SharedFont; @@ -22,8 +24,8 @@ use PhpOffice\PhpSpreadsheet\Style\NumberFormat; use PhpOffice\PhpSpreadsheet\Style\Style; use PhpOffice\PhpSpreadsheet\Worksheet\Drawing; use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing; +use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; -use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException; class Html extends BaseWriter { @@ -37,7 +39,7 @@ class Html extends BaseWriter /** * Sheet index to write. * - * @var int + * @var null|int */ private $sheetIndex = 0; @@ -53,7 +55,7 @@ class Html extends BaseWriter * * @var bool */ - private $embedImages = false; + protected $embedImages = false; /** * Use inline CSS? @@ -62,6 +64,13 @@ class Html extends BaseWriter */ private $useInlineCss = false; + /** + * Use embedded CSS? + * + * @var bool + */ + private $useEmbeddedCSS = true; + /** * Array of CSS styles. * @@ -118,6 +127,13 @@ class Html extends BaseWriter */ protected $isPdf = false; + /** + * Is the current writer creating mPDF? + * + * @var bool + */ + protected $isMPdf = false; + /** * Generate the Navigation block. * @@ -126,9 +142,14 @@ class Html extends BaseWriter private $generateSheetNavigationBlock = true; /** - * Create a new HTML. + * Callback for editing generated html. * - * @param Spreadsheet $spreadsheet + * @var null|callable + */ + private $editHtmlCallback; + + /** + * Create a new HTML. */ public function __construct(Spreadsheet $spreadsheet) { @@ -139,11 +160,28 @@ class Html extends BaseWriter /** * Save Spreadsheet to file. * - * @param string $pFilename - * - * @throws WriterException + * @param resource|string $filename */ - public function save($pFilename) + public function save($filename, int $flags = 0): void + { + $this->processFlags($flags); + + // Open file + $this->openFileHandle($filename); + + // Write html + fwrite($this->fileHandle, $this->generateHTMLAll()); + + // Close file + $this->maybeCloseFileHandle(); + } + + /** + * Save Spreadsheet as html to variable. + * + * @return string + */ + public function generateHtmlAll() { // garbage collect $this->spreadsheet->garbageCollect(); @@ -156,31 +194,41 @@ class Html extends BaseWriter // Build CSS $this->buildCSS(!$this->useInlineCss); - // Open file - $fileHandle = fopen($pFilename, 'wb+'); - if ($fileHandle === false) { - throw new WriterException("Could not open file $pFilename for writing."); - } + $html = ''; // Write headers - fwrite($fileHandle, $this->generateHTMLHeader(!$this->useInlineCss)); + $html .= $this->generateHTMLHeader(!$this->useInlineCss); // Write navigation (tabs) if ((!$this->isPdf) && ($this->generateSheetNavigationBlock)) { - fwrite($fileHandle, $this->generateNavigation()); + $html .= $this->generateNavigation(); } // Write data - fwrite($fileHandle, $this->generateSheetData()); + $html .= $this->generateSheetData(); // Write footer - fwrite($fileHandle, $this->generateHTMLFooter()); - - // Close file - fclose($fileHandle); + $html .= $this->generateHTMLFooter(); + $callback = $this->editHtmlCallback; + if ($callback) { + $html = $callback($html); + } Calculation::setArrayReturnType($saveArrayReturnType); Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog($saveDebugLog); + + return $html; + } + + /** + * Set a callback to edit the entire HTML. + * + * The callback must accept the HTML as string as first parameter, + * and it must return the edited HTML as string. + */ + public function setEditHtmlCallback(?callable $callback): void + { + $this->editHtmlCallback = $callback; } /** @@ -192,17 +240,7 @@ class Html extends BaseWriter */ private function mapVAlign($vAlign) { - switch ($vAlign) { - case Alignment::VERTICAL_BOTTOM: - return 'bottom'; - case Alignment::VERTICAL_TOP: - return 'top'; - case Alignment::VERTICAL_CENTER: - case Alignment::VERTICAL_JUSTIFY: - return 'middle'; - default: - return 'baseline'; - } + return Alignment::VERTICAL_ALIGNMENT_FOR_HTML[$vAlign] ?? ''; } /** @@ -210,77 +248,44 @@ class Html extends BaseWriter * * @param string $hAlign Horizontal alignment * - * @return false|string + * @return string */ private function mapHAlign($hAlign) { - switch ($hAlign) { - case Alignment::HORIZONTAL_GENERAL: - return false; - case Alignment::HORIZONTAL_LEFT: - return 'left'; - case Alignment::HORIZONTAL_RIGHT: - return 'right'; - case Alignment::HORIZONTAL_CENTER: - case Alignment::HORIZONTAL_CENTER_CONTINUOUS: - return 'center'; - case Alignment::HORIZONTAL_JUSTIFY: - return 'justify'; - default: - return false; - } + return Alignment::HORIZONTAL_ALIGNMENT_FOR_HTML[$hAlign] ?? ''; } + const BORDER_ARR = [ + Border::BORDER_NONE => 'none', + Border::BORDER_DASHDOT => '1px dashed', + Border::BORDER_DASHDOTDOT => '1px dotted', + Border::BORDER_DASHED => '1px dashed', + Border::BORDER_DOTTED => '1px dotted', + Border::BORDER_DOUBLE => '3px double', + Border::BORDER_HAIR => '1px solid', + Border::BORDER_MEDIUM => '2px solid', + Border::BORDER_MEDIUMDASHDOT => '2px dashed', + Border::BORDER_MEDIUMDASHDOTDOT => '2px dotted', + Border::BORDER_SLANTDASHDOT => '2px dashed', + Border::BORDER_THICK => '3px solid', + ]; + /** * Map border style. * - * @param int $borderStyle Sheet index + * @param int|string $borderStyle Sheet index * * @return string */ private function mapBorderStyle($borderStyle) { - switch ($borderStyle) { - case Border::BORDER_NONE: - return 'none'; - case Border::BORDER_DASHDOT: - return '1px dashed'; - case Border::BORDER_DASHDOTDOT: - return '1px dotted'; - case Border::BORDER_DASHED: - return '1px dashed'; - case Border::BORDER_DOTTED: - return '1px dotted'; - case Border::BORDER_DOUBLE: - return '3px double'; - case Border::BORDER_HAIR: - return '1px solid'; - case Border::BORDER_MEDIUM: - return '2px solid'; - case Border::BORDER_MEDIUMDASHDOT: - return '2px dashed'; - case Border::BORDER_MEDIUMDASHDOTDOT: - return '2px dotted'; - case Border::BORDER_MEDIUMDASHED: - return '2px dashed'; - case Border::BORDER_SLANTDASHDOT: - return '2px dashed'; - case Border::BORDER_THICK: - return '3px solid'; - case Border::BORDER_THIN: - return '1px solid'; - default: - // map others to thin - return '1px solid'; - } + return array_key_exists($borderStyle, self::BORDER_ARR) ? self::BORDER_ARR[$borderStyle] : '1px solid'; } /** * Get sheet index. - * - * @return int */ - public function getSheetIndex() + public function getSheetIndex(): ?int { return $this->sheetIndex; } @@ -288,13 +293,13 @@ class Html extends BaseWriter /** * Set sheet index. * - * @param int $pValue Sheet index + * @param int $sheetIndex Sheet index * - * @return HTML + * @return $this */ - public function setSheetIndex($pValue) + public function setSheetIndex($sheetIndex) { - $this->sheetIndex = $pValue; + $this->sheetIndex = $sheetIndex; return $this; } @@ -312,19 +317,21 @@ class Html extends BaseWriter /** * Set sheet index. * - * @param bool $pValue Flag indicating whether the sheet navigation block should be generated or not + * @param bool $generateSheetNavigationBlock Flag indicating whether the sheet navigation block should be generated or not * - * @return HTML + * @return $this */ - public function setGenerateSheetNavigationBlock($pValue) + public function setGenerateSheetNavigationBlock($generateSheetNavigationBlock) { - $this->generateSheetNavigationBlock = (bool) $pValue; + $this->generateSheetNavigationBlock = (bool) $generateSheetNavigationBlock; return $this; } /** * Write all sheets (resets sheetIndex to NULL). + * + * @return $this */ public function writeAllSheets() { @@ -333,84 +340,105 @@ class Html extends BaseWriter return $this; } + private static function generateMeta(?string $val, string $desc): string + { + return $val + ? (' ' . PHP_EOL) + : ''; + } + + public const BODY_LINE = ' ' . PHP_EOL; + /** * Generate HTML header. * - * @param bool $pIncludeStyles Include styles? - * - * @throws WriterException + * @param bool $includeStyles Include styles? * * @return string */ - public function generateHTMLHeader($pIncludeStyles = false) + public function generateHTMLHeader($includeStyles = false) { // Construct HTML $properties = $this->spreadsheet->getProperties(); - $html = '' . PHP_EOL; - $html .= '' . PHP_EOL; + $html = '' . PHP_EOL; + $html .= '' . PHP_EOL; $html .= ' ' . PHP_EOL; - $html .= ' ' . PHP_EOL; - $html .= ' ' . PHP_EOL; - if ($properties->getTitle() > '') { - $html .= ' ' . htmlspecialchars($properties->getTitle()) . '' . PHP_EOL; - } - if ($properties->getCreator() > '') { - $html .= ' ' . PHP_EOL; - } - if ($properties->getTitle() > '') { - $html .= ' ' . PHP_EOL; - } - if ($properties->getDescription() > '') { - $html .= ' ' . PHP_EOL; - } - if ($properties->getSubject() > '') { - $html .= ' ' . PHP_EOL; - } - if ($properties->getKeywords() > '') { - $html .= ' ' . PHP_EOL; - } - if ($properties->getCategory() > '') { - $html .= ' ' . PHP_EOL; - } - if ($properties->getCompany() > '') { - $html .= ' ' . PHP_EOL; - } - if ($properties->getManager() > '') { - $html .= ' ' . PHP_EOL; - } + $html .= ' ' . PHP_EOL; + $html .= ' ' . PHP_EOL; + $html .= ' ' . htmlspecialchars($properties->getTitle(), Settings::htmlEntityFlags()) . '' . PHP_EOL; + $html .= self::generateMeta($properties->getCreator(), 'author'); + $html .= self::generateMeta($properties->getTitle(), 'title'); + $html .= self::generateMeta($properties->getDescription(), 'description'); + $html .= self::generateMeta($properties->getSubject(), 'subject'); + $html .= self::generateMeta($properties->getKeywords(), 'keywords'); + $html .= self::generateMeta($properties->getCategory(), 'category'); + $html .= self::generateMeta($properties->getCompany(), 'company'); + $html .= self::generateMeta($properties->getManager(), 'manager'); - if ($pIncludeStyles) { - $html .= $this->generateStyles(true); - } + $html .= $includeStyles ? $this->generateStyles(true) : $this->generatePageDeclarations(true); $html .= ' ' . PHP_EOL; $html .= '' . PHP_EOL; - $html .= ' ' . PHP_EOL; + $html .= self::BODY_LINE; return $html; } + private function generateSheetPrep(): array + { + // Ensure that Spans have been calculated? + $this->calculateSpans(); + + // Fetch sheets + if ($this->sheetIndex === null) { + $sheets = $this->spreadsheet->getAllSheets(); + } else { + $sheets = [$this->spreadsheet->getSheet($this->sheetIndex)]; + } + + return $sheets; + } + + private function generateSheetStarts(Worksheet $sheet, int $rowMin): array + { + // calculate start of , + $tbodyStart = $rowMin; + $theadStart = $theadEnd = 0; // default: no no + if ($sheet->getPageSetup()->isRowsToRepeatAtTopSet()) { + $rowsToRepeatAtTop = $sheet->getPageSetup()->getRowsToRepeatAtTop(); + + // we can only support repeating rows that start at top row + if ($rowsToRepeatAtTop[0] == 1) { + $theadStart = $rowsToRepeatAtTop[0]; + $theadEnd = $rowsToRepeatAtTop[1]; + $tbodyStart = $rowsToRepeatAtTop[1] + 1; + } + } + + return [$theadStart, $theadEnd, $tbodyStart]; + } + + private function generateSheetTags(int $row, int $theadStart, int $theadEnd, int $tbodyStart): array + { + // ? + $startTag = ($row == $theadStart) ? (' ' . PHP_EOL) : ''; + if (!$startTag) { + $startTag = ($row == $tbodyStart) ? (' ' . PHP_EOL) : ''; + } + $endTag = ($row == $theadEnd) ? (' ' . PHP_EOL) : ''; + $cellType = ($row >= $tbodyStart) ? 'td' : 'th'; + + return [$cellType, $startTag, $endTag]; + } + /** * Generate sheet data. * - * @throws WriterException - * * @return string */ public function generateSheetData() { - // Ensure that Spans have been calculated? - if ($this->sheetIndex !== null || !$this->spansAreCalculated) { - $this->calculateSpans(); - } - - // Fetch sheets - $sheets = []; - if ($this->sheetIndex === null) { - $sheets = $this->spreadsheet->getAllSheets(); - } else { - $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex); - } + $sheets = $this->generateSheetPrep(); // Construct HTML $html = ''; @@ -422,80 +450,43 @@ class Html extends BaseWriter $html .= $this->generateTableHeader($sheet); // Get worksheet dimension - $dimension = explode(':', $sheet->calculateWorksheetDimension()); - $dimension[0] = Coordinate::coordinateFromString($dimension[0]); - $dimension[0][0] = Coordinate::columnIndexFromString($dimension[0][0]); - $dimension[1] = Coordinate::coordinateFromString($dimension[1]); - $dimension[1][0] = Coordinate::columnIndexFromString($dimension[1][0]); + [$min, $max] = explode(':', $sheet->calculateWorksheetDataDimension()); + [$minCol, $minRow] = Coordinate::indexesFromString($min); + [$maxCol, $maxRow] = Coordinate::indexesFromString($max); - // row min,max - $rowMin = $dimension[0][1]; - $rowMax = $dimension[1][1]; - - // calculate start of , - $tbodyStart = $rowMin; - $theadStart = $theadEnd = 0; // default: no no - if ($sheet->getPageSetup()->isRowsToRepeatAtTopSet()) { - $rowsToRepeatAtTop = $sheet->getPageSetup()->getRowsToRepeatAtTop(); - - // we can only support repeating rows that start at top row - if ($rowsToRepeatAtTop[0] == 1) { - $theadStart = $rowsToRepeatAtTop[0]; - $theadEnd = $rowsToRepeatAtTop[1]; - $tbodyStart = $rowsToRepeatAtTop[1] + 1; - } - } + [$theadStart, $theadEnd, $tbodyStart] = $this->generateSheetStarts($sheet, $minRow); // Loop through cells - $row = $rowMin - 1; - while ($row++ < $rowMax) { - // ? - if ($row == $theadStart) { - $html .= ' ' . PHP_EOL; - $cellType = 'th'; - } - - // ? - if ($row == $tbodyStart) { - $html .= ' ' . PHP_EOL; - $cellType = 'td'; - } + $row = $minRow - 1; + while ($row++ < $maxRow) { + [$cellType, $startTag, $endTag] = $this->generateSheetTags($row, $theadStart, $theadEnd, $tbodyStart); + $html .= $startTag; // Write row if there are HTML table cells in it if (!isset($this->isSpannedRow[$sheet->getParent()->getIndex($sheet)][$row])) { // Start a new rowData $rowData = []; // Loop through columns - $column = $dimension[0][0]; - while ($column <= $dimension[1][0]) { + $column = $minCol; + while ($column <= $maxCol) { // Cell exists? - if ($sheet->cellExistsByColumnAndRow($column, $row)) { - $rowData[$column] = Coordinate::stringFromColumnIndex($column) . $row; - } else { - $rowData[$column] = ''; - } - ++$column; + $cellAddress = Coordinate::stringFromColumnIndex($column) . $row; + $rowData[$column++] = ($sheet->getCellCollection()->has($cellAddress)) ? $cellAddress : ''; } $html .= $this->generateRow($sheet, $rowData, $row - 1, $cellType); } - // ? - if ($row == $theadEnd) { - $html .= ' ' . PHP_EOL; - } + $html .= $endTag; } + --$row; $html .= $this->extendRowsForChartsAndImages($sheet, $row); - // Close table body. - $html .= ' ' . PHP_EOL; - // Write table footer $html .= $this->generateTableFooter(); - // Writing PDF? - if ($this->isPdf) { + if ($this->isPdf && $this->useInlineCss) { if ($this->sheetIndex === null && $sheetId + 1 < $this->spreadsheet->getSheetCount()) { - $html .= '

    '; + $html .= '
    '; } } @@ -509,8 +500,6 @@ class Html extends BaseWriter /** * Generate sheet tabs. * - * @throws WriterException - * * @return string */ public function generateNavigation() @@ -544,13 +533,24 @@ class Html extends BaseWriter return $html; } - private function extendRowsForChartsAndImages(Worksheet $pSheet, $row) + /** + * Extend Row if chart is placed after nominal end of row. + * This code should be exercised by sample: + * Chart/32_Chart_read_write_PDF.php. + * + * @param int $row Row to check for charts + * + * @return array + */ + private function extendRowsForCharts(Worksheet $worksheet, int $row) { $rowMax = $row; $colMax = 'A'; + $anyfound = false; if ($this->includeCharts) { - foreach ($pSheet->getChartCollection() as $chart) { + foreach ($worksheet->getChartCollection() as $chart) { if ($chart instanceof Chart) { + $anyfound = true; $chartCoordinates = $chart->getTopLeftPosition(); $chartTL = Coordinate::coordinateFromString($chartCoordinates['cell']); $chartCol = Coordinate::columnIndexFromString($chartTL[0]); @@ -564,122 +564,137 @@ class Html extends BaseWriter } } - foreach ($pSheet->getDrawingCollection() as $drawing) { - if ($drawing instanceof Drawing) { - $imageTL = Coordinate::coordinateFromString($drawing->getCoordinates()); - $imageCol = Coordinate::columnIndexFromString($imageTL[0]); - if ($imageTL[1] > $rowMax) { - $rowMax = $imageTL[1]; - if ($imageCol > Coordinate::columnIndexFromString($colMax)) { - $colMax = $imageTL[0]; - } + return [$rowMax, $colMax, $anyfound]; + } + + private function extendRowsForChartsAndImages(Worksheet $worksheet, int $row): string + { + [$rowMax, $colMax, $anyfound] = $this->extendRowsForCharts($worksheet, $row); + + foreach ($worksheet->getDrawingCollection() as $drawing) { + $anyfound = true; + $imageTL = Coordinate::coordinateFromString($drawing->getCoordinates()); + $imageCol = Coordinate::columnIndexFromString($imageTL[0]); + if ($imageTL[1] > $rowMax) { + $rowMax = $imageTL[1]; + if ($imageCol > Coordinate::columnIndexFromString($colMax)) { + $colMax = $imageTL[0]; } } } // Don't extend rows if not needed - if ($row === $rowMax) { + if ($row === $rowMax || !$anyfound) { return ''; } $html = ''; ++$colMax; - + ++$row; while ($row <= $rowMax) { $html .= ''; for ($col = 'A'; $col != $colMax; ++$col) { - $html .= ''; - $html .= $this->writeImageInCell($pSheet, $col . $row); - if ($this->includeCharts) { - $html .= $this->writeChartInCell($pSheet, $col . $row); + $htmlx = $this->writeImageInCell($worksheet, $col . $row); + $htmlx .= $this->includeCharts ? $this->writeChartInCell($worksheet, $col . $row) : ''; + if ($htmlx) { + $html .= "$htmlx"; + } else { + $html .= ""; } - $html .= ''; } ++$row; - $html .= ''; + $html .= '' . PHP_EOL; } return $html; } + /** + * Convert Windows file name to file protocol URL. + * + * @param string $filename file name on local system + * + * @return string + */ + public static function winFileToUrl($filename, bool $mpdf = false) + { + // Windows filename + if (substr($filename, 1, 2) === ':\\') { + $protocol = $mpdf ? '' : 'file:///'; + $filename = $protocol . str_replace('\\', '/', $filename); + } + + return $filename; + } + /** * Generate image tag in cell. * - * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet + * @param Worksheet $worksheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet * @param string $coordinates Cell coordinates * * @return string */ - private function writeImageInCell(Worksheet $pSheet, $coordinates) + private function writeImageInCell(Worksheet $worksheet, $coordinates) { // Construct HTML $html = ''; // Write images - foreach ($pSheet->getDrawingCollection() as $drawing) { + foreach ($worksheet->getDrawingCollection() as $drawing) { + if ($drawing->getCoordinates() != $coordinates) { + continue; + } + $filedesc = $drawing->getDescription(); + $filedesc = $filedesc ? htmlspecialchars($filedesc, ENT_QUOTES) : 'Embedded image'; if ($drawing instanceof Drawing) { - if ($drawing->getCoordinates() == $coordinates) { - $filename = $drawing->getPath(); + $filename = $drawing->getPath(); - // Strip off eventual '.' - if (substr($filename, 0, 1) == '.') { - $filename = substr($filename, 1); + // Strip off eventual '.' + $filename = (string) preg_replace('/^[.]/', '', $filename); + + // Prepend images root + $filename = $this->getImagesRoot() . $filename; + + // Strip off eventual '.' if followed by non-/ + $filename = (string) preg_replace('@^[.]([^/])@', '$1', $filename); + + // Convert UTF8 data to PCDATA + $filename = htmlspecialchars($filename, Settings::htmlEntityFlags()); + + $html .= PHP_EOL; + $imageData = self::winFileToUrl($filename, $this->isMPdf); + + if ($this->embedImages || substr($imageData, 0, 6) === 'zip://') { + $picture = @file_get_contents($filename); + if ($picture !== false) { + $imageDetails = getimagesize($filename) ?: []; + // base64 encode the binary data + $base64 = base64_encode($picture); + $imageData = 'data:' . $imageDetails['mime'] . ';base64,' . $base64; } - - // Prepend images root - $filename = $this->getImagesRoot() . $filename; - - // Strip off eventual '.' - if (substr($filename, 0, 1) == '.' && substr($filename, 0, 2) != './') { - $filename = substr($filename, 1); - } - - // Convert UTF8 data to PCDATA - $filename = htmlspecialchars($filename); - - $html .= PHP_EOL; - if ((!$this->embedImages) || ($this->isPdf)) { - $imageData = $filename; - } else { - $imageDetails = getimagesize($filename); - if ($fp = fopen($filename, 'rb', 0)) { - $picture = ''; - while (!feof($fp)) { - $picture .= fread($fp, 1024); - } - fclose($fp); - // base64 encode the binary data, then break it - // into chunks according to RFC 2045 semantics - $base64 = chunk_split(base64_encode($picture)); - $imageData = 'data:' . $imageDetails['mime'] . ';base64,' . $base64; - } else { - $imageData = $filename; - } - } - - $html .= '
    '; - $html .= ''; - $html .= '
    '; } + + $html .= '' . $filedesc . ''; } elseif ($drawing instanceof MemoryDrawing) { - if ($drawing->getCoordinates() != $coordinates) { - continue; + $imageResource = $drawing->getImageResource(); + if ($imageResource) { + ob_start(); // Let's start output buffering. + imagepng($imageResource); // This will normally output the image, but because of ob_start(), it won't. + $contents = (string) ob_get_contents(); // Instead, output above is saved to $contents + ob_end_clean(); // End the output buffer. + + $dataUri = 'data:image/png;base64,' . base64_encode($contents); + + // Because of the nature of tables, width is more important than height. + // max-width: 100% ensures that image doesnt overflow containing cell + // width: X sets width of supplied image. + // As a result, images bigger than cell will be contained and images smaller will not get stretched + $html .= '' . $filedesc . ''; } - ob_start(); // Let's start output buffering. - imagepng($drawing->getImageResource()); // This will normally output the image, but because of ob_start(), it won't. - $contents = ob_get_contents(); // Instead, output above is saved to $contents - ob_end_clean(); // End the output buffer. - - $dataUri = 'data:image/jpeg;base64,' . base64_encode($contents); - - // Because of the nature of tables, width is more important than height. - // max-width: 100% ensures that image doesnt overflow containing cell - // width: X sets width of supplied image. - // As a result, images bigger than cell will be contained and images smaller will not get stretched - $html .= ''; } } @@ -688,43 +703,37 @@ class Html extends BaseWriter /** * Generate chart tag in cell. - * - * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet - * @param string $coordinates Cell coordinates - * - * @return string + * This code should be exercised by sample: + * Chart/32_Chart_read_write_PDF.php. */ - private function writeChartInCell(Worksheet $pSheet, $coordinates) + private function writeChartInCell(Worksheet $worksheet, string $coordinates): string { // Construct HTML $html = ''; // Write charts - foreach ($pSheet->getChartCollection() as $chart) { + foreach ($worksheet->getChartCollection() as $chart) { if ($chart instanceof Chart) { $chartCoordinates = $chart->getTopLeftPosition(); if ($chartCoordinates['cell'] == $coordinates) { $chartFileName = File::sysGetTempDir() . '/' . uniqid('', true) . '.png'; if (!$chart->render($chartFileName)) { - return; + return ''; } $html .= PHP_EOL; - $imageDetails = getimagesize($chartFileName); - if ($fp = fopen($chartFileName, 'rb', 0)) { - $picture = fread($fp, filesize($chartFileName)); - fclose($fp); - // base64 encode the binary data, then break it - // into chunks according to RFC 2045 semantics - $base64 = chunk_split(base64_encode($picture)); + $imageDetails = getimagesize($chartFileName) ?: []; + $filedesc = $chart->getTitle(); + $filedesc = $filedesc ? $filedesc->getCaptionText() : ''; + $filedesc = $filedesc ? htmlspecialchars($filedesc, ENT_QUOTES) : 'Embedded chart'; + $picture = file_get_contents($chartFileName); + if ($picture !== false) { + $base64 = base64_encode($picture); $imageData = 'data:' . $imageDetails['mime'] . ';base64,' . $base64; - $html .= '
    '; - $html .= '' . PHP_EOL; - $html .= '
    '; - - unlink($chartFileName); + $html .= '' . $filedesc . '' . PHP_EOL; } + unlink($chartFileName); } } } @@ -738,8 +747,6 @@ class Html extends BaseWriter * * @param bool $generateSurroundingHTML Generate surrounding HTML tags? (<style> and </style>) * - * @throws WriterException - * * @return string */ public function generateStyles($generateSurroundingHTML = true) @@ -753,7 +760,7 @@ class Html extends BaseWriter // Start styles if ($generateSurroundingHTML) { $html .= ' \n"; + if (!in_array($baseCell, $adjustedBaseCells, true)) { + // subtract rowspan by 1 + --$this->isBaseCell[$sheetIndex][$baseCell[0]][$baseCell[1]]['rowspan']; + $adjustedBaseCells[] = $baseCell; + } + } + } + } } /** @@ -1642,20 +1762,99 @@ class Html extends BaseWriter * * @see https://github.com/LibreOffice/core/blob/9fc9bf3240f8c62ad7859947ab8a033ac1fe93fa/sc/source/filter/html/htmlexp.cxx#L1073-L1092 * - * @param Worksheet $pSheet * @param string $coordinate * * @return string */ - private function writeComment(Worksheet $pSheet, $coordinate) + private function writeComment(Worksheet $worksheet, $coordinate) { $result = ''; - if (!$this->isPdf && isset($pSheet->getComments()[$coordinate])) { - $result .= ''; - $result .= '
    ' . nl2br($pSheet->getComment($coordinate)->getText()->getPlainText()) . '
    '; - $result .= PHP_EOL; + if (!$this->isPdf && isset($worksheet->getComments()[$coordinate])) { + $sanitizer = new HTMLPurifier(); + $cachePath = File::sysGetTempDir() . '/phpsppur'; + if (is_dir($cachePath) || mkdir($cachePath)) { + $sanitizer->config->set('Cache.SerializerPath', $cachePath); + } + $sanitizedString = $sanitizer->purify($worksheet->getComment($coordinate)->getText()->getPlainText()); + if ($sanitizedString !== '') { + $result .= ''; + $result .= '
    ' . nl2br($sanitizedString) . '
    '; + $result .= PHP_EOL; + } } return $result; } + + public function getOrientation(): ?string + { + // Expect Pdf classes to override this method. + return $this->isPdf ? PageSetup::ORIENTATION_PORTRAIT : null; + } + + /** + * Generate @page declarations. + * + * @param bool $generateSurroundingHTML + * + * @return string + */ + private function generatePageDeclarations($generateSurroundingHTML) + { + // Ensure that Spans have been calculated? + $this->calculateSpans(); + + // Fetch sheets + $sheets = []; + if ($this->sheetIndex === null) { + $sheets = $this->spreadsheet->getAllSheets(); + } else { + $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex); + } + + // Construct HTML + $htmlPage = $generateSurroundingHTML ? ('' . PHP_EOL) : ''; + + return $htmlPage; + } } diff --git a/PhpOffice/PhpSpreadsheet/Writer/IWriter.php b/PhpOffice/PhpSpreadsheet/Writer/IWriter.php old mode 100755 new mode 100644 index 448b532..a4bd5d5 --- a/PhpOffice/PhpSpreadsheet/Writer/IWriter.php +++ b/PhpOffice/PhpSpreadsheet/Writer/IWriter.php @@ -6,10 +6,14 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; interface IWriter { + public const SAVE_WITH_CHARTS = 1; + + public const DISABLE_PRECALCULATE_FORMULAE = 2; + /** * IWriter constructor. * - * @param Spreadsheet $spreadsheet + * @param Spreadsheet $spreadsheet The spreadsheet that we want to save using this Writer */ public function __construct(Spreadsheet $spreadsheet); @@ -27,11 +31,11 @@ interface IWriter * Set to true, to advise the Writer to include any charts that exist in the PhpSpreadsheet object. * Set to false (the default) to ignore charts. * - * @param bool $pValue + * @param bool $includeCharts * * @return IWriter */ - public function setIncludeCharts($pValue); + public function setIncludeCharts($includeCharts); /** * Get Pre-Calculate Formulas flag @@ -50,20 +54,23 @@ interface IWriter * Set to true (the default) to advise the Writer to calculate all formulae on save * Set to false to prevent precalculation of formulae on save. * - * @param bool $pValue Pre-Calculate Formulas? + * @param bool $precalculateFormulas Pre-Calculate Formulas? * * @return IWriter */ - public function setPreCalculateFormulas($pValue); + public function setPreCalculateFormulas($precalculateFormulas); /** * Save PhpSpreadsheet to file. * - * @param string $pFilename Name of the file to save + * @param resource|string $filename Name of the file to save + * @param int $flags Flags that can change the behaviour of the Writer: + * self::SAVE_WITH_CHARTS Save any charts that are defined (if the Writer supports Charts) + * self::DISABLE_PRECALCULATE_FORMULAE Don't Precalculate formulae before saving the file * - * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception + * @throws Exception */ - public function save($pFilename); + public function save($filename, int $flags = 0): void; /** * Get use disk caching where possible? @@ -75,14 +82,12 @@ interface IWriter /** * Set use disk caching where possible? * - * @param bool $pValue - * @param string $pDirectory Disk caching directory - * - * @throws Exception when directory does not exist + * @param bool $useDiskCache + * @param string $cacheDirectory Disk caching directory * * @return IWriter */ - public function setUseDiskCaching($pValue, $pDirectory = null); + public function setUseDiskCaching($useDiskCache, $cacheDirectory = null); /** * Get disk caching directory. diff --git a/PhpOffice/PhpSpreadsheet/Writer/Ods.php b/PhpOffice/PhpSpreadsheet/Writer/Ods.php old mode 100755 new mode 100644 index 83fc293..827c43b --- a/PhpOffice/PhpSpreadsheet/Writer/Ods.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Ods.php @@ -2,7 +2,6 @@ namespace PhpOffice\PhpSpreadsheet\Writer; -use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException; use PhpOffice\PhpSpreadsheet\Writer\Ods\Content; @@ -12,17 +11,12 @@ use PhpOffice\PhpSpreadsheet\Writer\Ods\Mimetype; use PhpOffice\PhpSpreadsheet\Writer\Ods\Settings; use PhpOffice\PhpSpreadsheet\Writer\Ods\Styles; use PhpOffice\PhpSpreadsheet\Writer\Ods\Thumbnails; -use ZipArchive; +use ZipStream\Exception\OverflowException; +use ZipStream\Option\Archive; +use ZipStream\ZipStream; class Ods extends BaseWriter { - /** - * Private writer parts. - * - * @var Ods\WriterPart[] - */ - private $writerParts = []; - /** * Private PhpSpreadsheet. * @@ -30,127 +24,154 @@ class Ods extends BaseWriter */ private $spreadSheet; + /** + * @var Content + */ + private $writerPartContent; + + /** + * @var Meta + */ + private $writerPartMeta; + + /** + * @var MetaInf + */ + private $writerPartMetaInf; + + /** + * @var Mimetype + */ + private $writerPartMimetype; + + /** + * @var Settings + */ + private $writerPartSettings; + + /** + * @var Styles + */ + private $writerPartStyles; + + /** + * @var Thumbnails + */ + private $writerPartThumbnails; + /** * Create a new Ods. - * - * @param Spreadsheet $spreadsheet */ public function __construct(Spreadsheet $spreadsheet) { $this->setSpreadsheet($spreadsheet); - $writerPartsArray = [ - 'content' => Content::class, - 'meta' => Meta::class, - 'meta_inf' => MetaInf::class, - 'mimetype' => Mimetype::class, - 'settings' => Settings::class, - 'styles' => Styles::class, - 'thumbnails' => Thumbnails::class, - ]; - - foreach ($writerPartsArray as $writer => $class) { - $this->writerParts[$writer] = new $class($this); - } + $this->writerPartContent = new Content($this); + $this->writerPartMeta = new Meta($this); + $this->writerPartMetaInf = new MetaInf($this); + $this->writerPartMimetype = new Mimetype($this); + $this->writerPartSettings = new Settings($this); + $this->writerPartStyles = new Styles($this); + $this->writerPartThumbnails = new Thumbnails($this); } - /** - * Get writer part. - * - * @param string $pPartName Writer part name - * - * @return null|Ods\WriterPart - */ - public function getWriterPart($pPartName) + public function getWriterPartContent(): Content { - if ($pPartName != '' && isset($this->writerParts[strtolower($pPartName)])) { - return $this->writerParts[strtolower($pPartName)]; - } + return $this->writerPartContent; + } - return null; + public function getWriterPartMeta(): Meta + { + return $this->writerPartMeta; + } + + public function getWriterPartMetaInf(): MetaInf + { + return $this->writerPartMetaInf; + } + + public function getWriterPartMimetype(): Mimetype + { + return $this->writerPartMimetype; + } + + public function getWriterPartSettings(): Settings + { + return $this->writerPartSettings; + } + + public function getWriterPartStyles(): Styles + { + return $this->writerPartStyles; + } + + public function getWriterPartThumbnails(): Thumbnails + { + return $this->writerPartThumbnails; } /** * Save PhpSpreadsheet to file. * - * @param string $pFilename - * - * @throws WriterException + * @param resource|string $filename */ - public function save($pFilename) + public function save($filename, int $flags = 0): void { if (!$this->spreadSheet) { throw new WriterException('PhpSpreadsheet object unassigned.'); } + $this->processFlags($flags); + // garbage collect $this->spreadSheet->garbageCollect(); - // If $pFilename is php://output or php://stdout, make it a temporary file... - $originalFilename = $pFilename; - if (strtolower($pFilename) == 'php://output' || strtolower($pFilename) == 'php://stdout') { - $pFilename = @tempnam(File::sysGetTempDir(), 'phpxltmp'); - if ($pFilename == '') { - $pFilename = $originalFilename; - } - } + $this->openFileHandle($filename); - $zip = $this->createZip($pFilename); + $zip = $this->createZip(); - $zip->addFromString('META-INF/manifest.xml', $this->getWriterPart('meta_inf')->writeManifest()); - $zip->addFromString('Thumbnails/thumbnail.png', $this->getWriterPart('thumbnails')->writeThumbnail()); - $zip->addFromString('content.xml', $this->getWriterPart('content')->write()); - $zip->addFromString('meta.xml', $this->getWriterPart('meta')->write()); - $zip->addFromString('mimetype', $this->getWriterPart('mimetype')->write()); - $zip->addFromString('settings.xml', $this->getWriterPart('settings')->write()); - $zip->addFromString('styles.xml', $this->getWriterPart('styles')->write()); + $zip->addFile('META-INF/manifest.xml', $this->getWriterPartMetaInf()->write()); + $zip->addFile('Thumbnails/thumbnail.png', $this->getWriterPartthumbnails()->write()); + // Settings always need to be written before Content; Styles after Content + $zip->addFile('settings.xml', $this->getWriterPartsettings()->write()); + $zip->addFile('content.xml', $this->getWriterPartcontent()->write()); + $zip->addFile('meta.xml', $this->getWriterPartmeta()->write()); + $zip->addFile('mimetype', $this->getWriterPartmimetype()->write()); + $zip->addFile('styles.xml', $this->getWriterPartstyles()->write()); // Close file - if ($zip->close() === false) { - throw new WriterException("Could not close zip file $pFilename."); + try { + $zip->finish(); + } catch (OverflowException $e) { + throw new WriterException('Could not close resource.'); } - // If a temporary file was used, copy it to the correct file stream - if ($originalFilename != $pFilename) { - if (copy($pFilename, $originalFilename) === false) { - throw new WriterException("Could not copy temporary zip file $pFilename to $originalFilename."); - } - @unlink($pFilename); - } + $this->maybeCloseFileHandle(); } /** * Create zip object. * - * @param string $pFilename - * - * @throws WriterException - * - * @return ZipArchive + * @return ZipStream */ - private function createZip($pFilename) + private function createZip() { - // Create new ZIP file and open it for writing - $zip = new ZipArchive(); - - if (file_exists($pFilename)) { - unlink($pFilename); - } // Try opening the ZIP file - if ($zip->open($pFilename, ZipArchive::OVERWRITE) !== true) { - if ($zip->open($pFilename, ZipArchive::CREATE) !== true) { - throw new WriterException("Could not open $pFilename for writing."); - } + if (!is_resource($this->fileHandle)) { + throw new WriterException('Could not open resource for writing.'); } - return $zip; + // Create new ZIP stream + $options = new Archive(); + $options->setEnableZip64(false); + $options->setOutputStream($this->fileHandle); + + return new ZipStream(null, $options); } /** * Get Spreadsheet object. * - * @throws WriterException - * * @return Spreadsheet */ public function getSpreadsheet() @@ -167,7 +188,7 @@ class Ods extends BaseWriter * * @param Spreadsheet $spreadsheet PhpSpreadsheet object * - * @return self + * @return $this */ public function setSpreadsheet(Spreadsheet $spreadsheet) { diff --git a/PhpOffice/PhpSpreadsheet/Writer/Ods/AutoFilters.php b/PhpOffice/PhpSpreadsheet/Writer/Ods/AutoFilters.php new file mode 100644 index 0000000..996ec1a --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Writer/Ods/AutoFilters.php @@ -0,0 +1,66 @@ +objWriter = $objWriter; + $this->spreadsheet = $spreadsheet; + } + + /** @var mixed */ + private static $scrutinizerFalse = false; + + public function write(): void + { + $wrapperWritten = self::$scrutinizerFalse; + $sheetCount = $this->spreadsheet->getSheetCount(); + for ($i = 0; $i < $sheetCount; ++$i) { + $worksheet = $this->spreadsheet->getSheet($i); + $autofilter = $worksheet->getAutoFilter(); + if ($autofilter !== null && !empty($autofilter->getRange())) { + if ($wrapperWritten === false) { + $this->objWriter->startElement('table:database-ranges'); + $wrapperWritten = true; + } + $this->objWriter->startElement('table:database-range'); + $this->objWriter->writeAttribute('table:orientation', 'column'); + $this->objWriter->writeAttribute('table:display-filter-buttons', 'true'); + $this->objWriter->writeAttribute( + 'table:target-range-address', + $this->formatRange($worksheet, $autofilter) + ); + $this->objWriter->endElement(); + } + } + + if ($wrapperWritten === true) { + $this->objWriter->endElement(); + } + } + + protected function formatRange(Worksheet $worksheet, Autofilter $autofilter): string + { + $title = $worksheet->getTitle(); + $range = $autofilter->getRange(); + + return "'{$title}'.{$range}"; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Writer/Ods/Cell/Comment.php b/PhpOffice/PhpSpreadsheet/Writer/Ods/Cell/Comment.php old mode 100755 new mode 100644 index 2f543be..b0829bf --- a/PhpOffice/PhpSpreadsheet/Writer/Ods/Cell/Comment.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Ods/Cell/Comment.php @@ -6,14 +6,11 @@ use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; /** - * @category PhpSpreadsheet - * - * @copyright Copyright (c) 2006 - 2015 PhpSpreadsheet (https://github.com/PHPOffice/PhpSpreadsheet) * @author Alexander Pervakov */ class Comment { - public static function write(XMLWriter $objWriter, Cell $cell) + public static function write(XMLWriter $objWriter, Cell $cell): void { $comments = $cell->getWorksheet()->getComments(); if (!isset($comments[$cell->getCoordinate()])) { diff --git a/PhpOffice/PhpSpreadsheet/Writer/Ods/Cell/Style.php b/PhpOffice/PhpSpreadsheet/Writer/Ods/Cell/Style.php new file mode 100644 index 0000000..1bf2c46 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Writer/Ods/Cell/Style.php @@ -0,0 +1,262 @@ +writer = $writer; + } + + private function mapHorizontalAlignment(string $horizontalAlignment): string + { + switch ($horizontalAlignment) { + case Alignment::HORIZONTAL_CENTER: + case Alignment::HORIZONTAL_CENTER_CONTINUOUS: + case Alignment::HORIZONTAL_DISTRIBUTED: + return 'center'; + case Alignment::HORIZONTAL_RIGHT: + return 'end'; + case Alignment::HORIZONTAL_FILL: + case Alignment::HORIZONTAL_JUSTIFY: + return 'justify'; + } + + return 'start'; + } + + private function mapVerticalAlignment(string $verticalAlignment): string + { + switch ($verticalAlignment) { + case Alignment::VERTICAL_TOP: + return 'top'; + case Alignment::VERTICAL_CENTER: + return 'middle'; + case Alignment::VERTICAL_DISTRIBUTED: + case Alignment::VERTICAL_JUSTIFY: + return 'automatic'; + } + + return 'bottom'; + } + + private function writeFillStyle(Fill $fill): void + { + switch ($fill->getFillType()) { + case Fill::FILL_SOLID: + $this->writer->writeAttribute('fo:background-color', sprintf( + '#%s', + strtolower($fill->getStartColor()->getRGB()) + )); + + break; + case Fill::FILL_GRADIENT_LINEAR: + case Fill::FILL_GRADIENT_PATH: + /// TODO :: To be implemented + break; + case Fill::FILL_NONE: + default: + } + } + + private function writeCellProperties(CellStyle $style): void + { + // Align + $hAlign = $style->getAlignment()->getHorizontal(); + $vAlign = $style->getAlignment()->getVertical(); + $wrap = $style->getAlignment()->getWrapText(); + + $this->writer->startElement('style:table-cell-properties'); + if (!empty($vAlign) || $wrap) { + if (!empty($vAlign)) { + $vAlign = $this->mapVerticalAlignment($vAlign); + $this->writer->writeAttribute('style:vertical-align', $vAlign); + } + if ($wrap) { + $this->writer->writeAttribute('fo:wrap-option', 'wrap'); + } + } + $this->writer->writeAttribute('style:rotation-align', 'none'); + + // Fill + if ($fill = $style->getFill()) { + $this->writeFillStyle($fill); + } + + $this->writer->endElement(); + + if (!empty($hAlign)) { + $hAlign = $this->mapHorizontalAlignment($hAlign); + $this->writer->startElement('style:paragraph-properties'); + $this->writer->writeAttribute('fo:text-align', $hAlign); + $this->writer->endElement(); + } + } + + protected function mapUnderlineStyle(Font $font): string + { + switch ($font->getUnderline()) { + case Font::UNDERLINE_DOUBLE: + case Font::UNDERLINE_DOUBLEACCOUNTING: + return'double'; + case Font::UNDERLINE_SINGLE: + case Font::UNDERLINE_SINGLEACCOUNTING: + return'single'; + } + + return 'none'; + } + + protected function writeTextProperties(CellStyle $style): void + { + // Font + $this->writer->startElement('style:text-properties'); + + $font = $style->getFont(); + + if ($font->getBold()) { + $this->writer->writeAttribute('fo:font-weight', 'bold'); + $this->writer->writeAttribute('style:font-weight-complex', 'bold'); + $this->writer->writeAttribute('style:font-weight-asian', 'bold'); + } + + if ($font->getItalic()) { + $this->writer->writeAttribute('fo:font-style', 'italic'); + } + + if ($color = $font->getColor()) { + $this->writer->writeAttribute('fo:color', sprintf('#%s', $color->getRGB())); + } + + if ($family = $font->getName()) { + $this->writer->writeAttribute('fo:font-family', $family); + } + + if ($size = $font->getSize()) { + $this->writer->writeAttribute('fo:font-size', sprintf('%.1Fpt', $size)); + } + + if ($font->getUnderline() && $font->getUnderline() !== Font::UNDERLINE_NONE) { + $this->writer->writeAttribute('style:text-underline-style', 'solid'); + $this->writer->writeAttribute('style:text-underline-width', 'auto'); + $this->writer->writeAttribute('style:text-underline-color', 'font-color'); + + $underline = $this->mapUnderlineStyle($font); + $this->writer->writeAttribute('style:text-underline-type', $underline); + } + + $this->writer->endElement(); // Close style:text-properties + } + + protected function writeColumnProperties(ColumnDimension $columnDimension): void + { + $this->writer->startElement('style:table-column-properties'); + $this->writer->writeAttribute( + 'style:column-width', + round($columnDimension->getWidth(Dimension::UOM_CENTIMETERS), 3) . 'cm' + ); + $this->writer->writeAttribute('fo:break-before', 'auto'); + + // End + $this->writer->endElement(); // Close style:table-column-properties + } + + public function writeColumnStyles(ColumnDimension $columnDimension, int $sheetId): void + { + $this->writer->startElement('style:style'); + $this->writer->writeAttribute('style:family', 'table-column'); + $this->writer->writeAttribute( + 'style:name', + sprintf('%s_%d_%d', self::COLUMN_STYLE_PREFIX, $sheetId, $columnDimension->getColumnNumeric()) + ); + + $this->writeColumnProperties($columnDimension); + + // End + $this->writer->endElement(); // Close style:style + } + + protected function writeRowProperties(RowDimension $rowDimension): void + { + $this->writer->startElement('style:table-row-properties'); + $this->writer->writeAttribute( + 'style:row-height', + round($rowDimension->getRowHeight(Dimension::UOM_CENTIMETERS), 3) . 'cm' + ); + $this->writer->writeAttribute('style:use-optimal-row-height', 'false'); + $this->writer->writeAttribute('fo:break-before', 'auto'); + + // End + $this->writer->endElement(); // Close style:table-row-properties + } + + public function writeRowStyles(RowDimension $rowDimension, int $sheetId): void + { + $this->writer->startElement('style:style'); + $this->writer->writeAttribute('style:family', 'table-row'); + $this->writer->writeAttribute( + 'style:name', + sprintf('%s_%d_%d', self::ROW_STYLE_PREFIX, $sheetId, $rowDimension->getRowIndex()) + ); + + $this->writeRowProperties($rowDimension); + + // End + $this->writer->endElement(); // Close style:style + } + + public function writeTableStyle(Worksheet $worksheet, int $sheetId): void + { + $this->writer->startElement('style:style'); + $this->writer->writeAttribute('style:family', 'table'); + $this->writer->writeAttribute( + 'style:name', + sprintf('%s%d', self::TABLE_STYLE_PREFIX, $sheetId) + ); + + $this->writer->startElement('style:table-properties'); + + $this->writer->writeAttribute( + 'table:display', + $worksheet->getSheetState() === Worksheet::SHEETSTATE_VISIBLE ? 'true' : 'false' + ); + + $this->writer->endElement(); // Close style:table-properties + $this->writer->endElement(); // Close style:style + } + + public function write(CellStyle $style): void + { + $this->writer->startElement('style:style'); + $this->writer->writeAttribute('style:name', self::CELL_STYLE_PREFIX . $style->getIndex()); + $this->writer->writeAttribute('style:family', 'table-cell'); + $this->writer->writeAttribute('style:parent-style-name', 'Default'); + + // Alignment, fill colour, etc + $this->writeCellProperties($style); + + // style:text-properties + $this->writeTextProperties($style); + + // End + $this->writer->endElement(); // Close style:style + } +} diff --git a/PhpOffice/PhpSpreadsheet/Writer/Ods/Content.php b/PhpOffice/PhpSpreadsheet/Writer/Ods/Content.php old mode 100755 new mode 100644 index 962f8ff..00ab064 --- a/PhpOffice/PhpSpreadsheet/Writer/Ods/Content.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Ods/Content.php @@ -7,36 +7,40 @@ use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Cell\DataType; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; use PhpOffice\PhpSpreadsheet\Spreadsheet; -use PhpOffice\PhpSpreadsheet\Style\Fill; -use PhpOffice\PhpSpreadsheet\Style\Font; use PhpOffice\PhpSpreadsheet\Worksheet\Row; +use PhpOffice\PhpSpreadsheet\Worksheet\RowCellIterator; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use PhpOffice\PhpSpreadsheet\Writer\Exception; use PhpOffice\PhpSpreadsheet\Writer\Ods; use PhpOffice\PhpSpreadsheet\Writer\Ods\Cell\Comment; +use PhpOffice\PhpSpreadsheet\Writer\Ods\Cell\Style; /** - * @category PhpSpreadsheet - * - * @method Ods getParentWriter - * - * @copyright Copyright (c) 2006 - 2015 PhpSpreadsheet (https://github.com/PHPOffice/PhpSpreadsheet) * @author Alexander Pervakov */ class Content extends WriterPart { const NUMBER_COLS_REPEATED_MAX = 1024; const NUMBER_ROWS_REPEATED_MAX = 1048576; - const CELL_STYLE_PREFIX = 'ce'; + + private $formulaConvertor; + + /** + * Set parent Ods writer. + */ + public function __construct(Ods $writer) + { + parent::__construct($writer); + + $this->formulaConvertor = new Formula($this->getParentWriter()->getSpreadsheet()->getDefinedNames()); + } /** * Write content.xml to XML format. * - * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception - * * @return string XML Output */ - public function write() + public function write(): string { $objWriter = null; if ($this->getParentWriter()->getUseDiskCaching()) { @@ -98,7 +102,10 @@ class Content extends WriterPart $this->writeSheets($objWriter); - $objWriter->writeElement('table:named-expressions'); + (new AutoFilters($objWriter, $this->getParentWriter()->getSpreadsheet()))->write(); + // Defined names (ranges and formulae) + (new NamedExpressions($objWriter, $this->getParentWriter()->getSpreadsheet(), $this->formulaConvertor))->write(); + $objWriter->endElement(); $objWriter->endElement(); $objWriter->endElement(); @@ -108,78 +115,77 @@ class Content extends WriterPart /** * Write sheets. - * - * @param XMLWriter $objWriter */ - private function writeSheets(XMLWriter $objWriter) + private function writeSheets(XMLWriter $objWriter): void { - $spreadsheet = $this->getParentWriter()->getSpreadsheet(); // @var $spreadsheet Spreadsheet - + $spreadsheet = $this->getParentWriter()->getSpreadsheet(); /** @var Spreadsheet $spreadsheet */ $sheetCount = $spreadsheet->getSheetCount(); - for ($i = 0; $i < $sheetCount; ++$i) { + for ($sheetIndex = 0; $sheetIndex < $sheetCount; ++$sheetIndex) { $objWriter->startElement('table:table'); - $objWriter->writeAttribute('table:name', $spreadsheet->getSheet($i)->getTitle()); + $objWriter->writeAttribute('table:name', $spreadsheet->getSheet($sheetIndex)->getTitle()); + $objWriter->writeAttribute('table:style-name', Style::TABLE_STYLE_PREFIX . (string) ($sheetIndex + 1)); $objWriter->writeElement('office:forms'); - $objWriter->startElement('table:table-column'); - $objWriter->writeAttribute('table:number-columns-repeated', self::NUMBER_COLS_REPEATED_MAX); - $objWriter->endElement(); - $this->writeRows($objWriter, $spreadsheet->getSheet($i)); + foreach ($spreadsheet->getSheet($sheetIndex)->getColumnDimensions() as $columnDimension) { + $objWriter->startElement('table:table-column'); + $objWriter->writeAttribute( + 'table:style-name', + sprintf('%s_%d_%d', Style::COLUMN_STYLE_PREFIX, $sheetIndex, $columnDimension->getColumnNumeric()) + ); + $objWriter->writeAttribute('table:default-cell-style-name', 'ce0'); +// $objWriter->writeAttribute('table:number-columns-repeated', self::NUMBER_COLS_REPEATED_MAX); + $objWriter->endElement(); + } + $this->writeRows($objWriter, $spreadsheet->getSheet($sheetIndex), $sheetIndex); $objWriter->endElement(); } } /** * Write rows of the specified sheet. - * - * @param XMLWriter $objWriter - * @param Worksheet $sheet */ - private function writeRows(XMLWriter $objWriter, Worksheet $sheet) + private function writeRows(XMLWriter $objWriter, Worksheet $sheet, int $sheetIndex): void { $numberRowsRepeated = self::NUMBER_ROWS_REPEATED_MAX; $span_row = 0; $rows = $sheet->getRowIterator(); - while ($rows->valid()) { + foreach ($rows as $row) { + $cellIterator = $row->getCellIterator(); --$numberRowsRepeated; - $row = $rows->current(); - if ($row->getCellIterator()->valid()) { + if ($cellIterator->valid()) { + $objWriter->startElement('table:table-row'); if ($span_row) { - $objWriter->startElement('table:table-row'); if ($span_row > 1) { $objWriter->writeAttribute('table:number-rows-repeated', $span_row); } $objWriter->startElement('table:table-cell'); - $objWriter->writeAttribute('table:number-columns-repeated', self::NUMBER_COLS_REPEATED_MAX); - $objWriter->endElement(); + $objWriter->writeAttribute('table:number-columns-repeated', (string) self::NUMBER_COLS_REPEATED_MAX); $objWriter->endElement(); $span_row = 0; + } else { + if ($sheet->getRowDimension($row->getRowIndex())->getRowHeight() > 0) { + $objWriter->writeAttribute( + 'table:style-name', + sprintf('%s_%d_%d', Style::ROW_STYLE_PREFIX, $sheetIndex, $row->getRowIndex()) + ); + } + $this->writeCells($objWriter, $cellIterator); } - $objWriter->startElement('table:table-row'); - $this->writeCells($objWriter, $row); $objWriter->endElement(); } else { ++$span_row; } - $rows->next(); } } /** * Write cells of the specified row. - * - * @param XMLWriter $objWriter - * @param Row $row - * - * @throws Exception */ - private function writeCells(XMLWriter $objWriter, Row $row) + private function writeCells(XMLWriter $objWriter, RowCellIterator $cells): void { $numberColsRepeated = self::NUMBER_COLS_REPEATED_MAX; $prevColumn = -1; - $cells = $row->getCellIterator(); - while ($cells->valid()) { + foreach ($cells as $cell) { /** @var \PhpOffice\PhpSpreadsheet\Cell\Cell $cell */ - $cell = $cells->current(); $column = Coordinate::columnIndexFromString($cell->getColumn()) - 1; $this->writeCellSpan($objWriter, $column, $prevColumn); @@ -189,7 +195,7 @@ class Content extends WriterPart // Style XF $style = $cell->getXfIndex(); if ($style !== null) { - $objWriter->writeAttribute('table:style-name', self::CELL_STYLE_PREFIX . $style); + $objWriter->writeAttribute('table:style-name', Style::CELL_STYLE_PREFIX . $style); } switch ($cell->getDataType()) { @@ -200,7 +206,10 @@ class Content extends WriterPart break; case DataType::TYPE_ERROR: - throw new Exception('Writing of error not implemented yet.'); + $objWriter->writeAttribute('table:formula', 'of:=#NULL!'); + $objWriter->writeAttribute('office:value-type', 'string'); + $objWriter->writeAttribute('office:string-value', ''); + $objWriter->writeElement('text:p', '#NULL!'); break; case DataType::TYPE_FORMULA: @@ -212,7 +221,7 @@ class Content extends WriterPart // don't do anything } } - $objWriter->writeAttribute('table:formula', 'of:' . $cell->getValue()); + $objWriter->writeAttribute('table:formula', $this->formulaConvertor->convertFormula($cell->getValue())); if (is_numeric($formulaValue)) { $objWriter->writeAttribute('office:value-type', 'float'); } else { @@ -221,10 +230,6 @@ class Content extends WriterPart $objWriter->writeAttribute('office:value', $formulaValue); $objWriter->writeElement('text:p', $formulaValue); - break; - case DataType::TYPE_INLINE: - throw new Exception('Writing of inline not implemented yet.'); - break; case DataType::TYPE_NUMERIC: $objWriter->writeAttribute('office:value-type', 'float'); @@ -232,6 +237,8 @@ class Content extends WriterPart $objWriter->writeElement('text:p', $cell->getValue()); break; + case DataType::TYPE_INLINE: + // break intentionally omitted case DataType::TYPE_STRING: $objWriter->writeAttribute('office:value-type', 'string'); $objWriter->writeElement('text:p', $cell->getValue()); @@ -241,8 +248,8 @@ class Content extends WriterPart Comment::write($objWriter, $cell); $objWriter->endElement(); $prevColumn = $column; - $cells->next(); } + $numberColsRepeated = $numberColsRepeated - $prevColumn - 1; if ($numberColsRepeated > 0) { if ($numberColsRepeated > 1) { @@ -258,11 +265,10 @@ class Content extends WriterPart /** * Write span. * - * @param XMLWriter $objWriter * @param int $curColumn * @param int $prevColumn */ - private function writeCellSpan(XMLWriter $objWriter, $curColumn, $prevColumn) + private function writeCellSpan(XMLWriter $objWriter, $curColumn, $prevColumn): void { $diff = $curColumn - $prevColumn - 1; if (1 === $diff) { @@ -276,107 +282,41 @@ class Content extends WriterPart /** * Write XF cell styles. - * - * @param XMLWriter $writer - * @param Spreadsheet $spreadsheet */ - private function writeXfStyles(XMLWriter $writer, Spreadsheet $spreadsheet) + private function writeXfStyles(XMLWriter $writer, Spreadsheet $spreadsheet): void { + $styleWriter = new Style($writer); + + $sheetCount = $spreadsheet->getSheetCount(); + for ($i = 0; $i < $sheetCount; ++$i) { + $worksheet = $spreadsheet->getSheet($i); + $styleWriter->writeTableStyle($worksheet, $i + 1); + + $worksheet->calculateColumnWidths(); + foreach ($worksheet->getColumnDimensions() as $columnDimension) { + if ($columnDimension->getWidth() !== -1.0) { + $styleWriter->writeColumnStyles($columnDimension, $i); + } + } + } + for ($i = 0; $i < $sheetCount; ++$i) { + $worksheet = $spreadsheet->getSheet($i); + foreach ($worksheet->getRowDimensions() as $rowDimension) { + if ($rowDimension->getRowHeight() > 0.0) { + $styleWriter->writeRowStyles($rowDimension, $i); + } + } + } + foreach ($spreadsheet->getCellXfCollection() as $style) { - $writer->startElement('style:style'); - $writer->writeAttribute('style:name', self::CELL_STYLE_PREFIX . $style->getIndex()); - $writer->writeAttribute('style:family', 'table-cell'); - $writer->writeAttribute('style:parent-style-name', 'Default'); - - // style:text-properties - - // Font - $writer->startElement('style:text-properties'); - - $font = $style->getFont(); - - if ($font->getBold()) { - $writer->writeAttribute('fo:font-weight', 'bold'); - $writer->writeAttribute('style:font-weight-complex', 'bold'); - $writer->writeAttribute('style:font-weight-asian', 'bold'); - } - - if ($font->getItalic()) { - $writer->writeAttribute('fo:font-style', 'italic'); - } - - if ($color = $font->getColor()) { - $writer->writeAttribute('fo:color', sprintf('#%s', $color->getRGB())); - } - - if ($family = $font->getName()) { - $writer->writeAttribute('fo:font-family', $family); - } - - if ($size = $font->getSize()) { - $writer->writeAttribute('fo:font-size', sprintf('%.1fpt', $size)); - } - - if ($font->getUnderline() && $font->getUnderline() != Font::UNDERLINE_NONE) { - $writer->writeAttribute('style:text-underline-style', 'solid'); - $writer->writeAttribute('style:text-underline-width', 'auto'); - $writer->writeAttribute('style:text-underline-color', 'font-color'); - - switch ($font->getUnderline()) { - case Font::UNDERLINE_DOUBLE: - $writer->writeAttribute('style:text-underline-type', 'double'); - - break; - case Font::UNDERLINE_SINGLE: - $writer->writeAttribute('style:text-underline-type', 'single'); - - break; - } - } - - $writer->endElement(); // Close style:text-properties - - // style:table-cell-properties - - $writer->startElement('style:table-cell-properties'); - $writer->writeAttribute('style:rotation-align', 'none'); - - // Fill - if ($fill = $style->getFill()) { - switch ($fill->getFillType()) { - case Fill::FILL_SOLID: - $writer->writeAttribute('fo:background-color', sprintf( - '#%s', - strtolower($fill->getStartColor()->getRGB()) - )); - - break; - case Fill::FILL_GRADIENT_LINEAR: - case Fill::FILL_GRADIENT_PATH: - /// TODO :: To be implemented - break; - case Fill::FILL_NONE: - default: - } - } - - $writer->endElement(); // Close style:table-cell-properties - - // End - - $writer->endElement(); // Close style:style + $styleWriter->write($style); } } /** * Write attributes for merged cell. - * - * @param XMLWriter $objWriter - * @param Cell $cell - * - * @throws \PhpOffice\PhpSpreadsheet\Exception */ - private function writeCellMerge(XMLWriter $objWriter, Cell $cell) + private function writeCellMerge(XMLWriter $objWriter, Cell $cell): void { if (!$cell->isMergeRangeValueCell()) { return; @@ -387,9 +327,9 @@ class Content extends WriterPart $start = Coordinate::coordinateFromString($startCell); $end = Coordinate::coordinateFromString($endCell); $columnSpan = Coordinate::columnIndexFromString($end[0]) - Coordinate::columnIndexFromString($start[0]) + 1; - $rowSpan = $end[1] - $start[1] + 1; + $rowSpan = ((int) $end[1]) - ((int) $start[1]) + 1; - $objWriter->writeAttribute('table:number-columns-spanned', $columnSpan); - $objWriter->writeAttribute('table:number-rows-spanned', $rowSpan); + $objWriter->writeAttribute('table:number-columns-spanned', (string) $columnSpan); + $objWriter->writeAttribute('table:number-rows-spanned', (string) $rowSpan); } } diff --git a/PhpOffice/PhpSpreadsheet/Writer/Ods/Formula.php b/PhpOffice/PhpSpreadsheet/Writer/Ods/Formula.php new file mode 100644 index 0000000..db766fb --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Writer/Ods/Formula.php @@ -0,0 +1,119 @@ +definedNames[] = $definedName->getName(); + } + } + + public function convertFormula(string $formula, string $worksheetName = ''): string + { + $formula = $this->convertCellReferences($formula, $worksheetName); + $formula = $this->convertDefinedNames($formula); + + if (substr($formula, 0, 1) !== '=') { + $formula = '=' . $formula; + } + + return 'of:' . $formula; + } + + private function convertDefinedNames(string $formula): string + { + $splitCount = preg_match_all( + '/' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '/mui', + $formula, + $splitRanges, + PREG_OFFSET_CAPTURE + ); + + $lengths = array_map('strlen', array_column($splitRanges[0], 0)); + $offsets = array_column($splitRanges[0], 1); + $values = array_column($splitRanges[0], 0); + + while ($splitCount > 0) { + --$splitCount; + $length = $lengths[$splitCount]; + $offset = $offsets[$splitCount]; + $value = $values[$splitCount]; + + if (in_array($value, $this->definedNames, true)) { + $formula = substr($formula, 0, $offset) . '$$' . $value . substr($formula, $offset + $length); + } + } + + return $formula; + } + + private function convertCellReferences(string $formula, string $worksheetName): string + { + $splitCount = preg_match_all( + '/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/mui', + $formula, + $splitRanges, + PREG_OFFSET_CAPTURE + ); + + $lengths = array_map('strlen', array_column($splitRanges[0], 0)); + $offsets = array_column($splitRanges[0], 1); + + $worksheets = $splitRanges[2]; + $columns = $splitRanges[6]; + $rows = $splitRanges[7]; + + // Replace any commas in the formula with semi-colons for Ods + // If by chance there are commas in worksheet names, then they will be "fixed" again in the loop + // because we've already extracted worksheet names with our preg_match_all() + $formula = str_replace(',', ';', $formula); + while ($splitCount > 0) { + --$splitCount; + $length = $lengths[$splitCount]; + $offset = $offsets[$splitCount]; + $worksheet = $worksheets[$splitCount][0]; + $column = $columns[$splitCount][0]; + $row = $rows[$splitCount][0]; + + $newRange = ''; + if (empty($worksheet)) { + if (($offset === 0) || ($formula[$offset - 1] !== ':')) { + // We need a worksheet + $worksheet = $worksheetName; + } + } else { + $worksheet = str_replace("''", "'", trim($worksheet, "'")); + } + if (!empty($worksheet)) { + $newRange = "['" . str_replace("'", "''", $worksheet) . "'"; + } elseif (substr($formula, $offset - 1, 1) !== ':') { + $newRange = '['; + } + $newRange .= '.'; + + if (!empty($column)) { + $newRange .= $column; + } + if (!empty($row)) { + $newRange .= $row; + } + // close the wrapping [] unless this is the first part of a range + $newRange .= substr($formula, $offset + $length, 1) !== ':' ? ']' : ''; + + $formula = substr($formula, 0, $offset) . $newRange . substr($formula, $offset + $length); + } + + return $formula; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Writer/Ods/Meta.php b/PhpOffice/PhpSpreadsheet/Writer/Ods/Meta.php old mode 100755 new mode 100644 index ffe5eff..16f7c8b --- a/PhpOffice/PhpSpreadsheet/Writer/Ods/Meta.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Ods/Meta.php @@ -2,6 +2,8 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Ods; +use PhpOffice\PhpSpreadsheet\Document\Properties; +use PhpOffice\PhpSpreadsheet\Shared\Date; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; use PhpOffice\PhpSpreadsheet\Spreadsheet; @@ -10,17 +12,11 @@ class Meta extends WriterPart /** * Write meta.xml to XML format. * - * @param Spreadsheet $spreadsheet - * - * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception - * * @return string XML Output */ - public function write(Spreadsheet $spreadsheet = null) + public function write(): string { - if (!$spreadsheet) { - $spreadsheet = $this->getParentWriter()->getSpreadsheet(); - } + $spreadsheet = $this->getParentWriter()->getSpreadsheet(); $objWriter = null; if ($this->getParentWriter()->getUseDiskCaching()) { @@ -47,15 +43,24 @@ class Meta extends WriterPart $objWriter->writeElement('meta:initial-creator', $spreadsheet->getProperties()->getCreator()); $objWriter->writeElement('dc:creator', $spreadsheet->getProperties()->getCreator()); - $objWriter->writeElement('meta:creation-date', date(DATE_W3C, $spreadsheet->getProperties()->getCreated())); - $objWriter->writeElement('dc:date', date(DATE_W3C, $spreadsheet->getProperties()->getCreated())); + $created = $spreadsheet->getProperties()->getCreated(); + $date = Date::dateTimeFromTimestamp("$created"); + $date->setTimeZone(Date::getDefaultOrLocalTimeZone()); + $objWriter->writeElement('meta:creation-date', $date->format(DATE_W3C)); + $created = $spreadsheet->getProperties()->getModified(); + $date = Date::dateTimeFromTimestamp("$created"); + $date->setTimeZone(Date::getDefaultOrLocalTimeZone()); + $objWriter->writeElement('dc:date', $date->format(DATE_W3C)); $objWriter->writeElement('dc:title', $spreadsheet->getProperties()->getTitle()); $objWriter->writeElement('dc:description', $spreadsheet->getProperties()->getDescription()); $objWriter->writeElement('dc:subject', $spreadsheet->getProperties()->getSubject()); - $keywords = explode(' ', $spreadsheet->getProperties()->getKeywords()); - foreach ($keywords as $keyword) { - $objWriter->writeElement('meta:keyword', $keyword); - } + $objWriter->writeElement('meta:keyword', $spreadsheet->getProperties()->getKeywords()); + // Don't know if this changed over time, but the keywords are all + // in a single declaration now. + //$keywords = explode(' ', $spreadsheet->getProperties()->getKeywords()); + //foreach ($keywords as $keyword) { + // $objWriter->writeElement('meta:keyword', $keyword); + //} // $objWriter->startElement('meta:user-defined'); @@ -68,10 +73,50 @@ class Meta extends WriterPart $objWriter->writeRaw($spreadsheet->getProperties()->getCategory()); $objWriter->endElement(); + self::writeDocPropsCustom($objWriter, $spreadsheet); + $objWriter->endElement(); $objWriter->endElement(); return $objWriter->getData(); } + + private static function writeDocPropsCustom(XMLWriter $objWriter, Spreadsheet $spreadsheet): void + { + $customPropertyList = $spreadsheet->getProperties()->getCustomProperties(); + foreach ($customPropertyList as $key => $customProperty) { + $propertyValue = $spreadsheet->getProperties()->getCustomPropertyValue($customProperty); + $propertyType = $spreadsheet->getProperties()->getCustomPropertyType($customProperty); + + $objWriter->startElement('meta:user-defined'); + $objWriter->writeAttribute('meta:name', $customProperty); + + switch ($propertyType) { + case Properties::PROPERTY_TYPE_INTEGER: + case Properties::PROPERTY_TYPE_FLOAT: + $objWriter->writeAttribute('meta:value-type', 'float'); + $objWriter->writeRawData($propertyValue); + + break; + case Properties::PROPERTY_TYPE_BOOLEAN: + $objWriter->writeAttribute('meta:value-type', 'boolean'); + $objWriter->writeRawData($propertyValue ? 'true' : 'false'); + + break; + case Properties::PROPERTY_TYPE_DATE: + $objWriter->writeAttribute('meta:value-type', 'date'); + $dtobj = Date::dateTimeFromTimestamp($propertyValue ?? 0); + $objWriter->writeRawData($dtobj->format(DATE_W3C)); + + break; + default: + $objWriter->writeRawData($propertyValue); + + break; + } + + $objWriter->endElement(); + } + } } diff --git a/PhpOffice/PhpSpreadsheet/Writer/Ods/MetaInf.php b/PhpOffice/PhpSpreadsheet/Writer/Ods/MetaInf.php old mode 100755 new mode 100644 index 1ec9d1e..f3f0d5f --- a/PhpOffice/PhpSpreadsheet/Writer/Ods/MetaInf.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Ods/MetaInf.php @@ -9,11 +9,9 @@ class MetaInf extends WriterPart /** * Write META-INF/manifest.xml to XML format. * - * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception - * * @return string XML Output */ - public function writeManifest() + public function write(): string { $objWriter = null; if ($this->getParentWriter()->getUseDiskCaching()) { diff --git a/PhpOffice/PhpSpreadsheet/Writer/Ods/Mimetype.php b/PhpOffice/PhpSpreadsheet/Writer/Ods/Mimetype.php old mode 100755 new mode 100644 index d0fed2b..e109e6e --- a/PhpOffice/PhpSpreadsheet/Writer/Ods/Mimetype.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Ods/Mimetype.php @@ -2,18 +2,14 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Ods; -use PhpOffice\PhpSpreadsheet\Spreadsheet; - class Mimetype extends WriterPart { /** * Write mimetype to plain text format. * - * @param Spreadsheet $spreadsheet - * * @return string XML Output */ - public function write(Spreadsheet $spreadsheet = null) + public function write(): string { return 'application/vnd.oasis.opendocument.spreadsheet'; } diff --git a/PhpOffice/PhpSpreadsheet/Writer/Ods/NamedExpressions.php b/PhpOffice/PhpSpreadsheet/Writer/Ods/NamedExpressions.php new file mode 100644 index 0000000..e309dab --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Writer/Ods/NamedExpressions.php @@ -0,0 +1,140 @@ +objWriter = $objWriter; + $this->spreadsheet = $spreadsheet; + $this->formulaConvertor = $formulaConvertor; + } + + public function write(): string + { + $this->objWriter->startElement('table:named-expressions'); + $this->writeExpressions(); + $this->objWriter->endElement(); + + return ''; + } + + private function writeExpressions(): void + { + $definedNames = $this->spreadsheet->getDefinedNames(); + + foreach ($definedNames as $definedName) { + if ($definedName->isFormula()) { + $this->objWriter->startElement('table:named-expression'); + $this->writeNamedFormula($definedName, $this->spreadsheet->getActiveSheet()); + } else { + $this->objWriter->startElement('table:named-range'); + $this->writeNamedRange($definedName); + } + + $this->objWriter->endElement(); + } + } + + private function writeNamedFormula(DefinedName $definedName, Worksheet $defaultWorksheet): void + { + $title = ($definedName->getWorksheet() !== null) ? $definedName->getWorksheet()->getTitle() : $defaultWorksheet->getTitle(); + $this->objWriter->writeAttribute('table:name', $definedName->getName()); + $this->objWriter->writeAttribute( + 'table:expression', + $this->formulaConvertor->convertFormula($definedName->getValue(), $title) + ); + $this->objWriter->writeAttribute('table:base-cell-address', $this->convertAddress( + $definedName, + "'" . $title . "'!\$A\$1" + )); + } + + private function writeNamedRange(DefinedName $definedName): void + { + $baseCell = '$A$1'; + $ws = $definedName->getWorksheet(); + if ($ws !== null) { + $baseCell = "'" . $ws->getTitle() . "'!$baseCell"; + } + $this->objWriter->writeAttribute('table:name', $definedName->getName()); + $this->objWriter->writeAttribute('table:base-cell-address', $this->convertAddress( + $definedName, + $baseCell + )); + $this->objWriter->writeAttribute('table:cell-range-address', $this->convertAddress($definedName, $definedName->getValue())); + } + + private function convertAddress(DefinedName $definedName, string $address): string + { + $splitCount = preg_match_all( + '/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/mui', + $address, + $splitRanges, + PREG_OFFSET_CAPTURE + ); + + $lengths = array_map('strlen', array_column($splitRanges[0], 0)); + $offsets = array_column($splitRanges[0], 1); + + $worksheets = $splitRanges[2]; + $columns = $splitRanges[6]; + $rows = $splitRanges[7]; + + while ($splitCount > 0) { + --$splitCount; + $length = $lengths[$splitCount]; + $offset = $offsets[$splitCount]; + $worksheet = $worksheets[$splitCount][0]; + $column = $columns[$splitCount][0]; + $row = $rows[$splitCount][0]; + + $newRange = ''; + if (empty($worksheet)) { + if (($offset === 0) || ($address[$offset - 1] !== ':')) { + // We need a worksheet + $ws = $definedName->getWorksheet(); + if ($ws !== null) { + $worksheet = $ws->getTitle(); + } + } + } else { + $worksheet = str_replace("''", "'", trim($worksheet, "'")); + } + if (!empty($worksheet)) { + $newRange = "'" . str_replace("'", "''", $worksheet) . "'."; + } + + if (!empty($column)) { + $newRange .= $column; + } + if (!empty($row)) { + $newRange .= $row; + } + + $address = substr($address, 0, $offset) . $newRange . substr($address, $offset + $length); + } + + if (substr($address, 0, 1) === '=') { + $address = substr($address, 1); + } + + return $address; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Writer/Ods/Settings.php b/PhpOffice/PhpSpreadsheet/Writer/Ods/Settings.php old mode 100755 new mode 100644 index 18f7ee6..0644559 --- a/PhpOffice/PhpSpreadsheet/Writer/Ods/Settings.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Ods/Settings.php @@ -2,23 +2,21 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Ods; +use PhpOffice\PhpSpreadsheet\Cell\CellAddress; +use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; class Settings extends WriterPart { /** * Write settings.xml to XML format. * - * @param Spreadsheet $spreadsheet - * - * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception - * * @return string XML Output */ - public function write(Spreadsheet $spreadsheet = null) + public function write(): string { - $objWriter = null; if ($this->getParentWriter()->getUseDiskCaching()) { $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); } else { @@ -41,14 +39,114 @@ class Settings extends WriterPart $objWriter->writeAttribute('config:name', 'ooo:view-settings'); $objWriter->startElement('config:config-item-map-indexed'); $objWriter->writeAttribute('config:name', 'Views'); - $objWriter->endElement(); - $objWriter->endElement(); + $objWriter->startElement('config:config-item-map-entry'); + $spreadsheet = $this->getParentWriter()->getSpreadsheet(); + + $objWriter->startElement('config:config-item'); + $objWriter->writeAttribute('config:name', 'ViewId'); + $objWriter->writeAttribute('config:type', 'string'); + $objWriter->text('view1'); + $objWriter->endElement(); // ViewId + $objWriter->startElement('config:config-item-map-named'); + + $this->writeAllWorksheetSettings($objWriter, $spreadsheet); + + $wstitle = $spreadsheet->getActiveSheet()->getTitle(); + $objWriter->startElement('config:config-item'); + $objWriter->writeAttribute('config:name', 'ActiveTable'); + $objWriter->writeAttribute('config:type', 'string'); + $objWriter->text($wstitle); + $objWriter->endElement(); // config:config-item ActiveTable + + $objWriter->endElement(); // config:config-item-map-entry + $objWriter->endElement(); // config:config-item-map-indexed Views + $objWriter->endElement(); // config:config-item-set ooo:view-settings $objWriter->startElement('config:config-item-set'); $objWriter->writeAttribute('config:name', 'ooo:configuration-settings'); - $objWriter->endElement(); - $objWriter->endElement(); - $objWriter->endElement(); + $objWriter->endElement(); // config:config-item-set ooo:configuration-settings + $objWriter->endElement(); // office:settings + $objWriter->endElement(); // office:document-settings return $objWriter->getData(); } + + private function writeAllWorksheetSettings(XMLWriter $objWriter, Spreadsheet $spreadsheet): void + { + $objWriter->writeAttribute('config:name', 'Tables'); + + foreach ($spreadsheet->getWorksheetIterator() as $worksheet) { + $this->writeWorksheetSettings($objWriter, $worksheet); + } + + $objWriter->endElement(); // config:config-item-map-entry Tables + } + + private function writeWorksheetSettings(XMLWriter $objWriter, Worksheet $worksheet): void + { + $objWriter->startElement('config:config-item-map-entry'); + $objWriter->writeAttribute('config:name', $worksheet->getTitle()); + + $this->writeSelectedCells($objWriter, $worksheet); + if ($worksheet->getFreezePane() !== null) { + $this->writeFreezePane($objWriter, $worksheet); + } + + $objWriter->endElement(); // config:config-item-map-entry Worksheet + } + + private function writeSelectedCells(XMLWriter $objWriter, Worksheet $worksheet): void + { + $selected = $worksheet->getSelectedCells(); + if (preg_match('/^([a-z]+)([0-9]+)/i', $selected, $matches) === 1) { + $colSel = Coordinate::columnIndexFromString($matches[1]) - 1; + $rowSel = (int) $matches[2] - 1; + $objWriter->startElement('config:config-item'); + $objWriter->writeAttribute('config:name', 'CursorPositionX'); + $objWriter->writeAttribute('config:type', 'int'); + $objWriter->text((string) $colSel); + $objWriter->endElement(); + $objWriter->startElement('config:config-item'); + $objWriter->writeAttribute('config:name', 'CursorPositionY'); + $objWriter->writeAttribute('config:type', 'int'); + $objWriter->text((string) $rowSel); + $objWriter->endElement(); + } + } + + private function writeSplitValue(XMLWriter $objWriter, string $splitMode, string $type, string $value): void + { + $objWriter->startElement('config:config-item'); + $objWriter->writeAttribute('config:name', $splitMode); + $objWriter->writeAttribute('config:type', $type); + $objWriter->text($value); + $objWriter->endElement(); + } + + private function writeFreezePane(XMLWriter $objWriter, Worksheet $worksheet): void + { + $freezePane = CellAddress::fromCellAddress($worksheet->getFreezePane()); + if ($freezePane->cellAddress() === 'A1') { + return; + } + + $columnId = $freezePane->columnId(); + $columnName = $freezePane->columnName(); + $row = $freezePane->rowId(); + + $this->writeSplitValue($objWriter, 'HorizontalSplitMode', 'short', '2'); + $this->writeSplitValue($objWriter, 'HorizontalSplitPosition', 'int', (string) ($columnId - 1)); + $this->writeSplitValue($objWriter, 'PositionLeft', 'short', '0'); + $this->writeSplitValue($objWriter, 'PositionRight', 'short', (string) ($columnId - 1)); + + for ($column = 'A'; $column !== $columnName; ++$column) { + $worksheet->getColumnDimension($column)->setAutoSize(true); + } + + $this->writeSplitValue($objWriter, 'VerticalSplitMode', 'short', '2'); + $this->writeSplitValue($objWriter, 'VerticalSplitPosition', 'int', (string) ($row - 1)); + $this->writeSplitValue($objWriter, 'PositionTop', 'short', '0'); + $this->writeSplitValue($objWriter, 'PositionBottom', 'short', (string) ($row - 1)); + + $this->writeSplitValue($objWriter, 'ActiveSplitRange', 'short', '3'); + } } diff --git a/PhpOffice/PhpSpreadsheet/Writer/Ods/Styles.php b/PhpOffice/PhpSpreadsheet/Writer/Ods/Styles.php old mode 100755 new mode 100644 index eaf5cad..448b1ef --- a/PhpOffice/PhpSpreadsheet/Writer/Ods/Styles.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Ods/Styles.php @@ -3,20 +3,15 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Ods; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; -use PhpOffice\PhpSpreadsheet\Spreadsheet; class Styles extends WriterPart { /** * Write styles.xml to XML format. * - * @param Spreadsheet $spreadsheet - * - * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception - * * @return string XML Output */ - public function write(Spreadsheet $spreadsheet = null) + public function write(): string { $objWriter = null; if ($this->getParentWriter()->getUseDiskCaching()) { diff --git a/PhpOffice/PhpSpreadsheet/Writer/Ods/Thumbnails.php b/PhpOffice/PhpSpreadsheet/Writer/Ods/Thumbnails.php old mode 100755 new mode 100644 index a29a14a..db9579d --- a/PhpOffice/PhpSpreadsheet/Writer/Ods/Thumbnails.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Ods/Thumbnails.php @@ -2,18 +2,14 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Ods; -use PhpOffice\PhpSpreadsheet\Spreadsheet; - class Thumbnails extends WriterPart { /** * Write Thumbnails/thumbnail.png to PNG format. * - * @param Spreadsheet $spreadsheet - * * @return string XML Output */ - public function writeThumbnail(Spreadsheet $spreadsheet = null) + public function write(): string { return ''; } diff --git a/PhpOffice/PhpSpreadsheet/Writer/Ods/WriterPart.php b/PhpOffice/PhpSpreadsheet/Writer/Ods/WriterPart.php old mode 100755 new mode 100644 index 3e2f968..17d5d16 --- a/PhpOffice/PhpSpreadsheet/Writer/Ods/WriterPart.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Ods/WriterPart.php @@ -25,11 +25,11 @@ abstract class WriterPart /** * Set parent Ods writer. - * - * @param Ods $writer */ public function __construct(Ods $writer) { $this->parentWriter = $writer; } + + abstract public function write(): string; } diff --git a/PhpOffice/PhpSpreadsheet/Writer/Pdf.php b/PhpOffice/PhpSpreadsheet/Writer/Pdf.php old mode 100755 new mode 100644 index b80083a..493bbba --- a/PhpOffice/PhpSpreadsheet/Writer/Pdf.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Pdf.php @@ -2,7 +2,6 @@ namespace PhpOffice\PhpSpreadsheet\Writer; -use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup; @@ -27,24 +26,17 @@ abstract class Pdf extends Html /** * Orientation (Over-ride). * - * @var string + * @var ?string */ protected $orientation; /** * Paper size (Over-ride). * - * @var int + * @var ?int */ protected $paperSize; - /** - * Temporary storage for Save Array Return type. - * - * @var string - */ - private $saveArrayReturnType; - /** * Paper Sizes xRef List. * @@ -127,8 +119,9 @@ abstract class Pdf extends Html public function __construct(Spreadsheet $spreadsheet) { parent::__construct($spreadsheet); - $this->setUseInlineCss(true); - $this->tempDir = File::sysGetTempDir(); + //$this->setUseInlineCss(true); + $this->tempDir = File::sysGetTempDir() . '/phpsppdf'; + $this->isPdf = true; } /** @@ -150,7 +143,7 @@ abstract class Pdf extends Html * * @param string $fontName * - * @return Pdf + * @return $this */ public function setFont($fontName) { @@ -162,7 +155,7 @@ abstract class Pdf extends Html /** * Get Paper Size. * - * @return int + * @return ?int */ public function getPaperSize() { @@ -172,23 +165,21 @@ abstract class Pdf extends Html /** * Set Paper Size. * - * @param string $pValue Paper size see PageSetup::PAPERSIZE_* + * @param int $paperSize Paper size see PageSetup::PAPERSIZE_* * * @return self */ - public function setPaperSize($pValue) + public function setPaperSize($paperSize) { - $this->paperSize = $pValue; + $this->paperSize = $paperSize; return $this; } /** * Get Orientation. - * - * @return string */ - public function getOrientation() + public function getOrientation(): ?string { return $this->orientation; } @@ -196,13 +187,13 @@ abstract class Pdf extends Html /** * Set Orientation. * - * @param string $pValue Page orientation see PageSetup::ORIENTATION_* + * @param string $orientation Page orientation see PageSetup::ORIENTATION_* * * @return self */ - public function setOrientation($pValue) + public function setOrientation($orientation) { - $this->orientation = $pValue; + $this->orientation = $orientation; return $this; } @@ -220,18 +211,16 @@ abstract class Pdf extends Html /** * Set temporary storage directory. * - * @param string $pValue Temporary storage directory - * - * @throws WriterException when directory does not exist + * @param string $temporaryDirectory Temporary storage directory * * @return self */ - public function setTempDir($pValue) + public function setTempDir($temporaryDirectory) { - if (is_dir($pValue)) { - $this->tempDir = $pValue; + if (is_dir($temporaryDirectory)) { + $this->tempDir = $temporaryDirectory; } else { - throw new WriterException("Directory does not exist: $pValue"); + throw new WriterException("Directory does not exist: $temporaryDirectory"); } return $this; @@ -240,44 +229,23 @@ abstract class Pdf extends Html /** * Save Spreadsheet to PDF file, pre-save. * - * @param string $pFilename Name of the file to save as - * - * @throws WriterException + * @param string $filename Name of the file to save as * * @return resource */ - protected function prepareForSave($pFilename) + protected function prepareForSave($filename) { - // garbage collect - $this->spreadsheet->garbageCollect(); - - $this->saveArrayReturnType = Calculation::getArrayReturnType(); - Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_VALUE); - // Open file - $fileHandle = fopen($pFilename, 'w'); - if ($fileHandle === false) { - throw new WriterException("Could not open file $pFilename for writing."); - } + $this->openFileHandle($filename); - // Set PDF - $this->isPdf = true; - // Build CSS - $this->buildCSS(true); - - return $fileHandle; + return $this->fileHandle; } /** * Save PhpSpreadsheet to PDF file, post-save. - * - * @param resource $fileHandle */ - protected function restoreStateAfterSave($fileHandle) + protected function restoreStateAfterSave(): void { - // Close file - fclose($fileHandle); - - Calculation::setArrayReturnType($this->saveArrayReturnType); + $this->maybeCloseFileHandle(); } } diff --git a/PhpOffice/PhpSpreadsheet/Writer/Pdf/Dompdf.php b/PhpOffice/PhpSpreadsheet/Writer/Pdf/Dompdf.php old mode 100755 new mode 100644 index 3c3044d..690b0c5 --- a/PhpOffice/PhpSpreadsheet/Writer/Pdf/Dompdf.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Pdf/Dompdf.php @@ -7,6 +7,13 @@ use PhpOffice\PhpSpreadsheet\Writer\Pdf; class Dompdf extends Pdf { + /** + * embed images, or link to images. + * + * @var bool + */ + protected $embedImages = true; + /** * Gets the implementation of external PDF library that should be used. * @@ -20,59 +27,34 @@ class Dompdf extends Pdf /** * Save Spreadsheet to file. * - * @param string $pFilename Name of the file to save as - * - * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception + * @param string $filename Name of the file to save as */ - public function save($pFilename) + public function save($filename, int $flags = 0): void { - $fileHandle = parent::prepareForSave($pFilename); - - // Default PDF paper size - $paperSize = 'LETTER'; // Letter (8.5 in. by 11 in.) + $fileHandle = parent::prepareForSave($filename); // Check for paper size and page orientation - if ($this->getSheetIndex() === null) { - $orientation = ($this->spreadsheet->getSheet(0)->getPageSetup()->getOrientation() - == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P'; - $printPaperSize = $this->spreadsheet->getSheet(0)->getPageSetup()->getPaperSize(); - } else { - $orientation = ($this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getOrientation() - == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P'; - $printPaperSize = $this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getPaperSize(); + $setup = $this->spreadsheet->getSheet($this->getSheetIndex() ?? 0)->getPageSetup(); + $orientation = $this->getOrientation() ?? $setup->getOrientation(); + $orientation = ($orientation === PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P'; + $printPaperSize = $this->getPaperSize() ?? $setup->getPaperSize(); + $paperSize = self::$paperSizes[$printPaperSize] ?? PageSetup::getPaperSizeDefault(); + if (is_array($paperSize) && count($paperSize) === 2) { + $paperSize = [0.0, 0.0, $paperSize[0], $paperSize[1]]; } $orientation = ($orientation == 'L') ? 'landscape' : 'portrait'; - // Override Page Orientation - if ($this->getOrientation() !== null) { - $orientation = ($this->getOrientation() == PageSetup::ORIENTATION_DEFAULT) - ? PageSetup::ORIENTATION_PORTRAIT - : $this->getOrientation(); - } - // Override Paper Size - if ($this->getPaperSize() !== null) { - $printPaperSize = $this->getPaperSize(); - } - - if (isset(self::$paperSizes[$printPaperSize])) { - $paperSize = self::$paperSizes[$printPaperSize]; - } - // Create PDF $pdf = $this->createExternalWriterInstance(); - $pdf->setPaper(strtolower($paperSize), $orientation); + $pdf->setPaper($paperSize, $orientation); - $pdf->loadHtml( - $this->generateHTMLHeader(false) . - $this->generateSheetData() . - $this->generateHTMLFooter() - ); + $pdf->loadHtml($this->generateHTMLAll()); $pdf->render(); // Write to file - fwrite($fileHandle, $pdf->output()); + fwrite($fileHandle, $pdf->output() ?? ''); - parent::restoreStateAfterSave($fileHandle); + parent::restoreStateAfterSave(); } } diff --git a/PhpOffice/PhpSpreadsheet/Writer/Pdf/Mpdf.php b/PhpOffice/PhpSpreadsheet/Writer/Pdf/Mpdf.php old mode 100755 new mode 100644 index fd2664a..d0ce9ed --- a/PhpOffice/PhpSpreadsheet/Writer/Pdf/Mpdf.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Pdf/Mpdf.php @@ -2,12 +2,15 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Pdf; -use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup; +use PhpOffice\PhpSpreadsheet\Writer\Html; use PhpOffice\PhpSpreadsheet\Writer\Pdf; class Mpdf extends Pdf { + /** @var bool */ + protected $isMPdf = true; + /** * Gets the implementation of external PDF library that should be used. * @@ -23,52 +26,24 @@ class Mpdf extends Pdf /** * Save Spreadsheet to file. * - * @param string $pFilename Name of the file to save as - * - * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception - * @throws PhpSpreadsheetException + * @param string $filename Name of the file to save as */ - public function save($pFilename) + public function save($filename, int $flags = 0): void { - $fileHandle = parent::prepareForSave($pFilename); - - // Default PDF paper size - $paperSize = 'LETTER'; // Letter (8.5 in. by 11 in.) + $fileHandle = parent::prepareForSave($filename); // Check for paper size and page orientation - if (null === $this->getSheetIndex()) { - $orientation = ($this->spreadsheet->getSheet(0)->getPageSetup()->getOrientation() - == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P'; - $printPaperSize = $this->spreadsheet->getSheet(0)->getPageSetup()->getPaperSize(); - } else { - $orientation = ($this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getOrientation() - == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P'; - $printPaperSize = $this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getPaperSize(); - } - $this->setOrientation($orientation); - - // Override Page Orientation - if (null !== $this->getOrientation()) { - $orientation = ($this->getOrientation() == PageSetup::ORIENTATION_DEFAULT) - ? PageSetup::ORIENTATION_PORTRAIT - : $this->getOrientation(); - } - $orientation = strtoupper($orientation); - - // Override Paper Size - if (null !== $this->getPaperSize()) { - $printPaperSize = $this->getPaperSize(); - } - - if (isset(self::$paperSizes[$printPaperSize])) { - $paperSize = self::$paperSizes[$printPaperSize]; - } + $setup = $this->spreadsheet->getSheet($this->getSheetIndex() ?? 0)->getPageSetup(); + $orientation = $this->getOrientation() ?? $setup->getOrientation(); + $orientation = ($orientation === PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P'; + $printPaperSize = $this->getPaperSize() ?? $setup->getPaperSize(); + $paperSize = self::$paperSizes[$printPaperSize] ?? PageSetup::getPaperSizeDefault(); // Create PDF - $config = ['tempDir' => $this->tempDir]; + $config = ['tempDir' => $this->tempDir . '/mpdf']; $pdf = $this->createExternalWriterInstance($config); $ortmp = $orientation; - $pdf->_setPageSize(strtoupper($paperSize), $ortmp); + $pdf->_setPageSize($paperSize, $ortmp); $pdf->DefOrientation = $orientation; $pdf->AddPageByArray([ 'orientation' => $orientation, @@ -85,17 +60,23 @@ class Mpdf extends Pdf $pdf->SetKeywords($this->spreadsheet->getProperties()->getKeywords()); $pdf->SetCreator($this->spreadsheet->getProperties()->getCreator()); - $pdf->WriteHTML($this->generateHTMLHeader(false)); - $html = $this->generateSheetData(); + $html = $this->generateHTMLAll(); + $bodyLocation = strpos($html, Html::BODY_LINE); + // Make sure first data presented to Mpdf includes body tag + // so that Mpdf doesn't parse it as content. Issue 2432. + if ($bodyLocation !== false) { + $bodyLocation += strlen(Html::BODY_LINE); + $pdf->WriteHTML(substr($html, 0, $bodyLocation)); + $html = substr($html, $bodyLocation); + } foreach (\array_chunk(\explode(PHP_EOL, $html), 1000) as $lines) { $pdf->WriteHTML(\implode(PHP_EOL, $lines)); } - $pdf->WriteHTML($this->generateHTMLFooter()); // Write to file fwrite($fileHandle, $pdf->Output('', 'S')); - parent::restoreStateAfterSave($fileHandle); + parent::restoreStateAfterSave(); } /** diff --git a/PhpOffice/PhpSpreadsheet/Writer/Pdf/Tcpdf.php b/PhpOffice/PhpSpreadsheet/Writer/Pdf/Tcpdf.php old mode 100755 new mode 100644 index 8a97b8f..aefc6b5 --- a/PhpOffice/PhpSpreadsheet/Writer/Pdf/Tcpdf.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Pdf/Tcpdf.php @@ -2,17 +2,29 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Pdf; +use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup; use PhpOffice\PhpSpreadsheet\Writer\Pdf; class Tcpdf extends Pdf { + /** + * Create a new PDF Writer instance. + * + * @param Spreadsheet $spreadsheet Spreadsheet object + */ + public function __construct(Spreadsheet $spreadsheet) + { + parent::__construct($spreadsheet); + $this->setUseInlineCss(true); + } + /** * Gets the implementation of external PDF library that should be used. * * @param string $orientation Page orientation * @param string $unit Unit measure - * @param string $paperSize Paper size + * @param array|string $paperSize Paper size * * @return \TCPDF implementation */ @@ -24,44 +36,22 @@ class Tcpdf extends Pdf /** * Save Spreadsheet to file. * - * @param string $pFilename Name of the file to save as - * - * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception + * @param string $filename Name of the file to save as */ - public function save($pFilename) + public function save($filename, int $flags = 0): void { - $fileHandle = parent::prepareForSave($pFilename); + $fileHandle = parent::prepareForSave($filename); // Default PDF paper size $paperSize = 'LETTER'; // Letter (8.5 in. by 11 in.) // Check for paper size and page orientation - if ($this->getSheetIndex() === null) { - $orientation = ($this->spreadsheet->getSheet(0)->getPageSetup()->getOrientation() - == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P'; - $printPaperSize = $this->spreadsheet->getSheet(0)->getPageSetup()->getPaperSize(); - $printMargins = $this->spreadsheet->getSheet(0)->getPageMargins(); - } else { - $orientation = ($this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getOrientation() - == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P'; - $printPaperSize = $this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getPaperSize(); - $printMargins = $this->spreadsheet->getSheet($this->getSheetIndex())->getPageMargins(); - } - - // Override Page Orientation - if ($this->getOrientation() !== null) { - $orientation = ($this->getOrientation() == PageSetup::ORIENTATION_LANDSCAPE) - ? 'L' - : 'P'; - } - // Override Paper Size - if ($this->getPaperSize() !== null) { - $printPaperSize = $this->getPaperSize(); - } - - if (isset(self::$paperSizes[$printPaperSize])) { - $paperSize = self::$paperSizes[$printPaperSize]; - } + $setup = $this->spreadsheet->getSheet($this->getSheetIndex() ?? 0)->getPageSetup(); + $orientation = $this->getOrientation() ?? $setup->getOrientation(); + $orientation = ($orientation === PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P'; + $printPaperSize = $this->getPaperSize() ?? $setup->getPaperSize(); + $paperSize = self::$paperSizes[$printPaperSize] ?? PageSetup::getPaperSizeDefault(); + $printMargins = $this->spreadsheet->getSheet($this->getSheetIndex() ?? 0)->getPageMargins(); // Create PDF $pdf = $this->createExternalWriterInstance($orientation, 'pt', $paperSize); @@ -77,11 +67,7 @@ class Tcpdf extends Pdf // Set the appropriate font $pdf->SetFont($this->getFont()); - $pdf->writeHTML( - $this->generateHTMLHeader(false) . - $this->generateSheetData() . - $this->generateHTMLFooter() - ); + $pdf->writeHTML($this->generateHTMLAll()); // Document info $pdf->SetTitle($this->spreadsheet->getProperties()->getTitle()); @@ -91,8 +77,8 @@ class Tcpdf extends Pdf $pdf->SetCreator($this->spreadsheet->getProperties()->getCreator()); // Write to file - fwrite($fileHandle, $pdf->output($pFilename, 'S')); + fwrite($fileHandle, $pdf->output('', 'S')); - parent::restoreStateAfterSave($fileHandle); + parent::restoreStateAfterSave(); } } diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xls.php b/PhpOffice/PhpSpreadsheet/Writer/Xls.php old mode 100755 new mode 100644 index 6932eb1..b740b6f --- a/PhpOffice/PhpSpreadsheet/Writer/Xls.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Xls.php @@ -4,10 +4,10 @@ namespace PhpOffice\PhpSpreadsheet\Writer; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Calculation\Functions; +use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\RichText\Run; -use PhpOffice\PhpSpreadsheet\Shared\Drawing as SharedDrawing; use PhpOffice\PhpSpreadsheet\Shared\Escher; use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer; use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer; @@ -23,7 +23,9 @@ use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing; use PhpOffice\PhpSpreadsheet\Worksheet\Drawing; use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing; -use RuntimeException; +use PhpOffice\PhpSpreadsheet\Writer\Xls\Parser; +use PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook; +use PhpOffice\PhpSpreadsheet\Writer\Xls\Worksheet; class Xls extends BaseWriter { @@ -65,7 +67,7 @@ class Xls extends BaseWriter /** * Formula parser. * - * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Parser + * @var Parser */ private $parser; @@ -79,24 +81,24 @@ class Xls extends BaseWriter /** * Basic OLE object summary information. * - * @var array + * @var string */ private $summaryInformation; /** * Extended OLE object document summary information. * - * @var array + * @var string */ private $documentSummaryInformation; /** - * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook + * @var Workbook */ private $writerWorkbook; /** - * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Worksheet[] + * @var Worksheet[] */ private $writerWorksheets; @@ -109,18 +111,18 @@ class Xls extends BaseWriter { $this->spreadsheet = $spreadsheet; - $this->parser = new Xls\Parser(); + $this->parser = new Xls\Parser($spreadsheet); } /** * Save Spreadsheet to file. * - * @param string $pFilename - * - * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception + * @param resource|string $filename */ - public function save($pFilename) + public function save($filename, int $flags = 0): void { + $this->processFlags($flags); + // garbage collect $this->spreadsheet->garbageCollect(); @@ -159,8 +161,9 @@ class Xls extends BaseWriter // add fonts from rich text eleemnts for ($i = 0; $i < $countSheets; ++$i) { - foreach ($this->writerWorksheets[$i]->phpSheet->getCoordinates() as $coordinate) { - $cell = $this->writerWorksheets[$i]->phpSheet->getCell($coordinate); + foreach ($this->writerWorksheets[$i]->phpSheet->getCellCollection()->getCoordinates() as $coordinate) { + /** @var Cell $cell */ + $cell = $this->writerWorksheets[$i]->phpSheet->getCellCollection()->get($coordinate); $cVal = $cell->getValue(); if ($cVal instanceof RichText) { $elements = $cVal->getRichTextElements(); @@ -219,9 +222,12 @@ class Xls extends BaseWriter $arrRootData[] = $OLE_DocumentSummaryInformation; } - $root = new Root(time(), time(), $arrRootData); + $time = $this->spreadsheet->getProperties()->getModified(); + $root = new Root($time, $time, $arrRootData); // save the OLE file - $root->save($pFilename); + $this->openFileHandle($filename); + $root->save($this->fileHandle); + $this->maybeCloseFileHandle(); Functions::setReturnDateType($saveDateReturnType); Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog($saveDebugLog); @@ -230,7 +236,7 @@ class Xls extends BaseWriter /** * Build the Worksheet Escher objects. */ - private function buildWorksheetEschers() + private function buildWorksheetEschers(): void { // 1-based index to BstoreContainer $blipIndex = 0; @@ -241,8 +247,6 @@ class Xls extends BaseWriter // sheet index $sheetIndex = $sheet->getParent()->getIndex($sheet); - $escher = null; - // check if there are any shapes for this sheet $filterRange = $sheet->getAutoFilter()->getRange(); if (count($sheet->getDrawingCollection()) == 0 && empty($filterRange)) { @@ -389,13 +393,103 @@ class Xls extends BaseWriter } } - /** - * Build the Escher object corresponding to the MSODRAWINGGROUP record. - */ - private function buildWorkbookEscher() + private function processMemoryDrawing(BstoreContainer &$bstoreContainer, MemoryDrawing $drawing, string $renderingFunctionx): void { - $escher = null; + switch ($renderingFunctionx) { + case MemoryDrawing::RENDERING_JPEG: + $blipType = BSE::BLIPTYPE_JPEG; + $renderingFunction = 'imagejpeg'; + break; + default: + $blipType = BSE::BLIPTYPE_PNG; + $renderingFunction = 'imagepng'; + + break; + } + + ob_start(); + call_user_func($renderingFunction, $drawing->getImageResource()); + $blipData = ob_get_contents(); + ob_end_clean(); + + $blip = new Blip(); + $blip->setData($blipData); + + $BSE = new BSE(); + $BSE->setBlipType($blipType); + $BSE->setBlip($blip); + + $bstoreContainer->addBSE($BSE); + } + + private function processDrawing(BstoreContainer &$bstoreContainer, Drawing $drawing): void + { + $blipType = null; + $blipData = ''; + $filename = $drawing->getPath(); + + [$imagesx, $imagesy, $imageFormat] = getimagesize($filename); + + switch ($imageFormat) { + case 1: // GIF, not supported by BIFF8, we convert to PNG + $blipType = BSE::BLIPTYPE_PNG; + $newImage = @imagecreatefromgif($filename); + if ($newImage === false) { + throw new Exception("Unable to create image from $filename"); + } + ob_start(); + imagepng($newImage); + $blipData = ob_get_contents(); + ob_end_clean(); + + break; + case 2: // JPEG + $blipType = BSE::BLIPTYPE_JPEG; + $blipData = file_get_contents($filename); + + break; + case 3: // PNG + $blipType = BSE::BLIPTYPE_PNG; + $blipData = file_get_contents($filename); + + break; + case 6: // Windows DIB (BMP), we convert to PNG + $blipType = BSE::BLIPTYPE_PNG; + $newImage = @imagecreatefrombmp($filename); + if ($newImage === false) { + throw new Exception("Unable to create image from $filename"); + } + ob_start(); + imagepng($newImage); + $blipData = ob_get_contents(); + ob_end_clean(); + + break; + } + if ($blipData) { + $blip = new Blip(); + $blip->setData($blipData); + + $BSE = new BSE(); + $BSE->setBlipType($blipType); + $BSE->setBlip($blip); + + $bstoreContainer->addBSE($BSE); + } + } + + private function processBaseDrawing(BstoreContainer &$bstoreContainer, BaseDrawing $drawing): void + { + if ($drawing instanceof Drawing) { + $this->processDrawing($bstoreContainer, $drawing); + } elseif ($drawing instanceof MemoryDrawing) { + $this->processMemoryDrawing($bstoreContainer, $drawing, $drawing->getRenderingFunction()); + } + } + + private function checkForDrawings(): bool + { // any drawings in this workbook? $found = false; foreach ($this->spreadsheet->getAllSheets() as $sheet) { @@ -406,8 +500,16 @@ class Xls extends BaseWriter } } + return $found; + } + + /** + * Build the Escher object corresponding to the MSODRAWINGGROUP record. + */ + private function buildWorkbookEscher(): void + { // nothing to do if there are no drawings - if (!$found) { + if (!$this->checkForDrawings()) { return; } @@ -429,17 +531,16 @@ class Xls extends BaseWriter foreach ($this->spreadsheet->getAllsheets() as $sheet) { $sheetCountShapes = 0; // count number of shapes (minus group shape), in sheet - if (count($sheet->getDrawingCollection()) > 0) { - ++$countDrawings; + $addCount = 0; + foreach ($sheet->getDrawingCollection() as $drawing) { + $addCount = 1; + ++$sheetCountShapes; + ++$totalCountShapes; - foreach ($sheet->getDrawingCollection() as $drawing) { - ++$sheetCountShapes; - ++$totalCountShapes; - - $spId = $sheetCountShapes | ($this->spreadsheet->getIndex($sheet) + 1) << 10; - $spIdMax = max($spId, $spIdMax); - } + $spId = $sheetCountShapes | ($this->spreadsheet->getIndex($sheet) + 1) << 10; + $spIdMax = max($spId, $spIdMax); } + $countDrawings += $addCount; } $dggContainer->setSpIdMax($spIdMax + 1); @@ -453,83 +554,7 @@ class Xls extends BaseWriter // the BSE's (all the images) foreach ($this->spreadsheet->getAllsheets() as $sheet) { foreach ($sheet->getDrawingCollection() as $drawing) { - if (!extension_loaded('gd')) { - throw new RuntimeException('Saving images in xls requires gd extension'); - } - if ($drawing instanceof Drawing) { - $filename = $drawing->getPath(); - - [$imagesx, $imagesy, $imageFormat] = getimagesize($filename); - - switch ($imageFormat) { - case 1: // GIF, not supported by BIFF8, we convert to PNG - $blipType = BSE::BLIPTYPE_PNG; - ob_start(); - imagepng(imagecreatefromgif($filename)); - $blipData = ob_get_contents(); - ob_end_clean(); - - break; - case 2: // JPEG - $blipType = BSE::BLIPTYPE_JPEG; - $blipData = file_get_contents($filename); - - break; - case 3: // PNG - $blipType = BSE::BLIPTYPE_PNG; - $blipData = file_get_contents($filename); - - break; - case 6: // Windows DIB (BMP), we convert to PNG - $blipType = BSE::BLIPTYPE_PNG; - ob_start(); - imagepng(SharedDrawing::imagecreatefrombmp($filename)); - $blipData = ob_get_contents(); - ob_end_clean(); - - break; - default: - continue 2; - } - - $blip = new Blip(); - $blip->setData($blipData); - - $BSE = new BSE(); - $BSE->setBlipType($blipType); - $BSE->setBlip($blip); - - $bstoreContainer->addBSE($BSE); - } elseif ($drawing instanceof MemoryDrawing) { - switch ($drawing->getRenderingFunction()) { - case MemoryDrawing::RENDERING_JPEG: - $blipType = BSE::BLIPTYPE_JPEG; - $renderingFunction = 'imagejpeg'; - - break; - case MemoryDrawing::RENDERING_GIF: - case MemoryDrawing::RENDERING_PNG: - case MemoryDrawing::RENDERING_DEFAULT: - $blipType = BSE::BLIPTYPE_PNG; - $renderingFunction = 'imagepng'; - - break; - } - - ob_start(); - call_user_func($renderingFunction, $drawing->getImageResource()); - $blipData = ob_get_contents(); - ob_end_clean(); - - $blip = new Blip(); - $blip->setData($blipData); - - $BSE = new BSE(); - $BSE->setBlipType($blipType); - $BSE->setBlip($blip); - - $bstoreContainer->addBSE($BSE); - } + $this->processBaseDrawing($bstoreContainer, $drawing); } } @@ -578,8 +603,8 @@ class Xls extends BaseWriter ++$dataSection_NumProps; // GKPIDDSI_CATEGORY : Category - if ($this->spreadsheet->getProperties()->getCategory()) { - $dataProp = $this->spreadsheet->getProperties()->getCategory(); + $dataProp = $this->spreadsheet->getProperties()->getCategory(); + if ($dataProp) { $dataSection[] = [ 'summary' => ['pack' => 'V', 'data' => 0x02], 'offset' => ['pack' => 'V'], @@ -707,16 +732,12 @@ class Xls extends BaseWriter $dataSection_Content_Offset += 4 + 4; } elseif ($dataProp['type']['data'] == 0x0B) { // Boolean - if ($dataProp['data']['data'] == false) { - $dataSection_Content .= pack('V', 0x0000); - } else { - $dataSection_Content .= pack('V', 0x0001); - } + $dataSection_Content .= pack('V', (int) $dataProp['data']['data']); $dataSection_Content_Offset += 4 + 4; } elseif ($dataProp['type']['data'] == 0x1E) { // null-terminated string prepended by dword string length // Null-terminated string $dataProp['data']['data'] .= chr(0); - $dataProp['data']['length'] += 1; + ++$dataProp['data']['length']; // Complete the string with null string for being a %4 $dataProp['data']['length'] = $dataProp['data']['length'] + ((4 - $dataProp['data']['length'] % 4) == 4 ? 0 : (4 - $dataProp['data']['length'] % 4)); $dataProp['data']['data'] = str_pad($dataProp['data']['data'], $dataProp['data']['length'], chr(0), STR_PAD_RIGHT); @@ -725,12 +746,12 @@ class Xls extends BaseWriter $dataSection_Content .= $dataProp['data']['data']; $dataSection_Content_Offset += 4 + 4 + strlen($dataProp['data']['data']); - } elseif ($dataProp['type']['data'] == 0x40) { // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601) - $dataSection_Content .= $dataProp['data']['data']; + // Condition below can never be true + //} elseif ($dataProp['type']['data'] == 0x40) { // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601) + // $dataSection_Content .= $dataProp['data']['data']; - $dataSection_Content_Offset += 4 + 8; + // $dataSection_Content_Offset += 4 + 8; } else { - // Data Type Not Used at the moment $dataSection_Content .= $dataProp['data']['data']; $dataSection_Content_Offset += 4 + $dataProp['data']['length']; @@ -752,6 +773,35 @@ class Xls extends BaseWriter return $data; } + /** + * @param float|int $dataProp + */ + private function writeSummaryPropOle($dataProp, int &$dataSection_NumProps, array &$dataSection, int $sumdata, int $typdata): void + { + if ($dataProp) { + $dataSection[] = [ + 'summary' => ['pack' => 'V', 'data' => $sumdata], + 'offset' => ['pack' => 'V'], + 'type' => ['pack' => 'V', 'data' => $typdata], // null-terminated string prepended by dword string length + 'data' => ['data' => OLE::localDateToOLE($dataProp)], + ]; + ++$dataSection_NumProps; + } + } + + private function writeSummaryProp(string $dataProp, int &$dataSection_NumProps, array &$dataSection, int $sumdata, int $typdata): void + { + if ($dataProp) { + $dataSection[] = [ + 'summary' => ['pack' => 'V', 'data' => $sumdata], + 'offset' => ['pack' => 'V'], + 'type' => ['pack' => 'V', 'data' => $typdata], // null-terminated string prepended by dword string length + 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)], + ]; + ++$dataSection_NumProps; + } + } + /** * Build the OLE Part for Summary Information. * @@ -792,94 +842,16 @@ class Xls extends BaseWriter ]; ++$dataSection_NumProps; - // Title - if ($this->spreadsheet->getProperties()->getTitle()) { - $dataProp = $this->spreadsheet->getProperties()->getTitle(); - $dataSection[] = [ - 'summary' => ['pack' => 'V', 'data' => 0x02], - 'offset' => ['pack' => 'V'], - 'type' => ['pack' => 'V', 'data' => 0x1E], // null-terminated string prepended by dword string length - 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)], - ]; - ++$dataSection_NumProps; - } - // Subject - if ($this->spreadsheet->getProperties()->getSubject()) { - $dataProp = $this->spreadsheet->getProperties()->getSubject(); - $dataSection[] = [ - 'summary' => ['pack' => 'V', 'data' => 0x03], - 'offset' => ['pack' => 'V'], - 'type' => ['pack' => 'V', 'data' => 0x1E], // null-terminated string prepended by dword string length - 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)], - ]; - ++$dataSection_NumProps; - } - // Author (Creator) - if ($this->spreadsheet->getProperties()->getCreator()) { - $dataProp = $this->spreadsheet->getProperties()->getCreator(); - $dataSection[] = [ - 'summary' => ['pack' => 'V', 'data' => 0x04], - 'offset' => ['pack' => 'V'], - 'type' => ['pack' => 'V', 'data' => 0x1E], // null-terminated string prepended by dword string length - 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)], - ]; - ++$dataSection_NumProps; - } - // Keywords - if ($this->spreadsheet->getProperties()->getKeywords()) { - $dataProp = $this->spreadsheet->getProperties()->getKeywords(); - $dataSection[] = [ - 'summary' => ['pack' => 'V', 'data' => 0x05], - 'offset' => ['pack' => 'V'], - 'type' => ['pack' => 'V', 'data' => 0x1E], // null-terminated string prepended by dword string length - 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)], - ]; - ++$dataSection_NumProps; - } - // Comments (Description) - if ($this->spreadsheet->getProperties()->getDescription()) { - $dataProp = $this->spreadsheet->getProperties()->getDescription(); - $dataSection[] = [ - 'summary' => ['pack' => 'V', 'data' => 0x06], - 'offset' => ['pack' => 'V'], - 'type' => ['pack' => 'V', 'data' => 0x1E], // null-terminated string prepended by dword string length - 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)], - ]; - ++$dataSection_NumProps; - } - // Last Saved By (LastModifiedBy) - if ($this->spreadsheet->getProperties()->getLastModifiedBy()) { - $dataProp = $this->spreadsheet->getProperties()->getLastModifiedBy(); - $dataSection[] = [ - 'summary' => ['pack' => 'V', 'data' => 0x08], - 'offset' => ['pack' => 'V'], - 'type' => ['pack' => 'V', 'data' => 0x1E], // null-terminated string prepended by dword string length - 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)], - ]; - ++$dataSection_NumProps; - } - // Created Date/Time - if ($this->spreadsheet->getProperties()->getCreated()) { - $dataProp = $this->spreadsheet->getProperties()->getCreated(); - $dataSection[] = [ - 'summary' => ['pack' => 'V', 'data' => 0x0C], - 'offset' => ['pack' => 'V'], - 'type' => ['pack' => 'V', 'data' => 0x40], // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601) - 'data' => ['data' => OLE::localDateToOLE($dataProp)], - ]; - ++$dataSection_NumProps; - } - // Modified Date/Time - if ($this->spreadsheet->getProperties()->getModified()) { - $dataProp = $this->spreadsheet->getProperties()->getModified(); - $dataSection[] = [ - 'summary' => ['pack' => 'V', 'data' => 0x0D], - 'offset' => ['pack' => 'V'], - 'type' => ['pack' => 'V', 'data' => 0x40], // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601) - 'data' => ['data' => OLE::localDateToOLE($dataProp)], - ]; - ++$dataSection_NumProps; - } + $props = $this->spreadsheet->getProperties(); + $this->writeSummaryProp($props->getTitle(), $dataSection_NumProps, $dataSection, 0x02, 0x1e); + $this->writeSummaryProp($props->getSubject(), $dataSection_NumProps, $dataSection, 0x03, 0x1e); + $this->writeSummaryProp($props->getCreator(), $dataSection_NumProps, $dataSection, 0x04, 0x1e); + $this->writeSummaryProp($props->getKeywords(), $dataSection_NumProps, $dataSection, 0x05, 0x1e); + $this->writeSummaryProp($props->getDescription(), $dataSection_NumProps, $dataSection, 0x06, 0x1e); + $this->writeSummaryProp($props->getLastModifiedBy(), $dataSection_NumProps, $dataSection, 0x08, 0x1e); + $this->writeSummaryPropOle($props->getCreated(), $dataSection_NumProps, $dataSection, 0x0c, 0x40); + $this->writeSummaryPropOle($props->getModified(), $dataSection_NumProps, $dataSection, 0x0d, 0x40); + // Security $dataSection[] = [ 'summary' => ['pack' => 'V', 'data' => 0x13], @@ -912,7 +884,7 @@ class Xls extends BaseWriter } elseif ($dataProp['type']['data'] == 0x1E) { // null-terminated string prepended by dword string length // Null-terminated string $dataProp['data']['data'] .= chr(0); - $dataProp['data']['length'] += 1; + ++$dataProp['data']['length']; // Complete the string with null string for being a %4 $dataProp['data']['length'] = $dataProp['data']['length'] + ((4 - $dataProp['data']['length'] % 4) == 4 ? 0 : (4 - $dataProp['data']['length'] % 4)); $dataProp['data']['data'] = str_pad($dataProp['data']['data'], $dataProp['data']['length'], chr(0), STR_PAD_RIGHT); diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xls/BIFFwriter.php b/PhpOffice/PhpSpreadsheet/Writer/Xls/BIFFwriter.php old mode 100755 new mode 100644 index 3b2eb9a..4a7e065 --- a/PhpOffice/PhpSpreadsheet/Writer/Xls/BIFFwriter.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Xls/BIFFwriter.php @@ -49,7 +49,7 @@ class BIFFwriter /** * The string containing the data of the BIFF stream. * - * @var string + * @var null|string */ public $_data; @@ -109,7 +109,7 @@ class BIFFwriter * * @param string $data binary data to append */ - protected function append($data) + protected function append($data): void { if (strlen($data) - 4 > $this->limit) { $data = $this->addContinue($data); @@ -142,7 +142,7 @@ class BIFFwriter * @param int $type type of BIFF file to write: 0x0005 Workbook, * 0x0010 Worksheet */ - protected function storeBof($type) + protected function storeBof($type): void { $record = 0x0809; // Record identifier (BIFF5-BIFF8) $length = 0x0010; @@ -163,7 +163,7 @@ class BIFFwriter /** * Writes Excel EOF record to indicate the end of a BIFF stream. */ - protected function storeEof() + protected function storeEof(): void { $record = 0x000A; // Record identifier $length = 0x0000; // Number of bytes to follow diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xls/CellDataValidation.php b/PhpOffice/PhpSpreadsheet/Writer/Xls/CellDataValidation.php new file mode 100644 index 0000000..7e9b3cf --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Writer/Xls/CellDataValidation.php @@ -0,0 +1,78 @@ + + */ + protected static $validationTypeMap = [ + DataValidation::TYPE_NONE => 0x00, + DataValidation::TYPE_WHOLE => 0x01, + DataValidation::TYPE_DECIMAL => 0x02, + DataValidation::TYPE_LIST => 0x03, + DataValidation::TYPE_DATE => 0x04, + DataValidation::TYPE_TIME => 0x05, + DataValidation::TYPE_TEXTLENGTH => 0x06, + DataValidation::TYPE_CUSTOM => 0x07, + ]; + + /** + * @var array + */ + protected static $errorStyleMap = [ + DataValidation::STYLE_STOP => 0x00, + DataValidation::STYLE_WARNING => 0x01, + DataValidation::STYLE_INFORMATION => 0x02, + ]; + + /** + * @var array + */ + protected static $operatorMap = [ + DataValidation::OPERATOR_BETWEEN => 0x00, + DataValidation::OPERATOR_NOTBETWEEN => 0x01, + DataValidation::OPERATOR_EQUAL => 0x02, + DataValidation::OPERATOR_NOTEQUAL => 0x03, + DataValidation::OPERATOR_GREATERTHAN => 0x04, + DataValidation::OPERATOR_LESSTHAN => 0x05, + DataValidation::OPERATOR_GREATERTHANOREQUAL => 0x06, + DataValidation::OPERATOR_LESSTHANOREQUAL => 0x07, + ]; + + public static function type(DataValidation $dataValidation): int + { + $validationType = $dataValidation->getType(); + + if (is_string($validationType) && array_key_exists($validationType, self::$validationTypeMap)) { + return self::$validationTypeMap[$validationType]; + } + + return self::$validationTypeMap[DataValidation::TYPE_NONE]; + } + + public static function errorStyle(DataValidation $dataValidation): int + { + $errorStyle = $dataValidation->getErrorStyle(); + + if (is_string($errorStyle) && array_key_exists($errorStyle, self::$errorStyleMap)) { + return self::$errorStyleMap[$errorStyle]; + } + + return self::$errorStyleMap[DataValidation::STYLE_STOP]; + } + + public static function operator(DataValidation $dataValidation): int + { + $operator = $dataValidation->getOperator(); + + if (is_string($operator) && array_key_exists($operator, self::$operatorMap)) { + return self::$operatorMap[$operator]; + } + + return self::$operatorMap[DataValidation::OPERATOR_BETWEEN]; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xls/ConditionalHelper.php b/PhpOffice/PhpSpreadsheet/Writer/Xls/ConditionalHelper.php new file mode 100644 index 0000000..0f78b8c --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Writer/Xls/ConditionalHelper.php @@ -0,0 +1,76 @@ +parser = $parser; + } + + /** + * @param mixed $condition + */ + public function processCondition($condition, string $cellRange): void + { + $this->condition = $condition; + $this->cellRange = $cellRange; + + if (is_int($condition) || is_float($condition)) { + $this->size = ($condition <= 65535 ? 3 : 0x0000); + $this->tokens = pack('Cv', 0x1E, $condition); + } else { + try { + $formula = Wizard\WizardAbstract::reverseAdjustCellRef((string) $condition, $cellRange); + $this->parser->parse($formula); + $this->tokens = $this->parser->toReversePolish(); + $this->size = strlen($this->tokens ?? ''); + } catch (PhpSpreadsheetException $e) { + // In the event of a parser error with a formula value, we set the expression to ptgInt + 0 + $this->tokens = pack('Cv', 0x1E, 0); + $this->size = 3; + } + } + } + + public function tokens(): ?string + { + return $this->tokens; + } + + public function size(): int + { + return $this->size; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xls/ErrorCode.php b/PhpOffice/PhpSpreadsheet/Writer/Xls/ErrorCode.php new file mode 100644 index 0000000..7a864f5 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Writer/Xls/ErrorCode.php @@ -0,0 +1,28 @@ + + */ + protected static $errorCodeMap = [ + '#NULL!' => 0x00, + '#DIV/0!' => 0x07, + '#VALUE!' => 0x0F, + '#REF!' => 0x17, + '#NAME?' => 0x1D, + '#NUM!' => 0x24, + '#N/A' => 0x2A, + ]; + + public static function error(string $errorCode): int + { + if (array_key_exists($errorCode, self::$errorCodeMap)) { + return self::$errorCodeMap[$errorCode]; + } + + return 0; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xls/Escher.php b/PhpOffice/PhpSpreadsheet/Writer/Xls/Escher.php old mode 100755 new mode 100644 index 1ee2e90..d5d60c6 --- a/PhpOffice/PhpSpreadsheet/Writer/Xls/Escher.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Xls/Escher.php @@ -413,15 +413,13 @@ class Escher // the client anchor if ($this->object->getStartCoordinates()) { - $clientAnchorData = ''; - $recVer = 0x0; $recInstance = 0x0; $recType = 0xF010; // start coordinates - [$column, $row] = Coordinate::coordinateFromString($this->object->getStartCoordinates()); - $c1 = Coordinate::columnIndexFromString($column) - 1; + [$column, $row] = Coordinate::indexesFromString($this->object->getStartCoordinates()); + $c1 = $column - 1; $r1 = $row - 1; // start offsetX @@ -431,8 +429,8 @@ class Escher $startOffsetY = $this->object->getStartOffsetY(); // end coordinates - [$column, $row] = Coordinate::coordinateFromString($this->object->getEndCoordinates()); - $c2 = Coordinate::columnIndexFromString($column) - 1; + [$column, $row] = Coordinate::indexesFromString($this->object->getEndCoordinates()); + $c2 = $column - 1; $r2 = $row - 1; // end offsetX diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xls/Font.php b/PhpOffice/PhpSpreadsheet/Writer/Xls/Font.php old mode 100755 new mode 100644 index df37dcb..1923200 --- a/PhpOffice/PhpSpreadsheet/Writer/Xls/Font.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Xls/Font.php @@ -22,8 +22,6 @@ class Font /** * Constructor. - * - * @param \PhpOffice\PhpSpreadsheet\Style\Font $font */ public function __construct(\PhpOffice\PhpSpreadsheet\Style\Font $font) { @@ -36,11 +34,14 @@ class Font * * @param int $colorIndex */ - public function setColorIndex($colorIndex) + public function setColorIndex($colorIndex): void { $this->colorIndex = $colorIndex; } + /** @var int */ + private static $notImplemented = 0; + /** * Get font record data. * @@ -48,8 +49,8 @@ class Font */ public function writeFont() { - $font_outline = 0; - $font_shadow = 0; + $font_outline = self::$notImplemented; + $font_shadow = self::$notImplemented; $icv = $this->colorIndex; // Index to color palette if ($this->font->getSuperscript()) { @@ -60,7 +61,7 @@ class Font $sss = 0; } $bFamily = 0; // Font family - $bCharSet = \PhpOffice\PhpSpreadsheet\Shared\Font::getCharsetFromFontName($this->font->getName()); // Character set + $bCharSet = \PhpOffice\PhpSpreadsheet\Shared\Font::getCharsetFromFontName((string) $this->font->getName()); // Character set $record = 0x31; // Record identifier $reserved = 0x00; // Reserved @@ -89,12 +90,12 @@ class Font self::mapBold($this->font->getBold()), // Superscript/Subscript $sss, - self::mapUnderline($this->font->getUnderline()), + self::mapUnderline((string) $this->font->getUnderline()), $bFamily, $bCharSet, $reserved ); - $data .= StringHelper::UTF8toBIFF8UnicodeShort($this->font->getName()); + $data .= StringHelper::UTF8toBIFF8UnicodeShort((string) $this->font->getName()); $length = strlen($data); $header = pack('vv', $record, $length); @@ -104,14 +105,10 @@ class Font /** * Map to BIFF5-BIFF8 codes for bold. - * - * @param bool $bold - * - * @return int */ - private static function mapBold($bold) + private static function mapBold(?bool $bold): int { - if ($bold) { + if ($bold === true) { return 0x2BC; // 700 = Bold font weight } @@ -121,7 +118,7 @@ class Font /** * Map of BIFF2-BIFF8 codes for underline styles. * - * @var array of int + * @var int[] */ private static $mapUnderline = [ \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_NONE => 0x00, diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xls/Parser.php b/PhpOffice/PhpSpreadsheet/Writer/Xls/Parser.php old mode 100755 new mode 100644 index 625a243..3fe9e87 --- a/PhpOffice/PhpSpreadsheet/Writer/Xls/Parser.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Xls/Parser.php @@ -2,7 +2,9 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Xls; +use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; +use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet as PhpspreadsheetWorksheet; use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException; @@ -76,9 +78,9 @@ class Parser /** * The parse tree to be generated. * - * @var string + * @var array|string */ - private $parseTree; + public $parseTree; /** * Array of external sheets. @@ -467,11 +469,16 @@ class Parser 'BAHTTEXT' => [368, 1, 0, 0], ]; + /** @var Spreadsheet */ + private $spreadsheet; + /** * The class constructor. */ - public function __construct() + public function __construct(Spreadsheet $spreadsheet) { + $this->spreadsheet = $spreadsheet; + $this->currentCharacter = 0; $this->currentToken = ''; // The token we are working on. $this->formula = ''; // The formula to parse. @@ -516,13 +523,15 @@ class Parser } elseif (isset($this->ptg[$token])) { return pack('C', $this->ptg[$token]); // match error codes - } elseif (preg_match('/^#[A-Z0\\/]{3,5}[!?]{1}$/', $token) or $token == '#N/A') { + } elseif (preg_match('/^#[A-Z0\\/]{3,5}[!?]{1}$/', $token) || $token == '#N/A') { return $this->convertError($token); + } elseif (preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/mui', $token) && $this->spreadsheet->getDefinedName($token) !== null) { + return $this->convertDefinedName($token); // commented so argument number can be processed correctly. See toReversePolish(). - /*elseif (preg_match("/[A-Z0-9\xc0-\xdc\.]+/", $token)) - { - return($this->convertFunction($token, $this->_func_args)); - }*/ + /*elseif (preg_match("/[A-Z0-9\xc0-\xdc\.]+/", $token)) + { + return($this->convertFunction($token, $this->_func_args)); + }*/ // if it's an argument, ignore the token (the argument remains) } elseif ($token == 'arg') { return ''; @@ -542,7 +551,7 @@ class Parser private function convertNumber($num) { // Integer in the range 0..2**16-1 - if ((preg_match('/^\\d+$/', $num)) and ($num <= 65535)) { + if ((preg_match('/^\\d+$/', $num)) && ($num <= 65535)) { return pack('Cv', $this->ptg['ptgInt'], $num); } @@ -589,10 +598,9 @@ class Parser if ($args >= 0) { return pack('Cv', $this->ptg['ptgFuncV'], $this->functions[$token][0]); } + // Variable number of args eg. SUM($i, $j, $k, ..). - if ($args == -1) { - return pack('CCv', $this->ptg['ptgFuncVarV'], $num_args, $this->functions[$token][0]); - } + return pack('CCv', $this->ptg['ptgFuncVarV'], $num_args, $this->functions[$token][0]); } /** @@ -739,6 +747,29 @@ class Parser return pack('C', 0xFF); } + private function convertDefinedName(string $name): string + { + if (strlen($name) > 255) { + throw new WriterException('Defined Name is too long'); + } + + throw new WriterException('Cannot yet write formulae with defined names to Xls'); + /* + $nameReference = 1; + foreach ($this->spreadsheet->getDefinedNames() as $definedName) { + if ($name === $definedName->getName()) { + break; + } + ++$nameReference; + } + + $ptgRef = pack('Cvxx', $this->ptg['ptgName'], $nameReference); + + + return $ptgRef; + */ + } + /** * Look up the REF index that corresponds to an external sheet name * (or range). If it doesn't exist yet add it to the workbook's references @@ -750,8 +781,7 @@ class Parser */ private function getRefIndex($ext_ref) { - $ext_ref = preg_replace("/^'/", '', $ext_ref); // Remove leading ' if any. - $ext_ref = preg_replace("/'$/", '', $ext_ref); // Remove trailing ' if any. + $ext_ref = (string) preg_replace(["/^'/", "/'$/"], ['', ''], $ext_ref); // Remove leading and trailing ' if any. $ext_ref = str_replace('\'\'', '\'', $ext_ref); // Replace escaped '' with ' // Check if there is a sheet range eg., Sheet1:Sheet2. @@ -823,12 +853,12 @@ class Parser * called by the addWorksheet() method of the * \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook class. * - * @see \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook::addWorksheet() - * * @param string $name The name of the worksheet being added * @param int $index The index of the worksheet being added + * + * @see \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook::addWorksheet() */ - public function setExtSheet($name, $index) + public function setExtSheet($name, $index): void { $this->externalSheets[$name] = $index; } @@ -885,7 +915,7 @@ class Parser $col2 = 65535; // FIXME: maximum possible value for Excel 5 (change this!!!) // FIXME: this changes for BIFF8 - if (($row1 >= 65536) or ($row2 >= 65536)) { + if (($row1 >= 65536) || ($row2 >= 65536)) { throw new WriterException("Row in: $range greater than 65536 "); } @@ -924,7 +954,7 @@ class Parser $col = 0; $col_ref_length = strlen($col_ref); for ($i = 0; $i < $col_ref_length; ++$i) { - $col += (ord($col_ref[$i]) - 64) * pow(26, $expn); + $col += (ord($col_ref[$i]) - 64) * 26 ** $expn; --$expn; } @@ -938,8 +968,9 @@ class Parser /** * Advance to the next valid token. */ - private function advance() + private function advance(): void { + $token = ''; $i = $this->currentCharacter; $formula_length = strlen($this->formula); // eat up white spaces @@ -967,7 +998,7 @@ class Parser $this->currentCharacter = $i + 1; $this->currentToken = $token; - return 1; + return; } if ($i < ($formula_length - 2)) { @@ -1007,7 +1038,6 @@ class Parser case '%': return $token; - break; case '>': if ($this->lookAhead === '=') { // it's a GE token break; @@ -1015,47 +1045,47 @@ class Parser return $token; - break; case '<': // it's a LE or a NE token - if (($this->lookAhead === '=') or ($this->lookAhead === '>')) { + if (($this->lookAhead === '=') || ($this->lookAhead === '>')) { break; } return $token; - break; default: // if it's a reference A1 or $A$1 or $A1 or A$1 - if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?\d+$/', $token) and !preg_match('/\d/', $this->lookAhead) and ($this->lookAhead !== ':') and ($this->lookAhead !== '.') and ($this->lookAhead !== '!')) { + if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?\d+$/', $token) && !preg_match('/\d/', $this->lookAhead) && ($this->lookAhead !== ':') && ($this->lookAhead !== '.') && ($this->lookAhead !== '!')) { return $token; - } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . '(\\:' . self::REGEX_SHEET_TITLE_UNQUOTED . ')?\\!\$?[A-Ia-i]?[A-Za-z]\$?\\d+$/u', $token) and !preg_match('/\d/', $this->lookAhead) and ($this->lookAhead !== ':') and ($this->lookAhead !== '.')) { + } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . '(\\:' . self::REGEX_SHEET_TITLE_UNQUOTED . ')?\\!\$?[A-Ia-i]?[A-Za-z]\$?\\d+$/u', $token) && !preg_match('/\d/', $this->lookAhead) && ($this->lookAhead !== ':') && ($this->lookAhead !== '.')) { // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1 or Sheet1!$A$1 or Sheet1:Sheet2!$A$1) return $token; - } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?[A-Ia-i]?[A-Za-z]\\$?\\d+$/u", $token) and !preg_match('/\d/', $this->lookAhead) and ($this->lookAhead !== ':') and ($this->lookAhead !== '.')) { + } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?[A-Ia-i]?[A-Za-z]\\$?\\d+$/u", $token) && !preg_match('/\d/', $this->lookAhead) && ($this->lookAhead !== ':') && ($this->lookAhead !== '.')) { // If it's an external reference ('Sheet1'!A1 or 'Sheet1:Sheet2'!A1 or 'Sheet1'!$A$1 or 'Sheet1:Sheet2'!$A$1) return $token; } elseif (preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+:(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+$/', $token) && !preg_match('/\d/', $this->lookAhead)) { // if it's a range A1:A2 or $A$1:$A$2 return $token; - } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . '(\\:' . self::REGEX_SHEET_TITLE_UNQUOTED . ')?\\!\$?([A-Ia-i]?[A-Za-z])?\$?\\d+:\$?([A-Ia-i]?[A-Za-z])?\$?\\d+$/u', $token) and !preg_match('/\d/', $this->lookAhead)) { + } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . '(\\:' . self::REGEX_SHEET_TITLE_UNQUOTED . ')?\\!\$?([A-Ia-i]?[A-Za-z])?\$?\\d+:\$?([A-Ia-i]?[A-Za-z])?\$?\\d+$/u', $token) && !preg_match('/\d/', $this->lookAhead)) { // If it's an external range like Sheet1!A1:B2 or Sheet1:Sheet2!A1:B2 or Sheet1!$A$1:$B$2 or Sheet1:Sheet2!$A$1:$B$2 return $token; - } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?([A-Ia-i]?[A-Za-z])?\\$?\\d+:\\$?([A-Ia-i]?[A-Za-z])?\\$?\\d+$/u", $token) and !preg_match('/\d/', $this->lookAhead)) { + } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?([A-Ia-i]?[A-Za-z])?\\$?\\d+:\\$?([A-Ia-i]?[A-Za-z])?\\$?\\d+$/u", $token) && !preg_match('/\d/', $this->lookAhead)) { // If it's an external range like 'Sheet1'!A1:B2 or 'Sheet1:Sheet2'!A1:B2 or 'Sheet1'!$A$1:$B$2 or 'Sheet1:Sheet2'!$A$1:$B$2 return $token; - } elseif (is_numeric($token) and (!is_numeric($token . $this->lookAhead) or ($this->lookAhead == '')) and ($this->lookAhead !== '!') and ($this->lookAhead !== ':')) { + } elseif (is_numeric($token) && (!is_numeric($token . $this->lookAhead) || ($this->lookAhead == '')) && ($this->lookAhead !== '!') && ($this->lookAhead !== ':')) { // If it's a number (check that it's not a sheet name or range) return $token; - } elseif (preg_match('/"([^"]|""){0,255}"/', $token) and $this->lookAhead !== '"' and (substr_count($token, '"') % 2 == 0)) { + } elseif (preg_match('/"([^"]|""){0,255}"/', $token) && $this->lookAhead !== '"' && (substr_count($token, '"') % 2 == 0)) { // If it's a string (of maximum 255 characters) return $token; - } elseif (preg_match('/^#[A-Z0\\/]{3,5}[!?]{1}$/', $token) or $token === '#N/A') { + } elseif (preg_match('/^#[A-Z0\\/]{3,5}[!?]{1}$/', $token) || $token === '#N/A') { // If it's an error code return $token; - } elseif (preg_match("/^[A-Z0-9\xc0-\xdc\\.]+$/i", $token) and ($this->lookAhead === '(')) { + } elseif (preg_match("/^[A-Z0-9\xc0-\xdc\\.]+$/i", $token) && ($this->lookAhead === '(')) { // if it's a function call return $token; + } elseif (preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/miu', $token) && $this->spreadsheet->getDefinedName($token) !== null) { + return $token; } elseif (substr($token, -1) === ')') { // It's an argument of some description (e.g. a named range), // precise nature yet to be determined @@ -1077,7 +1107,7 @@ class Parser public function parse($formula) { $this->currentCharacter = 0; - $this->formula = $formula; + $this->formula = (string) $formula; $this->lookAhead = $formula[1] ?? ''; $this->advance(); $this->parseTree = $this->condition(); @@ -1118,10 +1148,6 @@ class Parser $this->advance(); $result2 = $this->expression(); $result = $this->createTree('ptgNE', $result, $result2); - } elseif ($this->currentToken == '&') { - $this->advance(); - $result2 = $this->expression(); - $result = $this->createTree('ptgConcat', $result, $result2); } return $result; @@ -1151,7 +1177,7 @@ class Parser return $result; // If it's an error code - } elseif (preg_match('/^#[A-Z0\\/]{3,5}[!?]{1}$/', $this->currentToken) or $this->currentToken == '#N/A') { + } elseif (preg_match('/^#[A-Z0\\/]{3,5}[!?]{1}$/', $this->currentToken) || $this->currentToken == '#N/A') { $result = $this->createTree($this->currentToken, 'ptgErr', ''); $this->advance(); @@ -1161,22 +1187,27 @@ class Parser // catch "-" Term $this->advance(); $result2 = $this->expression(); - $result = $this->createTree('ptgUminus', $result2, ''); - return $result; + return $this->createTree('ptgUminus', $result2, ''); // If it's a positive value } elseif ($this->currentToken == '+') { // catch "+" Term $this->advance(); $result2 = $this->expression(); - $result = $this->createTree('ptgUplus', $result2, ''); - return $result; + return $this->createTree('ptgUplus', $result2, ''); } $result = $this->term(); - while (($this->currentToken == '+') or - ($this->currentToken == '-') or - ($this->currentToken == '^')) { + while ($this->currentToken === '&') { + $this->advance(); + $result2 = $this->expression(); + $result = $this->createTree('ptgConcat', $result, $result2); + } + while ( + ($this->currentToken == '+') || + ($this->currentToken == '-') || + ($this->currentToken == '^') + ) { if ($this->currentToken == '+') { $this->advance(); $result2 = $this->term(); @@ -1199,15 +1230,13 @@ class Parser * This function just introduces a ptgParen element in the tree, so that Excel * doesn't get confused when working with a parenthesized formula afterwards. * - * @see fact() - * * @return array The parsed ptg'd tree + * + * @see fact() */ private function parenthesizedExpression() { - $result = $this->createTree('ptgParen', $this->expression(), ''); - - return $result; + return $this->createTree('ptgParen', $this->expression(), ''); } /** @@ -1219,8 +1248,10 @@ class Parser private function term() { $result = $this->fact(); - while (($this->currentToken == '*') or - ($this->currentToken == '/')) { + while ( + ($this->currentToken == '*') || + ($this->currentToken == '/') + ) { if ($this->currentToken == '*') { $this->advance(); $result2 = $this->fact(); @@ -1247,13 +1278,15 @@ class Parser */ private function fact() { - if ($this->currentToken === '(') { + $currentToken = $this->currentToken; + if ($currentToken === '(') { $this->advance(); // eat the "(" $result = $this->parenthesizedExpression(); if ($this->currentToken !== ')') { throw new WriterException("')' token expected."); } $this->advance(); // eat the ")" + return $result; } // if it's a reference @@ -1274,8 +1307,10 @@ class Parser $this->advance(); return $result; - } elseif (preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+:(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+$/', $this->currentToken) or - preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+\.\.(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+$/', $this->currentToken)) { + } elseif ( + preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+:(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+$/', $this->currentToken) || + preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+\.\.(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+$/', $this->currentToken) + ) { // if it's a range A1:B2 or $A$1:$B$2 // must be an error? $result = $this->createTree($this->currentToken, '', ''); @@ -1307,9 +1342,12 @@ class Parser $this->advance(); return $result; - } elseif (preg_match("/^[A-Z0-9\xc0-\xdc\\.]+$/i", $this->currentToken)) { + } elseif (preg_match("/^[A-Z0-9\xc0-\xdc\\.]+$/i", $this->currentToken) && ($this->lookAhead === '(')) { // if it's a function call - $result = $this->func(); + return $this->func(); + } elseif (preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/miu', $this->currentToken) && $this->spreadsheet->getDefinedName($this->currentToken) !== null) { + $result = $this->createTree('ptgName', $this->currentToken, ''); + $this->advance(); return $result; } @@ -1350,12 +1388,13 @@ class Parser } $args = $this->functions[$function][1]; // If fixed number of args eg. TIME($i, $j, $k). Check that the number of args is valid. - if (($args >= 0) and ($args != $num_args)) { + if (($args >= 0) && ($args != $num_args)) { throw new WriterException("Incorrect number of arguments in function $function() "); } $result = $this->createTree($function, $result, $num_args); $this->advance(); // eat the ")" + return $result; } @@ -1407,6 +1446,9 @@ class Parser if (empty($tree)) { // If it's the first call use parseTree $tree = $this->parseTree; } + if (!is_array($tree) || !isset($tree['left'], $tree['right'], $tree['value'])) { + throw new WriterException('Unexpected non-array'); + } if (is_array($tree['left'])) { $converted_tree = $this->toReversePolish($tree['left']); @@ -1423,24 +1465,25 @@ class Parser $polish .= $converted_tree; } // if it's a function convert it here (so we can set it's arguments) - if (preg_match("/^[A-Z0-9\xc0-\xdc\\.]+$/", $tree['value']) and - !preg_match('/^([A-Ia-i]?[A-Za-z])(\d+)$/', $tree['value']) and - !preg_match('/^[A-Ia-i]?[A-Za-z](\\d+)\\.\\.[A-Ia-i]?[A-Za-z](\\d+)$/', $tree['value']) and - !is_numeric($tree['value']) and - !isset($this->ptg[$tree['value']])) { + if ( + preg_match("/^[A-Z0-9\xc0-\xdc\\.]+$/", $tree['value']) && + !preg_match('/^([A-Ia-i]?[A-Za-z])(\d+)$/', $tree['value']) && + !preg_match('/^[A-Ia-i]?[A-Za-z](\\d+)\\.\\.[A-Ia-i]?[A-Za-z](\\d+)$/', $tree['value']) && + !is_numeric($tree['value']) && + !isset($this->ptg[$tree['value']]) + ) { // left subtree for a function is always an array. if ($tree['left'] != '') { $left_tree = $this->toReversePolish($tree['left']); } else { $left_tree = ''; } - // add it's left subtree and return. + + // add its left subtree and return. return $left_tree . $this->convertFunction($tree['value'], $tree['right']); } $converted_tree = $this->convert($tree['value']); - $polish .= $converted_tree; - - return $polish; + return $polish . $converted_tree; } } diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xls/Style/CellAlignment.php b/PhpOffice/PhpSpreadsheet/Writer/Xls/Style/CellAlignment.php new file mode 100644 index 0000000..711d88d --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Writer/Xls/Style/CellAlignment.php @@ -0,0 +1,59 @@ + + */ + private static $horizontalMap = [ + Alignment::HORIZONTAL_GENERAL => 0, + Alignment::HORIZONTAL_LEFT => 1, + Alignment::HORIZONTAL_RIGHT => 3, + Alignment::HORIZONTAL_CENTER => 2, + Alignment::HORIZONTAL_CENTER_CONTINUOUS => 6, + Alignment::HORIZONTAL_JUSTIFY => 5, + ]; + + /** + * @var array + */ + private static $verticalMap = [ + Alignment::VERTICAL_BOTTOM => 2, + Alignment::VERTICAL_TOP => 0, + Alignment::VERTICAL_CENTER => 1, + Alignment::VERTICAL_JUSTIFY => 3, + ]; + + public static function horizontal(Alignment $alignment): int + { + $horizontalAlignment = $alignment->getHorizontal(); + + if (is_string($horizontalAlignment) && array_key_exists($horizontalAlignment, self::$horizontalMap)) { + return self::$horizontalMap[$horizontalAlignment]; + } + + return self::$horizontalMap[Alignment::HORIZONTAL_GENERAL]; + } + + public static function wrap(Alignment $alignment): int + { + $wrap = $alignment->getWrapText(); + + return ($wrap === true) ? 1 : 0; + } + + public static function vertical(Alignment $alignment): int + { + $verticalAlignment = $alignment->getVertical(); + + if (is_string($verticalAlignment) && array_key_exists($verticalAlignment, self::$verticalMap)) { + return self::$verticalMap[$verticalAlignment]; + } + + return self::$verticalMap[Alignment::VERTICAL_BOTTOM]; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xls/Style/CellBorder.php b/PhpOffice/PhpSpreadsheet/Writer/Xls/Style/CellBorder.php new file mode 100644 index 0000000..8d47d6a --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Writer/Xls/Style/CellBorder.php @@ -0,0 +1,39 @@ + + */ + protected static $styleMap = [ + Border::BORDER_NONE => 0x00, + Border::BORDER_THIN => 0x01, + Border::BORDER_MEDIUM => 0x02, + Border::BORDER_DASHED => 0x03, + Border::BORDER_DOTTED => 0x04, + Border::BORDER_THICK => 0x05, + Border::BORDER_DOUBLE => 0x06, + Border::BORDER_HAIR => 0x07, + Border::BORDER_MEDIUMDASHED => 0x08, + Border::BORDER_DASHDOT => 0x09, + Border::BORDER_MEDIUMDASHDOT => 0x0A, + Border::BORDER_DASHDOTDOT => 0x0B, + Border::BORDER_MEDIUMDASHDOTDOT => 0x0C, + Border::BORDER_SLANTDASHDOT => 0x0D, + ]; + + public static function style(Border $border): int + { + $borderStyle = $border->getBorderStyle(); + + if (is_string($borderStyle) && array_key_exists($borderStyle, self::$styleMap)) { + return self::$styleMap[$borderStyle]; + } + + return self::$styleMap[Border::BORDER_NONE]; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xls/Style/CellFill.php b/PhpOffice/PhpSpreadsheet/Writer/Xls/Style/CellFill.php new file mode 100644 index 0000000..f5a8c47 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Writer/Xls/Style/CellFill.php @@ -0,0 +1,46 @@ + + */ + protected static $fillStyleMap = [ + Fill::FILL_NONE => 0x00, + Fill::FILL_SOLID => 0x01, + Fill::FILL_PATTERN_MEDIUMGRAY => 0x02, + Fill::FILL_PATTERN_DARKGRAY => 0x03, + Fill::FILL_PATTERN_LIGHTGRAY => 0x04, + Fill::FILL_PATTERN_DARKHORIZONTAL => 0x05, + Fill::FILL_PATTERN_DARKVERTICAL => 0x06, + Fill::FILL_PATTERN_DARKDOWN => 0x07, + Fill::FILL_PATTERN_DARKUP => 0x08, + Fill::FILL_PATTERN_DARKGRID => 0x09, + Fill::FILL_PATTERN_DARKTRELLIS => 0x0A, + Fill::FILL_PATTERN_LIGHTHORIZONTAL => 0x0B, + Fill::FILL_PATTERN_LIGHTVERTICAL => 0x0C, + Fill::FILL_PATTERN_LIGHTDOWN => 0x0D, + Fill::FILL_PATTERN_LIGHTUP => 0x0E, + Fill::FILL_PATTERN_LIGHTGRID => 0x0F, + Fill::FILL_PATTERN_LIGHTTRELLIS => 0x10, + Fill::FILL_PATTERN_GRAY125 => 0x11, + Fill::FILL_PATTERN_GRAY0625 => 0x12, + Fill::FILL_GRADIENT_LINEAR => 0x00, // does not exist in BIFF8 + Fill::FILL_GRADIENT_PATH => 0x00, // does not exist in BIFF8 + ]; + + public static function style(Fill $fill): int + { + $fillStyle = $fill->getFillType(); + + if (is_string($fillStyle) && array_key_exists($fillStyle, self::$fillStyleMap)) { + return self::$fillStyleMap[$fillStyle]; + } + + return self::$fillStyleMap[Fill::FILL_NONE]; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xls/Style/ColorMap.php b/PhpOffice/PhpSpreadsheet/Writer/Xls/Style/ColorMap.php new file mode 100644 index 0000000..caf85c0 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Writer/Xls/Style/ColorMap.php @@ -0,0 +1,90 @@ + + */ + private static $colorMap = [ + '#000000' => 0x08, + '#FFFFFF' => 0x09, + '#FF0000' => 0x0A, + '#00FF00' => 0x0B, + '#0000FF' => 0x0C, + '#FFFF00' => 0x0D, + '#FF00FF' => 0x0E, + '#00FFFF' => 0x0F, + '#800000' => 0x10, + '#008000' => 0x11, + '#000080' => 0x12, + '#808000' => 0x13, + '#800080' => 0x14, + '#008080' => 0x15, + '#C0C0C0' => 0x16, + '#808080' => 0x17, + '#9999FF' => 0x18, + '#993366' => 0x19, + '#FFFFCC' => 0x1A, + '#CCFFFF' => 0x1B, + '#660066' => 0x1C, + '#FF8080' => 0x1D, + '#0066CC' => 0x1E, + '#CCCCFF' => 0x1F, + // '#000080' => 0x20, + // '#FF00FF' => 0x21, + // '#FFFF00' => 0x22, + // '#00FFFF' => 0x23, + // '#800080' => 0x24, + // '#800000' => 0x25, + // '#008080' => 0x26, + // '#0000FF' => 0x27, + '#00CCFF' => 0x28, + // '#CCFFFF' => 0x29, + '#CCFFCC' => 0x2A, + '#FFFF99' => 0x2B, + '#99CCFF' => 0x2C, + '#FF99CC' => 0x2D, + '#CC99FF' => 0x2E, + '#FFCC99' => 0x2F, + '#3366FF' => 0x30, + '#33CCCC' => 0x31, + '#99CC00' => 0x32, + '#FFCC00' => 0x33, + '#FF9900' => 0x34, + '#FF6600' => 0x35, + '#666699' => 0x36, + '#969696' => 0x37, + '#003366' => 0x38, + '#339966' => 0x39, + '#003300' => 0x3A, + '#333300' => 0x3B, + '#993300' => 0x3C, + // '#993366' => 0x3D, + '#333399' => 0x3E, + '#333333' => 0x3F, + ]; + + public static function lookup(Color $color, int $defaultIndex = 0x00): int + { + $colorRgb = $color->getRGB(); + if (is_string($colorRgb) && array_key_exists("#{$colorRgb}", self::$colorMap)) { + return self::$colorMap["#{$colorRgb}"]; + } + +// TODO Try and map RGB value to nearest colour within the define pallette +// $red = Color::getRed($colorRgb, false); +// $green = Color::getGreen($colorRgb, false); +// $blue = Color::getBlue($colorRgb, false); + +// $paletteSpace = 3; +// $newColor = ($red * $paletteSpace / 256) * ($paletteSpace * $paletteSpace) + +// ($green * $paletteSpace / 256) * $paletteSpace + +// ($blue * $paletteSpace / 256); + + return $defaultIndex; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xls/Workbook.php b/PhpOffice/PhpSpreadsheet/Writer/Xls/Workbook.php old mode 100755 new mode 100644 index 41c8e64..fa7349d --- a/PhpOffice/PhpSpreadsheet/Writer/Xls/Workbook.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Xls/Workbook.php @@ -2,7 +2,9 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Xls; +use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; +use PhpOffice\PhpSpreadsheet\DefinedName; use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; use PhpOffice\PhpSpreadsheet\Shared\Date; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; @@ -167,10 +169,13 @@ class Workbook extends BIFFwriter /** * Escher object corresponding to MSODRAWINGGROUP. * - * @var \PhpOffice\PhpSpreadsheet\Shared\Escher + * @var null|\PhpOffice\PhpSpreadsheet\Shared\Escher */ private $escher; + /** @var mixed */ + private static $scrutinizerFalse = false; + /** * Class constructor. * @@ -222,7 +227,6 @@ class Workbook extends BIFFwriter /** * Add a new XF writer. * - * @param Style $style * @param bool $isStyleXf Is it a style XF? * * @return int Index to XF record @@ -248,7 +252,7 @@ class Workbook extends BIFFwriter $xfWriter->setDiagColor($this->addColor($style->getBorders()->getDiagonal()->getColor()->getRGB())); // Add the number format if it is not a built-in one and not already added - if ($style->getNumberFormat()->getBuiltInFormatCode() === false) { + if ($style->getNumberFormat()->getBuiltInFormatCode() === self::$scrutinizerFalse) { $numberFormatHashCode = $style->getNumberFormat()->getHashCode(); if (isset($this->addedNumberFormats[$numberFormatHashCode])) { @@ -273,8 +277,6 @@ class Workbook extends BIFFwriter /** * Add a font to added fonts. * - * @param \PhpOffice\PhpSpreadsheet\Style\Font $font - * * @return int Index to FONT record */ public function addFont(\PhpOffice\PhpSpreadsheet\Style\Font $font) @@ -343,7 +345,7 @@ class Workbook extends BIFFwriter /** * Sets the colour palette to the Excel 97+ default. */ - private function setPaletteXl97() + private function setPaletteXl97(): void { $this->palette = [ 0x08 => [0x00, 0x00, 0x00, 0x00], @@ -409,13 +411,13 @@ class Workbook extends BIFFwriter * Assemble worksheets into a workbook and send the BIFF data to an OLE * storage. * - * @param array $pWorksheetSizes The sizes in bytes of the binary worksheet streams + * @param array $worksheetSizes The sizes in bytes of the binary worksheet streams * * @return string Binary data for workbook stream */ - public function writeWorkbook(array $pWorksheetSizes) + public function writeWorkbook(array $worksheetSizes) { - $this->worksheetSizes = $pWorksheetSizes; + $this->worksheetSizes = $worksheetSizes; // Calculate the number of selected worksheet tabs and call the finalization // methods for each worksheet @@ -465,7 +467,7 @@ class Workbook extends BIFFwriter /** * Calculate offsets for Worksheet BOF records. */ - private function calcSheetOffsets() + private function calcSheetOffsets(): void { $boundsheet_length = 10; // fixed length for a BOUNDSHEET record @@ -489,7 +491,7 @@ class Workbook extends BIFFwriter /** * Store the Excel FONT records. */ - private function writeAllFonts() + private function writeAllFonts(): void { foreach ($this->fontWriters as $fontWriter) { $this->append($fontWriter->writeFont()); @@ -499,7 +501,7 @@ class Workbook extends BIFFwriter /** * Store user defined numerical formats i.e. FORMAT records. */ - private function writeAllNumberFormats() + private function writeAllNumberFormats(): void { foreach ($this->numberFormats as $numberFormatIndex => $numberFormat) { $this->writeNumberFormat($numberFormat->getFormatCode(), $numberFormatIndex); @@ -509,7 +511,7 @@ class Workbook extends BIFFwriter /** * Write all XF records. */ - private function writeAllXfs() + private function writeAllXfs(): void { foreach ($this->xfWriters as $xfWriter) { $this->append($xfWriter->writeXf()); @@ -519,11 +521,62 @@ class Workbook extends BIFFwriter /** * Write all STYLE records. */ - private function writeAllStyles() + private function writeAllStyles(): void { $this->writeStyle(); } + private function parseDefinedNameValue(DefinedName $definedName): string + { + $definedRange = $definedName->getValue(); + $splitCount = preg_match_all( + '/' . Calculation::CALCULATION_REGEXP_CELLREF . '/mui', + $definedRange, + $splitRanges, + PREG_OFFSET_CAPTURE + ); + + $lengths = array_map('strlen', array_column($splitRanges[0], 0)); + $offsets = array_column($splitRanges[0], 1); + + $worksheets = $splitRanges[2]; + $columns = $splitRanges[6]; + $rows = $splitRanges[7]; + + while ($splitCount > 0) { + --$splitCount; + $length = $lengths[$splitCount]; + $offset = $offsets[$splitCount]; + $worksheet = $worksheets[$splitCount][0]; + $column = $columns[$splitCount][0]; + $row = $rows[$splitCount][0]; + + $newRange = ''; + if (empty($worksheet)) { + if (($offset === 0) || ($definedRange[$offset - 1] !== ':')) { + // We should have a worksheet + $worksheet = $definedName->getWorksheet() ? $definedName->getWorksheet()->getTitle() : null; + } + } else { + $worksheet = str_replace("''", "'", trim($worksheet, "'")); + } + if (!empty($worksheet)) { + $newRange = "'" . str_replace("'", "''", $worksheet) . "'!"; + } + + if (!empty($column)) { + $newRange .= "\${$column}"; + } + if (!empty($row)) { + $newRange .= "\${$row}"; + } + + $definedRange = substr($definedRange, 0, $offset) . $newRange . substr($definedRange, $offset + $length); + } + + return $definedRange; + } + /** * Writes all the DEFINEDNAME records (BIFF8). * So far this is only used for repeating rows/columns (print titles) and print areas. @@ -533,39 +586,34 @@ class Workbook extends BIFFwriter $chunk = ''; // Named ranges - if (count($this->spreadsheet->getNamedRanges()) > 0) { + $definedNames = $this->spreadsheet->getDefinedNames(); + if (count($definedNames) > 0) { // Loop named ranges - $namedRanges = $this->spreadsheet->getNamedRanges(); - foreach ($namedRanges as $namedRange) { - // Create absolute coordinate - $range = Coordinate::splitRange($namedRange->getRange()); - $iMax = count($range); - for ($i = 0; $i < $iMax; ++$i) { - $range[$i][0] = '\'' . str_replace("'", "''", $namedRange->getWorksheet()->getTitle()) . '\'!' . Coordinate::absoluteCoordinate($range[$i][0]); - if (isset($range[$i][1])) { - $range[$i][1] = Coordinate::absoluteCoordinate($range[$i][1]); - } - } - $range = Coordinate::buildRange($range); // e.g. Sheet1!$A$1:$B$2 + foreach ($definedNames as $definedName) { + $range = $this->parseDefinedNameValue($definedName); // parse formula try { - $error = $this->parser->parse($range); + $this->parser->parse($range); $formulaData = $this->parser->toReversePolish(); // make sure tRef3d is of type tRef3dR (0x3A) - if (isset($formulaData[0]) and ($formulaData[0] == "\x7A" or $formulaData[0] == "\x5A")) { + if (isset($formulaData[0]) && ($formulaData[0] == "\x7A" || $formulaData[0] == "\x5A")) { $formulaData = "\x3A" . substr($formulaData, 1); } - if ($namedRange->getLocalOnly()) { - // local scope - $scope = $this->spreadsheet->getIndex($namedRange->getScope()) + 1; + if ($definedName->getLocalOnly()) { + /** + * local scope. + * + * @phpstan-ignore-next-line + */ + $scope = $this->spreadsheet->getIndex($definedName->getScope()) + 1; } else { // global scope $scope = 0; } - $chunk .= $this->writeData($this->writeDefinedNameBiff8($namedRange->getName(), $formulaData, $scope, false)); + $chunk .= $this->writeData($this->writeDefinedNameBiff8($definedName->getName(), $formulaData, $scope, false)); } catch (PhpSpreadsheetException $e) { // do nothing } @@ -637,13 +685,13 @@ class Workbook extends BIFFwriter $formulaData = ''; for ($j = 0; $j < $countPrintArea; ++$j) { $printAreaRect = $printArea[$j]; // e.g. A3:J6 - $printAreaRect[0] = Coordinate::coordinateFromString($printAreaRect[0]); - $printAreaRect[1] = Coordinate::coordinateFromString($printAreaRect[1]); + $printAreaRect[0] = Coordinate::indexesFromString($printAreaRect[0]); + $printAreaRect[1] = Coordinate::indexesFromString($printAreaRect[1]); $print_rowmin = $printAreaRect[0][1] - 1; $print_rowmax = $printAreaRect[1][1] - 1; - $print_colmin = Coordinate::columnIndexFromString($printAreaRect[0][0]) - 1; - $print_colmax = Coordinate::columnIndexFromString($printAreaRect[1][0]) - 1; + $print_colmin = $printAreaRect[0][0] - 1; + $print_colmax = $printAreaRect[1][0] - 1; // construct formula data manually because parser does not recognize absolute 3d cell references $formulaData .= pack('Cvvvvv', 0x3B, $i, $print_rowmin, $print_rowmax, $print_colmin, $print_colmax); @@ -715,8 +763,8 @@ class Workbook extends BIFFwriter * Write a short NAME record. * * @param string $name - * @param string $sheetIndex 1-based sheet index the defined name applies to. 0 = global - * @param integer[][] $rangeBounds range boundaries + * @param int $sheetIndex 1-based sheet index the defined name applies to. 0 = global + * @param int[][] $rangeBounds range boundaries * @param bool $isHidden * * @return string Complete binary record data @@ -754,7 +802,7 @@ class Workbook extends BIFFwriter /** * Stores the CODEPAGE biff record. */ - private function writeCodepage() + private function writeCodepage(): void { $record = 0x0042; // Record identifier $length = 0x0002; // Number of bytes to follow @@ -769,7 +817,7 @@ class Workbook extends BIFFwriter /** * Write Excel BIFF WINDOW1 record. */ - private function writeWindow1() + private function writeWindow1(): void { $record = 0x003D; // Record identifier $length = 0x0012; // Number of bytes to follow @@ -798,10 +846,9 @@ class Workbook extends BIFFwriter /** * Writes Excel BIFF BOUNDSHEET record. * - * @param Worksheet $sheet Worksheet name * @param int $offset Location of worksheet BOF */ - private function writeBoundSheet($sheet, $offset) + private function writeBoundSheet(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $sheet, $offset): void { $sheetname = $sheet->getTitle(); $record = 0x0085; // Record identifier @@ -829,7 +876,7 @@ class Workbook extends BIFFwriter // sheet type $st = 0x00; - $grbit = 0x0000; // Visibility and sheet type + //$grbit = 0x0000; // Visibility and sheet type $data = pack('VCC', $offset, $ss, $st); $data .= StringHelper::UTF8toBIFF8UnicodeShort($sheetname); @@ -863,7 +910,7 @@ class Workbook extends BIFFwriter $record = 0x0017; // Record identifier $length = 2 + 6 * $totalReferences; // Number of bytes to follow - $supbook_index = 0; // FIXME: only using internal SUPBOOK record + //$supbook_index = 0; // FIXME: only using internal SUPBOOK record $header = pack('vv', $record, $length); $data = pack('v', $totalReferences); for ($i = 0; $i < $totalReferences; ++$i) { @@ -876,7 +923,7 @@ class Workbook extends BIFFwriter /** * Write Excel BIFF STYLE records. */ - private function writeStyle() + private function writeStyle(): void { $record = 0x0293; // Record identifier $length = 0x0004; // Bytes to follow @@ -896,7 +943,7 @@ class Workbook extends BIFFwriter * @param string $format Custom format string * @param int $ifmt Format index code */ - private function writeNumberFormat($format, $ifmt) + private function writeNumberFormat($format, $ifmt): void { $record = 0x041E; // Record identifier @@ -911,7 +958,7 @@ class Workbook extends BIFFwriter /** * Write DATEMODE record to indicate the date system in use (1904 or 1900). */ - private function writeDateMode() + private function writeDateMode(): void { $record = 0x0022; // Record identifier $length = 0x0002; // Bytes to follow @@ -963,7 +1010,7 @@ class Workbook extends BIFFwriter /** * Stores the PALETTE biff record. */ - private function writePalette() + private function writePalette(): void { $aref = $this->palette; @@ -1130,21 +1177,17 @@ class Workbook extends BIFFwriter /** * Get Escher object. - * - * @return \PhpOffice\PhpSpreadsheet\Shared\Escher */ - public function getEscher() + public function getEscher(): ?\PhpOffice\PhpSpreadsheet\Shared\Escher { return $this->escher; } /** * Set Escher object. - * - * @param \PhpOffice\PhpSpreadsheet\Shared\Escher $pValue */ - public function setEscher(\PhpOffice\PhpSpreadsheet\Shared\Escher $pValue = null) + public function setEscher(?\PhpOffice\PhpSpreadsheet\Shared\Escher $escher): void { - $this->escher = $pValue; + $this->escher = $escher; } } diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xls/Worksheet.php b/PhpOffice/PhpSpreadsheet/Writer/Xls/Worksheet.php old mode 100755 new mode 100644 index d8822d8..1657eca --- a/PhpOffice/PhpSpreadsheet/Writer/Xls/Worksheet.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Xls/Worksheet.php @@ -2,19 +2,18 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Xls; +use GdImage; +use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Cell\DataType; -use PhpOffice\PhpSpreadsheet\Cell\DataValidation; use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException; use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\RichText\Run; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Shared\Xls; -use PhpOffice\PhpSpreadsheet\Style\Alignment; use PhpOffice\PhpSpreadsheet\Style\Border; use PhpOffice\PhpSpreadsheet\Style\Color; use PhpOffice\PhpSpreadsheet\Style\Conditional; -use PhpOffice\PhpSpreadsheet\Style\Fill; use PhpOffice\PhpSpreadsheet\Style\Protection; use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup; use PhpOffice\PhpSpreadsheet\Worksheet\SheetView; @@ -56,6 +55,12 @@ use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException; // */ class Worksheet extends BIFFwriter { + /** @var int */ + private static $always0 = 0; + + /** @var int */ + private static $always1 = 1; + /** * Formula parser. * @@ -94,7 +99,7 @@ class Worksheet extends BIFFwriter /** * Whether to use outline. * - * @var int + * @var bool */ private $outlineOn; @@ -190,7 +195,7 @@ class Worksheet extends BIFFwriter /** * Escher object corresponding to MSODRAWING. * - * @var \PhpOffice\PhpSpreadsheet\Shared\Escher + * @var null|\PhpOffice\PhpSpreadsheet\Shared\Escher */ private $escher; @@ -216,8 +221,8 @@ class Worksheet extends BIFFwriter * * @param int $str_total Total number of strings * @param int $str_unique Total number of unique strings - * @param array &$str_table String Table - * @param array &$colors Colour Table + * @param array $str_table String Table + * @param array $colors Colour Table * @param Parser $parser The formula parser created for the Workbook * @param bool $preCalculateFormulas Flag indicating whether formulas should be calculated or just written * @param \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $phpSheet The worksheet to write @@ -243,10 +248,10 @@ class Worksheet extends BIFFwriter $this->printHeaders = 0; - $this->outlineStyle = 0; - $this->outlineBelow = 1; - $this->outlineRight = 1; - $this->outlineOn = 1; + $this->outlineStyle = false; + $this->outlineBelow = true; + $this->outlineRight = true; + $this->outlineOn = true; $this->fontHashIndex = []; @@ -258,12 +263,12 @@ class Worksheet extends BIFFwriter $maxC = $this->phpSheet->getHighestColumn(); // Determine lowest and highest column and row + $this->firstRowIndex = $minR; $this->lastRowIndex = ($maxR > 65535) ? 65535 : $maxR; $this->firstColumnIndex = Coordinate::columnIndexFromString($minC); $this->lastColumnIndex = Coordinate::columnIndexFromString($maxC); -// if ($this->firstColumnIndex > 255) $this->firstColumnIndex = 255; if ($this->lastColumnIndex > 255) { $this->lastColumnIndex = 255; } @@ -277,10 +282,14 @@ class Worksheet extends BIFFwriter * * @see \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook::storeWorkbook() */ - public function close() + public function close(): void { $phpSheet = $this->phpSheet; + // Storing selected cells and active sheet because it changes while parsing cells with formulas. + $selectedCells = $this->phpSheet->getSelectedCells(); + $activeSheetIndex = $this->phpSheet->getParent()->getActiveSheetIndex(); + // Write BOF record $this->storeBof(0x0010); @@ -388,12 +397,19 @@ class Worksheet extends BIFFwriter // Row dimensions foreach ($phpSheet->getRowDimensions() as $rowDimension) { $xfIndex = $rowDimension->getXfIndex() + 15; // there are 15 cellXfs - $this->writeRow($rowDimension->getRowIndex() - 1, $rowDimension->getRowHeight(), $xfIndex, ($rowDimension->getVisible() ? '0' : '1'), $rowDimension->getOutlineLevel()); + $this->writeRow( + $rowDimension->getRowIndex() - 1, + (int) $rowDimension->getRowHeight(), + $xfIndex, + !$rowDimension->getVisible(), + $rowDimension->getOutlineLevel() + ); } // Write Cells - foreach ($phpSheet->getCoordinates() as $coordinate) { - $cell = $phpSheet->getCell($coordinate); + foreach ($phpSheet->getCellCollection()->getSortedCoordinates() as $coordinate) { + /** @var Cell $cell */ + $cell = $phpSheet->getCellCollection()->get($coordinate); $row = $cell->getRow() - 1; $column = Coordinate::columnIndexFromString($cell->getColumn()) - 1; @@ -408,7 +424,6 @@ class Worksheet extends BIFFwriter $cVal = $cell->getValue(); if ($cVal instanceof RichText) { $arrcRun = []; - $str_len = StringHelper::countCharacters($cVal->getPlainText(), 'UTF-8'); $str_pos = 0; $elements = $cVal->getRichTextElements(); foreach ($elements as $element) { @@ -426,6 +441,7 @@ class Worksheet extends BIFFwriter } else { switch ($cell->getDatatype()) { case DataType::TYPE_STRING: + case DataType::TYPE_INLINE: case DataType::TYPE_NULL: if ($cVal === '' || $cVal === null) { $this->writeBlank($row, $column, $xfIndex); @@ -441,7 +457,29 @@ class Worksheet extends BIFFwriter case DataType::TYPE_FORMULA: $calculatedValue = $this->preCalculateFormulas ? $cell->getCalculatedValue() : null; - $this->writeFormula($row, $column, $cVal, $xfIndex, $calculatedValue); + if (self::WRITE_FORMULA_EXCEPTION == $this->writeFormula($row, $column, $cVal, $xfIndex, $calculatedValue)) { + if ($calculatedValue === null) { + $calculatedValue = $cell->getCalculatedValue(); + } + $calctype = gettype($calculatedValue); + switch ($calctype) { + case 'integer': + case 'double': + $this->writeNumber($row, $column, (float) $calculatedValue, $xfIndex); + + break; + case 'string': + $this->writeString($row, $column, $calculatedValue, $xfIndex); + + break; + case 'boolean': + $this->writeBoolErr($row, $column, (int) $calculatedValue, 0, $xfIndex); + + break; + default: + $this->writeString($row, $column, $cVal, $xfIndex); + } + } break; case DataType::TYPE_BOOL: @@ -449,7 +487,7 @@ class Worksheet extends BIFFwriter break; case DataType::TYPE_ERROR: - $this->writeBoolErr($row, $column, self::mapErrorCode($cVal), 1, $xfIndex); + $this->writeBoolErr($row, $column, ErrorCode::error($cVal), 1, $xfIndex); break; } @@ -459,6 +497,9 @@ class Worksheet extends BIFFwriter // Append $this->writeMsoDrawing(); + // Restoring active sheet. + $this->phpSheet->getParent()->setActiveSheetIndex($activeSheetIndex); + // Write WINDOW2 record $this->writeWindow2(); @@ -471,6 +512,9 @@ class Worksheet extends BIFFwriter $this->writePanes(); } + // Restoring selected cells. + $this->phpSheet->setSelectedCells($selectedCells); + // Write SELECTION record $this->writeSelection(); @@ -479,7 +523,7 @@ class Worksheet extends BIFFwriter // Hyperlinks foreach ($phpSheet->getHyperLinkCollection() as $coordinate => $hyperlink) { - [$column, $row] = Coordinate::coordinateFromString($coordinate); + [$column, $row] = Coordinate::indexesFromString($coordinate); $url = $hyperlink->getUrl(); @@ -493,7 +537,7 @@ class Worksheet extends BIFFwriter $url = 'external:' . $url; } - $this->writeUrl($row - 1, Coordinate::columnIndexFromString($column) - 1, $url); + $this->writeUrl($row - 1, $column - 1, $url); } $this->writeDataValidity(); @@ -503,30 +547,44 @@ class Worksheet extends BIFFwriter $this->writeSheetProtection(); $this->writeRangeProtection(); - $arrConditionalStyles = $phpSheet->getConditionalStylesCollection(); + // Write Conditional Formatting Rules and Styles + $this->writeConditionalFormatting(); + + $this->storeEof(); + } + + private function writeConditionalFormatting(): void + { + $conditionalFormulaHelper = new ConditionalHelper($this->parser); + + $arrConditionalStyles = $this->phpSheet->getConditionalStylesCollection(); if (!empty($arrConditionalStyles)) { $arrConditional = []; - // @todo CFRule & CFHeader - // Write CFHEADER record - $this->writeCFHeader(); + // Write ConditionalFormattingTable records foreach ($arrConditionalStyles as $cellCoordinate => $conditionalStyles) { + $cfHeaderWritten = false; foreach ($conditionalStyles as $conditional) { - if ($conditional->getConditionType() == Conditional::CONDITION_EXPRESSION - || $conditional->getConditionType() == Conditional::CONDITION_CELLIS) { - if (!isset($arrConditional[$conditional->getHashCode()])) { + /** @var Conditional $conditional */ + if ( + $conditional->getConditionType() === Conditional::CONDITION_EXPRESSION || + $conditional->getConditionType() === Conditional::CONDITION_CELLIS + ) { + // Write CFHEADER record (only if there are Conditional Styles that we are able to write) + if ($cfHeaderWritten === false) { + $cfHeaderWritten = $this->writeCFHeader($cellCoordinate, $conditionalStyles); + } + if ($cfHeaderWritten === true && !isset($arrConditional[$conditional->getHashCode()])) { // This hash code has been handled $arrConditional[$conditional->getHashCode()] = true; // Write CFRULE record - $this->writeCFRule($conditional); + $this->writeCFRule($conditionalFormulaHelper, $conditional, $cellCoordinate); } } } } } - - $this->storeEof(); } /** @@ -552,31 +610,30 @@ class Worksheet extends BIFFwriter $lastCell = $explodes[1]; } - $firstCellCoordinates = Coordinate::coordinateFromString($firstCell); // e.g. [0, 1] - $lastCellCoordinates = Coordinate::coordinateFromString($lastCell); // e.g. [1, 6] + $firstCellCoordinates = Coordinate::indexesFromString($firstCell); // e.g. [0, 1] + $lastCellCoordinates = Coordinate::indexesFromString($lastCell); // e.g. [1, 6] - return pack('vvvv', $firstCellCoordinates[1] - 1, $lastCellCoordinates[1] - 1, Coordinate::columnIndexFromString($firstCellCoordinates[0]) - 1, Coordinate::columnIndexFromString($lastCellCoordinates[0]) - 1); + return pack('vvvv', $firstCellCoordinates[1] - 1, $lastCellCoordinates[1] - 1, $firstCellCoordinates[0] - 1, $lastCellCoordinates[0] - 1); } /** - * Retrieves data from memory in one chunk, or from disk in $buffer + * Retrieves data from memory in one chunk, or from disk * sized chunks. * * @return string The data */ public function getData() { - $buffer = 4096; - // Return data stored in memory if (isset($this->_data)) { $tmp = $this->_data; - unset($this->_data); + $this->_data = null; return $tmp; } + // No data to return - return false; + return ''; } /** @@ -584,7 +641,7 @@ class Worksheet extends BIFFwriter * * @param int $print Whether to print the headers or not. Defaults to 1 (print). */ - public function printRowColHeaders($print = 1) + public function printRowColHeaders($print = 1): void { $this->printHeaders = $print; } @@ -598,17 +655,12 @@ class Worksheet extends BIFFwriter * @param bool $symbols_right * @param bool $auto_style */ - public function setOutline($visible = true, $symbols_below = true, $symbols_right = true, $auto_style = false) + public function setOutline($visible = true, $symbols_below = true, $symbols_right = true, $auto_style = false): void { $this->outlineOn = $visible; $this->outlineBelow = $symbols_below; $this->outlineRight = $symbols_right; $this->outlineStyle = $auto_style; - - // Ensure this is a boolean vale for Window2 - if ($this->outlineOn) { - $this->outlineOn = 1; - } } /** @@ -651,7 +703,7 @@ class Worksheet extends BIFFwriter * @param string $str The string * @param int $xfIndex Index to XF record */ - private function writeString($row, $col, $str, $xfIndex) + private function writeString($row, $col, $str, $xfIndex): void { $this->writeLabelSst($row, $col, $str, $xfIndex); } @@ -666,7 +718,7 @@ class Worksheet extends BIFFwriter * @param int $xfIndex The XF format index for the cell * @param array $arrcRun Index to Font record and characters beginning */ - private function writeRichTextString($row, $col, $str, $xfIndex, $arrcRun) + private function writeRichTextString($row, $col, $str, $xfIndex, $arrcRun): void { $record = 0x00FD; // Record identifier $length = 0x000A; // Bytes to follow @@ -693,7 +745,7 @@ class Worksheet extends BIFFwriter * @param string $str The string to write * @param mixed $xfIndex The XF format index for the cell */ - private function writeLabelSst($row, $col, $str, $xfIndex) + private function writeLabelSst($row, $col, $str, $xfIndex): void { $record = 0x00FD; // Record identifier $length = 0x000A; // Bytes to follow @@ -764,14 +816,20 @@ class Worksheet extends BIFFwriter return 0; } + const WRITE_FORMULA_NORMAL = 0; + const WRITE_FORMULA_ERRORS = -1; + const WRITE_FORMULA_RANGE = -2; + const WRITE_FORMULA_EXCEPTION = -3; + /** * Write a formula to the specified row and column (zero indexed). * The textual representation of the formula is passed to the parser in * Parser.php which returns a packed binary string. * - * Returns 0 : normal termination - * -1 : formula errors (bad formula) - * -2 : row or column out of range + * Returns 0 : WRITE_FORMULA_NORMAL normal termination + * -1 : WRITE_FORMULA_ERRORS formula errors (bad formula) + * -2 : WRITE_FORMULA_RANGE row or column out of range + * -3 : WRITE_FORMULA_EXCEPTION parse raised exception, probably due to definedname * * @param int $row Zero indexed row * @param int $col Zero indexed column @@ -784,7 +842,6 @@ class Worksheet extends BIFFwriter private function writeFormula($row, $col, $formula, $xfIndex, $calculatedValue) { $record = 0x0006; // Record identifier - // Initialize possible additional value for STRING record that should be written after the FORMULA record? $stringValue = null; @@ -802,7 +859,7 @@ class Worksheet extends BIFFwriter $errorCodes = DataType::getErrorCodes(); if (isset($errorCodes[$calculatedValue])) { // Error value - $num = pack('CCCvCv', 0x02, 0x00, self::mapErrorCode($calculatedValue), 0x00, 0x00, 0xFFFF); + $num = pack('CCCvCv', 0x02, 0x00, ErrorCode::error($calculatedValue), 0x00, 0x00, 0xFFFF); } elseif ($calculatedValue === '') { // Empty string (and BIFF8) $num = pack('CCCvCv', 0x03, 0x00, 0x00, 0x00, 0x00, 0xFFFF); @@ -829,12 +886,12 @@ class Worksheet extends BIFFwriter // Error handling $this->writeString($row, $col, 'Unrecognised character for formula', 0); - return -1; + return self::WRITE_FORMULA_ERRORS; } // Parse the formula using the parser in Parser.php try { - $error = $this->parser->parse($formula); + $this->parser->parse($formula); $formula = $this->parser->toReversePolish(); $formlen = strlen($formula); // Length of the binary string @@ -843,8 +900,8 @@ class Worksheet extends BIFFwriter $header = pack('vv', $record, $length); $data = pack('vvv', $row, $col, $xfIndex) - . $num - . pack('vVv', $grbit, $unknown, $formlen); + . $num + . pack('vVv', $grbit, $unknown, $formlen); $this->append($header . $data . $formula); // Append also a STRING record if necessary @@ -852,9 +909,9 @@ class Worksheet extends BIFFwriter $this->writeStringRecord($stringValue); } - return 0; + return self::WRITE_FORMULA_NORMAL; } catch (PhpSpreadsheetException $e) { - // do nothing + return self::WRITE_FORMULA_EXCEPTION; } } @@ -863,7 +920,7 @@ class Worksheet extends BIFFwriter * * @param string $stringValue */ - private function writeStringRecord($stringValue) + private function writeStringRecord($stringValue): void { $record = 0x0207; // Record identifier $data = StringHelper::UTF8toBIFF8UnicodeLong($stringValue); @@ -885,20 +942,14 @@ class Worksheet extends BIFFwriter * The hyperlink can be to a http, ftp, mail, internal sheet (not yet), or external * directory url. * - * Returns 0 : normal termination - * -2 : row or column out of range - * -3 : long string truncated to 255 chars - * * @param int $row Row * @param int $col Column * @param string $url URL string - * - * @return int */ - private function writeUrl($row, $col, $url) + private function writeUrl($row, $col, $url): void { // Add start row and col to arg list - return $this->writeUrlRange($row, $col, $row, $col, $url); + $this->writeUrlRange($row, $col, $row, $col, $url); } /** @@ -907,27 +958,25 @@ class Worksheet extends BIFFwriter * to be written. These are either, Web (http, ftp, mailto), Internal * (Sheet1!A1) or external ('c:\temp\foo.xls#Sheet1!A1'). * - * @see writeUrl() - * * @param int $row1 Start row * @param int $col1 Start column * @param int $row2 End row * @param int $col2 End column * @param string $url URL string * - * @return int + * @see writeUrl() */ - public function writeUrlRange($row1, $col1, $row2, $col2, $url) + private function writeUrlRange($row1, $col1, $row2, $col2, $url): void { // Check for internal/external sheet links or default to web link if (preg_match('[^internal:]', $url)) { - return $this->writeUrlInternal($row1, $col1, $row2, $col2, $url); + $this->writeUrlInternal($row1, $col1, $row2, $col2, $url); } if (preg_match('[^external:]', $url)) { - return $this->writeUrlExternal($row1, $col1, $row2, $col2, $url); + $this->writeUrlExternal($row1, $col1, $row2, $col2, $url); } - return $this->writeUrlWeb($row1, $col1, $row2, $col2, $url); + $this->writeUrlWeb($row1, $col1, $row2, $col2, $url); } /** @@ -935,20 +984,17 @@ class Worksheet extends BIFFwriter * The link type ($options) is 0x03 is the same as absolute dir ref without * sheet. However it is differentiated by the $unknown2 data stream. * - * @see writeUrl() - * * @param int $row1 Start row * @param int $col1 Start column * @param int $row2 End row * @param int $col2 End column * @param string $url URL string * - * @return int + * @see writeUrl() */ - public function writeUrlWeb($row1, $col1, $row2, $col2, $url) + public function writeUrlWeb($row1, $col1, $row2, $col2, $url): void { $record = 0x01B8; // Record identifier - $length = 0x00000; // Bytes to follow // Pack the undocumented parts of the hyperlink stream $unknown1 = pack('H*', 'D0C9EA79F9BACE118C8200AA004BA90B02000000'); @@ -958,6 +1004,8 @@ class Worksheet extends BIFFwriter $options = pack('V', 0x03); // Convert URL to a null terminated wchar string + + /** @phpstan-ignore-next-line */ $url = implode("\0", preg_split("''", $url, -1, PREG_SPLIT_NO_EMPTY)); $url = $url . "\0\0\0"; @@ -973,30 +1021,25 @@ class Worksheet extends BIFFwriter // Write the packed data $this->append($header . $data . $unknown1 . $options . $unknown2 . $url_len . $url); - - return 0; } /** * Used to write internal reference hyperlinks such as "Sheet1!A1". * - * @see writeUrl() - * * @param int $row1 Start row * @param int $col1 Start column * @param int $row2 End row * @param int $col2 End column * @param string $url URL string * - * @return int + * @see writeUrl() */ - public function writeUrlInternal($row1, $col1, $row2, $col2, $url) + private function writeUrlInternal($row1, $col1, $row2, $col2, $url): void { $record = 0x01B8; // Record identifier - $length = 0x00000; // Bytes to follow // Strip URL type - $url = preg_replace('/^internal:/', '', $url); + $url = (string) preg_replace('/^internal:/', '', $url); // Pack the undocumented parts of the hyperlink stream $unknown1 = pack('H*', 'D0C9EA79F9BACE118C8200AA004BA90B02000000'); @@ -1022,8 +1065,6 @@ class Worksheet extends BIFFwriter // Write the packed data $this->append($header . $data . $unknown1 . $options . $url_len . $url); - - return 0; } /** @@ -1033,31 +1074,27 @@ class Worksheet extends BIFFwriter * Note: Excel writes some relative links with the $dir_long string. We ignore * these cases for the sake of simpler code. * - * @see writeUrl() - * * @param int $row1 Start row * @param int $col1 Start column * @param int $row2 End row * @param int $col2 End column * @param string $url URL string * - * @return int + * @see writeUrl() */ - public function writeUrlExternal($row1, $col1, $row2, $col2, $url) + private function writeUrlExternal($row1, $col1, $row2, $col2, $url): void { // Network drives are different. We will handle them separately // MS/Novell network drives and shares start with \\ if (preg_match('[^external:\\\\]', $url)) { - return; //($this->writeUrlExternal_net($row1, $col1, $row2, $col2, $url, $str, $format)); + return; } $record = 0x01B8; // Record identifier - $length = 0x00000; // Bytes to follow // Strip URL type and change Unix dir separator to Dos style (if needed) // - $url = preg_replace('/^external:/', '', $url); - $url = preg_replace('/\//', '\\', $url); + $url = (string) preg_replace(['/^external:/', '/\//'], ['', '\\'], $url); // Determine if the link is relative or absolute: // relative if link contains no dir separator, "somefile.xls" @@ -1086,32 +1123,32 @@ class Worksheet extends BIFFwriter $up_count = pack('v', $up_count); // Store the short dos dir name (null terminated) - $dir_short = preg_replace('/\\.\\.\\\\/', '', $dir_long) . "\0"; + $dir_short = (string) preg_replace('/\\.\\.\\\\/', '', $dir_long) . "\0"; // Store the long dir name as a wchar string (non-null terminated) - $dir_long = $dir_long . "\0"; + //$dir_long = $dir_long . "\0"; // Pack the lengths of the dir strings $dir_short_len = pack('V', strlen($dir_short)); - $dir_long_len = pack('V', strlen($dir_long)); + //$dir_long_len = pack('V', strlen($dir_long)); $stream_len = pack('V', 0); //strlen($dir_long) + 0x06); // Pack the undocumented parts of the hyperlink stream $unknown1 = pack('H*', 'D0C9EA79F9BACE118C8200AA004BA90B02000000'); $unknown2 = pack('H*', '0303000000000000C000000000000046'); $unknown3 = pack('H*', 'FFFFADDE000000000000000000000000000000000000000'); - $unknown4 = pack('v', 0x03); + //$unknown4 = pack('v', 0x03); // Pack the main data stream $data = pack('vvvv', $row1, $row2, $col1, $col2) . - $unknown1 . - $link_type . - $unknown2 . - $up_count . - $dir_short_len . - $dir_short . - $unknown3 . - $stream_len; /*. + $unknown1 . + $link_type . + $unknown2 . + $up_count . + $dir_short_len . + $dir_short . + $unknown3 . + $stream_len; /*. $dir_long_len . $unknown4 . $dir_long . @@ -1124,8 +1161,6 @@ class Worksheet extends BIFFwriter // Write the packed data $this->append($header . $data); - - return 0; } /** @@ -1138,7 +1173,7 @@ class Worksheet extends BIFFwriter * @param bool $hidden The optional hidden attribute * @param int $level The optional outline level for row, in range [0,7] */ - private function writeRow($row, $height, $xfIndex, $hidden = false, $level = 0) + private function writeRow($row, $height, $xfIndex, $hidden = false, $level = 0): void { $record = 0x0208; // Record identifier $length = 0x0010; // Number of bytes to follow @@ -1155,7 +1190,7 @@ class Worksheet extends BIFFwriter } // Use writeRow($row, null, $XF) to set XF format without setting height - if ($height != null) { + if ($height !== null) { $miyRw = $height * 20; // row height } else { $miyRw = 0xff; // default row height is 256 @@ -1168,7 +1203,7 @@ class Worksheet extends BIFFwriter // collapsed. The zero height flag, 0x20, is used to collapse a row. $grbit |= $level; - if ($hidden) { + if ($hidden === true) { $grbit |= 0x0030; } if ($height !== null) { @@ -1187,7 +1222,7 @@ class Worksheet extends BIFFwriter /** * Writes Excel DIMENSIONS to define the area in which there is data. */ - private function writeDimensions() + private function writeDimensions(): void { $record = 0x0200; // Record identifier @@ -1201,12 +1236,11 @@ class Worksheet extends BIFFwriter /** * Write BIFF record Window2. */ - private function writeWindow2() + private function writeWindow2(): void { $record = 0x023E; // Record identifier $length = 0x0012; - $grbit = 0x00B6; // Option flags $rwTop = 0x0000; // Top row visible in window $colLeft = 0x0000; // Leftmost column visible in window @@ -1222,7 +1256,6 @@ class Worksheet extends BIFFwriter $fFrozenNoSplit = 0; // 0 - bit // no support in PhpSpreadsheet for selected sheet, therefore sheet is only selected if it is the active sheet $fSelected = ($this->phpSheet === $this->phpSheet->getParent()->getActiveSheet()) ? 1 : 0; - $fPaged = 1; // 2 $fPageBreakPreview = $this->phpSheet->getSheetView()->getView() === SheetView::SHEETVIEW_PAGE_BREAK_PREVIEW; $grbit = $fDspFmla; @@ -1234,8 +1267,8 @@ class Worksheet extends BIFFwriter $grbit |= $fArabic << 6; $grbit |= $fDspGuts << 7; $grbit |= $fFrozenNoSplit << 8; - $grbit |= $fSelected << 9; - $grbit |= $fPaged << 10; + $grbit |= $fSelected << 9; // Selected sheets. + $grbit |= $fSelected << 10; // Active sheet. $grbit |= $fPageBreakPreview << 11; $header = pack('vv', $record, $length); @@ -1254,7 +1287,7 @@ class Worksheet extends BIFFwriter /** * Write BIFF record DEFAULTROWHEIGHT. */ - private function writeDefaultRowHeight() + private function writeDefaultRowHeight(): void { $defaultRowHeight = $this->phpSheet->getDefaultRowDimension()->getRowHeight(); @@ -1276,7 +1309,7 @@ class Worksheet extends BIFFwriter /** * Write BIFF record DEFCOLWIDTH if COLINFO records are in use. */ - private function writeDefcol() + private function writeDefcol(): void { $defaultColWidth = 8; @@ -1302,34 +1335,15 @@ class Worksheet extends BIFFwriter * 4 => Option flags. * 5 => Optional outline level */ - private function writeColinfo($col_array) + private function writeColinfo($col_array): void { - if (isset($col_array[0])) { - $colFirst = $col_array[0]; - } - if (isset($col_array[1])) { - $colLast = $col_array[1]; - } - if (isset($col_array[2])) { - $coldx = $col_array[2]; - } else { - $coldx = 8.43; - } - if (isset($col_array[3])) { - $xfIndex = $col_array[3]; - } else { - $xfIndex = 15; - } - if (isset($col_array[4])) { - $grbit = $col_array[4]; - } else { - $grbit = 0; - } - if (isset($col_array[5])) { - $level = $col_array[5]; - } else { - $level = 0; - } + $colFirst = $col_array[0] ?? null; + $colLast = $col_array[1] ?? null; + $coldx = $col_array[2] ?? 8.43; + $xfIndex = $col_array[3] ?? 15; + $grbit = $col_array[4] ?? 0; + $level = $col_array[5] ?? 0; + $record = 0x007D; // Record identifier $length = 0x000C; // Number of bytes to follow @@ -1349,7 +1363,7 @@ class Worksheet extends BIFFwriter /** * Write BIFF record SELECTION. */ - private function writeSelection() + private function writeSelection(): void { // look up the selected cell range $selectedCells = Coordinate::splitRange($this->phpSheet->getSelectedCells()); @@ -1385,13 +1399,6 @@ class Worksheet extends BIFFwriter $irefAct = 0; // Active cell ref $cref = 1; // Number of refs - if (!isset($rwLast)) { - $rwLast = $rwFirst; // Last row in reference - } - if (!isset($colLast)) { - $colLast = $colFirst; // Last col in reference - } - // Swap last row/col for first row/col as necessary if ($rwFirst > $rwLast) { [$rwFirst, $rwLast] = [$rwLast, $rwFirst]; @@ -1409,7 +1416,7 @@ class Worksheet extends BIFFwriter /** * Store the MERGEDCELLS records for all ranges of merged cells. */ - private function writeMergedCells() + private function writeMergedCells(): void { $mergeCells = $this->phpSheet->getMergeCells(); $countMergeCells = count($mergeCells); @@ -1441,13 +1448,13 @@ class Worksheet extends BIFFwriter // extract the row and column indexes $range = Coordinate::splitRange($mergeCell); [$first, $last] = $range[0]; - [$firstColumn, $firstRow] = Coordinate::coordinateFromString($first); - [$lastColumn, $lastRow] = Coordinate::coordinateFromString($last); + [$firstColumn, $firstRow] = Coordinate::indexesFromString($first); + [$lastColumn, $lastRow] = Coordinate::indexesFromString($last); - $recordData .= pack('vvvv', $firstRow - 1, $lastRow - 1, Coordinate::columnIndexFromString($firstColumn) - 1, Coordinate::columnIndexFromString($lastColumn) - 1); + $recordData .= pack('vvvv', $firstRow - 1, $lastRow - 1, $firstColumn - 1, $lastColumn - 1); // flush record if we have reached limit for number of merged cells, or reached final merged cell - if ($j == $maxCountMergeCellsPerRecord or $i == $countMergeCells) { + if ($j == $maxCountMergeCellsPerRecord || $i == $countMergeCells) { $recordData = pack('v', $j) . $recordData; $length = strlen($recordData); $header = pack('vv', $record, $length); @@ -1463,7 +1470,7 @@ class Worksheet extends BIFFwriter /** * Write SHEETLAYOUT record. */ - private function writeSheetLayout() + private function writeSheetLayout(): void { if (!$this->phpSheet->isTabColorSet()) { return; @@ -1487,30 +1494,49 @@ class Worksheet extends BIFFwriter $this->append($header . $recordData); } + private static function protectionBitsDefaultFalse(?bool $value, int $shift): int + { + if ($value === false) { + return 1 << $shift; + } + + return 0; + } + + private static function protectionBitsDefaultTrue(?bool $value, int $shift): int + { + if ($value !== false) { + return 1 << $shift; + } + + return 0; + } + /** * Write SHEETPROTECTION. */ - private function writeSheetProtection() + private function writeSheetProtection(): void { // record identifier $record = 0x0867; // prepare options - $options = (int) !$this->phpSheet->getProtection()->getObjects() - | (int) !$this->phpSheet->getProtection()->getScenarios() << 1 - | (int) !$this->phpSheet->getProtection()->getFormatCells() << 2 - | (int) !$this->phpSheet->getProtection()->getFormatColumns() << 3 - | (int) !$this->phpSheet->getProtection()->getFormatRows() << 4 - | (int) !$this->phpSheet->getProtection()->getInsertColumns() << 5 - | (int) !$this->phpSheet->getProtection()->getInsertRows() << 6 - | (int) !$this->phpSheet->getProtection()->getInsertHyperlinks() << 7 - | (int) !$this->phpSheet->getProtection()->getDeleteColumns() << 8 - | (int) !$this->phpSheet->getProtection()->getDeleteRows() << 9 - | (int) !$this->phpSheet->getProtection()->getSelectLockedCells() << 10 - | (int) !$this->phpSheet->getProtection()->getSort() << 11 - | (int) !$this->phpSheet->getProtection()->getAutoFilter() << 12 - | (int) !$this->phpSheet->getProtection()->getPivotTables() << 13 - | (int) !$this->phpSheet->getProtection()->getSelectUnlockedCells() << 14; + $protection = $this->phpSheet->getProtection(); + $options = self::protectionBitsDefaultTrue($protection->getObjects(), 0) + | self::protectionBitsDefaultTrue($protection->getScenarios(), 1) + | self::protectionBitsDefaultFalse($protection->getFormatCells(), 2) + | self::protectionBitsDefaultFalse($protection->getFormatColumns(), 3) + | self::protectionBitsDefaultFalse($protection->getFormatRows(), 4) + | self::protectionBitsDefaultFalse($protection->getInsertColumns(), 5) + | self::protectionBitsDefaultFalse($protection->getInsertRows(), 6) + | self::protectionBitsDefaultFalse($protection->getInsertHyperlinks(), 7) + | self::protectionBitsDefaultFalse($protection->getDeleteColumns(), 8) + | self::protectionBitsDefaultFalse($protection->getDeleteRows(), 9) + | self::protectionBitsDefaultTrue($protection->getSelectLockedCells(), 10) + | self::protectionBitsDefaultFalse($protection->getSort(), 11) + | self::protectionBitsDefaultFalse($protection->getAutoFilter(), 12) + | self::protectionBitsDefaultFalse($protection->getPivotTables(), 13) + | self::protectionBitsDefaultTrue($protection->getSelectUnlockedCells(), 14); // record data $recordData = pack( @@ -1534,10 +1560,10 @@ class Worksheet extends BIFFwriter /** * Write BIFF record RANGEPROTECTION. * - * Openoffice.org's Documentaion of the Microsoft Excel File Format uses term RANGEPROTECTION for these records + * Openoffice.org's Documentation of the Microsoft Excel File Format uses term RANGEPROTECTION for these records * Microsoft Office Excel 97-2007 Binary File Format Specification uses term FEAT for these records */ - private function writeRangeProtection() + private function writeRangeProtection(): void { foreach ($this->phpSheet->getProtectedCells() as $range => $password) { // number of ranges, e.g. 'A1:B3 C20:D25' @@ -1585,78 +1611,39 @@ class Worksheet extends BIFFwriter * Frozen panes are specified in terms of an integer number of rows and columns. * Thawed panes are specified in terms of Excel's units for rows and columns. */ - private function writePanes() + private function writePanes(): void { - $panes = []; - if ($this->phpSheet->getFreezePane()) { - [$column, $row] = Coordinate::coordinateFromString($this->phpSheet->getFreezePane()); - $panes[0] = Coordinate::columnIndexFromString($column) - 1; - $panes[1] = $row - 1; - - [$leftMostColumn, $topRow] = Coordinate::coordinateFromString($this->phpSheet->getTopLeftCell()); - //Coordinates are zero-based in xls files - $panes[2] = $topRow - 1; - $panes[3] = Coordinate::columnIndexFromString($leftMostColumn) - 1; - } else { + if (!$this->phpSheet->getFreezePane()) { // thaw panes return; } - $x = $panes[0] ?? null; - $y = $panes[1] ?? null; - $rwTop = $panes[2] ?? null; - $colLeft = $panes[3] ?? null; - if (count($panes) > 4) { // if Active pane was received - $pnnAct = $panes[4]; - } else { - $pnnAct = null; - } + [$column, $row] = Coordinate::indexesFromString($this->phpSheet->getFreezePane() ?? ''); + $x = $column - 1; + $y = $row - 1; + + [$leftMostColumn, $topRow] = Coordinate::indexesFromString($this->phpSheet->getTopLeftCell() ?? ''); + //Coordinates are zero-based in xls files + $rwTop = $topRow - 1; + $colLeft = $leftMostColumn - 1; + $record = 0x0041; // Record identifier $length = 0x000A; // Number of bytes to follow - // Code specific to frozen or thawed panes. - if ($this->phpSheet->getFreezePane()) { - // Set default values for $rwTop and $colLeft - if (!isset($rwTop)) { - $rwTop = $y; - } - if (!isset($colLeft)) { - $colLeft = $x; - } - } else { - // Set default values for $rwTop and $colLeft - if (!isset($rwTop)) { - $rwTop = 0; - } - if (!isset($colLeft)) { - $colLeft = 0; - } - - // Convert Excel's row and column units to the internal units. - // The default row height is 12.75 - // The default column width is 8.43 - // The following slope and intersection values were interpolated. - // - $y = 20 * $y + 255; - $x = 113.879 * $x + 390; - } - // Determine which pane should be active. There is also the undocumented // option to override this should it be necessary: may be removed later. - // - if (!isset($pnnAct)) { - if ($x != 0 && $y != 0) { - $pnnAct = 0; // Bottom right - } - if ($x != 0 && $y == 0) { - $pnnAct = 1; // Top right - } - if ($x == 0 && $y != 0) { - $pnnAct = 2; // Bottom left - } - if ($x == 0 && $y == 0) { - $pnnAct = 3; // Top left - } + $pnnAct = null; + if ($x != 0 && $y != 0) { + $pnnAct = 0; // Bottom right + } + if ($x != 0 && $y == 0) { + $pnnAct = 1; // Top right + } + if ($x == 0 && $y != 0) { + $pnnAct = 2; // Bottom left + } + if ($x == 0 && $y == 0) { + $pnnAct = 3; // Top left } $this->activePane = $pnnAct; // Used in writeSelection @@ -1669,20 +1656,17 @@ class Worksheet extends BIFFwriter /** * Store the page setup SETUP BIFF record. */ - private function writeSetup() + private function writeSetup(): void { $record = 0x00A1; // Record identifier $length = 0x0022; // Number of bytes to follow $iPaperSize = $this->phpSheet->getPageSetup()->getPaperSize(); // Paper size - - $iScale = $this->phpSheet->getPageSetup()->getScale() ? - $this->phpSheet->getPageSetup()->getScale() : 100; // Print scaling factor + $iScale = $this->phpSheet->getPageSetup()->getScale() ?: 100; // Print scaling factor $iPageStart = 0x01; // Starting page number $iFitWidth = (int) $this->phpSheet->getPageSetup()->getFitToWidth(); // Fit to number of pages wide $iFitHeight = (int) $this->phpSheet->getPageSetup()->getFitToHeight(); // Fit to number of pages high - $grbit = 0x00; // Option flags $iRes = 0x0258; // Print resolution $iVRes = 0x0258; // Vertical print resolution @@ -1691,11 +1675,12 @@ class Worksheet extends BIFFwriter $numFtr = $this->phpSheet->getPageMargins()->getFooter(); // Footer Margin $iCopies = 0x01; // Number of copies - $fLeftToRight = 0x0; // Print over then down - + // Order of printing pages + $fLeftToRight = $this->phpSheet->getPageSetup()->getPageOrder() === PageSetup::PAGEORDER_DOWN_THEN_OVER + ? 0x1 : 0x0; // Page orientation - $fLandscape = ($this->phpSheet->getPageSetup()->getOrientation() == PageSetup::ORIENTATION_LANDSCAPE) ? - 0x0 : 0x1; + $fLandscape = ($this->phpSheet->getPageSetup()->getOrientation() == PageSetup::ORIENTATION_LANDSCAPE) + ? 0x0 : 0x1; $fNoPls = 0x0; // Setup not read from printer $fNoColor = 0x0; // Print black and white @@ -1730,7 +1715,7 @@ class Worksheet extends BIFFwriter /** * Store the header caption BIFF record. */ - private function writeHeader() + private function writeHeader(): void { $record = 0x0014; // Record identifier @@ -1754,7 +1739,7 @@ class Worksheet extends BIFFwriter /** * Store the footer caption BIFF record. */ - private function writeFooter() + private function writeFooter(): void { $record = 0x0015; // Record identifier @@ -1778,7 +1763,7 @@ class Worksheet extends BIFFwriter /** * Store the horizontal centering HCENTER BIFF record. */ - private function writeHcenter() + private function writeHcenter(): void { $record = 0x0083; // Record identifier $length = 0x0002; // Bytes to follow @@ -1794,7 +1779,7 @@ class Worksheet extends BIFFwriter /** * Store the vertical centering VCENTER BIFF record. */ - private function writeVcenter() + private function writeVcenter(): void { $record = 0x0084; // Record identifier $length = 0x0002; // Bytes to follow @@ -1809,7 +1794,7 @@ class Worksheet extends BIFFwriter /** * Store the LEFTMARGIN BIFF record. */ - private function writeMarginLeft() + private function writeMarginLeft(): void { $record = 0x0026; // Record identifier $length = 0x0008; // Bytes to follow @@ -1828,7 +1813,7 @@ class Worksheet extends BIFFwriter /** * Store the RIGHTMARGIN BIFF record. */ - private function writeMarginRight() + private function writeMarginRight(): void { $record = 0x0027; // Record identifier $length = 0x0008; // Bytes to follow @@ -1847,7 +1832,7 @@ class Worksheet extends BIFFwriter /** * Store the TOPMARGIN BIFF record. */ - private function writeMarginTop() + private function writeMarginTop(): void { $record = 0x0028; // Record identifier $length = 0x0008; // Bytes to follow @@ -1866,7 +1851,7 @@ class Worksheet extends BIFFwriter /** * Store the BOTTOMMARGIN BIFF record. */ - private function writeMarginBottom() + private function writeMarginBottom(): void { $record = 0x0029; // Record identifier $length = 0x0008; // Bytes to follow @@ -1885,7 +1870,7 @@ class Worksheet extends BIFFwriter /** * Write the PRINTHEADERS BIFF record. */ - private function writePrintHeaders() + private function writePrintHeaders(): void { $record = 0x002a; // Record identifier $length = 0x0002; // Bytes to follow @@ -1901,7 +1886,7 @@ class Worksheet extends BIFFwriter * Write the PRINTGRIDLINES BIFF record. Must be used in conjunction with the * GRIDSET record. */ - private function writePrintGridlines() + private function writePrintGridlines(): void { $record = 0x002b; // Record identifier $length = 0x0002; // Bytes to follow @@ -1917,7 +1902,7 @@ class Worksheet extends BIFFwriter * Write the GRIDSET BIFF record. Must be used in conjunction with the * PRINTGRIDLINES record. */ - private function writeGridset() + private function writeGridset(): void { $record = 0x0082; // Record identifier $length = 0x0002; // Bytes to follow @@ -1932,7 +1917,7 @@ class Worksheet extends BIFFwriter /** * Write the AUTOFILTERINFO BIFF record. This is used to configure the number of autofilter select used in the sheet. */ - private function writeAutoFilterInfo() + private function writeAutoFilterInfo(): void { $record = 0x009D; // Record identifier $length = 0x0002; // Bytes to follow @@ -1952,7 +1937,7 @@ class Worksheet extends BIFFwriter * * @see writeWsbool() */ - private function writeGuts() + private function writeGuts(): void { $record = 0x0080; // Record identifier $length = 0x0008; // Bytes to follow @@ -1996,7 +1981,7 @@ class Worksheet extends BIFFwriter * Write the WSBOOL BIFF record, mainly for fit-to-page. Used in conjunction * with the SETUP record. */ - private function writeWsbool() + private function writeWsbool(): void { $record = 0x0081; // Record identifier $length = 0x0002; // Bytes to follow @@ -2031,7 +2016,7 @@ class Worksheet extends BIFFwriter /** * Write the HORIZONTALPAGEBREAKS and VERTICALPAGEBREAKS BIFF records. */ - private function writeBreaks() + private function writeBreaks(): void { // initialize $vbreaks = []; @@ -2114,10 +2099,10 @@ class Worksheet extends BIFFwriter /** * Set the Biff PROTECT record to indicate that the worksheet is protected. */ - private function writeProtect() + private function writeProtect(): void { // Exit unless sheet protection has been specified - if (!$this->phpSheet->getProtection()->getSheet()) { + if ($this->phpSheet->getProtection()->getSheet() !== true) { return; } @@ -2135,15 +2120,15 @@ class Worksheet extends BIFFwriter /** * Write SCENPROTECT. */ - private function writeScenProtect() + private function writeScenProtect(): void { // Exit if sheet protection is not active - if (!$this->phpSheet->getProtection()->getSheet()) { + if ($this->phpSheet->getProtection()->getSheet() !== true) { return; } // Exit if scenarios are not protected - if (!$this->phpSheet->getProtection()->getScenarios()) { + if ($this->phpSheet->getProtection()->getScenarios() !== true) { return; } @@ -2159,15 +2144,15 @@ class Worksheet extends BIFFwriter /** * Write OBJECTPROTECT. */ - private function writeObjectProtect() + private function writeObjectProtect(): void { // Exit if sheet protection is not active - if (!$this->phpSheet->getProtection()->getSheet()) { + if ($this->phpSheet->getProtection()->getSheet() !== true) { return; } // Exit if objects are not protected - if (!$this->phpSheet->getProtection()->getObjects()) { + if ($this->phpSheet->getProtection()->getObjects() !== true) { return; } @@ -2183,10 +2168,10 @@ class Worksheet extends BIFFwriter /** * Write the worksheet PASSWORD record. */ - private function writePassword() + private function writePassword(): void { // Exit unless sheet protection and password have been specified - if (!$this->phpSheet->getProtection()->getSheet() || !$this->phpSheet->getProtection()->getPassword()) { + if ($this->phpSheet->getProtection()->getSheet() !== true || !$this->phpSheet->getProtection()->getPassword() || $this->phpSheet->getProtection()->getAlgorithm() !== '') { return; } @@ -2212,9 +2197,11 @@ class Worksheet extends BIFFwriter * @param float $scale_x The horizontal scale * @param float $scale_y The vertical scale */ - public function insertBitmap($row, $col, $bitmap, $x = 0, $y = 0, $scale_x = 1, $scale_y = 1) + public function insertBitmap($row, $col, $bitmap, $x = 0, $y = 0, $scale_x = 1, $scale_y = 1): void { - $bitmap_array = (is_resource($bitmap) ? $this->processBitmapGd($bitmap) : $this->processBitmap($bitmap)); + $bitmap_array = (is_resource($bitmap) || $bitmap instanceof GdImage + ? $this->processBitmapGd($bitmap) + : $this->processBitmap($bitmap)); [$width, $height, $size, $data] = $bitmap_array; // Scale the frame of the image. @@ -2285,7 +2272,7 @@ class Worksheet extends BIFFwriter * @param int $width Width of image frame * @param int $height Height of image frame */ - public function positionImage($col_start, $row_start, $x1, $y1, $width, $height) + public function positionImage($col_start, $row_start, $x1, $y1, $width, $height): void { // Initialise end cell to the same as the start cell $col_end = $col_start; // Col containing lower right corner of object @@ -2352,7 +2339,7 @@ class Worksheet extends BIFFwriter * @param int $rwB Row containing bottom right corner of object * @param int $dyB Distance from bottom of cell */ - private function writeObjPicture($colL, $dxL, $rwT, $dyT, $colR, $dxR, $rwB, $dyB) + private function writeObjPicture($colL, $dxL, $rwT, $dyT, $colR, $dxR, $rwB, $dyB): void { $record = 0x005d; // Record identifier $length = 0x003c; // Bytes to follow @@ -2420,7 +2407,7 @@ class Worksheet extends BIFFwriter /** * Convert a GD-image into the internal format. * - * @param resource $image The image to process + * @param GdImage|resource $image The image to process * * @return array Array with data and properties of the bitmap */ @@ -2432,11 +2419,14 @@ class Worksheet extends BIFFwriter $data = pack('Vvvvv', 0x000c, $width, $height, 0x01, 0x18); for ($j = $height; --$j;) { for ($i = 0; $i < $width; ++$i) { + /** @phpstan-ignore-next-line */ $color = imagecolorsforindex($image, imagecolorat($image, $i, $j)); - foreach (['red', 'green', 'blue'] as $key) { - $color[$key] = $color[$key] + round((255 - $color[$key]) * $color['alpha'] / 127); + if ($color !== false) { + foreach (['red', 'green', 'blue'] as $key) { + $color[$key] = $color[$key] + (int) round((255 - $color[$key]) * $color['alpha'] / 127); + } + $data .= chr($color['blue']) . chr($color['green']) . chr($color['red']); } - $data .= chr($color['blue']) . chr($color['green']) . chr($color['red']); } if (3 * $width % 4) { $data .= str_repeat("\x00", 4 - 3 * $width % 4); @@ -2459,7 +2449,7 @@ class Worksheet extends BIFFwriter { // Open file. $bmp_fd = @fopen($bitmap, 'rb'); - if (!$bmp_fd) { + if ($bmp_fd === false) { throw new WriterException("Couldn't import $bitmap"); } @@ -2472,6 +2462,8 @@ class Worksheet extends BIFFwriter } // The first 2 bytes are used to identify the bitmap. + + /** @phpstan-ignore-next-line */ $identity = unpack('A2ident', $data); if ($identity['ident'] != 'BM') { throw new WriterException("$bitmap doesn't appear to be a valid bitmap image.\n"); @@ -2536,7 +2528,7 @@ class Worksheet extends BIFFwriter * Store the window zoom factor. This should be a reduced fraction but for * simplicity we will store all fractions with a numerator of 100. */ - private function writeZoom() + private function writeZoom(): void { // If scale is 100 we don't need to write a record if ($this->phpSheet->getSheetView()->getZoomScale() == 100) { @@ -2553,28 +2545,24 @@ class Worksheet extends BIFFwriter /** * Get Escher object. - * - * @return \PhpOffice\PhpSpreadsheet\Shared\Escher */ - public function getEscher() + public function getEscher(): ?\PhpOffice\PhpSpreadsheet\Shared\Escher { return $this->escher; } /** * Set Escher object. - * - * @param \PhpOffice\PhpSpreadsheet\Shared\Escher $pValue */ - public function setEscher(\PhpOffice\PhpSpreadsheet\Shared\Escher $pValue = null) + public function setEscher(?\PhpOffice\PhpSpreadsheet\Shared\Escher $escher): void { - $this->escher = $pValue; + $this->escher = $escher; } /** * Write MSODRAWING record. */ - private function writeMsoDrawing() + private function writeMsoDrawing(): void { // write the Escher stream if necessary if (isset($this->escher)) { @@ -2659,7 +2647,7 @@ class Worksheet extends BIFFwriter /** * Store the DATAVALIDATIONS and DATAVALIDATION records. */ - private function writeDataValidity() + private function writeDataValidity(): void { // Datavalidation collection $dataValidationCollection = $this->phpSheet->getDataValidationCollection(); @@ -2683,67 +2671,16 @@ class Worksheet extends BIFFwriter $record = 0x01BE; // Record identifier foreach ($dataValidationCollection as $cellCoordinate => $dataValidation) { - // initialize record data - $data = ''; - // options $options = 0x00000000; // data type - $type = 0x00; - switch ($dataValidation->getType()) { - case DataValidation::TYPE_NONE: - $type = 0x00; - - break; - case DataValidation::TYPE_WHOLE: - $type = 0x01; - - break; - case DataValidation::TYPE_DECIMAL: - $type = 0x02; - - break; - case DataValidation::TYPE_LIST: - $type = 0x03; - - break; - case DataValidation::TYPE_DATE: - $type = 0x04; - - break; - case DataValidation::TYPE_TIME: - $type = 0x05; - - break; - case DataValidation::TYPE_TEXTLENGTH: - $type = 0x06; - - break; - case DataValidation::TYPE_CUSTOM: - $type = 0x07; - - break; - } + $type = CellDataValidation::type($dataValidation); $options |= $type << 0; // error style - $errorStyle = 0x00; - switch ($dataValidation->getErrorStyle()) { - case DataValidation::STYLE_STOP: - $errorStyle = 0x00; - - break; - case DataValidation::STYLE_WARNING: - $errorStyle = 0x01; - - break; - case DataValidation::STYLE_INFORMATION: - $errorStyle = 0x02; - - break; - } + $errorStyle = CellDataValidation::errorStyle($dataValidation); $options |= $errorStyle << 4; @@ -2765,41 +2702,7 @@ class Worksheet extends BIFFwriter $options |= $dataValidation->getShowErrorMessage() << 19; // condition operator - $operator = 0x00; - switch ($dataValidation->getOperator()) { - case DataValidation::OPERATOR_BETWEEN: - $operator = 0x00; - - break; - case DataValidation::OPERATOR_NOTBETWEEN: - $operator = 0x01; - - break; - case DataValidation::OPERATOR_EQUAL: - $operator = 0x02; - - break; - case DataValidation::OPERATOR_NOTEQUAL: - $operator = 0x03; - - break; - case DataValidation::OPERATOR_GREATERTHAN: - $operator = 0x04; - - break; - case DataValidation::OPERATOR_LESSTHAN: - $operator = 0x05; - - break; - case DataValidation::OPERATOR_GREATERTHANOREQUAL: - $operator = 0x06; - - break; - case DataValidation::OPERATOR_LESSTHANOREQUAL: - $operator = 0x07; - - break; - } + $operator = CellDataValidation::operator($dataValidation); $options |= $operator << 20; @@ -2869,46 +2772,17 @@ class Worksheet extends BIFFwriter } } - /** - * Map Error code. - * - * @param string $errorCode - * - * @return int - */ - private static function mapErrorCode($errorCode) - { - switch ($errorCode) { - case '#NULL!': - return 0x00; - case '#DIV/0!': - return 0x07; - case '#VALUE!': - return 0x0F; - case '#REF!': - return 0x17; - case '#NAME?': - return 0x1D; - case '#NUM!': - return 0x24; - case '#N/A': - return 0x2A; - } - - return 0; - } - /** * Write PLV Record. */ - private function writePageLayoutView() + private function writePageLayoutView(): void { $record = 0x088B; // Record identifier $length = 0x0010; // Bytes to follow $rt = 0x088B; // 2 $grbitFrt = 0x0000; // 2 - $reserved = 0x0000000000000000; // 8 + //$reserved = 0x0000000000000000; // 8 $wScalvePLV = $this->phpSheet->getSheetView()->getZoomScale(); // 2 // The options flags that comprise $grbit @@ -2931,15 +2805,16 @@ class Worksheet extends BIFFwriter /** * Write CFRule Record. - * - * @param Conditional $conditional */ - private function writeCFRule(Conditional $conditional) - { + private function writeCFRule( + ConditionalHelper $conditionalFormulaHelper, + Conditional $conditional, + string $cellRange + ): void { $record = 0x01B1; // Record identifier + $type = null; // Type of the CF + $operatorType = null; // Comparison operator - // $type : Type of the CF - // $operatorType : Comparison operator if ($conditional->getConditionType() == Conditional::CONDITION_EXPRESSION) { $type = 0x02; $operatorType = 0x00; @@ -2987,31 +2862,33 @@ class Worksheet extends BIFFwriter // $szValue2 : size of the formula data for second value or formula $arrConditions = $conditional->getConditions(); $numConditions = count($arrConditions); - if ($numConditions == 1) { - $szValue1 = ($arrConditions[0] <= 65535 ? 3 : 0x0000); - $szValue2 = 0x0000; - $operand1 = pack('Cv', 0x1E, $arrConditions[0]); - $operand2 = null; - } elseif ($numConditions == 2 && ($conditional->getOperatorType() == Conditional::OPERATOR_BETWEEN)) { - $szValue1 = ($arrConditions[0] <= 65535 ? 3 : 0x0000); - $szValue2 = ($arrConditions[1] <= 65535 ? 3 : 0x0000); - $operand1 = pack('Cv', 0x1E, $arrConditions[0]); - $operand2 = pack('Cv', 0x1E, $arrConditions[1]); - } else { - $szValue1 = 0x0000; - $szValue2 = 0x0000; - $operand1 = null; - $operand2 = null; + + $szValue1 = 0x0000; + $szValue2 = 0x0000; + $operand1 = null; + $operand2 = null; + + if ($numConditions === 1) { + $conditionalFormulaHelper->processCondition($arrConditions[0], $cellRange); + $szValue1 = $conditionalFormulaHelper->size(); + $operand1 = $conditionalFormulaHelper->tokens(); + } elseif ($numConditions === 2 && ($conditional->getOperatorType() === Conditional::OPERATOR_BETWEEN)) { + $conditionalFormulaHelper->processCondition($arrConditions[0], $cellRange); + $szValue1 = $conditionalFormulaHelper->size(); + $operand1 = $conditionalFormulaHelper->tokens(); + $conditionalFormulaHelper->processCondition($arrConditions[1], $cellRange); + $szValue2 = $conditionalFormulaHelper->size(); + $operand2 = $conditionalFormulaHelper->tokens(); } // $flags : Option flags // Alignment - $bAlignHz = ($conditional->getStyle()->getAlignment()->getHorizontal() == null ? 1 : 0); - $bAlignVt = ($conditional->getStyle()->getAlignment()->getVertical() == null ? 1 : 0); - $bAlignWrapTx = ($conditional->getStyle()->getAlignment()->getWrapText() == false ? 1 : 0); - $bTxRotation = ($conditional->getStyle()->getAlignment()->getTextRotation() == null ? 1 : 0); - $bIndent = ($conditional->getStyle()->getAlignment()->getIndent() == 0 ? 1 : 0); - $bShrinkToFit = ($conditional->getStyle()->getAlignment()->getShrinkToFit() == false ? 1 : 0); + $bAlignHz = ($conditional->getStyle()->getAlignment()->getHorizontal() === null ? 1 : 0); + $bAlignVt = ($conditional->getStyle()->getAlignment()->getVertical() === null ? 1 : 0); + $bAlignWrapTx = ($conditional->getStyle()->getAlignment()->getWrapText() === false ? 1 : 0); + $bTxRotation = ($conditional->getStyle()->getAlignment()->getTextRotation() === null ? 1 : 0); + $bIndent = ($conditional->getStyle()->getAlignment()->getIndent() === 0 ? 1 : 0); + $bShrinkToFit = ($conditional->getStyle()->getAlignment()->getShrinkToFit() === false ? 1 : 0); if ($bAlignHz == 0 || $bAlignVt == 0 || $bAlignWrapTx == 0 || $bTxRotation == 0 || $bIndent == 0 || $bShrinkToFit == 0) { $bFormatAlign = 1; } else { @@ -3027,37 +2904,39 @@ class Worksheet extends BIFFwriter } // Border $bBorderLeft = ($conditional->getStyle()->getBorders()->getLeft()->getColor()->getARGB() == Color::COLOR_BLACK - && $conditional->getStyle()->getBorders()->getLeft()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0); + && $conditional->getStyle()->getBorders()->getLeft()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0); $bBorderRight = ($conditional->getStyle()->getBorders()->getRight()->getColor()->getARGB() == Color::COLOR_BLACK - && $conditional->getStyle()->getBorders()->getRight()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0); + && $conditional->getStyle()->getBorders()->getRight()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0); $bBorderTop = ($conditional->getStyle()->getBorders()->getTop()->getColor()->getARGB() == Color::COLOR_BLACK - && $conditional->getStyle()->getBorders()->getTop()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0); + && $conditional->getStyle()->getBorders()->getTop()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0); $bBorderBottom = ($conditional->getStyle()->getBorders()->getBottom()->getColor()->getARGB() == Color::COLOR_BLACK - && $conditional->getStyle()->getBorders()->getBottom()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0); + && $conditional->getStyle()->getBorders()->getBottom()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0); if ($bBorderLeft == 0 || $bBorderRight == 0 || $bBorderTop == 0 || $bBorderBottom == 0) { $bFormatBorder = 1; } else { $bFormatBorder = 0; } // Pattern - $bFillStyle = ($conditional->getStyle()->getFill()->getFillType() == null ? 0 : 1); - $bFillColor = ($conditional->getStyle()->getFill()->getStartColor()->getARGB() == null ? 0 : 1); - $bFillColorBg = ($conditional->getStyle()->getFill()->getEndColor()->getARGB() == null ? 0 : 1); + $bFillStyle = ($conditional->getStyle()->getFill()->getFillType() === null ? 0 : 1); + $bFillColor = ($conditional->getStyle()->getFill()->getStartColor()->getARGB() === null ? 0 : 1); + $bFillColorBg = ($conditional->getStyle()->getFill()->getEndColor()->getARGB() === null ? 0 : 1); if ($bFillStyle == 0 || $bFillColor == 0 || $bFillColorBg == 0) { $bFormatFill = 1; } else { $bFormatFill = 0; } // Font - if ($conditional->getStyle()->getFont()->getName() != null - || $conditional->getStyle()->getFont()->getSize() != null - || $conditional->getStyle()->getFont()->getBold() != null - || $conditional->getStyle()->getFont()->getItalic() != null - || $conditional->getStyle()->getFont()->getSuperscript() != null - || $conditional->getStyle()->getFont()->getSubscript() != null - || $conditional->getStyle()->getFont()->getUnderline() != null - || $conditional->getStyle()->getFont()->getStrikethrough() != null - || $conditional->getStyle()->getFont()->getColor()->getARGB() != null) { + if ( + $conditional->getStyle()->getFont()->getName() !== null + || $conditional->getStyle()->getFont()->getSize() !== null + || $conditional->getStyle()->getFont()->getBold() !== null + || $conditional->getStyle()->getFont()->getItalic() !== null + || $conditional->getStyle()->getFont()->getSuperscript() !== null + || $conditional->getStyle()->getFont()->getSubscript() !== null + || $conditional->getStyle()->getFont()->getUnderline() !== null + || $conditional->getStyle()->getFont()->getStrikethrough() !== null + || $conditional->getStyle()->getFont()->getColor()->getARGB() !== null + ) { $bFormatFont = 1; } else { $bFormatFont = 0; @@ -3069,11 +2948,11 @@ class Worksheet extends BIFFwriter $flags |= (1 == $bAlignWrapTx ? 0x00000004 : 0); $flags |= (1 == $bTxRotation ? 0x00000008 : 0); // Justify last line flag - $flags |= (1 == 1 ? 0x00000010 : 0); + $flags |= (1 == self::$always1 ? 0x00000010 : 0); $flags |= (1 == $bIndent ? 0x00000020 : 0); $flags |= (1 == $bShrinkToFit ? 0x00000040 : 0); // Default - $flags |= (1 == 1 ? 0x00000080 : 0); + $flags |= (1 == self::$always1 ? 0x00000080 : 0); // Protection $flags |= (1 == $bProtLocked ? 0x00000100 : 0); $flags |= (1 == $bProtHidden ? 0x00000200 : 0); @@ -3082,13 +2961,13 @@ class Worksheet extends BIFFwriter $flags |= (1 == $bBorderRight ? 0x00000800 : 0); $flags |= (1 == $bBorderTop ? 0x00001000 : 0); $flags |= (1 == $bBorderBottom ? 0x00002000 : 0); - $flags |= (1 == 1 ? 0x00004000 : 0); // Top left to Bottom right border - $flags |= (1 == 1 ? 0x00008000 : 0); // Bottom left to Top right border + $flags |= (1 == self::$always1 ? 0x00004000 : 0); // Top left to Bottom right border + $flags |= (1 == self::$always1 ? 0x00008000 : 0); // Bottom left to Top right border // Pattern $flags |= (1 == $bFillStyle ? 0x00010000 : 0); $flags |= (1 == $bFillColor ? 0x00020000 : 0); $flags |= (1 == $bFillColorBg ? 0x00040000 : 0); - $flags |= (1 == 1 ? 0x00380000 : 0); + $flags |= (1 == self::$always1 ? 0x00380000 : 0); // Font $flags |= (1 == $bFormatFont ? 0x04000000 : 0); // Alignment: @@ -3100,19 +2979,24 @@ class Worksheet extends BIFFwriter // Protection $flags |= (1 == $bFormatProt ? 0x40000000 : 0); // Text direction - $flags |= (1 == 0 ? 0x80000000 : 0); + $flags |= (1 == self::$always0 ? 0x80000000 : 0); + + $dataBlockFont = null; + $dataBlockAlign = null; + $dataBlockBorder = null; + $dataBlockFill = null; // Data Blocks if ($bFormatFont == 1) { // Font Name - if ($conditional->getStyle()->getFont()->getName() == null) { + if ($conditional->getStyle()->getFont()->getName() === null) { $dataBlockFont = pack('VVVVVVVV', 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000); $dataBlockFont .= pack('VVVVVVVV', 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000); } else { $dataBlockFont = StringHelper::UTF8toBIFF8UnicodeLong($conditional->getStyle()->getFont()->getName()); } // Font Size - if ($conditional->getStyle()->getFont()->getSize() == null) { + if ($conditional->getStyle()->getFont()->getSize() === null) { $dataBlockFont .= pack('V', 20 * 11); } else { $dataBlockFont .= pack('V', 20 * $conditional->getStyle()->getFont()->getSize()); @@ -3120,16 +3004,16 @@ class Worksheet extends BIFFwriter // Font Options $dataBlockFont .= pack('V', 0); // Font weight - if ($conditional->getStyle()->getFont()->getBold() == true) { + if ($conditional->getStyle()->getFont()->getBold() === true) { $dataBlockFont .= pack('v', 0x02BC); } else { $dataBlockFont .= pack('v', 0x0190); } // Escapement type - if ($conditional->getStyle()->getFont()->getSubscript() == true) { + if ($conditional->getStyle()->getFont()->getSubscript() === true) { $dataBlockFont .= pack('v', 0x02); $fontEscapement = 0; - } elseif ($conditional->getStyle()->getFont()->getSuperscript() == true) { + } elseif ($conditional->getStyle()->getFont()->getSuperscript() === true) { $dataBlockFont .= pack('v', 0x01); $fontEscapement = 0; } else { @@ -3172,247 +3056,19 @@ class Worksheet extends BIFFwriter // Not used (3) $dataBlockFont .= pack('vC', 0x0000, 0x00); // Font color index - switch ($conditional->getStyle()->getFont()->getColor()->getRGB()) { - case '000000': - $colorIdx = 0x08; + $colorIdx = Style\ColorMap::lookup($conditional->getStyle()->getFont()->getColor(), 0x00); - break; - case 'FFFFFF': - $colorIdx = 0x09; - - break; - case 'FF0000': - $colorIdx = 0x0A; - - break; - case '00FF00': - $colorIdx = 0x0B; - - break; - case '0000FF': - $colorIdx = 0x0C; - - break; - case 'FFFF00': - $colorIdx = 0x0D; - - break; - case 'FF00FF': - $colorIdx = 0x0E; - - break; - case '00FFFF': - $colorIdx = 0x0F; - - break; - case '800000': - $colorIdx = 0x10; - - break; - case '008000': - $colorIdx = 0x11; - - break; - case '000080': - $colorIdx = 0x12; - - break; - case '808000': - $colorIdx = 0x13; - - break; - case '800080': - $colorIdx = 0x14; - - break; - case '008080': - $colorIdx = 0x15; - - break; - case 'C0C0C0': - $colorIdx = 0x16; - - break; - case '808080': - $colorIdx = 0x17; - - break; - case '9999FF': - $colorIdx = 0x18; - - break; - case '993366': - $colorIdx = 0x19; - - break; - case 'FFFFCC': - $colorIdx = 0x1A; - - break; - case 'CCFFFF': - $colorIdx = 0x1B; - - break; - case '660066': - $colorIdx = 0x1C; - - break; - case 'FF8080': - $colorIdx = 0x1D; - - break; - case '0066CC': - $colorIdx = 0x1E; - - break; - case 'CCCCFF': - $colorIdx = 0x1F; - - break; - case '000080': - $colorIdx = 0x20; - - break; - case 'FF00FF': - $colorIdx = 0x21; - - break; - case 'FFFF00': - $colorIdx = 0x22; - - break; - case '00FFFF': - $colorIdx = 0x23; - - break; - case '800080': - $colorIdx = 0x24; - - break; - case '800000': - $colorIdx = 0x25; - - break; - case '008080': - $colorIdx = 0x26; - - break; - case '0000FF': - $colorIdx = 0x27; - - break; - case '00CCFF': - $colorIdx = 0x28; - - break; - case 'CCFFFF': - $colorIdx = 0x29; - - break; - case 'CCFFCC': - $colorIdx = 0x2A; - - break; - case 'FFFF99': - $colorIdx = 0x2B; - - break; - case '99CCFF': - $colorIdx = 0x2C; - - break; - case 'FF99CC': - $colorIdx = 0x2D; - - break; - case 'CC99FF': - $colorIdx = 0x2E; - - break; - case 'FFCC99': - $colorIdx = 0x2F; - - break; - case '3366FF': - $colorIdx = 0x30; - - break; - case '33CCCC': - $colorIdx = 0x31; - - break; - case '99CC00': - $colorIdx = 0x32; - - break; - case 'FFCC00': - $colorIdx = 0x33; - - break; - case 'FF9900': - $colorIdx = 0x34; - - break; - case 'FF6600': - $colorIdx = 0x35; - - break; - case '666699': - $colorIdx = 0x36; - - break; - case '969696': - $colorIdx = 0x37; - - break; - case '003366': - $colorIdx = 0x38; - - break; - case '339966': - $colorIdx = 0x39; - - break; - case '003300': - $colorIdx = 0x3A; - - break; - case '333300': - $colorIdx = 0x3B; - - break; - case '993300': - $colorIdx = 0x3C; - - break; - case '993366': - $colorIdx = 0x3D; - - break; - case '333399': - $colorIdx = 0x3E; - - break; - case '333333': - $colorIdx = 0x3F; - - break; - default: - $colorIdx = 0x00; - - break; - } $dataBlockFont .= pack('V', $colorIdx); // Not used (4) $dataBlockFont .= pack('V', 0x00000000); // Options flags for modified font attributes $optionsFlags = 0; - $optionsFlagsBold = ($conditional->getStyle()->getFont()->getBold() == null ? 1 : 0); + $optionsFlagsBold = ($conditional->getStyle()->getFont()->getBold() === null ? 1 : 0); $optionsFlags |= (1 == $optionsFlagsBold ? 0x00000002 : 0); - $optionsFlags |= (1 == 1 ? 0x00000008 : 0); - $optionsFlags |= (1 == 1 ? 0x00000010 : 0); - $optionsFlags |= (1 == 0 ? 0x00000020 : 0); - $optionsFlags |= (1 == 1 ? 0x00000080 : 0); + $optionsFlags |= (1 == self::$always1 ? 0x00000008 : 0); + $optionsFlags |= (1 == self::$always1 ? 0x00000010 : 0); + $optionsFlags |= (1 == self::$always0 ? 0x00000020 : 0); + $optionsFlags |= (1 == self::$always1 ? 0x00000080 : 0); $dataBlockFont .= pack('V', $optionsFlags); // Escapement type $dataBlockFont .= pack('V', $fontEscapement); @@ -3427,58 +3083,11 @@ class Worksheet extends BIFFwriter // Always $dataBlockFont .= pack('v', 0x0001); } - if ($bFormatAlign == 1) { - $blockAlign = 0; + if ($bFormatAlign === 1) { // Alignment and text break - switch ($conditional->getStyle()->getAlignment()->getHorizontal()) { - case Alignment::HORIZONTAL_GENERAL: - $blockAlign = 0; - - break; - case Alignment::HORIZONTAL_LEFT: - $blockAlign = 1; - - break; - case Alignment::HORIZONTAL_RIGHT: - $blockAlign = 3; - - break; - case Alignment::HORIZONTAL_CENTER: - $blockAlign = 2; - - break; - case Alignment::HORIZONTAL_CENTER_CONTINUOUS: - $blockAlign = 6; - - break; - case Alignment::HORIZONTAL_JUSTIFY: - $blockAlign = 5; - - break; - } - if ($conditional->getStyle()->getAlignment()->getWrapText() == true) { - $blockAlign |= 1 << 3; - } else { - $blockAlign |= 0 << 3; - } - switch ($conditional->getStyle()->getAlignment()->getVertical()) { - case Alignment::VERTICAL_BOTTOM: - $blockAlign = 2 << 4; - - break; - case Alignment::VERTICAL_TOP: - $blockAlign = 0 << 4; - - break; - case Alignment::VERTICAL_CENTER: - $blockAlign = 1 << 4; - - break; - case Alignment::VERTICAL_JUSTIFY: - $blockAlign = 3 << 4; - - break; - } + $blockAlign = Style\CellAlignment::horizontal($conditional->getStyle()->getAlignment()); + $blockAlign |= Style\CellAlignment::wrap($conditional->getStyle()->getAlignment()) << 3; + $blockAlign |= Style\CellAlignment::vertical($conditional->getStyle()->getAlignment()) << 4; $blockAlign |= 0 << 7; // Text rotation angle @@ -3486,7 +3095,7 @@ class Worksheet extends BIFFwriter // Indentation $blockIndent = $conditional->getStyle()->getAlignment()->getIndent(); - if ($conditional->getStyle()->getAlignment()->getShrinkToFit() == true) { + if ($conditional->getStyle()->getAlignment()->getShrinkToFit() === true) { $blockIndent |= 1 << 4; } else { $blockIndent |= 0 << 4; @@ -3498,891 +3107,50 @@ class Worksheet extends BIFFwriter $dataBlockAlign = pack('CCvvv', $blockAlign, $blockRotation, $blockIndent, $blockIndentRelative, 0x0000); } - if ($bFormatBorder == 1) { - $blockLineStyle = 0; - switch ($conditional->getStyle()->getBorders()->getLeft()->getBorderStyle()) { - case Border::BORDER_NONE: - $blockLineStyle |= 0x00; + if ($bFormatBorder === 1) { + $blockLineStyle = Style\CellBorder::style($conditional->getStyle()->getBorders()->getLeft()); + $blockLineStyle |= Style\CellBorder::style($conditional->getStyle()->getBorders()->getRight()) << 4; + $blockLineStyle |= Style\CellBorder::style($conditional->getStyle()->getBorders()->getTop()) << 8; + $blockLineStyle |= Style\CellBorder::style($conditional->getStyle()->getBorders()->getBottom()) << 12; - break; - case Border::BORDER_THIN: - $blockLineStyle |= 0x01; - - break; - case Border::BORDER_MEDIUM: - $blockLineStyle |= 0x02; - - break; - case Border::BORDER_DASHED: - $blockLineStyle |= 0x03; - - break; - case Border::BORDER_DOTTED: - $blockLineStyle |= 0x04; - - break; - case Border::BORDER_THICK: - $blockLineStyle |= 0x05; - - break; - case Border::BORDER_DOUBLE: - $blockLineStyle |= 0x06; - - break; - case Border::BORDER_HAIR: - $blockLineStyle |= 0x07; - - break; - case Border::BORDER_MEDIUMDASHED: - $blockLineStyle |= 0x08; - - break; - case Border::BORDER_DASHDOT: - $blockLineStyle |= 0x09; - - break; - case Border::BORDER_MEDIUMDASHDOT: - $blockLineStyle |= 0x0A; - - break; - case Border::BORDER_DASHDOTDOT: - $blockLineStyle |= 0x0B; - - break; - case Border::BORDER_MEDIUMDASHDOTDOT: - $blockLineStyle |= 0x0C; - - break; - case Border::BORDER_SLANTDASHDOT: - $blockLineStyle |= 0x0D; - - break; - } - switch ($conditional->getStyle()->getBorders()->getRight()->getBorderStyle()) { - case Border::BORDER_NONE: - $blockLineStyle |= 0x00 << 4; - - break; - case Border::BORDER_THIN: - $blockLineStyle |= 0x01 << 4; - - break; - case Border::BORDER_MEDIUM: - $blockLineStyle |= 0x02 << 4; - - break; - case Border::BORDER_DASHED: - $blockLineStyle |= 0x03 << 4; - - break; - case Border::BORDER_DOTTED: - $blockLineStyle |= 0x04 << 4; - - break; - case Border::BORDER_THICK: - $blockLineStyle |= 0x05 << 4; - - break; - case Border::BORDER_DOUBLE: - $blockLineStyle |= 0x06 << 4; - - break; - case Border::BORDER_HAIR: - $blockLineStyle |= 0x07 << 4; - - break; - case Border::BORDER_MEDIUMDASHED: - $blockLineStyle |= 0x08 << 4; - - break; - case Border::BORDER_DASHDOT: - $blockLineStyle |= 0x09 << 4; - - break; - case Border::BORDER_MEDIUMDASHDOT: - $blockLineStyle |= 0x0A << 4; - - break; - case Border::BORDER_DASHDOTDOT: - $blockLineStyle |= 0x0B << 4; - - break; - case Border::BORDER_MEDIUMDASHDOTDOT: - $blockLineStyle |= 0x0C << 4; - - break; - case Border::BORDER_SLANTDASHDOT: - $blockLineStyle |= 0x0D << 4; - - break; - } - switch ($conditional->getStyle()->getBorders()->getTop()->getBorderStyle()) { - case Border::BORDER_NONE: - $blockLineStyle |= 0x00 << 8; - - break; - case Border::BORDER_THIN: - $blockLineStyle |= 0x01 << 8; - - break; - case Border::BORDER_MEDIUM: - $blockLineStyle |= 0x02 << 8; - - break; - case Border::BORDER_DASHED: - $blockLineStyle |= 0x03 << 8; - - break; - case Border::BORDER_DOTTED: - $blockLineStyle |= 0x04 << 8; - - break; - case Border::BORDER_THICK: - $blockLineStyle |= 0x05 << 8; - - break; - case Border::BORDER_DOUBLE: - $blockLineStyle |= 0x06 << 8; - - break; - case Border::BORDER_HAIR: - $blockLineStyle |= 0x07 << 8; - - break; - case Border::BORDER_MEDIUMDASHED: - $blockLineStyle |= 0x08 << 8; - - break; - case Border::BORDER_DASHDOT: - $blockLineStyle |= 0x09 << 8; - - break; - case Border::BORDER_MEDIUMDASHDOT: - $blockLineStyle |= 0x0A << 8; - - break; - case Border::BORDER_DASHDOTDOT: - $blockLineStyle |= 0x0B << 8; - - break; - case Border::BORDER_MEDIUMDASHDOTDOT: - $blockLineStyle |= 0x0C << 8; - - break; - case Border::BORDER_SLANTDASHDOT: - $blockLineStyle |= 0x0D << 8; - - break; - } - switch ($conditional->getStyle()->getBorders()->getBottom()->getBorderStyle()) { - case Border::BORDER_NONE: - $blockLineStyle |= 0x00 << 12; - - break; - case Border::BORDER_THIN: - $blockLineStyle |= 0x01 << 12; - - break; - case Border::BORDER_MEDIUM: - $blockLineStyle |= 0x02 << 12; - - break; - case Border::BORDER_DASHED: - $blockLineStyle |= 0x03 << 12; - - break; - case Border::BORDER_DOTTED: - $blockLineStyle |= 0x04 << 12; - - break; - case Border::BORDER_THICK: - $blockLineStyle |= 0x05 << 12; - - break; - case Border::BORDER_DOUBLE: - $blockLineStyle |= 0x06 << 12; - - break; - case Border::BORDER_HAIR: - $blockLineStyle |= 0x07 << 12; - - break; - case Border::BORDER_MEDIUMDASHED: - $blockLineStyle |= 0x08 << 12; - - break; - case Border::BORDER_DASHDOT: - $blockLineStyle |= 0x09 << 12; - - break; - case Border::BORDER_MEDIUMDASHDOT: - $blockLineStyle |= 0x0A << 12; - - break; - case Border::BORDER_DASHDOTDOT: - $blockLineStyle |= 0x0B << 12; - - break; - case Border::BORDER_MEDIUMDASHDOTDOT: - $blockLineStyle |= 0x0C << 12; - - break; - case Border::BORDER_SLANTDASHDOT: - $blockLineStyle |= 0x0D << 12; - - break; - } - //@todo writeCFRule() => $blockLineStyle => Index Color for left line - //@todo writeCFRule() => $blockLineStyle => Index Color for right line - //@todo writeCFRule() => $blockLineStyle => Top-left to bottom-right on/off - //@todo writeCFRule() => $blockLineStyle => Bottom-left to top-right on/off + // TODO writeCFRule() => $blockLineStyle => Index Color for left line + // TODO writeCFRule() => $blockLineStyle => Index Color for right line + // TODO writeCFRule() => $blockLineStyle => Top-left to bottom-right on/off + // TODO writeCFRule() => $blockLineStyle => Bottom-left to top-right on/off $blockColor = 0; - //@todo writeCFRule() => $blockColor => Index Color for top line - //@todo writeCFRule() => $blockColor => Index Color for bottom line - //@todo writeCFRule() => $blockColor => Index Color for diagonal line - switch ($conditional->getStyle()->getBorders()->getDiagonal()->getBorderStyle()) { - case Border::BORDER_NONE: - $blockColor |= 0x00 << 21; - - break; - case Border::BORDER_THIN: - $blockColor |= 0x01 << 21; - - break; - case Border::BORDER_MEDIUM: - $blockColor |= 0x02 << 21; - - break; - case Border::BORDER_DASHED: - $blockColor |= 0x03 << 21; - - break; - case Border::BORDER_DOTTED: - $blockColor |= 0x04 << 21; - - break; - case Border::BORDER_THICK: - $blockColor |= 0x05 << 21; - - break; - case Border::BORDER_DOUBLE: - $blockColor |= 0x06 << 21; - - break; - case Border::BORDER_HAIR: - $blockColor |= 0x07 << 21; - - break; - case Border::BORDER_MEDIUMDASHED: - $blockColor |= 0x08 << 21; - - break; - case Border::BORDER_DASHDOT: - $blockColor |= 0x09 << 21; - - break; - case Border::BORDER_MEDIUMDASHDOT: - $blockColor |= 0x0A << 21; - - break; - case Border::BORDER_DASHDOTDOT: - $blockColor |= 0x0B << 21; - - break; - case Border::BORDER_MEDIUMDASHDOTDOT: - $blockColor |= 0x0C << 21; - - break; - case Border::BORDER_SLANTDASHDOT: - $blockColor |= 0x0D << 21; - - break; - } + // TODO writeCFRule() => $blockColor => Index Color for top line + // TODO writeCFRule() => $blockColor => Index Color for bottom line + // TODO writeCFRule() => $blockColor => Index Color for diagonal line + $blockColor |= Style\CellBorder::style($conditional->getStyle()->getBorders()->getDiagonal()) << 21; $dataBlockBorder = pack('vv', $blockLineStyle, $blockColor); } - if ($bFormatFill == 1) { - // Fill Patern Style - $blockFillPatternStyle = 0; - switch ($conditional->getStyle()->getFill()->getFillType()) { - case Fill::FILL_NONE: - $blockFillPatternStyle = 0x00; + if ($bFormatFill === 1) { + // Fill Pattern Style + $blockFillPatternStyle = Style\CellFill::style($conditional->getStyle()->getFill()); + // Background Color + $colorIdxBg = Style\ColorMap::lookup($conditional->getStyle()->getFill()->getStartColor(), 0x41); + // Foreground Color + $colorIdxFg = Style\ColorMap::lookup($conditional->getStyle()->getFill()->getEndColor(), 0x40); - break; - case Fill::FILL_SOLID: - $blockFillPatternStyle = 0x01; - - break; - case Fill::FILL_PATTERN_MEDIUMGRAY: - $blockFillPatternStyle = 0x02; - - break; - case Fill::FILL_PATTERN_DARKGRAY: - $blockFillPatternStyle = 0x03; - - break; - case Fill::FILL_PATTERN_LIGHTGRAY: - $blockFillPatternStyle = 0x04; - - break; - case Fill::FILL_PATTERN_DARKHORIZONTAL: - $blockFillPatternStyle = 0x05; - - break; - case Fill::FILL_PATTERN_DARKVERTICAL: - $blockFillPatternStyle = 0x06; - - break; - case Fill::FILL_PATTERN_DARKDOWN: - $blockFillPatternStyle = 0x07; - - break; - case Fill::FILL_PATTERN_DARKUP: - $blockFillPatternStyle = 0x08; - - break; - case Fill::FILL_PATTERN_DARKGRID: - $blockFillPatternStyle = 0x09; - - break; - case Fill::FILL_PATTERN_DARKTRELLIS: - $blockFillPatternStyle = 0x0A; - - break; - case Fill::FILL_PATTERN_LIGHTHORIZONTAL: - $blockFillPatternStyle = 0x0B; - - break; - case Fill::FILL_PATTERN_LIGHTVERTICAL: - $blockFillPatternStyle = 0x0C; - - break; - case Fill::FILL_PATTERN_LIGHTDOWN: - $blockFillPatternStyle = 0x0D; - - break; - case Fill::FILL_PATTERN_LIGHTUP: - $blockFillPatternStyle = 0x0E; - - break; - case Fill::FILL_PATTERN_LIGHTGRID: - $blockFillPatternStyle = 0x0F; - - break; - case Fill::FILL_PATTERN_LIGHTTRELLIS: - $blockFillPatternStyle = 0x10; - - break; - case Fill::FILL_PATTERN_GRAY125: - $blockFillPatternStyle = 0x11; - - break; - case Fill::FILL_PATTERN_GRAY0625: - $blockFillPatternStyle = 0x12; - - break; - case Fill::FILL_GRADIENT_LINEAR: - $blockFillPatternStyle = 0x00; - - break; // does not exist in BIFF8 - case Fill::FILL_GRADIENT_PATH: - $blockFillPatternStyle = 0x00; - - break; // does not exist in BIFF8 - default: - $blockFillPatternStyle = 0x00; - - break; - } - // Color - switch ($conditional->getStyle()->getFill()->getStartColor()->getRGB()) { - case '000000': - $colorIdxBg = 0x08; - - break; - case 'FFFFFF': - $colorIdxBg = 0x09; - - break; - case 'FF0000': - $colorIdxBg = 0x0A; - - break; - case '00FF00': - $colorIdxBg = 0x0B; - - break; - case '0000FF': - $colorIdxBg = 0x0C; - - break; - case 'FFFF00': - $colorIdxBg = 0x0D; - - break; - case 'FF00FF': - $colorIdxBg = 0x0E; - - break; - case '00FFFF': - $colorIdxBg = 0x0F; - - break; - case '800000': - $colorIdxBg = 0x10; - - break; - case '008000': - $colorIdxBg = 0x11; - - break; - case '000080': - $colorIdxBg = 0x12; - - break; - case '808000': - $colorIdxBg = 0x13; - - break; - case '800080': - $colorIdxBg = 0x14; - - break; - case '008080': - $colorIdxBg = 0x15; - - break; - case 'C0C0C0': - $colorIdxBg = 0x16; - - break; - case '808080': - $colorIdxBg = 0x17; - - break; - case '9999FF': - $colorIdxBg = 0x18; - - break; - case '993366': - $colorIdxBg = 0x19; - - break; - case 'FFFFCC': - $colorIdxBg = 0x1A; - - break; - case 'CCFFFF': - $colorIdxBg = 0x1B; - - break; - case '660066': - $colorIdxBg = 0x1C; - - break; - case 'FF8080': - $colorIdxBg = 0x1D; - - break; - case '0066CC': - $colorIdxBg = 0x1E; - - break; - case 'CCCCFF': - $colorIdxBg = 0x1F; - - break; - case '000080': - $colorIdxBg = 0x20; - - break; - case 'FF00FF': - $colorIdxBg = 0x21; - - break; - case 'FFFF00': - $colorIdxBg = 0x22; - - break; - case '00FFFF': - $colorIdxBg = 0x23; - - break; - case '800080': - $colorIdxBg = 0x24; - - break; - case '800000': - $colorIdxBg = 0x25; - - break; - case '008080': - $colorIdxBg = 0x26; - - break; - case '0000FF': - $colorIdxBg = 0x27; - - break; - case '00CCFF': - $colorIdxBg = 0x28; - - break; - case 'CCFFFF': - $colorIdxBg = 0x29; - - break; - case 'CCFFCC': - $colorIdxBg = 0x2A; - - break; - case 'FFFF99': - $colorIdxBg = 0x2B; - - break; - case '99CCFF': - $colorIdxBg = 0x2C; - - break; - case 'FF99CC': - $colorIdxBg = 0x2D; - - break; - case 'CC99FF': - $colorIdxBg = 0x2E; - - break; - case 'FFCC99': - $colorIdxBg = 0x2F; - - break; - case '3366FF': - $colorIdxBg = 0x30; - - break; - case '33CCCC': - $colorIdxBg = 0x31; - - break; - case '99CC00': - $colorIdxBg = 0x32; - - break; - case 'FFCC00': - $colorIdxBg = 0x33; - - break; - case 'FF9900': - $colorIdxBg = 0x34; - - break; - case 'FF6600': - $colorIdxBg = 0x35; - - break; - case '666699': - $colorIdxBg = 0x36; - - break; - case '969696': - $colorIdxBg = 0x37; - - break; - case '003366': - $colorIdxBg = 0x38; - - break; - case '339966': - $colorIdxBg = 0x39; - - break; - case '003300': - $colorIdxBg = 0x3A; - - break; - case '333300': - $colorIdxBg = 0x3B; - - break; - case '993300': - $colorIdxBg = 0x3C; - - break; - case '993366': - $colorIdxBg = 0x3D; - - break; - case '333399': - $colorIdxBg = 0x3E; - - break; - case '333333': - $colorIdxBg = 0x3F; - - break; - default: - $colorIdxBg = 0x41; - - break; - } - // Fg Color - switch ($conditional->getStyle()->getFill()->getEndColor()->getRGB()) { - case '000000': - $colorIdxFg = 0x08; - - break; - case 'FFFFFF': - $colorIdxFg = 0x09; - - break; - case 'FF0000': - $colorIdxFg = 0x0A; - - break; - case '00FF00': - $colorIdxFg = 0x0B; - - break; - case '0000FF': - $colorIdxFg = 0x0C; - - break; - case 'FFFF00': - $colorIdxFg = 0x0D; - - break; - case 'FF00FF': - $colorIdxFg = 0x0E; - - break; - case '00FFFF': - $colorIdxFg = 0x0F; - - break; - case '800000': - $colorIdxFg = 0x10; - - break; - case '008000': - $colorIdxFg = 0x11; - - break; - case '000080': - $colorIdxFg = 0x12; - - break; - case '808000': - $colorIdxFg = 0x13; - - break; - case '800080': - $colorIdxFg = 0x14; - - break; - case '008080': - $colorIdxFg = 0x15; - - break; - case 'C0C0C0': - $colorIdxFg = 0x16; - - break; - case '808080': - $colorIdxFg = 0x17; - - break; - case '9999FF': - $colorIdxFg = 0x18; - - break; - case '993366': - $colorIdxFg = 0x19; - - break; - case 'FFFFCC': - $colorIdxFg = 0x1A; - - break; - case 'CCFFFF': - $colorIdxFg = 0x1B; - - break; - case '660066': - $colorIdxFg = 0x1C; - - break; - case 'FF8080': - $colorIdxFg = 0x1D; - - break; - case '0066CC': - $colorIdxFg = 0x1E; - - break; - case 'CCCCFF': - $colorIdxFg = 0x1F; - - break; - case '000080': - $colorIdxFg = 0x20; - - break; - case 'FF00FF': - $colorIdxFg = 0x21; - - break; - case 'FFFF00': - $colorIdxFg = 0x22; - - break; - case '00FFFF': - $colorIdxFg = 0x23; - - break; - case '800080': - $colorIdxFg = 0x24; - - break; - case '800000': - $colorIdxFg = 0x25; - - break; - case '008080': - $colorIdxFg = 0x26; - - break; - case '0000FF': - $colorIdxFg = 0x27; - - break; - case '00CCFF': - $colorIdxFg = 0x28; - - break; - case 'CCFFFF': - $colorIdxFg = 0x29; - - break; - case 'CCFFCC': - $colorIdxFg = 0x2A; - - break; - case 'FFFF99': - $colorIdxFg = 0x2B; - - break; - case '99CCFF': - $colorIdxFg = 0x2C; - - break; - case 'FF99CC': - $colorIdxFg = 0x2D; - - break; - case 'CC99FF': - $colorIdxFg = 0x2E; - - break; - case 'FFCC99': - $colorIdxFg = 0x2F; - - break; - case '3366FF': - $colorIdxFg = 0x30; - - break; - case '33CCCC': - $colorIdxFg = 0x31; - - break; - case '99CC00': - $colorIdxFg = 0x32; - - break; - case 'FFCC00': - $colorIdxFg = 0x33; - - break; - case 'FF9900': - $colorIdxFg = 0x34; - - break; - case 'FF6600': - $colorIdxFg = 0x35; - - break; - case '666699': - $colorIdxFg = 0x36; - - break; - case '969696': - $colorIdxFg = 0x37; - - break; - case '003366': - $colorIdxFg = 0x38; - - break; - case '339966': - $colorIdxFg = 0x39; - - break; - case '003300': - $colorIdxFg = 0x3A; - - break; - case '333300': - $colorIdxFg = 0x3B; - - break; - case '993300': - $colorIdxFg = 0x3C; - - break; - case '993366': - $colorIdxFg = 0x3D; - - break; - case '333399': - $colorIdxFg = 0x3E; - - break; - case '333333': - $colorIdxFg = 0x3F; - - break; - default: - $colorIdxFg = 0x40; - - break; - } $dataBlockFill = pack('v', $blockFillPatternStyle); $dataBlockFill .= pack('v', $colorIdxFg | ($colorIdxBg << 7)); } - if ($bFormatProt == 1) { - $dataBlockProtection = 0; - if ($conditional->getStyle()->getProtection()->getLocked() == Protection::PROTECTION_PROTECTED) { - $dataBlockProtection = 1; - } - if ($conditional->getStyle()->getProtection()->getHidden() == Protection::PROTECTION_PROTECTED) { - $dataBlockProtection = 1 << 1; - } - } $data = pack('CCvvVv', $type, $operatorType, $szValue1, $szValue2, $flags, 0x0000); - if ($bFormatFont == 1) { // Block Formatting : OK + if ($bFormatFont === 1) { // Block Formatting : OK $data .= $dataBlockFont; } - if ($bFormatAlign == 1) { + if ($bFormatAlign === 1) { $data .= $dataBlockAlign; } - if ($bFormatBorder == 1) { + if ($bFormatBorder === 1) { $data .= $dataBlockBorder; } - if ($bFormatFill == 1) { // Block Formatting : OK + if ($bFormatFill === 1) { // Block Formatting : OK $data .= $dataBlockFill; } if ($bFormatProt == 1) { - $data .= $dataBlockProtection; + $data .= $this->getDataBlockProtection($conditional); } if ($operand1 !== null) { $data .= $operand1; @@ -4396,8 +3164,10 @@ class Worksheet extends BIFFwriter /** * Write CFHeader record. + * + * @param Conditional[] $conditionalStyles */ - private function writeCFHeader() + private function writeCFHeader(string $cellCoordinate, array $conditionalStyles): bool { $record = 0x01B0; // Record identifier $length = 0x0016; // Bytes to follow @@ -4406,34 +3176,32 @@ class Worksheet extends BIFFwriter $numColumnMax = null; $numRowMin = null; $numRowMax = null; + $arrConditional = []; - foreach ($this->phpSheet->getConditionalStylesCollection() as $cellCoordinate => $conditionalStyles) { - foreach ($conditionalStyles as $conditional) { - if ($conditional->getConditionType() == Conditional::CONDITION_EXPRESSION - || $conditional->getConditionType() == Conditional::CONDITION_CELLIS) { - if (!in_array($conditional->getHashCode(), $arrConditional)) { - $arrConditional[] = $conditional->getHashCode(); - } - // Cells - $arrCoord = Coordinate::coordinateFromString($cellCoordinate); - if (!is_numeric($arrCoord[0])) { - $arrCoord[0] = Coordinate::columnIndexFromString($arrCoord[0]); - } - if ($numColumnMin === null || ($numColumnMin > $arrCoord[0])) { - $numColumnMin = $arrCoord[0]; - } - if ($numColumnMax === null || ($numColumnMax < $arrCoord[0])) { - $numColumnMax = $arrCoord[0]; - } - if ($numRowMin === null || ($numRowMin > $arrCoord[1])) { - $numRowMin = $arrCoord[1]; - } - if ($numRowMax === null || ($numRowMax < $arrCoord[1])) { - $numRowMax = $arrCoord[1]; - } - } + foreach ($conditionalStyles as $conditional) { + if (!in_array($conditional->getHashCode(), $arrConditional)) { + $arrConditional[] = $conditional->getHashCode(); + } + // Cells + $rangeCoordinates = Coordinate::rangeBoundaries($cellCoordinate); + if ($numColumnMin === null || ($numColumnMin > $rangeCoordinates[0][0])) { + $numColumnMin = $rangeCoordinates[0][0]; + } + if ($numColumnMax === null || ($numColumnMax < $rangeCoordinates[1][0])) { + $numColumnMax = $rangeCoordinates[1][0]; + } + if ($numRowMin === null || ($numRowMin > $rangeCoordinates[0][1])) { + $numRowMin = (int) $rangeCoordinates[0][1]; + } + if ($numRowMax === null || ($numRowMax < $rangeCoordinates[1][1])) { + $numRowMax = (int) $rangeCoordinates[1][1]; } } + + if (count($arrConditional) === 0) { + return false; + } + $needRedraw = 1; $cellRange = pack('vvvv', $numRowMin - 1, $numRowMax - 1, $numColumnMin - 1, $numColumnMax - 1); @@ -4443,5 +3211,20 @@ class Worksheet extends BIFFwriter $data .= pack('v', 0x0001); $data .= $cellRange; $this->append($header . $data); + + return true; + } + + private function getDataBlockProtection(Conditional $conditional): int + { + $dataBlockProtection = 0; + if ($conditional->getStyle()->getProtection()->getLocked() == Protection::PROTECTION_PROTECTED) { + $dataBlockProtection = 1; + } + if ($conditional->getStyle()->getProtection()->getHidden() == Protection::PROTECTION_PROTECTED) { + $dataBlockProtection = 1 << 1; + } + + return $dataBlockProtection; } } diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xls/Xf.php b/PhpOffice/PhpSpreadsheet/Writer/Xls/Xf.php old mode 100755 new mode 100644 index 238fb34..b2dbc67 --- a/PhpOffice/PhpSpreadsheet/Writer/Xls/Xf.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Xls/Xf.php @@ -3,11 +3,12 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Xls; use PhpOffice\PhpSpreadsheet\Style\Alignment; -use PhpOffice\PhpSpreadsheet\Style\Border; use PhpOffice\PhpSpreadsheet\Style\Borders; -use PhpOffice\PhpSpreadsheet\Style\Fill; use PhpOffice\PhpSpreadsheet\Style\Protection; use PhpOffice\PhpSpreadsheet\Style\Style; +use PhpOffice\PhpSpreadsheet\Writer\Xls\Style\CellAlignment; +use PhpOffice\PhpSpreadsheet\Writer\Xls\Style\CellBorder; +use PhpOffice\PhpSpreadsheet\Writer\Xls\Style\CellFill; // Original file header of PEAR::Spreadsheet_Excel_Writer_Format (used as the base for this class): // ----------------------------------------------------------------------------------------- @@ -115,6 +116,21 @@ class Xf */ private $rightBorderColor; + /** + * @var int + */ + private $diag; + + /** + * @var int + */ + private $diagColor; + + /** + * @var Style + */ + private $style; + /** * Constructor. * @@ -132,14 +148,14 @@ class Xf $this->foregroundColor = 0x40; $this->backgroundColor = 0x41; - $this->_diag = 0; + $this->diag = 0; $this->bottomBorderColor = 0x40; $this->topBorderColor = 0x40; $this->leftBorderColor = 0x40; $this->rightBorderColor = 0x40; - $this->_diag_color = 0x40; - $this->_style = $style; + $this->diagColor = 0x40; + $this->style = $style; } /** @@ -153,39 +169,39 @@ class Xf if ($this->isStyleXf) { $style = 0xFFF5; } else { - $style = self::mapLocked($this->_style->getProtection()->getLocked()); - $style |= self::mapHidden($this->_style->getProtection()->getHidden()) << 1; + $style = self::mapLocked($this->style->getProtection()->getLocked()); + $style |= self::mapHidden($this->style->getProtection()->getHidden()) << 1; } // Flags to indicate if attributes have been set. $atr_num = ($this->numberFormatIndex != 0) ? 1 : 0; $atr_fnt = ($this->fontIndex != 0) ? 1 : 0; - $atr_alc = ((int) $this->_style->getAlignment()->getWrapText()) ? 1 : 0; - $atr_bdr = (self::mapBorderStyle($this->_style->getBorders()->getBottom()->getBorderStyle()) || - self::mapBorderStyle($this->_style->getBorders()->getTop()->getBorderStyle()) || - self::mapBorderStyle($this->_style->getBorders()->getLeft()->getBorderStyle()) || - self::mapBorderStyle($this->_style->getBorders()->getRight()->getBorderStyle())) ? 1 : 0; - $atr_pat = (($this->foregroundColor != 0x40) || - ($this->backgroundColor != 0x41) || - self::mapFillType($this->_style->getFill()->getFillType())) ? 1 : 0; - $atr_prot = self::mapLocked($this->_style->getProtection()->getLocked()) - | self::mapHidden($this->_style->getProtection()->getHidden()); + $atr_alc = ((int) $this->style->getAlignment()->getWrapText()) ? 1 : 0; + $atr_bdr = (CellBorder::style($this->style->getBorders()->getBottom()) || + CellBorder::style($this->style->getBorders()->getTop()) || + CellBorder::style($this->style->getBorders()->getLeft()) || + CellBorder::style($this->style->getBorders()->getRight())) ? 1 : 0; + $atr_pat = ($this->foregroundColor != 0x40) ? 1 : 0; + $atr_pat = ($this->backgroundColor != 0x41) ? 1 : $atr_pat; + $atr_pat = CellFill::style($this->style->getFill()) ? 1 : $atr_pat; + $atr_prot = self::mapLocked($this->style->getProtection()->getLocked()) + | self::mapHidden($this->style->getProtection()->getHidden()); // Zero the default border colour if the border has not been set. - if (self::mapBorderStyle($this->_style->getBorders()->getBottom()->getBorderStyle()) == 0) { + if (CellBorder::style($this->style->getBorders()->getBottom()) == 0) { $this->bottomBorderColor = 0; } - if (self::mapBorderStyle($this->_style->getBorders()->getTop()->getBorderStyle()) == 0) { + if (CellBorder::style($this->style->getBorders()->getTop()) == 0) { $this->topBorderColor = 0; } - if (self::mapBorderStyle($this->_style->getBorders()->getRight()->getBorderStyle()) == 0) { + if (CellBorder::style($this->style->getBorders()->getRight()) == 0) { $this->rightBorderColor = 0; } - if (self::mapBorderStyle($this->_style->getBorders()->getLeft()->getBorderStyle()) == 0) { + if (CellBorder::style($this->style->getBorders()->getLeft()) == 0) { $this->leftBorderColor = 0; } - if (self::mapBorderStyle($this->_style->getBorders()->getDiagonal()->getBorderStyle()) == 0) { - $this->_diag_color = 0; + if (CellBorder::style($this->style->getBorders()->getDiagonal()) == 0) { + $this->diagColor = 0; } $record = 0x00E0; // Record identifier @@ -194,9 +210,10 @@ class Xf $ifnt = $this->fontIndex; // Index to FONT record $ifmt = $this->numberFormatIndex; // Index to FORMAT record - $align = $this->mapHAlign($this->_style->getAlignment()->getHorizontal()); // Alignment - $align |= (int) $this->_style->getAlignment()->getWrapText() << 3; - $align |= self::mapVAlign($this->_style->getAlignment()->getVertical()) << 4; + // Alignment + $align = CellAlignment::horizontal($this->style->getAlignment()); + $align |= CellAlignment::wrap($this->style->getAlignment()) << 3; + $align |= CellAlignment::vertical($this->style->getAlignment()) << 4; $align |= $this->textJustLast << 7; $used_attrib = $atr_num << 2; @@ -209,35 +226,35 @@ class Xf $icv = $this->foregroundColor; // fg and bg pattern colors $icv |= $this->backgroundColor << 7; - $border1 = self::mapBorderStyle($this->_style->getBorders()->getLeft()->getBorderStyle()); // Border line style and color - $border1 |= self::mapBorderStyle($this->_style->getBorders()->getRight()->getBorderStyle()) << 4; - $border1 |= self::mapBorderStyle($this->_style->getBorders()->getTop()->getBorderStyle()) << 8; - $border1 |= self::mapBorderStyle($this->_style->getBorders()->getBottom()->getBorderStyle()) << 12; + $border1 = CellBorder::style($this->style->getBorders()->getLeft()); // Border line style and color + $border1 |= CellBorder::style($this->style->getBorders()->getRight()) << 4; + $border1 |= CellBorder::style($this->style->getBorders()->getTop()) << 8; + $border1 |= CellBorder::style($this->style->getBorders()->getBottom()) << 12; $border1 |= $this->leftBorderColor << 16; $border1 |= $this->rightBorderColor << 23; - $diagonalDirection = $this->_style->getBorders()->getDiagonalDirection(); + $diagonalDirection = $this->style->getBorders()->getDiagonalDirection(); $diag_tl_to_rb = $diagonalDirection == Borders::DIAGONAL_BOTH - || $diagonalDirection == Borders::DIAGONAL_DOWN; + || $diagonalDirection == Borders::DIAGONAL_DOWN; $diag_tr_to_lb = $diagonalDirection == Borders::DIAGONAL_BOTH - || $diagonalDirection == Borders::DIAGONAL_UP; + || $diagonalDirection == Borders::DIAGONAL_UP; $border1 |= $diag_tl_to_rb << 30; $border1 |= $diag_tr_to_lb << 31; $border2 = $this->topBorderColor; // Border color $border2 |= $this->bottomBorderColor << 7; - $border2 |= $this->_diag_color << 14; - $border2 |= self::mapBorderStyle($this->_style->getBorders()->getDiagonal()->getBorderStyle()) << 21; - $border2 |= self::mapFillType($this->_style->getFill()->getFillType()) << 26; + $border2 |= $this->diagColor << 14; + $border2 |= CellBorder::style($this->style->getBorders()->getDiagonal()) << 21; + $border2 |= CellFill::style($this->style->getFill()) << 26; $header = pack('vv', $record, $length); //BIFF8 options: identation, shrinkToFit and text direction - $biff8_options = $this->_style->getAlignment()->getIndent(); - $biff8_options |= (int) $this->_style->getAlignment()->getShrinkToFit() << 4; + $biff8_options = $this->style->getAlignment()->getIndent(); + $biff8_options |= (int) $this->style->getAlignment()->getShrinkToFit() << 4; $data = pack('vvvC', $ifnt, $ifmt, $style, $align); - $data .= pack('CCC', self::mapTextRotation($this->_style->getAlignment()->getTextRotation()), $biff8_options, $used_attrib); + $data .= pack('CCC', self::mapTextRotation($this->style->getAlignment()->getTextRotation()), $biff8_options, $used_attrib); $data .= pack('VVv', $border1, $border2, $icv); return $header . $data; @@ -248,7 +265,7 @@ class Xf * * @param bool $value */ - public function setIsStyleXf($value) + public function setIsStyleXf($value): void { $this->isStyleXf = $value; } @@ -258,7 +275,7 @@ class Xf * * @param int $colorIndex Color index */ - public function setBottomColor($colorIndex) + public function setBottomColor($colorIndex): void { $this->bottomBorderColor = $colorIndex; } @@ -268,7 +285,7 @@ class Xf * * @param int $colorIndex Color index */ - public function setTopColor($colorIndex) + public function setTopColor($colorIndex): void { $this->topBorderColor = $colorIndex; } @@ -278,7 +295,7 @@ class Xf * * @param int $colorIndex Color index */ - public function setLeftColor($colorIndex) + public function setLeftColor($colorIndex): void { $this->leftBorderColor = $colorIndex; } @@ -288,7 +305,7 @@ class Xf * * @param int $colorIndex Color index */ - public function setRightColor($colorIndex) + public function setRightColor($colorIndex): void { $this->rightBorderColor = $colorIndex; } @@ -298,9 +315,9 @@ class Xf * * @param int $colorIndex Color index */ - public function setDiagColor($colorIndex) + public function setDiagColor($colorIndex): void { - $this->_diag_color = $colorIndex; + $this->diagColor = $colorIndex; } /** @@ -308,7 +325,7 @@ class Xf * * @param int $colorIndex Color index */ - public function setFgColor($colorIndex) + public function setFgColor($colorIndex): void { $this->foregroundColor = $colorIndex; } @@ -318,7 +335,7 @@ class Xf * * @param int $colorIndex Color index */ - public function setBgColor($colorIndex) + public function setBgColor($colorIndex): void { $this->backgroundColor = $colorIndex; } @@ -329,7 +346,7 @@ class Xf * * @param int $numberFormatIndex Index to format record */ - public function setNumberFormatIndex($numberFormatIndex) + public function setNumberFormatIndex($numberFormatIndex): void { $this->numberFormatIndex = $numberFormatIndex; } @@ -339,153 +356,11 @@ class Xf * * @param int $value Font index, note that value 4 does not exist */ - public function setFontIndex($value) + public function setFontIndex($value): void { $this->fontIndex = $value; } - /** - * Map of BIFF2-BIFF8 codes for border styles. - * - * @var array of int - */ - private static $mapBorderStyles = [ - Border::BORDER_NONE => 0x00, - Border::BORDER_THIN => 0x01, - Border::BORDER_MEDIUM => 0x02, - Border::BORDER_DASHED => 0x03, - Border::BORDER_DOTTED => 0x04, - Border::BORDER_THICK => 0x05, - Border::BORDER_DOUBLE => 0x06, - Border::BORDER_HAIR => 0x07, - Border::BORDER_MEDIUMDASHED => 0x08, - Border::BORDER_DASHDOT => 0x09, - Border::BORDER_MEDIUMDASHDOT => 0x0A, - Border::BORDER_DASHDOTDOT => 0x0B, - Border::BORDER_MEDIUMDASHDOTDOT => 0x0C, - Border::BORDER_SLANTDASHDOT => 0x0D, - ]; - - /** - * Map border style. - * - * @param string $borderStyle - * - * @return int - */ - private static function mapBorderStyle($borderStyle) - { - if (isset(self::$mapBorderStyles[$borderStyle])) { - return self::$mapBorderStyles[$borderStyle]; - } - - return 0x00; - } - - /** - * Map of BIFF2-BIFF8 codes for fill types. - * - * @var array of int - */ - private static $mapFillTypes = [ - Fill::FILL_NONE => 0x00, - Fill::FILL_SOLID => 0x01, - Fill::FILL_PATTERN_MEDIUMGRAY => 0x02, - Fill::FILL_PATTERN_DARKGRAY => 0x03, - Fill::FILL_PATTERN_LIGHTGRAY => 0x04, - Fill::FILL_PATTERN_DARKHORIZONTAL => 0x05, - Fill::FILL_PATTERN_DARKVERTICAL => 0x06, - Fill::FILL_PATTERN_DARKDOWN => 0x07, - Fill::FILL_PATTERN_DARKUP => 0x08, - Fill::FILL_PATTERN_DARKGRID => 0x09, - Fill::FILL_PATTERN_DARKTRELLIS => 0x0A, - Fill::FILL_PATTERN_LIGHTHORIZONTAL => 0x0B, - Fill::FILL_PATTERN_LIGHTVERTICAL => 0x0C, - Fill::FILL_PATTERN_LIGHTDOWN => 0x0D, - Fill::FILL_PATTERN_LIGHTUP => 0x0E, - Fill::FILL_PATTERN_LIGHTGRID => 0x0F, - Fill::FILL_PATTERN_LIGHTTRELLIS => 0x10, - Fill::FILL_PATTERN_GRAY125 => 0x11, - Fill::FILL_PATTERN_GRAY0625 => 0x12, - Fill::FILL_GRADIENT_LINEAR => 0x00, // does not exist in BIFF8 - Fill::FILL_GRADIENT_PATH => 0x00, // does not exist in BIFF8 - ]; - - /** - * Map fill type. - * - * @param string $fillType - * - * @return int - */ - private static function mapFillType($fillType) - { - if (isset(self::$mapFillTypes[$fillType])) { - return self::$mapFillTypes[$fillType]; - } - - return 0x00; - } - - /** - * Map of BIFF2-BIFF8 codes for horizontal alignment. - * - * @var array of int - */ - private static $mapHAlignments = [ - Alignment::HORIZONTAL_GENERAL => 0, - Alignment::HORIZONTAL_LEFT => 1, - Alignment::HORIZONTAL_CENTER => 2, - Alignment::HORIZONTAL_RIGHT => 3, - Alignment::HORIZONTAL_FILL => 4, - Alignment::HORIZONTAL_JUSTIFY => 5, - Alignment::HORIZONTAL_CENTER_CONTINUOUS => 6, - ]; - - /** - * Map to BIFF2-BIFF8 codes for horizontal alignment. - * - * @param string $hAlign - * - * @return int - */ - private function mapHAlign($hAlign) - { - if (isset(self::$mapHAlignments[$hAlign])) { - return self::$mapHAlignments[$hAlign]; - } - - return 0; - } - - /** - * Map of BIFF2-BIFF8 codes for vertical alignment. - * - * @var array of int - */ - private static $mapVAlignments = [ - Alignment::VERTICAL_TOP => 0, - Alignment::VERTICAL_CENTER => 1, - Alignment::VERTICAL_BOTTOM => 2, - Alignment::VERTICAL_JUSTIFY => 3, - ]; - - /** - * Map to BIFF2-BIFF8 codes for vertical alignment. - * - * @param string $vAlign - * - * @return int - */ - private static function mapVAlign($vAlign) - { - if (isset(self::$mapVAlignments[$vAlign])) { - return self::$mapVAlignments[$vAlign]; - } - - return 2; - } - /** * Map to BIFF8 codes for text rotation angle. * @@ -497,15 +372,22 @@ class Xf { if ($textRotation >= 0) { return $textRotation; - } elseif ($textRotation == -165) { - return 255; - } elseif ($textRotation < 0) { - return 90 - $textRotation; } + if ($textRotation == Alignment::TEXTROTATION_STACK_PHPSPREADSHEET) { + return Alignment::TEXTROTATION_STACK_EXCEL; + } + + return 90 - $textRotation; } + private const LOCK_ARRAY = [ + Protection::PROTECTION_INHERIT => 1, + Protection::PROTECTION_PROTECTED => 1, + Protection::PROTECTION_UNPROTECTED => 0, + ]; + /** - * Map locked. + * Map locked values. * * @param string $locked * @@ -513,18 +395,15 @@ class Xf */ private static function mapLocked($locked) { - switch ($locked) { - case Protection::PROTECTION_INHERIT: - return 1; - case Protection::PROTECTION_PROTECTED: - return 1; - case Protection::PROTECTION_UNPROTECTED: - return 0; - default: - return 1; - } + return array_key_exists($locked, self::LOCK_ARRAY) ? self::LOCK_ARRAY[$locked] : 1; } + private const HIDDEN_ARRAY = [ + Protection::PROTECTION_INHERIT => 0, + Protection::PROTECTION_PROTECTED => 1, + Protection::PROTECTION_UNPROTECTED => 0, + ]; + /** * Map hidden. * @@ -534,15 +413,6 @@ class Xf */ private static function mapHidden($hidden) { - switch ($hidden) { - case Protection::PROTECTION_INHERIT: - return 0; - case Protection::PROTECTION_PROTECTED: - return 1; - case Protection::PROTECTION_UNPROTECTED: - return 0; - default: - return 0; - } + return array_key_exists($hidden, self::HIDDEN_ARRAY) ? self::HIDDEN_ARRAY[$hidden] : 0; } } diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xlsx.php b/PhpOffice/PhpSpreadsheet/Writer/Xlsx.php old mode 100755 new mode 100644 index 5889763..07b7904 --- a/PhpOffice/PhpSpreadsheet/Writer/Xlsx.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Xlsx.php @@ -5,8 +5,13 @@ namespace PhpOffice\PhpSpreadsheet\Writer; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Calculation\Functions; use PhpOffice\PhpSpreadsheet\HashTable; -use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Style\Borders; +use PhpOffice\PhpSpreadsheet\Style\Conditional; +use PhpOffice\PhpSpreadsheet\Style\Fill; +use PhpOffice\PhpSpreadsheet\Style\Font; +use PhpOffice\PhpSpreadsheet\Style\NumberFormat; +use PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing; use PhpOffice\PhpSpreadsheet\Worksheet\Drawing as WorksheetDrawing; use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing; use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException; @@ -20,10 +25,14 @@ use PhpOffice\PhpSpreadsheet\Writer\Xlsx\RelsRibbon; use PhpOffice\PhpSpreadsheet\Writer\Xlsx\RelsVBA; use PhpOffice\PhpSpreadsheet\Writer\Xlsx\StringTable; use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Style; +use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Table; use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Theme; use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Workbook; use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Worksheet; use ZipArchive; +use ZipStream\Exception\OverflowException; +use ZipStream\Option\Archive; +use ZipStream\ZipStream; class Xlsx extends BaseWriter { @@ -34,13 +43,6 @@ class Xlsx extends BaseWriter */ private $office2003compatibility = false; - /** - * Private writer parts. - * - * @var Xlsx\WriterPart[] - */ - private $writerParts = []; - /** * Private Spreadsheet. * @@ -58,384 +60,518 @@ class Xlsx extends BaseWriter /** * Private unique Conditional HashTable. * - * @var HashTable + * @var HashTable */ private $stylesConditionalHashTable; /** * Private unique Style HashTable. * - * @var HashTable + * @var HashTable<\PhpOffice\PhpSpreadsheet\Style\Style> */ private $styleHashTable; /** * Private unique Fill HashTable. * - * @var HashTable + * @var HashTable */ private $fillHashTable; /** * Private unique \PhpOffice\PhpSpreadsheet\Style\Font HashTable. * - * @var HashTable + * @var HashTable */ private $fontHashTable; /** * Private unique Borders HashTable. * - * @var HashTable + * @var HashTable */ private $bordersHashTable; /** * Private unique NumberFormat HashTable. * - * @var HashTable + * @var HashTable */ private $numFmtHashTable; /** * Private unique \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\BaseDrawing HashTable. * - * @var HashTable + * @var HashTable */ private $drawingHashTable; /** - * Create a new Xlsx Writer. + * Private handle for zip stream. * - * @param Spreadsheet $spreadsheet + * @var ZipStream + */ + private $zip; + + /** + * @var Chart + */ + private $writerPartChart; + + /** + * @var Comments + */ + private $writerPartComments; + + /** + * @var ContentTypes + */ + private $writerPartContentTypes; + + /** + * @var DocProps + */ + private $writerPartDocProps; + + /** + * @var Drawing + */ + private $writerPartDrawing; + + /** + * @var Rels + */ + private $writerPartRels; + + /** + * @var RelsRibbon + */ + private $writerPartRelsRibbon; + + /** + * @var RelsVBA + */ + private $writerPartRelsVBA; + + /** + * @var StringTable + */ + private $writerPartStringTable; + + /** + * @var Style + */ + private $writerPartStyle; + + /** + * @var Theme + */ + private $writerPartTheme; + + /** + * @var Table + */ + private $writerPartTable; + + /** + * @var Workbook + */ + private $writerPartWorkbook; + + /** + * @var Worksheet + */ + private $writerPartWorksheet; + + /** + * Create a new Xlsx Writer. */ public function __construct(Spreadsheet $spreadsheet) { // Assign PhpSpreadsheet $this->setSpreadsheet($spreadsheet); - $writerPartsArray = [ - 'stringtable' => StringTable::class, - 'contenttypes' => ContentTypes::class, - 'docprops' => DocProps::class, - 'rels' => Rels::class, - 'theme' => Theme::class, - 'style' => Style::class, - 'workbook' => Workbook::class, - 'worksheet' => Worksheet::class, - 'drawing' => Drawing::class, - 'comments' => Comments::class, - 'chart' => Chart::class, - 'relsvba' => RelsVBA::class, - 'relsribbonobjects' => RelsRibbon::class, - ]; - - // Initialise writer parts - // and Assign their parent IWriters - foreach ($writerPartsArray as $writer => $class) { - $this->writerParts[$writer] = new $class($this); - } - - $hashTablesArray = ['stylesConditionalHashTable', 'fillHashTable', 'fontHashTable', - 'bordersHashTable', 'numFmtHashTable', 'drawingHashTable', - 'styleHashTable', - ]; + $this->writerPartChart = new Chart($this); + $this->writerPartComments = new Comments($this); + $this->writerPartContentTypes = new ContentTypes($this); + $this->writerPartDocProps = new DocProps($this); + $this->writerPartDrawing = new Drawing($this); + $this->writerPartRels = new Rels($this); + $this->writerPartRelsRibbon = new RelsRibbon($this); + $this->writerPartRelsVBA = new RelsVBA($this); + $this->writerPartStringTable = new StringTable($this); + $this->writerPartStyle = new Style($this); + $this->writerPartTheme = new Theme($this); + $this->writerPartTable = new Table($this); + $this->writerPartWorkbook = new Workbook($this); + $this->writerPartWorksheet = new Worksheet($this); // Set HashTable variables - foreach ($hashTablesArray as $tableName) { - $this->$tableName = new HashTable(); - } + // @phpstan-ignore-next-line + $this->bordersHashTable = new HashTable(); + // @phpstan-ignore-next-line + $this->drawingHashTable = new HashTable(); + // @phpstan-ignore-next-line + $this->fillHashTable = new HashTable(); + // @phpstan-ignore-next-line + $this->fontHashTable = new HashTable(); + // @phpstan-ignore-next-line + $this->numFmtHashTable = new HashTable(); + // @phpstan-ignore-next-line + $this->styleHashTable = new HashTable(); + // @phpstan-ignore-next-line + $this->stylesConditionalHashTable = new HashTable(); } - /** - * Get writer part. - * - * @param string $pPartName Writer part name - * - * @return \PhpOffice\PhpSpreadsheet\Writer\Xlsx\WriterPart - */ - public function getWriterPart($pPartName) + public function getWriterPartChart(): Chart { - if ($pPartName != '' && isset($this->writerParts[strtolower($pPartName)])) { - return $this->writerParts[strtolower($pPartName)]; - } + return $this->writerPartChart; + } - return null; + public function getWriterPartComments(): Comments + { + return $this->writerPartComments; + } + + public function getWriterPartContentTypes(): ContentTypes + { + return $this->writerPartContentTypes; + } + + public function getWriterPartDocProps(): DocProps + { + return $this->writerPartDocProps; + } + + public function getWriterPartDrawing(): Drawing + { + return $this->writerPartDrawing; + } + + public function getWriterPartRels(): Rels + { + return $this->writerPartRels; + } + + public function getWriterPartRelsRibbon(): RelsRibbon + { + return $this->writerPartRelsRibbon; + } + + public function getWriterPartRelsVBA(): RelsVBA + { + return $this->writerPartRelsVBA; + } + + public function getWriterPartStringTable(): StringTable + { + return $this->writerPartStringTable; + } + + public function getWriterPartStyle(): Style + { + return $this->writerPartStyle; + } + + public function getWriterPartTheme(): Theme + { + return $this->writerPartTheme; + } + + public function getWriterPartTable(): Table + { + return $this->writerPartTable; + } + + public function getWriterPartWorkbook(): Workbook + { + return $this->writerPartWorkbook; + } + + public function getWriterPartWorksheet(): Worksheet + { + return $this->writerPartWorksheet; } /** * Save PhpSpreadsheet to file. * - * @param string $pFilename - * - * @throws WriterException + * @param resource|string $filename */ - public function save($pFilename) + public function save($filename, int $flags = 0): void { - if ($this->spreadSheet !== null) { - // garbage collect - $this->spreadSheet->garbageCollect(); + $this->processFlags($flags); - // If $pFilename is php://output or php://stdout, make it a temporary file... - $originalFilename = $pFilename; - if (strtolower($pFilename) == 'php://output' || strtolower($pFilename) == 'php://stdout') { - $pFilename = @tempnam(File::sysGetTempDir(), 'phpxltmp'); - if ($pFilename == '') { - $pFilename = $originalFilename; - } - } + // garbage collect + $this->pathNames = []; + $this->spreadSheet->garbageCollect(); - $saveDebugLog = Calculation::getInstance($this->spreadSheet)->getDebugLog()->getWriteDebugLog(); - Calculation::getInstance($this->spreadSheet)->getDebugLog()->setWriteDebugLog(false); - $saveDateReturnType = Functions::getReturnDateType(); - Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); + $saveDebugLog = Calculation::getInstance($this->spreadSheet)->getDebugLog()->getWriteDebugLog(); + Calculation::getInstance($this->spreadSheet)->getDebugLog()->setWriteDebugLog(false); + $saveDateReturnType = Functions::getReturnDateType(); + Functions::setReturnDateType(Functions::RETURNDATE_EXCEL); - // Create string lookup table - $this->stringTable = []; - for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) { - $this->stringTable = $this->getWriterPart('StringTable')->createStringTable($this->spreadSheet->getSheet($i), $this->stringTable); - } - - // Create styles dictionaries - $this->styleHashTable->addFromSource($this->getWriterPart('Style')->allStyles($this->spreadSheet)); - $this->stylesConditionalHashTable->addFromSource($this->getWriterPart('Style')->allConditionalStyles($this->spreadSheet)); - $this->fillHashTable->addFromSource($this->getWriterPart('Style')->allFills($this->spreadSheet)); - $this->fontHashTable->addFromSource($this->getWriterPart('Style')->allFonts($this->spreadSheet)); - $this->bordersHashTable->addFromSource($this->getWriterPart('Style')->allBorders($this->spreadSheet)); - $this->numFmtHashTable->addFromSource($this->getWriterPart('Style')->allNumberFormats($this->spreadSheet)); - - // Create drawing dictionary - $this->drawingHashTable->addFromSource($this->getWriterPart('Drawing')->allDrawings($this->spreadSheet)); - - $zip = new ZipArchive(); - - if (file_exists($pFilename)) { - unlink($pFilename); - } - // Try opening the ZIP file - if ($zip->open($pFilename, ZipArchive::OVERWRITE) !== true) { - if ($zip->open($pFilename, ZipArchive::CREATE) !== true) { - throw new WriterException('Could not open ' . $pFilename . ' for writing.'); - } - } - - // Add [Content_Types].xml to ZIP file - $zip->addFromString('[Content_Types].xml', $this->getWriterPart('ContentTypes')->writeContentTypes($this->spreadSheet, $this->includeCharts)); - - //if hasMacros, add the vbaProject.bin file, Certificate file(if exists) - if ($this->spreadSheet->hasMacros()) { - $macrosCode = $this->spreadSheet->getMacrosCode(); - if ($macrosCode !== null) { - // we have the code ? - $zip->addFromString('xl/vbaProject.bin', $macrosCode); //allways in 'xl', allways named vbaProject.bin - if ($this->spreadSheet->hasMacrosCertificate()) { - //signed macros ? - // Yes : add the certificate file and the related rels file - $zip->addFromString('xl/vbaProjectSignature.bin', $this->spreadSheet->getMacrosCertificate()); - $zip->addFromString('xl/_rels/vbaProject.bin.rels', $this->getWriterPart('RelsVBA')->writeVBARelationships($this->spreadSheet)); - } - } - } - //a custom UI in this workbook ? add it ("base" xml and additional objects (pictures) and rels) - if ($this->spreadSheet->hasRibbon()) { - $tmpRibbonTarget = $this->spreadSheet->getRibbonXMLData('target'); - $zip->addFromString($tmpRibbonTarget, $this->spreadSheet->getRibbonXMLData('data')); - if ($this->spreadSheet->hasRibbonBinObjects()) { - $tmpRootPath = dirname($tmpRibbonTarget) . '/'; - $ribbonBinObjects = $this->spreadSheet->getRibbonBinObjects('data'); //the files to write - foreach ($ribbonBinObjects as $aPath => $aContent) { - $zip->addFromString($tmpRootPath . $aPath, $aContent); - } - //the rels for files - $zip->addFromString($tmpRootPath . '_rels/' . basename($tmpRibbonTarget) . '.rels', $this->getWriterPart('RelsRibbonObjects')->writeRibbonRelationships($this->spreadSheet)); - } - } - - // Add relationships to ZIP file - $zip->addFromString('_rels/.rels', $this->getWriterPart('Rels')->writeRelationships($this->spreadSheet)); - $zip->addFromString('xl/_rels/workbook.xml.rels', $this->getWriterPart('Rels')->writeWorkbookRelationships($this->spreadSheet)); - - // Add document properties to ZIP file - $zip->addFromString('docProps/app.xml', $this->getWriterPart('DocProps')->writeDocPropsApp($this->spreadSheet)); - $zip->addFromString('docProps/core.xml', $this->getWriterPart('DocProps')->writeDocPropsCore($this->spreadSheet)); - $customPropertiesPart = $this->getWriterPart('DocProps')->writeDocPropsCustom($this->spreadSheet); - if ($customPropertiesPart !== null) { - $zip->addFromString('docProps/custom.xml', $customPropertiesPart); - } - - // Add theme to ZIP file - $zip->addFromString('xl/theme/theme1.xml', $this->getWriterPart('Theme')->writeTheme($this->spreadSheet)); - - // Add string table to ZIP file - $zip->addFromString('xl/sharedStrings.xml', $this->getWriterPart('StringTable')->writeStringTable($this->stringTable)); - - // Add styles to ZIP file - $zip->addFromString('xl/styles.xml', $this->getWriterPart('Style')->writeStyles($this->spreadSheet)); - - // Add workbook to ZIP file - $zip->addFromString('xl/workbook.xml', $this->getWriterPart('Workbook')->writeWorkbook($this->spreadSheet, $this->preCalculateFormulas)); - - $chartCount = 0; - // Add worksheets - for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) { - $zip->addFromString('xl/worksheets/sheet' . ($i + 1) . '.xml', $this->getWriterPart('Worksheet')->writeWorksheet($this->spreadSheet->getSheet($i), $this->stringTable, $this->includeCharts)); - if ($this->includeCharts) { - $charts = $this->spreadSheet->getSheet($i)->getChartCollection(); - if (count($charts) > 0) { - foreach ($charts as $chart) { - $zip->addFromString('xl/charts/chart' . ($chartCount + 1) . '.xml', $this->getWriterPart('Chart')->writeChart($chart, $this->preCalculateFormulas)); - ++$chartCount; - } - } - } - } - - $chartRef1 = 0; - // Add worksheet relationships (drawings, ...) - for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) { - // Add relationships - $zip->addFromString('xl/worksheets/_rels/sheet' . ($i + 1) . '.xml.rels', $this->getWriterPart('Rels')->writeWorksheetRelationships($this->spreadSheet->getSheet($i), ($i + 1), $this->includeCharts)); - - // Add unparsedLoadedData - $sheetCodeName = $this->spreadSheet->getSheet($i)->getCodeName(); - $unparsedLoadedData = $this->spreadSheet->getUnparsedLoadedData(); - if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['ctrlProps'])) { - foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['ctrlProps'] as $ctrlProp) { - $zip->addFromString($ctrlProp['filePath'], $ctrlProp['content']); - } - } - if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['printerSettings'])) { - foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['printerSettings'] as $ctrlProp) { - $zip->addFromString($ctrlProp['filePath'], $ctrlProp['content']); - } - } - - $drawings = $this->spreadSheet->getSheet($i)->getDrawingCollection(); - $drawingCount = count($drawings); - if ($this->includeCharts) { - $chartCount = $this->spreadSheet->getSheet($i)->getChartCount(); - } - - // Add drawing and image relationship parts - if (($drawingCount > 0) || ($chartCount > 0)) { - // Drawing relationships - $zip->addFromString('xl/drawings/_rels/drawing' . ($i + 1) . '.xml.rels', $this->getWriterPart('Rels')->writeDrawingRelationships($this->spreadSheet->getSheet($i), $chartRef1, $this->includeCharts)); - - // Drawings - $zip->addFromString('xl/drawings/drawing' . ($i + 1) . '.xml', $this->getWriterPart('Drawing')->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts)); - } elseif (isset($unparsedLoadedData['sheets'][$sheetCodeName]['drawingAlternateContents'])) { - // Drawings - $zip->addFromString('xl/drawings/drawing' . ($i + 1) . '.xml', $this->getWriterPart('Drawing')->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts)); - } - - // Add unparsed drawings - if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['Drawings'])) { - foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['Drawings'] as $relId => $drawingXml) { - $drawingFile = array_search($relId, $unparsedLoadedData['sheets'][$sheetCodeName]['drawingOriginalIds']); - if ($drawingFile !== false) { - $drawingFile = ltrim($drawingFile, '.'); - $zip->addFromString('xl' . $drawingFile, $drawingXml); - } - } - } - - // Add comment relationship parts - if (count($this->spreadSheet->getSheet($i)->getComments()) > 0) { - // VML Comments - $zip->addFromString('xl/drawings/vmlDrawing' . ($i + 1) . '.vml', $this->getWriterPart('Comments')->writeVMLComments($this->spreadSheet->getSheet($i))); - - // Comments - $zip->addFromString('xl/comments' . ($i + 1) . '.xml', $this->getWriterPart('Comments')->writeComments($this->spreadSheet->getSheet($i))); - } - - // Add unparsed relationship parts - if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'])) { - foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'] as $vmlDrawing) { - $zip->addFromString($vmlDrawing['filePath'], $vmlDrawing['content']); - } - } - - // Add header/footer relationship parts - if (count($this->spreadSheet->getSheet($i)->getHeaderFooter()->getImages()) > 0) { - // VML Drawings - $zip->addFromString('xl/drawings/vmlDrawingHF' . ($i + 1) . '.vml', $this->getWriterPart('Drawing')->writeVMLHeaderFooterImages($this->spreadSheet->getSheet($i))); - - // VML Drawing relationships - $zip->addFromString('xl/drawings/_rels/vmlDrawingHF' . ($i + 1) . '.vml.rels', $this->getWriterPart('Rels')->writeHeaderFooterDrawingRelationships($this->spreadSheet->getSheet($i))); - - // Media - foreach ($this->spreadSheet->getSheet($i)->getHeaderFooter()->getImages() as $image) { - $zip->addFromString('xl/media/' . $image->getIndexedFilename(), file_get_contents($image->getPath())); - } - } - } - - // Add media - for ($i = 0; $i < $this->getDrawingHashTable()->count(); ++$i) { - if ($this->getDrawingHashTable()->getByIndex($i) instanceof WorksheetDrawing) { - $imageContents = null; - $imagePath = $this->getDrawingHashTable()->getByIndex($i)->getPath(); - if (strpos($imagePath, 'zip://') !== false) { - $imagePath = substr($imagePath, 6); - $imagePathSplitted = explode('#', $imagePath); - - $imageZip = new ZipArchive(); - $imageZip->open($imagePathSplitted[0]); - $imageContents = $imageZip->getFromName($imagePathSplitted[1]); - $imageZip->close(); - unset($imageZip); - } else { - $imageContents = file_get_contents($imagePath); - } - - $zip->addFromString('xl/media/' . str_replace(' ', '_', $this->getDrawingHashTable()->getByIndex($i)->getIndexedFilename()), $imageContents); - } elseif ($this->getDrawingHashTable()->getByIndex($i) instanceof MemoryDrawing) { - ob_start(); - call_user_func( - $this->getDrawingHashTable()->getByIndex($i)->getRenderingFunction(), - $this->getDrawingHashTable()->getByIndex($i)->getImageResource() - ); - $imageContents = ob_get_contents(); - ob_end_clean(); - - $zip->addFromString('xl/media/' . str_replace(' ', '_', $this->getDrawingHashTable()->getByIndex($i)->getIndexedFilename()), $imageContents); - } - } - - Functions::setReturnDateType($saveDateReturnType); - Calculation::getInstance($this->spreadSheet)->getDebugLog()->setWriteDebugLog($saveDebugLog); - - // Close file - if ($zip->close() === false) { - throw new WriterException("Could not close zip file $pFilename."); - } - - // If a temporary file was used, copy it to the correct file stream - if ($originalFilename != $pFilename) { - if (copy($pFilename, $originalFilename) === false) { - throw new WriterException("Could not copy temporary zip file $pFilename to $originalFilename."); - } - @unlink($pFilename); - } - } else { - throw new WriterException('PhpSpreadsheet object unassigned.'); + // Create string lookup table + $this->stringTable = []; + for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) { + $this->stringTable = $this->getWriterPartStringTable()->createStringTable($this->spreadSheet->getSheet($i), $this->stringTable); } + + // Create styles dictionaries + $this->styleHashTable->addFromSource($this->getWriterPartStyle()->allStyles($this->spreadSheet)); + $this->stylesConditionalHashTable->addFromSource($this->getWriterPartStyle()->allConditionalStyles($this->spreadSheet)); + $this->fillHashTable->addFromSource($this->getWriterPartStyle()->allFills($this->spreadSheet)); + $this->fontHashTable->addFromSource($this->getWriterPartStyle()->allFonts($this->spreadSheet)); + $this->bordersHashTable->addFromSource($this->getWriterPartStyle()->allBorders($this->spreadSheet)); + $this->numFmtHashTable->addFromSource($this->getWriterPartStyle()->allNumberFormats($this->spreadSheet)); + + // Create drawing dictionary + $this->drawingHashTable->addFromSource($this->getWriterPartDrawing()->allDrawings($this->spreadSheet)); + + $zipContent = []; + // Add [Content_Types].xml to ZIP file + $zipContent['[Content_Types].xml'] = $this->getWriterPartContentTypes()->writeContentTypes($this->spreadSheet, $this->includeCharts); + + //if hasMacros, add the vbaProject.bin file, Certificate file(if exists) + if ($this->spreadSheet->hasMacros()) { + $macrosCode = $this->spreadSheet->getMacrosCode(); + if ($macrosCode !== null) { + // we have the code ? + $zipContent['xl/vbaProject.bin'] = $macrosCode; //allways in 'xl', allways named vbaProject.bin + if ($this->spreadSheet->hasMacrosCertificate()) { + //signed macros ? + // Yes : add the certificate file and the related rels file + $zipContent['xl/vbaProjectSignature.bin'] = $this->spreadSheet->getMacrosCertificate(); + $zipContent['xl/_rels/vbaProject.bin.rels'] = $this->getWriterPartRelsVBA()->writeVBARelationships(); + } + } + } + //a custom UI in this workbook ? add it ("base" xml and additional objects (pictures) and rels) + if ($this->spreadSheet->hasRibbon()) { + $tmpRibbonTarget = $this->spreadSheet->getRibbonXMLData('target'); + $tmpRibbonTarget = is_string($tmpRibbonTarget) ? $tmpRibbonTarget : ''; + $zipContent[$tmpRibbonTarget] = $this->spreadSheet->getRibbonXMLData('data'); + if ($this->spreadSheet->hasRibbonBinObjects()) { + $tmpRootPath = dirname($tmpRibbonTarget) . '/'; + $ribbonBinObjects = $this->spreadSheet->getRibbonBinObjects('data'); //the files to write + if (is_array($ribbonBinObjects)) { + foreach ($ribbonBinObjects as $aPath => $aContent) { + $zipContent[$tmpRootPath . $aPath] = $aContent; + } + } + //the rels for files + $zipContent[$tmpRootPath . '_rels/' . basename($tmpRibbonTarget) . '.rels'] = $this->getWriterPartRelsRibbon()->writeRibbonRelationships($this->spreadSheet); + } + } + + // Add relationships to ZIP file + $zipContent['_rels/.rels'] = $this->getWriterPartRels()->writeRelationships($this->spreadSheet); + $zipContent['xl/_rels/workbook.xml.rels'] = $this->getWriterPartRels()->writeWorkbookRelationships($this->spreadSheet); + + // Add document properties to ZIP file + $zipContent['docProps/app.xml'] = $this->getWriterPartDocProps()->writeDocPropsApp($this->spreadSheet); + $zipContent['docProps/core.xml'] = $this->getWriterPartDocProps()->writeDocPropsCore($this->spreadSheet); + $customPropertiesPart = $this->getWriterPartDocProps()->writeDocPropsCustom($this->spreadSheet); + if ($customPropertiesPart !== null) { + $zipContent['docProps/custom.xml'] = $customPropertiesPart; + } + + // Add theme to ZIP file + $zipContent['xl/theme/theme1.xml'] = $this->getWriterPartTheme()->writeTheme(); + + // Add string table to ZIP file + $zipContent['xl/sharedStrings.xml'] = $this->getWriterPartStringTable()->writeStringTable($this->stringTable); + + // Add styles to ZIP file + $zipContent['xl/styles.xml'] = $this->getWriterPartStyle()->writeStyles($this->spreadSheet); + + // Add workbook to ZIP file + $zipContent['xl/workbook.xml'] = $this->getWriterPartWorkbook()->writeWorkbook($this->spreadSheet, $this->preCalculateFormulas); + + $chartCount = 0; + // Add worksheets + for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) { + $zipContent['xl/worksheets/sheet' . ($i + 1) . '.xml'] = $this->getWriterPartWorksheet()->writeWorksheet($this->spreadSheet->getSheet($i), $this->stringTable, $this->includeCharts); + if ($this->includeCharts) { + $charts = $this->spreadSheet->getSheet($i)->getChartCollection(); + if (count($charts) > 0) { + foreach ($charts as $chart) { + $zipContent['xl/charts/chart' . ($chartCount + 1) . '.xml'] = $this->getWriterPartChart()->writeChart($chart, $this->preCalculateFormulas); + ++$chartCount; + } + } + } + } + + $chartRef1 = 0; + $tableRef1 = 1; + // Add worksheet relationships (drawings, ...) + for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) { + // Add relationships + $zipContent['xl/worksheets/_rels/sheet' . ($i + 1) . '.xml.rels'] = $this->getWriterPartRels()->writeWorksheetRelationships($this->spreadSheet->getSheet($i), ($i + 1), $this->includeCharts, $tableRef1); + + // Add unparsedLoadedData + $sheetCodeName = $this->spreadSheet->getSheet($i)->getCodeName(); + $unparsedLoadedData = $this->spreadSheet->getUnparsedLoadedData(); + if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['ctrlProps'])) { + foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['ctrlProps'] as $ctrlProp) { + $zipContent[$ctrlProp['filePath']] = $ctrlProp['content']; + } + } + if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['printerSettings'])) { + foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['printerSettings'] as $ctrlProp) { + $zipContent[$ctrlProp['filePath']] = $ctrlProp['content']; + } + } + + $drawings = $this->spreadSheet->getSheet($i)->getDrawingCollection(); + $drawingCount = count($drawings); + if ($this->includeCharts) { + $chartCount = $this->spreadSheet->getSheet($i)->getChartCount(); + } + + // Add drawing and image relationship parts + if (($drawingCount > 0) || ($chartCount > 0)) { + // Drawing relationships + $zipContent['xl/drawings/_rels/drawing' . ($i + 1) . '.xml.rels'] = $this->getWriterPartRels()->writeDrawingRelationships($this->spreadSheet->getSheet($i), $chartRef1, $this->includeCharts); + + // Drawings + $zipContent['xl/drawings/drawing' . ($i + 1) . '.xml'] = $this->getWriterPartDrawing()->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts); + } elseif (isset($unparsedLoadedData['sheets'][$sheetCodeName]['drawingAlternateContents'])) { + // Drawings + $zipContent['xl/drawings/drawing' . ($i + 1) . '.xml'] = $this->getWriterPartDrawing()->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts); + } + + // Add unparsed drawings + if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['Drawings'])) { + foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['Drawings'] as $relId => $drawingXml) { + $drawingFile = array_search($relId, $unparsedLoadedData['sheets'][$sheetCodeName]['drawingOriginalIds']); + if ($drawingFile !== false) { + //$drawingFile = ltrim($drawingFile, '.'); + //$zipContent['xl' . $drawingFile] = $drawingXml; + $zipContent['xl/drawings/drawing' . ($i + 1) . '.xml'] = $drawingXml; + } + } + } + + // Add comment relationship parts + $legacy = $unparsedLoadedData['sheets'][$this->spreadSheet->getSheet($i)->getCodeName()]['legacyDrawing'] ?? null; + if (count($this->spreadSheet->getSheet($i)->getComments()) > 0 || $legacy !== null) { + // VML Comments relationships + $zipContent['xl/drawings/_rels/vmlDrawing' . ($i + 1) . '.vml.rels'] = $this->getWriterPartRels()->writeVMLDrawingRelationships($this->spreadSheet->getSheet($i)); + + // VML Comments + $zipContent['xl/drawings/vmlDrawing' . ($i + 1) . '.vml'] = $legacy ?? $this->getWriterPartComments()->writeVMLComments($this->spreadSheet->getSheet($i)); + } + + // Comments + if (count($this->spreadSheet->getSheet($i)->getComments()) > 0) { + $zipContent['xl/comments' . ($i + 1) . '.xml'] = $this->getWriterPartComments()->writeComments($this->spreadSheet->getSheet($i)); + + // Media + foreach ($this->spreadSheet->getSheet($i)->getComments() as $comment) { + if ($comment->hasBackgroundImage()) { + $image = $comment->getBackgroundImage(); + $zipContent['xl/media/' . $image->getMediaFilename()] = $this->processDrawing($image); + } + } + } + + // Add unparsed relationship parts + if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'])) { + foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'] as $vmlDrawing) { + if (!isset($zipContent[$vmlDrawing['filePath']])) { + $zipContent[$vmlDrawing['filePath']] = $vmlDrawing['content']; + } + } + } + + // Add header/footer relationship parts + if (count($this->spreadSheet->getSheet($i)->getHeaderFooter()->getImages()) > 0) { + // VML Drawings + $zipContent['xl/drawings/vmlDrawingHF' . ($i + 1) . '.vml'] = $this->getWriterPartDrawing()->writeVMLHeaderFooterImages($this->spreadSheet->getSheet($i)); + + // VML Drawing relationships + $zipContent['xl/drawings/_rels/vmlDrawingHF' . ($i + 1) . '.vml.rels'] = $this->getWriterPartRels()->writeHeaderFooterDrawingRelationships($this->spreadSheet->getSheet($i)); + + // Media + foreach ($this->spreadSheet->getSheet($i)->getHeaderFooter()->getImages() as $image) { + $zipContent['xl/media/' . $image->getIndexedFilename()] = file_get_contents($image->getPath()); + } + } + + // Add Table parts + $tables = $this->spreadSheet->getSheet($i)->getTableCollection(); + foreach ($tables as $table) { + $zipContent['xl/tables/table' . $tableRef1 . '.xml'] = $this->getWriterPartTable()->writeTable($table, $tableRef1++); + } + } + + // Add media + for ($i = 0; $i < $this->getDrawingHashTable()->count(); ++$i) { + if ($this->getDrawingHashTable()->getByIndex($i) instanceof WorksheetDrawing) { + $imageContents = null; + $imagePath = $this->getDrawingHashTable()->getByIndex($i)->getPath(); + if (strpos($imagePath, 'zip://') !== false) { + $imagePath = substr($imagePath, 6); + $imagePathSplitted = explode('#', $imagePath); + + $imageZip = new ZipArchive(); + $imageZip->open($imagePathSplitted[0]); + $imageContents = $imageZip->getFromName($imagePathSplitted[1]); + $imageZip->close(); + unset($imageZip); + } else { + $imageContents = file_get_contents($imagePath); + } + + $zipContent['xl/media/' . $this->getDrawingHashTable()->getByIndex($i)->getIndexedFilename()] = $imageContents; + } elseif ($this->getDrawingHashTable()->getByIndex($i) instanceof MemoryDrawing) { + ob_start(); + /** @var callable */ + $callable = $this->getDrawingHashTable()->getByIndex($i)->getRenderingFunction(); + call_user_func( + $callable, + $this->getDrawingHashTable()->getByIndex($i)->getImageResource() + ); + $imageContents = ob_get_contents(); + ob_end_clean(); + + $zipContent['xl/media/' . $this->getDrawingHashTable()->getByIndex($i)->getIndexedFilename()] = $imageContents; + } + } + + Functions::setReturnDateType($saveDateReturnType); + Calculation::getInstance($this->spreadSheet)->getDebugLog()->setWriteDebugLog($saveDebugLog); + + $this->openFileHandle($filename); + + $options = new Archive(); + $options->setEnableZip64(false); + $options->setOutputStream($this->fileHandle); + + $this->zip = new ZipStream(null, $options); + + $this->addZipFiles($zipContent); + + // Close file + try { + $this->zip->finish(); + } catch (OverflowException $e) { + throw new WriterException('Could not close resource.'); + } + + $this->maybeCloseFileHandle(); } /** * Get Spreadsheet object. * - * @throws WriterException - * * @return Spreadsheet */ public function getSpreadsheet() { - if ($this->spreadSheet !== null) { - return $this->spreadSheet; - } - - throw new WriterException('No Spreadsheet object assigned.'); + return $this->spreadSheet; } /** @@ -443,7 +579,7 @@ class Xlsx extends BaseWriter * * @param Spreadsheet $spreadsheet PhpSpreadsheet object * - * @return Xlsx + * @return $this */ public function setSpreadsheet(Spreadsheet $spreadsheet) { @@ -465,7 +601,7 @@ class Xlsx extends BaseWriter /** * Get Style HashTable. * - * @return HashTable + * @return HashTable<\PhpOffice\PhpSpreadsheet\Style\Style> */ public function getStyleHashTable() { @@ -475,7 +611,7 @@ class Xlsx extends BaseWriter /** * Get Conditional HashTable. * - * @return HashTable + * @return HashTable */ public function getStylesConditionalHashTable() { @@ -485,7 +621,7 @@ class Xlsx extends BaseWriter /** * Get Fill HashTable. * - * @return HashTable + * @return HashTable */ public function getFillHashTable() { @@ -495,7 +631,7 @@ class Xlsx extends BaseWriter /** * Get \PhpOffice\PhpSpreadsheet\Style\Font HashTable. * - * @return HashTable + * @return HashTable */ public function getFontHashTable() { @@ -505,7 +641,7 @@ class Xlsx extends BaseWriter /** * Get Borders HashTable. * - * @return HashTable + * @return HashTable */ public function getBordersHashTable() { @@ -515,7 +651,7 @@ class Xlsx extends BaseWriter /** * Get NumberFormat HashTable. * - * @return HashTable + * @return HashTable */ public function getNumFmtHashTable() { @@ -525,7 +661,7 @@ class Xlsx extends BaseWriter /** * Get \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\BaseDrawing HashTable. * - * @return HashTable + * @return HashTable */ public function getDrawingHashTable() { @@ -545,14 +681,80 @@ class Xlsx extends BaseWriter /** * Set Office2003 compatibility. * - * @param bool $pValue Office2003 compatibility? + * @param bool $office2003compatibility Office2003 compatibility? * - * @return Xlsx + * @return $this */ - public function setOffice2003Compatibility($pValue) + public function setOffice2003Compatibility($office2003compatibility) { - $this->office2003compatibility = $pValue; + $this->office2003compatibility = $office2003compatibility; return $this; } + + /** @var array */ + private $pathNames = []; + + private function addZipFile(string $path, string $content): void + { + if (!in_array($path, $this->pathNames)) { + $this->pathNames[] = $path; + $this->zip->addFile($path, $content); + } + } + + private function addZipFiles(array $zipContent): void + { + foreach ($zipContent as $path => $content) { + $this->addZipFile($path, $content); + } + } + + /** + * @return mixed + */ + private function processDrawing(WorksheetDrawing $drawing) + { + $data = null; + $filename = $drawing->getPath(); + $imageData = getimagesize($filename); + + if (!empty($imageData)) { + switch ($imageData[2]) { + case 1: // GIF, not supported by BIFF8, we convert to PNG + $image = imagecreatefromgif($filename); + if ($image !== false) { + ob_start(); + imagepng($image); + $data = ob_get_contents(); + ob_end_clean(); + } + + break; + + case 2: // JPEG + $data = file_get_contents($filename); + + break; + + case 3: // PNG + $data = file_get_contents($filename); + + break; + + case 6: // Windows DIB (BMP), we convert to PNG + $image = imagecreatefrombmp($filename); + if ($image !== false) { + ob_start(); + imagepng($image); + $data = ob_get_contents(); + ob_end_clean(); + } + + break; + } + } + + return $data; + } } diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xlsx/AutoFilter.php b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/AutoFilter.php new file mode 100644 index 0000000..ebe4341 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/AutoFilter.php @@ -0,0 +1,125 @@ +getAutoFilter()->getRange(); + if (!empty($autoFilterRange)) { + // autoFilter + $objWriter->startElement('autoFilter'); + + // Strip any worksheet reference from the filter coordinates + $range = Coordinate::splitRange($autoFilterRange); + $range = $range[0]; + // Strip any worksheet ref + [$ws, $range[0]] = ActualWorksheet::extractSheetTitle($range[0], true); + $range = implode(':', $range); + + $objWriter->writeAttribute('ref', str_replace('$', '', $range)); + + $columns = $worksheet->getAutoFilter()->getColumns(); + if (count($columns) > 0) { + foreach ($columns as $columnID => $column) { + $colId = $worksheet->getAutoFilter()->getColumnOffset($columnID); + self::writeAutoFilterColumn($objWriter, $column, $colId); + } + } + $objWriter->endElement(); + } + } + + /** + * Write AutoFilter's filterColumn. + */ + public static function writeAutoFilterColumn(XMLWriter $objWriter, Column $column, int $colId): void + { + $rules = $column->getRules(); + if (count($rules) > 0) { + $objWriter->startElement('filterColumn'); + $objWriter->writeAttribute('colId', "$colId"); + + $objWriter->startElement($column->getFilterType()); + if ($column->getJoin() == Column::AUTOFILTER_COLUMN_JOIN_AND) { + $objWriter->writeAttribute('and', '1'); + } + + foreach ($rules as $rule) { + self::writeAutoFilterColumnRule($column, $rule, $objWriter); + } + + $objWriter->endElement(); + + $objWriter->endElement(); + } + } + + /** + * Write AutoFilter's filterColumn Rule. + */ + private static function writeAutoFilterColumnRule(Column $column, Rule $rule, XMLWriter $objWriter): void + { + if ( + ($column->getFilterType() === Column::AUTOFILTER_FILTERTYPE_FILTER) && + ($rule->getOperator() === Rule::AUTOFILTER_COLUMN_RULE_EQUAL) && + ($rule->getValue() === '') + ) { + // Filter rule for Blanks + $objWriter->writeAttribute('blank', '1'); + } elseif ($rule->getRuleType() === Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER) { + // Dynamic Filter Rule + $objWriter->writeAttribute('type', $rule->getGrouping()); + $val = $column->getAttribute('val'); + if ($val !== null) { + $objWriter->writeAttribute('val', "$val"); + } + $maxVal = $column->getAttribute('maxVal'); + if ($maxVal !== null) { + $objWriter->writeAttribute('maxVal', "$maxVal"); + } + } elseif ($rule->getRuleType() === Rule::AUTOFILTER_RULETYPE_TOPTENFILTER) { + // Top 10 Filter Rule + $ruleValue = $rule->getValue(); + if (!is_array($ruleValue)) { + $objWriter->writeAttribute('val', "$ruleValue"); + } + $objWriter->writeAttribute('percent', (($rule->getOperator() === Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT) ? '1' : '0')); + $objWriter->writeAttribute('top', (($rule->getGrouping() === Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP) ? '1' : '0')); + } else { + // Filter, DateGroupItem or CustomFilter + $objWriter->startElement($rule->getRuleType()); + + if ($rule->getOperator() !== Rule::AUTOFILTER_COLUMN_RULE_EQUAL) { + $objWriter->writeAttribute('operator', $rule->getOperator()); + } + if ($rule->getRuleType() === Rule::AUTOFILTER_RULETYPE_DATEGROUP) { + // Date Group filters + $ruleValue = $rule->getValue(); + if (is_array($ruleValue)) { + foreach ($ruleValue as $key => $value) { + $objWriter->writeAttribute($key, "$value"); + } + } + $objWriter->writeAttribute('dateTimeGrouping', $rule->getGrouping()); + } else { + $ruleValue = $rule->getValue(); + if (!is_array($ruleValue)) { + $objWriter->writeAttribute('val', "$ruleValue"); + } + } + + $objWriter->endElement(); + } + } +} diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Chart.php b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Chart.php old mode 100755 new mode 100644 index 3241d40..4195de0 --- a/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Chart.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Chart.php @@ -3,21 +3,21 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx; use PhpOffice\PhpSpreadsheet\Chart\Axis; +use PhpOffice\PhpSpreadsheet\Chart\ChartColor; use PhpOffice\PhpSpreadsheet\Chart\DataSeries; use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues; -use PhpOffice\PhpSpreadsheet\Chart\GridLines; use PhpOffice\PhpSpreadsheet\Chart\Layout; use PhpOffice\PhpSpreadsheet\Chart\Legend; use PhpOffice\PhpSpreadsheet\Chart\PlotArea; +use PhpOffice\PhpSpreadsheet\Chart\Properties; use PhpOffice\PhpSpreadsheet\Chart\Title; -use PhpOffice\PhpSpreadsheet\Shared\StringHelper; +use PhpOffice\PhpSpreadsheet\Chart\TrendLine; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException; class Chart extends WriterPart { - protected $calculateCellValues; - /** * @var int */ @@ -26,17 +26,12 @@ class Chart extends WriterPart /** * Write charts to XML format. * - * @param \PhpOffice\PhpSpreadsheet\Chart\Chart $pChart * @param mixed $calculateCellValues * - * @throws WriterException - * * @return string XML Output */ - public function writeChart(\PhpOffice\PhpSpreadsheet\Chart\Chart $pChart, $calculateCellValues = true) + public function writeChart(\PhpOffice\PhpSpreadsheet\Chart\Chart $chart, $calculateCellValues = true) { - $this->calculateCellValues = $calculateCellValues; - // Create XML writer $objWriter = null; if ($this->getParentWriter()->getUseDiskCaching()) { @@ -45,8 +40,8 @@ class Chart extends WriterPart $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); } // Ensure that data series values are up-to-date before we save - if ($this->calculateCellValues) { - $pChart->refresh(); + if ($calculateCellValues) { + $chart->refresh(); } // XML header @@ -54,65 +49,97 @@ class Chart extends WriterPart // c:chartSpace $objWriter->startElement('c:chartSpace'); - $objWriter->writeAttribute('xmlns:c', 'http://schemas.openxmlformats.org/drawingml/2006/chart'); - $objWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main'); - $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $objWriter->writeAttribute('xmlns:c', Namespaces::CHART); + $objWriter->writeAttribute('xmlns:a', Namespaces::DRAWINGML); + $objWriter->writeAttribute('xmlns:r', Namespaces::SCHEMA_OFFICE_DOCUMENT); $objWriter->startElement('c:date1904'); - $objWriter->writeAttribute('val', 0); + $objWriter->writeAttribute('val', '0'); $objWriter->endElement(); $objWriter->startElement('c:lang'); $objWriter->writeAttribute('val', 'en-GB'); $objWriter->endElement(); $objWriter->startElement('c:roundedCorners'); - $objWriter->writeAttribute('val', 0); + $objWriter->writeAttribute('val', $chart->getRoundedCorners() ? '1' : '0'); $objWriter->endElement(); $this->writeAlternateContent($objWriter); $objWriter->startElement('c:chart'); - $this->writeTitle($objWriter, $pChart->getTitle()); + $this->writeTitle($objWriter, $chart->getTitle()); $objWriter->startElement('c:autoTitleDeleted'); - $objWriter->writeAttribute('val', 0); + $objWriter->writeAttribute('val', (string) (int) $chart->getAutoTitleDeleted()); $objWriter->endElement(); - $this->writePlotArea($objWriter, $pChart->getWorksheet(), $pChart->getPlotArea(), $pChart->getXAxisLabel(), $pChart->getYAxisLabel(), $pChart->getChartAxisX(), $pChart->getChartAxisY(), $pChart->getMajorGridlines(), $pChart->getMinorGridlines()); + $objWriter->startElement('c:view3D'); + $surface2D = false; + $plotArea = $chart->getPlotArea(); + if ($plotArea !== null) { + $seriesArray = $plotArea->getPlotGroup(); + foreach ($seriesArray as $series) { + if ($series->getPlotType() === DataSeries::TYPE_SURFACECHART) { + $surface2D = true; - $this->writeLegend($objWriter, $pChart->getLegend()); + break; + } + } + } + $this->writeView3D($objWriter, $chart->getRotX(), 'c:rotX', $surface2D, 90); + $this->writeView3D($objWriter, $chart->getRotY(), 'c:rotY', $surface2D); + $this->writeView3D($objWriter, $chart->getRAngAx(), 'c:rAngAx', $surface2D); + $this->writeView3D($objWriter, $chart->getPerspective(), 'c:perspective', $surface2D); + $objWriter->endElement(); // view3D + + $this->writePlotArea($objWriter, $chart->getPlotArea(), $chart->getXAxisLabel(), $chart->getYAxisLabel(), $chart->getChartAxisX(), $chart->getChartAxisY()); + + $this->writeLegend($objWriter, $chart->getLegend()); $objWriter->startElement('c:plotVisOnly'); - $objWriter->writeAttribute('val', 1); + $objWriter->writeAttribute('val', (string) (int) $chart->getPlotVisibleOnly()); $objWriter->endElement(); $objWriter->startElement('c:dispBlanksAs'); - $objWriter->writeAttribute('val', 'gap'); + $objWriter->writeAttribute('val', $chart->getDisplayBlanksAs()); $objWriter->endElement(); $objWriter->startElement('c:showDLblsOverMax'); - $objWriter->writeAttribute('val', 0); + $objWriter->writeAttribute('val', '0'); $objWriter->endElement(); - $objWriter->endElement(); + $objWriter->endElement(); // c:chart + if ($chart->getNoFill()) { + $objWriter->startElement('c:spPr'); + $objWriter->startElement('a:noFill'); + $objWriter->endElement(); // a:noFill + $objWriter->endElement(); // c:spPr + } $this->writePrintSettings($objWriter); - $objWriter->endElement(); + $objWriter->endElement(); // c:chartSpace // Return return $objWriter->getData(); } + private function writeView3D(XMLWriter $objWriter, ?int $value, string $tag, bool $surface2D, int $default = 0): void + { + if ($value === null && $surface2D) { + $value = $default; + } + if ($value !== null) { + $objWriter->startElement($tag); + $objWriter->writeAttribute('val', "$value"); + $objWriter->endElement(); + } + } + /** * Write Chart Title. - * - * @param XMLWriter $objWriter XML Writer - * @param Title $title - * - * @throws WriterException */ - private function writeTitle(XMLWriter $objWriter, Title $title = null) + private function writeTitle(XMLWriter $objWriter, ?Title $title = null): void { if ($title === null) { return; @@ -129,12 +156,16 @@ class Chart extends WriterPart $objWriter->endElement(); $objWriter->startElement('a:p'); + $objWriter->startElement('a:pPr'); + $objWriter->startElement('a:defRPr'); + $objWriter->endElement(); + $objWriter->endElement(); $caption = $title->getCaption(); if ((is_array($caption)) && (count($caption) > 0)) { $caption = $caption[0]; } - $this->getParentWriter()->getWriterPart('stringtable')->writeRichTextForCharts($objWriter, $caption, 'a'); + $this->getParentWriter()->getWriterPartstringtable()->writeRichTextForCharts($objWriter, $caption, 'a'); $objWriter->endElement(); $objWriter->endElement(); @@ -143,7 +174,7 @@ class Chart extends WriterPart $this->writeLayout($objWriter, $title->getLayout()); $objWriter->startElement('c:overlay'); - $objWriter->writeAttribute('val', 0); + $objWriter->writeAttribute('val', '0'); $objWriter->endElement(); $objWriter->endElement(); @@ -151,13 +182,8 @@ class Chart extends WriterPart /** * Write Chart Legend. - * - * @param XMLWriter $objWriter XML Writer - * @param Legend $legend - * - * @throws WriterException */ - private function writeLegend(XMLWriter $objWriter, Legend $legend = null) + private function writeLegend(XMLWriter $objWriter, ?Legend $legend = null): void { if ($legend === null) { return; @@ -184,7 +210,7 @@ class Chart extends WriterPart $objWriter->startElement('a:p'); $objWriter->startElement('a:pPr'); - $objWriter->writeAttribute('rtl', 0); + $objWriter->writeAttribute('rtl', '0'); $objWriter->startElement('a:defRPr'); $objWriter->endElement(); @@ -202,26 +228,14 @@ class Chart extends WriterPart /** * Write Chart Plot Area. - * - * @param XMLWriter $objWriter XML Writer - * @param \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pSheet - * @param PlotArea $plotArea - * @param Title $xAxisLabel - * @param Title $yAxisLabel - * @param Axis $xAxis - * @param Axis $yAxis - * @param null|GridLines $majorGridlines - * @param null|GridLines $minorGridlines - * - * @throws WriterException */ - private function writePlotArea(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pSheet, PlotArea $plotArea, Title $xAxisLabel = null, Title $yAxisLabel = null, Axis $xAxis = null, Axis $yAxis = null, GridLines $majorGridlines = null, GridLines $minorGridlines = null) + private function writePlotArea(XMLWriter $objWriter, ?PlotArea $plotArea, ?Title $xAxisLabel = null, ?Title $yAxisLabel = null, ?Axis $xAxis = null, ?Axis $yAxis = null): void { if ($plotArea === null) { return; } - $id1 = $id2 = 0; + $id1 = $id2 = $id3 = '0'; $this->seriesIndex = 0; $objWriter->startElement('c:plotArea'); @@ -232,23 +246,29 @@ class Chart extends WriterPart $chartTypes = self::getChartType($plotArea); $catIsMultiLevelSeries = $valIsMultiLevelSeries = false; $plotGroupingType = ''; + $chartType = null; foreach ($chartTypes as $chartType) { $objWriter->startElement('c:' . $chartType); $groupCount = $plotArea->getPlotGroupCount(); + $plotGroup = null; for ($i = 0; $i < $groupCount; ++$i) { $plotGroup = $plotArea->getPlotGroupByIndex($i); $groupType = $plotGroup->getPlotType(); if ($groupType == $chartType) { $plotStyle = $plotGroup->getPlotStyle(); - if ($groupType === DataSeries::TYPE_RADARCHART) { + if (!empty($plotStyle) && $groupType === DataSeries::TYPE_RADARCHART) { $objWriter->startElement('c:radarStyle'); $objWriter->writeAttribute('val', $plotStyle); $objWriter->endElement(); - } elseif ($groupType === DataSeries::TYPE_SCATTERCHART) { + } elseif (!empty($plotStyle) && $groupType === DataSeries::TYPE_SCATTERCHART) { $objWriter->startElement('c:scatterStyle'); $objWriter->writeAttribute('val', $plotStyle); $objWriter->endElement(); + } elseif ($groupType === DataSeries::TYPE_SURFACECHART_3D || $groupType === DataSeries::TYPE_SURFACECHART) { + $objWriter->startElement('c:wireframe'); + $objWriter->writeAttribute('val', $plotStyle ? '1' : '0'); + $objWriter->endElement(); } $this->writePlotGroup($plotGroup, $chartType, $objWriter, $catIsMultiLevelSeries, $valIsMultiLevelSeries, $plotGroupingType); @@ -257,28 +277,31 @@ class Chart extends WriterPart $this->writeDataLabels($objWriter, $layout); - if ($chartType === DataSeries::TYPE_LINECHART) { + if ($chartType === DataSeries::TYPE_LINECHART && $plotGroup) { // Line only, Line3D can't be smoothed $objWriter->startElement('c:smooth'); - $objWriter->writeAttribute('val', (int) $plotGroup->getSmoothLine()); + $objWriter->writeAttribute('val', (string) (int) $plotGroup->getSmoothLine()); $objWriter->endElement(); } elseif (($chartType === DataSeries::TYPE_BARCHART) || ($chartType === DataSeries::TYPE_BARCHART_3D)) { $objWriter->startElement('c:gapWidth'); - $objWriter->writeAttribute('val', 150); + $objWriter->writeAttribute('val', '150'); $objWriter->endElement(); if ($plotGroupingType == 'percentStacked' || $plotGroupingType == 'stacked') { $objWriter->startElement('c:overlap'); - $objWriter->writeAttribute('val', 100); + $objWriter->writeAttribute('val', '100'); $objWriter->endElement(); } } elseif ($chartType === DataSeries::TYPE_BUBBLECHART) { - $objWriter->startElement('c:bubbleScale'); - $objWriter->writeAttribute('val', 25); - $objWriter->endElement(); + $scale = ($plotGroup === null) ? '' : (string) $plotGroup->getPlotStyle(); + if ($scale !== '') { + $objWriter->startElement('c:bubbleScale'); + $objWriter->writeAttribute('val', $scale); + $objWriter->endElement(); + } $objWriter->startElement('c:showNegBubbles'); - $objWriter->writeAttribute('val', 0); + $objWriter->writeAttribute('val', '0'); $objWriter->endElement(); } elseif ($chartType === DataSeries::TYPE_STOCKCHART) { $objWriter->startElement('c:hiLowLines'); @@ -287,7 +310,7 @@ class Chart extends WriterPart $objWriter->startElement('c:upDownBars'); $objWriter->startElement('c:gapWidth'); - $objWriter->writeAttribute('val', 300); + $objWriter->writeAttribute('val', '300'); $objWriter->endElement(); $objWriter->startElement('c:upBars'); @@ -299,9 +322,10 @@ class Chart extends WriterPart $objWriter->endElement(); } - // Generate 2 unique numbers to use for axId values - $id1 = '75091328'; - $id2 = '75089408'; + // Generate 3 unique numbers to use for axId values + $id1 = '110438656'; + $id2 = '110444544'; + $id3 = '110365312'; // used in Surface Chart if (($chartType !== DataSeries::TYPE_PIECHART) && ($chartType !== DataSeries::TYPE_PIECHART_3D) && ($chartType !== DataSeries::TYPE_DONUTCHART)) { $objWriter->startElement('c:axId'); @@ -310,14 +334,19 @@ class Chart extends WriterPart $objWriter->startElement('c:axId'); $objWriter->writeAttribute('val', $id2); $objWriter->endElement(); + if ($chartType === DataSeries::TYPE_SURFACECHART_3D || $chartType === DataSeries::TYPE_SURFACECHART) { + $objWriter->startElement('c:axId'); + $objWriter->writeAttribute('val', $id3); + $objWriter->endElement(); + } } else { $objWriter->startElement('c:firstSliceAng'); - $objWriter->writeAttribute('val', 0); + $objWriter->writeAttribute('val', '0'); $objWriter->endElement(); if ($chartType === DataSeries::TYPE_DONUTCHART) { $objWriter->startElement('c:holeSize'); - $objWriter->writeAttribute('val', 50); + $objWriter->writeAttribute('val', '50'); $objWriter->endElement(); } } @@ -327,101 +356,201 @@ class Chart extends WriterPart if (($chartType !== DataSeries::TYPE_PIECHART) && ($chartType !== DataSeries::TYPE_PIECHART_3D) && ($chartType !== DataSeries::TYPE_DONUTCHART)) { if ($chartType === DataSeries::TYPE_BUBBLECHART) { - $this->writeValueAxis($objWriter, $xAxisLabel, $chartType, $id1, $id2, $catIsMultiLevelSeries, $xAxis, $majorGridlines, $minorGridlines); + $this->writeValueAxis($objWriter, $xAxisLabel, $chartType, $id2, $id1, $catIsMultiLevelSeries, $xAxis ?? new Axis()); } else { - $this->writeCategoryAxis($objWriter, $xAxisLabel, $id1, $id2, $catIsMultiLevelSeries, $yAxis); + $this->writeCategoryAxis($objWriter, $xAxisLabel, $id1, $id2, $catIsMultiLevelSeries, $xAxis ?? new Axis()); } - $this->writeValueAxis($objWriter, $yAxisLabel, $chartType, $id1, $id2, $valIsMultiLevelSeries, $xAxis, $majorGridlines, $minorGridlines); + $this->writeValueAxis($objWriter, $yAxisLabel, $chartType, $id1, $id2, $valIsMultiLevelSeries, $yAxis ?? new Axis()); + if ($chartType === DataSeries::TYPE_SURFACECHART_3D || $chartType === DataSeries::TYPE_SURFACECHART) { + $this->writeSerAxis($objWriter, $id2, $id3); + } + } + $stops = $plotArea->getGradientFillStops(); + if ($plotArea->getNoFill() || !empty($stops)) { + $objWriter->startElement('c:spPr'); + if ($plotArea->getNoFill()) { + $objWriter->startElement('a:noFill'); + $objWriter->endElement(); // a:noFill + } + if (!empty($stops)) { + $objWriter->startElement('a:gradFill'); + $objWriter->startElement('a:gsLst'); + foreach ($stops as $stop) { + $objWriter->startElement('a:gs'); + $objWriter->writeAttribute('pos', (string) (Properties::PERCENTAGE_MULTIPLIER * (float) $stop[0])); + $this->writeColor($objWriter, $stop[1], false); + $objWriter->endElement(); // a:gs + } + $objWriter->endElement(); // a:gsLst + $angle = $plotArea->getGradientFillAngle(); + if ($angle !== null) { + $objWriter->startElement('a:lin'); + $objWriter->writeAttribute('ang', Properties::angleToXml($angle)); + $objWriter->endElement(); // a:lin + } + $objWriter->endElement(); // a:gradFill + } + $objWriter->endElement(); // c:spPr } - $objWriter->endElement(); + $objWriter->endElement(); // c:plotArea + } + + private function writeDataLabelsBool(XMLWriter $objWriter, string $name, ?bool $value): void + { + if ($value !== null) { + $objWriter->startElement("c:$name"); + $objWriter->writeAttribute('val', $value ? '1' : '0'); + $objWriter->endElement(); + } } /** * Write Data Labels. - * - * @param XMLWriter $objWriter XML Writer - * @param \PhpOffice\PhpSpreadsheet\Chart\Layout $chartLayout Chart layout */ - private function writeDataLabels(XMLWriter $objWriter, Layout $chartLayout = null) + private function writeDataLabels(XMLWriter $objWriter, ?Layout $chartLayout = null): void { + if (!isset($chartLayout)) { + return; + } $objWriter->startElement('c:dLbls'); - $objWriter->startElement('c:showLegendKey'); - $showLegendKey = (empty($chartLayout)) ? 0 : $chartLayout->getShowLegendKey(); - $objWriter->writeAttribute('val', ((empty($showLegendKey)) ? 0 : 1)); - $objWriter->endElement(); + $fillColor = $chartLayout->getLabelFillColor(); + $borderColor = $chartLayout->getLabelBorderColor(); + if ($fillColor && $fillColor->isUsable()) { + $objWriter->startElement('c:spPr'); + $this->writeColor($objWriter, $fillColor); + if ($borderColor && $borderColor->isUsable()) { + $objWriter->startElement('a:ln'); + $this->writeColor($objWriter, $borderColor); + $objWriter->endElement(); // a:ln + } + $objWriter->endElement(); // c:spPr + } + $fontColor = $chartLayout->getLabelFontColor(); + if ($fontColor && $fontColor->isUsable()) { + $objWriter->startElement('c:txPr'); - $objWriter->startElement('c:showVal'); - $showVal = (empty($chartLayout)) ? 0 : $chartLayout->getShowVal(); - $objWriter->writeAttribute('val', ((empty($showVal)) ? 0 : 1)); - $objWriter->endElement(); + $objWriter->startElement('a:bodyPr'); + $objWriter->writeAttribute('wrap', 'square'); + $objWriter->writeAttribute('lIns', '38100'); + $objWriter->writeAttribute('tIns', '19050'); + $objWriter->writeAttribute('rIns', '38100'); + $objWriter->writeAttribute('bIns', '19050'); + $objWriter->writeAttribute('anchor', 'ctr'); + $objWriter->startElement('a:spAutoFit'); + $objWriter->endElement(); // a:spAutoFit + $objWriter->endElement(); // a:bodyPr - $objWriter->startElement('c:showCatName'); - $showCatName = (empty($chartLayout)) ? 0 : $chartLayout->getShowCatName(); - $objWriter->writeAttribute('val', ((empty($showCatName)) ? 0 : 1)); - $objWriter->endElement(); + $objWriter->startElement('a:lstStyle'); + $objWriter->endElement(); // a:lstStyle - $objWriter->startElement('c:showSerName'); - $showSerName = (empty($chartLayout)) ? 0 : $chartLayout->getShowSerName(); - $objWriter->writeAttribute('val', ((empty($showSerName)) ? 0 : 1)); - $objWriter->endElement(); + $objWriter->startElement('a:p'); + $objWriter->startElement('a:pPr'); + $objWriter->startElement('a:defRPr'); + $this->writeColor($objWriter, $fontColor); + $objWriter->endElement(); // a:defRPr + $objWriter->endElement(); // a:pPr + $objWriter->endElement(); // a:p - $objWriter->startElement('c:showPercent'); - $showPercent = (empty($chartLayout)) ? 0 : $chartLayout->getShowPercent(); - $objWriter->writeAttribute('val', ((empty($showPercent)) ? 0 : 1)); - $objWriter->endElement(); + $objWriter->endElement(); // c:txPr + } - $objWriter->startElement('c:showBubbleSize'); - $showBubbleSize = (empty($chartLayout)) ? 0 : $chartLayout->getShowBubbleSize(); - $objWriter->writeAttribute('val', ((empty($showBubbleSize)) ? 0 : 1)); - $objWriter->endElement(); + if ($chartLayout->getNumFmtCode() !== '') { + $objWriter->startElement('c:numFmt'); + $objWriter->writeAttribute('formatCode', $chartLayout->getnumFmtCode()); + $objWriter->writeAttribute('sourceLinked', (string) (int) $chartLayout->getnumFmtLinked()); + $objWriter->endElement(); // c:numFmt + } + if ($chartLayout->getDLblPos() !== '') { + $objWriter->startElement('c:dLblPos'); + $objWriter->writeAttribute('val', $chartLayout->getDLblPos()); + $objWriter->endElement(); // c:dLblPos + } + $this->writeDataLabelsBool($objWriter, 'showLegendKey', $chartLayout->getShowLegendKey()); + $this->writeDataLabelsBool($objWriter, 'showVal', $chartLayout->getShowVal()); + $this->writeDataLabelsBool($objWriter, 'showCatName', $chartLayout->getShowCatName()); + $this->writeDataLabelsBool($objWriter, 'showSerName', $chartLayout->getShowSerName()); + $this->writeDataLabelsBool($objWriter, 'showPercent', $chartLayout->getShowPercent()); + $this->writeDataLabelsBool($objWriter, 'showBubbleSize', $chartLayout->getShowBubbleSize()); + $this->writeDataLabelsBool($objWriter, 'showLeaderLines', $chartLayout->getShowLeaderLines()); - $objWriter->startElement('c:showLeaderLines'); - $showLeaderLines = (empty($chartLayout)) ? 1 : $chartLayout->getShowLeaderLines(); - $objWriter->writeAttribute('val', ((empty($showLeaderLines)) ? 0 : 1)); - $objWriter->endElement(); - - $objWriter->endElement(); + $objWriter->endElement(); // c:dLbls } /** * Write Category Axis. * - * @param XMLWriter $objWriter XML Writer - * @param Title $xAxisLabel * @param string $id1 * @param string $id2 * @param bool $isMultiLevelSeries - * @param Axis $yAxis - * - * @throws WriterException */ - private function writeCategoryAxis($objWriter, $xAxisLabel, $id1, $id2, $isMultiLevelSeries, Axis $yAxis) + private function writeCategoryAxis(XMLWriter $objWriter, ?Title $xAxisLabel, $id1, $id2, $isMultiLevelSeries, Axis $yAxis): void { - $objWriter->startElement('c:catAx'); + // N.B. writeCategoryAxis may be invoked with the last parameter($yAxis) using $xAxis for ScatterChart, etc + // In that case, xAxis may contain values like the yAxis, or it may be a date axis (LINECHART). + $axisType = $yAxis->getAxisType(); + if ($axisType !== '') { + $objWriter->startElement("c:$axisType"); + } elseif ($yAxis->getAxisIsNumericFormat()) { + $objWriter->startElement('c:' . Axis::AXIS_TYPE_VALUE); + } else { + $objWriter->startElement('c:' . Axis::AXIS_TYPE_CATEGORY); + } + $majorGridlines = $yAxis->getMajorGridlines(); + $minorGridlines = $yAxis->getMinorGridlines(); - if ($id1 > 0) { + if ($id1 !== '0') { $objWriter->startElement('c:axId'); $objWriter->writeAttribute('val', $id1); $objWriter->endElement(); } $objWriter->startElement('c:scaling'); - $objWriter->startElement('c:orientation'); - $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('orientation')); - $objWriter->endElement(); - $objWriter->endElement(); + if ($yAxis->getAxisOptionsProperty('maximum') !== null) { + $objWriter->startElement('c:max'); + $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('maximum')); + $objWriter->endElement(); + } + if ($yAxis->getAxisOptionsProperty('minimum') !== null) { + $objWriter->startElement('c:min'); + $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('minimum')); + $objWriter->endElement(); + } + if (!empty($yAxis->getAxisOptionsProperty('orientation'))) { + $objWriter->startElement('c:orientation'); + $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('orientation')); + $objWriter->endElement(); + } + $objWriter->endElement(); // c:scaling $objWriter->startElement('c:delete'); - $objWriter->writeAttribute('val', 0); + $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('hidden') ?? '0'); $objWriter->endElement(); $objWriter->startElement('c:axPos'); $objWriter->writeAttribute('val', 'b'); $objWriter->endElement(); + if ($majorGridlines !== null) { + $objWriter->startElement('c:majorGridlines'); + $objWriter->startElement('c:spPr'); + $this->writeLineStyles($objWriter, $majorGridlines); + $this->writeEffects($objWriter, $majorGridlines); + $objWriter->endElement(); //end spPr + $objWriter->endElement(); //end majorGridLines + } + + if ($minorGridlines !== null && $minorGridlines->getObjectState()) { + $objWriter->startElement('c:minorGridlines'); + $objWriter->startElement('c:spPr'); + $this->writeLineStyles($objWriter, $minorGridlines); + $this->writeEffects($objWriter, $minorGridlines); + $objWriter->endElement(); //end spPr + $objWriter->endElement(); //end minorGridLines + } + if ($xAxisLabel !== null) { $objWriter->startElement('c:title'); $objWriter->startElement('c:tx'); @@ -434,26 +563,22 @@ class Chart extends WriterPart $objWriter->endElement(); $objWriter->startElement('a:p'); - $objWriter->startElement('a:r'); $caption = $xAxisLabel->getCaption(); if (is_array($caption)) { $caption = $caption[0]; } - $objWriter->startElement('a:t'); - $objWriter->writeRawData(StringHelper::controlCharacterPHP2OOXML($caption)); - $objWriter->endElement(); + $this->getParentWriter()->getWriterPartstringtable()->writeRichTextForCharts($objWriter, $caption, 'a'); $objWriter->endElement(); $objWriter->endElement(); $objWriter->endElement(); - $objWriter->endElement(); $layout = $xAxisLabel->getLayout(); $this->writeLayout($objWriter, $layout); $objWriter->startElement('c:overlay'); - $objWriter->writeAttribute('val', 0); + $objWriter->writeAttribute('val', '0'); $objWriter->endElement(); $objWriter->endElement(); @@ -464,30 +589,74 @@ class Chart extends WriterPart $objWriter->writeAttribute('sourceLinked', $yAxis->getAxisNumberSourceLinked()); $objWriter->endElement(); - $objWriter->startElement('c:majorTickMark'); - $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('major_tick_mark')); - $objWriter->endElement(); + if (!empty($yAxis->getAxisOptionsProperty('major_tick_mark'))) { + $objWriter->startElement('c:majorTickMark'); + $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('major_tick_mark')); + $objWriter->endElement(); + } - $objWriter->startElement('c:minorTickMark'); - $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('minor_tick_mark')); - $objWriter->endElement(); + if (!empty($yAxis->getAxisOptionsProperty('minor_tick_mark'))) { + $objWriter->startElement('c:minorTickMark'); + $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('minor_tick_mark')); + $objWriter->endElement(); + } - $objWriter->startElement('c:tickLblPos'); - $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('axis_labels')); - $objWriter->endElement(); + if (!empty($yAxis->getAxisOptionsProperty('axis_labels'))) { + $objWriter->startElement('c:tickLblPos'); + $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('axis_labels')); + $objWriter->endElement(); + } - if ($id2 > 0) { + $textRotation = $yAxis->getAxisOptionsProperty('textRotation'); + if (is_numeric($textRotation)) { + $objWriter->startElement('c:txPr'); + $objWriter->startElement('a:bodyPr'); + $objWriter->writeAttribute('rot', Properties::angleToXml((float) $textRotation)); + $objWriter->endElement(); // a:bodyPr + $objWriter->startElement('a:lstStyle'); + $objWriter->endElement(); // a:lstStyle + $objWriter->startElement('a:p'); + $objWriter->startElement('a:pPr'); + $objWriter->startElement('a:defRPr'); + $objWriter->endElement(); // a:defRPr + $objWriter->endElement(); // a:pPr + $objWriter->endElement(); // a:p + $objWriter->endElement(); // c:txPr + } + + $objWriter->startElement('c:spPr'); + $this->writeColor($objWriter, $yAxis->getFillColorObject()); + $this->writeLineStyles($objWriter, $yAxis); + $this->writeEffects($objWriter, $yAxis); + $objWriter->endElement(); // spPr + + if ($yAxis->getAxisOptionsProperty('major_unit') !== null) { + $objWriter->startElement('c:majorUnit'); + $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('major_unit')); + $objWriter->endElement(); + } + + if ($yAxis->getAxisOptionsProperty('minor_unit') !== null) { + $objWriter->startElement('c:minorUnit'); + $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('minor_unit')); + $objWriter->endElement(); + } + + if ($id2 !== '0') { $objWriter->startElement('c:crossAx'); $objWriter->writeAttribute('val', $id2); $objWriter->endElement(); - $objWriter->startElement('c:crosses'); - $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('horizontal_crosses')); - $objWriter->endElement(); + if (!empty($yAxis->getAxisOptionsProperty('horizontal_crosses'))) { + $objWriter->startElement('c:crosses'); + $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('horizontal_crosses')); + $objWriter->endElement(); + } } $objWriter->startElement('c:auto'); - $objWriter->writeAttribute('val', 1); + // LineChart with dateAx wants '0' + $objWriter->writeAttribute('val', ($axisType === Axis::AXIS_TYPE_DATE) ? '0' : '1'); $objWriter->endElement(); $objWriter->startElement('c:lblAlgn'); @@ -495,12 +664,36 @@ class Chart extends WriterPart $objWriter->endElement(); $objWriter->startElement('c:lblOffset'); - $objWriter->writeAttribute('val', 100); + $objWriter->writeAttribute('val', '100'); $objWriter->endElement(); + if ($axisType === Axis::AXIS_TYPE_DATE) { + $property = 'baseTimeUnit'; + $propertyVal = $yAxis->getAxisOptionsProperty($property); + if (!empty($propertyVal)) { + $objWriter->startElement("c:$property"); + $objWriter->writeAttribute('val', $propertyVal); + $objWriter->endElement(); + } + $property = 'majorTimeUnit'; + $propertyVal = $yAxis->getAxisOptionsProperty($property); + if (!empty($propertyVal)) { + $objWriter->startElement("c:$property"); + $objWriter->writeAttribute('val', $propertyVal); + $objWriter->endElement(); + } + $property = 'minorTimeUnit'; + $propertyVal = $yAxis->getAxisOptionsProperty($property); + if (!empty($propertyVal)) { + $objWriter->startElement("c:$property"); + $objWriter->writeAttribute('val', $propertyVal); + $objWriter->endElement(); + } + } + if ($isMultiLevelSeries) { $objWriter->startElement('c:noMultiLvlLbl'); - $objWriter->writeAttribute('val', 0); + $objWriter->writeAttribute('val', '0'); $objWriter->endElement(); } $objWriter->endElement(); @@ -509,23 +702,18 @@ class Chart extends WriterPart /** * Write Value Axis. * - * @param XMLWriter $objWriter XML Writer - * @param Title $yAxisLabel - * @param string $groupType Chart type + * @param null|string $groupType Chart type * @param string $id1 * @param string $id2 * @param bool $isMultiLevelSeries - * @param Axis $xAxis - * @param GridLines $majorGridlines - * @param GridLines $minorGridlines - * - * @throws WriterException */ - private function writeValueAxis($objWriter, $yAxisLabel, $groupType, $id1, $id2, $isMultiLevelSeries, Axis $xAxis, GridLines $majorGridlines, GridLines $minorGridlines) + private function writeValueAxis(XMLWriter $objWriter, ?Title $yAxisLabel, $groupType, $id1, $id2, $isMultiLevelSeries, Axis $xAxis): void { - $objWriter->startElement('c:valAx'); + $objWriter->startElement('c:' . Axis::AXIS_TYPE_VALUE); + $majorGridlines = $xAxis->getMajorGridlines(); + $minorGridlines = $xAxis->getMinorGridlines(); - if ($id2 > 0) { + if ($id2 !== '0') { $objWriter->startElement('c:axId'); $objWriter->writeAttribute('val', $id2); $objWriter->endElement(); @@ -545,229 +733,36 @@ class Chart extends WriterPart $objWriter->endElement(); } - $objWriter->startElement('c:orientation'); - $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('orientation')); + if (!empty($xAxis->getAxisOptionsProperty('orientation'))) { + $objWriter->startElement('c:orientation'); + $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('orientation')); + $objWriter->endElement(); + } - $objWriter->endElement(); - $objWriter->endElement(); + $objWriter->endElement(); // c:scaling $objWriter->startElement('c:delete'); - $objWriter->writeAttribute('val', 0); + $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('hidden') ?? '0'); $objWriter->endElement(); $objWriter->startElement('c:axPos'); $objWriter->writeAttribute('val', 'l'); $objWriter->endElement(); - $objWriter->startElement('c:majorGridlines'); - $objWriter->startElement('c:spPr'); - - if ($majorGridlines->getLineColorProperty('value') !== null) { - $objWriter->startElement('a:ln'); - $objWriter->writeAttribute('w', $majorGridlines->getLineStyleProperty('width')); - $objWriter->startElement('a:solidFill'); - $objWriter->startElement("a:{$majorGridlines->getLineColorProperty('type')}"); - $objWriter->writeAttribute('val', $majorGridlines->getLineColorProperty('value')); - $objWriter->startElement('a:alpha'); - $objWriter->writeAttribute('val', $majorGridlines->getLineColorProperty('alpha')); - $objWriter->endElement(); //end alpha - $objWriter->endElement(); //end srgbClr - $objWriter->endElement(); //end solidFill - - $objWriter->startElement('a:prstDash'); - $objWriter->writeAttribute('val', $majorGridlines->getLineStyleProperty('dash')); - $objWriter->endElement(); - - if ($majorGridlines->getLineStyleProperty('join') == 'miter') { - $objWriter->startElement('a:miter'); - $objWriter->writeAttribute('lim', '800000'); - $objWriter->endElement(); - } else { - $objWriter->startElement('a:bevel'); - $objWriter->endElement(); - } - - if ($majorGridlines->getLineStyleProperty(['arrow', 'head', 'type']) !== null) { - $objWriter->startElement('a:headEnd'); - $objWriter->writeAttribute('type', $majorGridlines->getLineStyleProperty(['arrow', 'head', 'type'])); - $objWriter->writeAttribute('w', $majorGridlines->getLineStyleArrowParameters('head', 'w')); - $objWriter->writeAttribute('len', $majorGridlines->getLineStyleArrowParameters('head', 'len')); - $objWriter->endElement(); - } - - if ($majorGridlines->getLineStyleProperty(['arrow', 'end', 'type']) !== null) { - $objWriter->startElement('a:tailEnd'); - $objWriter->writeAttribute('type', $majorGridlines->getLineStyleProperty(['arrow', 'end', 'type'])); - $objWriter->writeAttribute('w', $majorGridlines->getLineStyleArrowParameters('end', 'w')); - $objWriter->writeAttribute('len', $majorGridlines->getLineStyleArrowParameters('end', 'len')); - $objWriter->endElement(); - } - $objWriter->endElement(); //end ln - } - $objWriter->startElement('a:effectLst'); - - if ($majorGridlines->getGlowSize() !== null) { - $objWriter->startElement('a:glow'); - $objWriter->writeAttribute('rad', $majorGridlines->getGlowSize()); - $objWriter->startElement("a:{$majorGridlines->getGlowColor('type')}"); - $objWriter->writeAttribute('val', $majorGridlines->getGlowColor('value')); - $objWriter->startElement('a:alpha'); - $objWriter->writeAttribute('val', $majorGridlines->getGlowColor('alpha')); - $objWriter->endElement(); //end alpha - $objWriter->endElement(); //end schemeClr - $objWriter->endElement(); //end glow + if ($majorGridlines !== null) { + $objWriter->startElement('c:majorGridlines'); + $objWriter->startElement('c:spPr'); + $this->writeLineStyles($objWriter, $majorGridlines); + $this->writeEffects($objWriter, $majorGridlines); + $objWriter->endElement(); //end spPr + $objWriter->endElement(); //end majorGridLines } - if ($majorGridlines->getShadowProperty('presets') !== null) { - $objWriter->startElement("a:{$majorGridlines->getShadowProperty('effect')}"); - if ($majorGridlines->getShadowProperty('blur') !== null) { - $objWriter->writeAttribute('blurRad', $majorGridlines->getShadowProperty('blur')); - } - if ($majorGridlines->getShadowProperty('distance') !== null) { - $objWriter->writeAttribute('dist', $majorGridlines->getShadowProperty('distance')); - } - if ($majorGridlines->getShadowProperty('direction') !== null) { - $objWriter->writeAttribute('dir', $majorGridlines->getShadowProperty('direction')); - } - if ($majorGridlines->getShadowProperty('algn') !== null) { - $objWriter->writeAttribute('algn', $majorGridlines->getShadowProperty('algn')); - } - if ($majorGridlines->getShadowProperty(['size', 'sx']) !== null) { - $objWriter->writeAttribute('sx', $majorGridlines->getShadowProperty(['size', 'sx'])); - } - if ($majorGridlines->getShadowProperty(['size', 'sy']) !== null) { - $objWriter->writeAttribute('sy', $majorGridlines->getShadowProperty(['size', 'sy'])); - } - if ($majorGridlines->getShadowProperty(['size', 'kx']) !== null) { - $objWriter->writeAttribute('kx', $majorGridlines->getShadowProperty(['size', 'kx'])); - } - if ($majorGridlines->getShadowProperty('rotWithShape') !== null) { - $objWriter->writeAttribute('rotWithShape', $majorGridlines->getShadowProperty('rotWithShape')); - } - $objWriter->startElement("a:{$majorGridlines->getShadowProperty(['color', 'type'])}"); - $objWriter->writeAttribute('val', $majorGridlines->getShadowProperty(['color', 'value'])); - - $objWriter->startElement('a:alpha'); - $objWriter->writeAttribute('val', $majorGridlines->getShadowProperty(['color', 'alpha'])); - $objWriter->endElement(); //end alpha - - $objWriter->endElement(); //end color:type - $objWriter->endElement(); //end shadow - } - - if ($majorGridlines->getSoftEdgesSize() !== null) { - $objWriter->startElement('a:softEdge'); - $objWriter->writeAttribute('rad', $majorGridlines->getSoftEdgesSize()); - $objWriter->endElement(); //end softEdge - } - - $objWriter->endElement(); //end effectLst - $objWriter->endElement(); //end spPr - $objWriter->endElement(); //end majorGridLines - - if ($minorGridlines->getObjectState()) { + if ($minorGridlines !== null && $minorGridlines->getObjectState()) { $objWriter->startElement('c:minorGridlines'); $objWriter->startElement('c:spPr'); - - if ($minorGridlines->getLineColorProperty('value') !== null) { - $objWriter->startElement('a:ln'); - $objWriter->writeAttribute('w', $minorGridlines->getLineStyleProperty('width')); - $objWriter->startElement('a:solidFill'); - $objWriter->startElement("a:{$minorGridlines->getLineColorProperty('type')}"); - $objWriter->writeAttribute('val', $minorGridlines->getLineColorProperty('value')); - $objWriter->startElement('a:alpha'); - $objWriter->writeAttribute('val', $minorGridlines->getLineColorProperty('alpha')); - $objWriter->endElement(); //end alpha - $objWriter->endElement(); //end srgbClr - $objWriter->endElement(); //end solidFill - - $objWriter->startElement('a:prstDash'); - $objWriter->writeAttribute('val', $minorGridlines->getLineStyleProperty('dash')); - $objWriter->endElement(); - - if ($minorGridlines->getLineStyleProperty('join') == 'miter') { - $objWriter->startElement('a:miter'); - $objWriter->writeAttribute('lim', '800000'); - $objWriter->endElement(); - } else { - $objWriter->startElement('a:bevel'); - $objWriter->endElement(); - } - - if ($minorGridlines->getLineStyleProperty(['arrow', 'head', 'type']) !== null) { - $objWriter->startElement('a:headEnd'); - $objWriter->writeAttribute('type', $minorGridlines->getLineStyleProperty(['arrow', 'head', 'type'])); - $objWriter->writeAttribute('w', $minorGridlines->getLineStyleArrowParameters('head', 'w')); - $objWriter->writeAttribute('len', $minorGridlines->getLineStyleArrowParameters('head', 'len')); - $objWriter->endElement(); - } - - if ($minorGridlines->getLineStyleProperty(['arrow', 'end', 'type']) !== null) { - $objWriter->startElement('a:tailEnd'); - $objWriter->writeAttribute('type', $minorGridlines->getLineStyleProperty(['arrow', 'end', 'type'])); - $objWriter->writeAttribute('w', $minorGridlines->getLineStyleArrowParameters('end', 'w')); - $objWriter->writeAttribute('len', $minorGridlines->getLineStyleArrowParameters('end', 'len')); - $objWriter->endElement(); - } - $objWriter->endElement(); //end ln - } - - $objWriter->startElement('a:effectLst'); - - if ($minorGridlines->getGlowSize() !== null) { - $objWriter->startElement('a:glow'); - $objWriter->writeAttribute('rad', $minorGridlines->getGlowSize()); - $objWriter->startElement("a:{$minorGridlines->getGlowColor('type')}"); - $objWriter->writeAttribute('val', $minorGridlines->getGlowColor('value')); - $objWriter->startElement('a:alpha'); - $objWriter->writeAttribute('val', $minorGridlines->getGlowColor('alpha')); - $objWriter->endElement(); //end alpha - $objWriter->endElement(); //end schemeClr - $objWriter->endElement(); //end glow - } - - if ($minorGridlines->getShadowProperty('presets') !== null) { - $objWriter->startElement("a:{$minorGridlines->getShadowProperty('effect')}"); - if ($minorGridlines->getShadowProperty('blur') !== null) { - $objWriter->writeAttribute('blurRad', $minorGridlines->getShadowProperty('blur')); - } - if ($minorGridlines->getShadowProperty('distance') !== null) { - $objWriter->writeAttribute('dist', $minorGridlines->getShadowProperty('distance')); - } - if ($minorGridlines->getShadowProperty('direction') !== null) { - $objWriter->writeAttribute('dir', $minorGridlines->getShadowProperty('direction')); - } - if ($minorGridlines->getShadowProperty('algn') !== null) { - $objWriter->writeAttribute('algn', $minorGridlines->getShadowProperty('algn')); - } - if ($minorGridlines->getShadowProperty(['size', 'sx']) !== null) { - $objWriter->writeAttribute('sx', $minorGridlines->getShadowProperty(['size', 'sx'])); - } - if ($minorGridlines->getShadowProperty(['size', 'sy']) !== null) { - $objWriter->writeAttribute('sy', $minorGridlines->getShadowProperty(['size', 'sy'])); - } - if ($minorGridlines->getShadowProperty(['size', 'kx']) !== null) { - $objWriter->writeAttribute('kx', $minorGridlines->getShadowProperty(['size', 'kx'])); - } - if ($minorGridlines->getShadowProperty('rotWithShape') !== null) { - $objWriter->writeAttribute('rotWithShape', $minorGridlines->getShadowProperty('rotWithShape')); - } - $objWriter->startElement("a:{$minorGridlines->getShadowProperty(['color', 'type'])}"); - $objWriter->writeAttribute('val', $minorGridlines->getShadowProperty(['color', 'value'])); - $objWriter->startElement('a:alpha'); - $objWriter->writeAttribute('val', $minorGridlines->getShadowProperty(['color', 'alpha'])); - $objWriter->endElement(); //end alpha - $objWriter->endElement(); //end color:type - $objWriter->endElement(); //end shadow - } - - if ($minorGridlines->getSoftEdgesSize() !== null) { - $objWriter->startElement('a:softEdge'); - $objWriter->writeAttribute('rad', $minorGridlines->getSoftEdgesSize()); - $objWriter->endElement(); //end softEdge - } - - $objWriter->endElement(); //end effectLst + $this->writeLineStyles($objWriter, $minorGridlines); + $this->writeEffects($objWriter, $minorGridlines); $objWriter->endElement(); //end spPr $objWriter->endElement(); //end minorGridLines } @@ -784,18 +779,13 @@ class Chart extends WriterPart $objWriter->endElement(); $objWriter->startElement('a:p'); - $objWriter->startElement('a:r'); $caption = $yAxisLabel->getCaption(); if (is_array($caption)) { $caption = $caption[0]; } + $this->getParentWriter()->getWriterPartstringtable()->writeRichTextForCharts($objWriter, $caption, 'a'); - $objWriter->startElement('a:t'); - $objWriter->writeRawData(StringHelper::controlCharacterPHP2OOXML($caption)); - $objWriter->endElement(); - - $objWriter->endElement(); $objWriter->endElement(); $objWriter->endElement(); $objWriter->endElement(); @@ -806,7 +796,7 @@ class Chart extends WriterPart } $objWriter->startElement('c:overlay'); - $objWriter->writeAttribute('val', 0); + $objWriter->writeAttribute('val', '0'); $objWriter->endElement(); $objWriter->endElement(); @@ -817,143 +807,50 @@ class Chart extends WriterPart $objWriter->writeAttribute('sourceLinked', $xAxis->getAxisNumberSourceLinked()); $objWriter->endElement(); - $objWriter->startElement('c:majorTickMark'); - $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('major_tick_mark')); - $objWriter->endElement(); + if (!empty($xAxis->getAxisOptionsProperty('major_tick_mark'))) { + $objWriter->startElement('c:majorTickMark'); + $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('major_tick_mark')); + $objWriter->endElement(); + } - $objWriter->startElement('c:minorTickMark'); - $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('minor_tick_mark')); - $objWriter->endElement(); + if (!empty($xAxis->getAxisOptionsProperty('minor_tick_mark'))) { + $objWriter->startElement('c:minorTickMark'); + $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('minor_tick_mark')); + $objWriter->endElement(); + } - $objWriter->startElement('c:tickLblPos'); - $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('axis_labels')); - $objWriter->endElement(); + if (!empty($xAxis->getAxisOptionsProperty('axis_labels'))) { + $objWriter->startElement('c:tickLblPos'); + $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('axis_labels')); + $objWriter->endElement(); + } + + $textRotation = $xAxis->getAxisOptionsProperty('textRotation'); + if (is_numeric($textRotation)) { + $objWriter->startElement('c:txPr'); + $objWriter->startElement('a:bodyPr'); + $objWriter->writeAttribute('rot', Properties::angleToXml((float) $textRotation)); + $objWriter->endElement(); // a:bodyPr + $objWriter->startElement('a:lstStyle'); + $objWriter->endElement(); // a:lstStyle + $objWriter->startElement('a:p'); + $objWriter->startElement('a:pPr'); + $objWriter->startElement('a:defRPr'); + $objWriter->endElement(); // a:defRPr + $objWriter->endElement(); // a:pPr + $objWriter->endElement(); // a:p + $objWriter->endElement(); // c:txPr + } $objWriter->startElement('c:spPr'); - - if ($xAxis->getFillProperty('value') !== null) { - $objWriter->startElement('a:solidFill'); - $objWriter->startElement('a:' . $xAxis->getFillProperty('type')); - $objWriter->writeAttribute('val', $xAxis->getFillProperty('value')); - $objWriter->startElement('a:alpha'); - $objWriter->writeAttribute('val', $xAxis->getFillProperty('alpha')); - $objWriter->endElement(); - $objWriter->endElement(); - $objWriter->endElement(); - } - - $objWriter->startElement('a:ln'); - - $objWriter->writeAttribute('w', $xAxis->getLineStyleProperty('width')); - $objWriter->writeAttribute('cap', $xAxis->getLineStyleProperty('cap')); - $objWriter->writeAttribute('cmpd', $xAxis->getLineStyleProperty('compound')); - - if ($xAxis->getLineProperty('value') !== null) { - $objWriter->startElement('a:solidFill'); - $objWriter->startElement('a:' . $xAxis->getLineProperty('type')); - $objWriter->writeAttribute('val', $xAxis->getLineProperty('value')); - $objWriter->startElement('a:alpha'); - $objWriter->writeAttribute('val', $xAxis->getLineProperty('alpha')); - $objWriter->endElement(); - $objWriter->endElement(); - $objWriter->endElement(); - } - - $objWriter->startElement('a:prstDash'); - $objWriter->writeAttribute('val', $xAxis->getLineStyleProperty('dash')); - $objWriter->endElement(); - - if ($xAxis->getLineStyleProperty('join') == 'miter') { - $objWriter->startElement('a:miter'); - $objWriter->writeAttribute('lim', '800000'); - $objWriter->endElement(); - } else { - $objWriter->startElement('a:bevel'); - $objWriter->endElement(); - } - - if ($xAxis->getLineStyleProperty(['arrow', 'head', 'type']) !== null) { - $objWriter->startElement('a:headEnd'); - $objWriter->writeAttribute('type', $xAxis->getLineStyleProperty(['arrow', 'head', 'type'])); - $objWriter->writeAttribute('w', $xAxis->getLineStyleArrowWidth('head')); - $objWriter->writeAttribute('len', $xAxis->getLineStyleArrowLength('head')); - $objWriter->endElement(); - } - - if ($xAxis->getLineStyleProperty(['arrow', 'end', 'type']) !== null) { - $objWriter->startElement('a:tailEnd'); - $objWriter->writeAttribute('type', $xAxis->getLineStyleProperty(['arrow', 'end', 'type'])); - $objWriter->writeAttribute('w', $xAxis->getLineStyleArrowWidth('end')); - $objWriter->writeAttribute('len', $xAxis->getLineStyleArrowLength('end')); - $objWriter->endElement(); - } - - $objWriter->endElement(); - - $objWriter->startElement('a:effectLst'); - - if ($xAxis->getGlowProperty('size') !== null) { - $objWriter->startElement('a:glow'); - $objWriter->writeAttribute('rad', $xAxis->getGlowProperty('size')); - $objWriter->startElement("a:{$xAxis->getGlowProperty(['color', 'type'])}"); - $objWriter->writeAttribute('val', $xAxis->getGlowProperty(['color', 'value'])); - $objWriter->startElement('a:alpha'); - $objWriter->writeAttribute('val', $xAxis->getGlowProperty(['color', 'alpha'])); - $objWriter->endElement(); - $objWriter->endElement(); - $objWriter->endElement(); - } - - if ($xAxis->getShadowProperty('presets') !== null) { - $objWriter->startElement("a:{$xAxis->getShadowProperty('effect')}"); - - if ($xAxis->getShadowProperty('blur') !== null) { - $objWriter->writeAttribute('blurRad', $xAxis->getShadowProperty('blur')); - } - if ($xAxis->getShadowProperty('distance') !== null) { - $objWriter->writeAttribute('dist', $xAxis->getShadowProperty('distance')); - } - if ($xAxis->getShadowProperty('direction') !== null) { - $objWriter->writeAttribute('dir', $xAxis->getShadowProperty('direction')); - } - if ($xAxis->getShadowProperty('algn') !== null) { - $objWriter->writeAttribute('algn', $xAxis->getShadowProperty('algn')); - } - if ($xAxis->getShadowProperty(['size', 'sx']) !== null) { - $objWriter->writeAttribute('sx', $xAxis->getShadowProperty(['size', 'sx'])); - } - if ($xAxis->getShadowProperty(['size', 'sy']) !== null) { - $objWriter->writeAttribute('sy', $xAxis->getShadowProperty(['size', 'sy'])); - } - if ($xAxis->getShadowProperty(['size', 'kx']) !== null) { - $objWriter->writeAttribute('kx', $xAxis->getShadowProperty(['size', 'kx'])); - } - if ($xAxis->getShadowProperty('rotWithShape') !== null) { - $objWriter->writeAttribute('rotWithShape', $xAxis->getShadowProperty('rotWithShape')); - } - - $objWriter->startElement("a:{$xAxis->getShadowProperty(['color', 'type'])}"); - $objWriter->writeAttribute('val', $xAxis->getShadowProperty(['color', 'value'])); - $objWriter->startElement('a:alpha'); - $objWriter->writeAttribute('val', $xAxis->getShadowProperty(['color', 'alpha'])); - $objWriter->endElement(); - $objWriter->endElement(); - - $objWriter->endElement(); - } - - if ($xAxis->getSoftEdgesSize() !== null) { - $objWriter->startElement('a:softEdge'); - $objWriter->writeAttribute('rad', $xAxis->getSoftEdgesSize()); - $objWriter->endElement(); - } - - $objWriter->endElement(); //effectList + $this->writeColor($objWriter, $xAxis->getFillColorObject()); + $this->writeLineStyles($objWriter, $xAxis); + $this->writeEffects($objWriter, $xAxis); $objWriter->endElement(); //end spPr - if ($id1 > 0) { + if ($id1 !== '0') { $objWriter->startElement('c:crossAx'); - $objWriter->writeAttribute('val', $id2); + $objWriter->writeAttribute('val', $id1); $objWriter->endElement(); if ($xAxis->getAxisOptionsProperty('horizontal_crosses_value') !== null) { @@ -961,14 +858,20 @@ class Chart extends WriterPart $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('horizontal_crosses_value')); $objWriter->endElement(); } else { - $objWriter->startElement('c:crosses'); - $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('horizontal_crosses')); - $objWriter->endElement(); + $crosses = $xAxis->getAxisOptionsProperty('horizontal_crosses'); + if ($crosses) { + $objWriter->startElement('c:crosses'); + $objWriter->writeAttribute('val', $crosses); + $objWriter->endElement(); + } } - $objWriter->startElement('c:crossBetween'); - $objWriter->writeAttribute('val', 'midCat'); - $objWriter->endElement(); + $crossBetween = $xAxis->getCrossBetween(); + if ($crossBetween !== '') { + $objWriter->startElement('c:crossBetween'); + $objWriter->writeAttribute('val', $crossBetween); + $objWriter->endElement(); + } if ($xAxis->getAxisOptionsProperty('major_unit') !== null) { $objWriter->startElement('c:majorUnit'); @@ -986,7 +889,7 @@ class Chart extends WriterPart if ($isMultiLevelSeries) { if ($groupType !== DataSeries::TYPE_BUBBLECHART) { $objWriter->startElement('c:noMultiLvlLbl'); - $objWriter->writeAttribute('val', 0); + $objWriter->writeAttribute('val', '0'); $objWriter->endElement(); } } @@ -994,16 +897,60 @@ class Chart extends WriterPart $objWriter->endElement(); } + /** + * Write Ser Axis, for Surface chart. + */ + private function writeSerAxis(XMLWriter $objWriter, string $id2, string $id3): void + { + $objWriter->startElement('c:serAx'); + + $objWriter->startElement('c:axId'); + $objWriter->writeAttribute('val', $id3); + $objWriter->endElement(); // axId + + $objWriter->startElement('c:scaling'); + $objWriter->startElement('c:orientation'); + $objWriter->writeAttribute('val', 'minMax'); + $objWriter->endElement(); // orientation + $objWriter->endElement(); // scaling + + $objWriter->startElement('c:delete'); + $objWriter->writeAttribute('val', '0'); + $objWriter->endElement(); // delete + + $objWriter->startElement('c:axPos'); + $objWriter->writeAttribute('val', 'b'); + $objWriter->endElement(); // axPos + + $objWriter->startElement('c:majorTickMark'); + $objWriter->writeAttribute('val', 'out'); + $objWriter->endElement(); // majorTickMark + + $objWriter->startElement('c:minorTickMark'); + $objWriter->writeAttribute('val', 'none'); + $objWriter->endElement(); // minorTickMark + + $objWriter->startElement('c:tickLblPos'); + $objWriter->writeAttribute('val', 'nextTo'); + $objWriter->endElement(); // tickLblPos + + $objWriter->startElement('c:crossAx'); + $objWriter->writeAttribute('val', $id2); + $objWriter->endElement(); // crossAx + + $objWriter->startElement('c:crosses'); + $objWriter->writeAttribute('val', 'autoZero'); + $objWriter->endElement(); // crosses + + $objWriter->endElement(); //serAx + } + /** * Get the data series type(s) for a chart plot series. * - * @param PlotArea $plotArea - * - * @throws WriterException - * - * @return array|string + * @return string[] */ - private static function getChartType($plotArea) + private static function getChartType(PlotArea $plotArea): array { $groupCount = $plotArea->getPlotGroupCount(); @@ -1025,49 +972,34 @@ class Chart extends WriterPart /** * Method writing plot series values. - * - * @param XMLWriter $objWriter XML Writer - * @param int $val value for idx (default: 3) - * @param string $fillColor hex color (default: FF9900) - * - * @return XMLWriter XML Writer */ - private function writePlotSeriesValuesElement($objWriter, $val = 3, $fillColor = 'FF9900') + private function writePlotSeriesValuesElement(XMLWriter $objWriter, int $val, ?ChartColor $fillColor): void { + if ($fillColor === null || !$fillColor->isUsable()) { + return; + } $objWriter->startElement('c:dPt'); - $objWriter->startElement('c:idx'); - $objWriter->writeAttribute('val', $val); - $objWriter->endElement(); - $objWriter->startElement('c:bubble3D'); - $objWriter->writeAttribute('val', 0); - $objWriter->endElement(); + $objWriter->startElement('c:idx'); + $objWriter->writeAttribute('val', "$val"); + $objWriter->endElement(); // c:idx $objWriter->startElement('c:spPr'); - $objWriter->startElement('a:solidFill'); - $objWriter->startElement('a:srgbClr'); - $objWriter->writeAttribute('val', $fillColor); - $objWriter->endElement(); - $objWriter->endElement(); - $objWriter->endElement(); - $objWriter->endElement(); + $this->writeColor($objWriter, $fillColor); + $objWriter->endElement(); // c:spPr - return $objWriter; + $objWriter->endElement(); // c:dPt } /** * Write Plot Group (series of related plots). * - * @param DataSeries $plotGroup * @param string $groupType Type of plot for dataseries - * @param XMLWriter $objWriter XML Writer - * @param bool &$catIsMultiLevelSeries Is category a multi-series category - * @param bool &$valIsMultiLevelSeries Is value set a multi-series set - * @param string &$plotGroupingType Type of grouping for multi-series values - * - * @throws WriterException + * @param bool $catIsMultiLevelSeries Is category a multi-series category + * @param bool $valIsMultiLevelSeries Is value set a multi-series set + * @param string $plotGroupingType Type of grouping for multi-series values */ - private function writePlotGroup($plotGroup, $groupType, $objWriter, &$catIsMultiLevelSeries, &$valIsMultiLevelSeries, &$plotGroupingType) + private function writePlotGroup(?DataSeries $plotGroup, string $groupType, XMLWriter $objWriter, &$catIsMultiLevelSeries, &$valIsMultiLevelSeries, &$plotGroupingType): void { if ($plotGroup === null) { return; @@ -1079,8 +1011,8 @@ class Chart extends WriterPart $objWriter->endElement(); } - if ($plotGroup->getPlotGrouping() !== null) { - $plotGroupingType = $plotGroup->getPlotGrouping(); + $plotGroupingType = $plotGroup->getPlotGrouping(); + if ($plotGroupingType !== null && $groupType !== DataSeries::TYPE_SURFACECHART && $groupType !== DataSeries::TYPE_SURFACECHART_3D) { $objWriter->startElement('c:grouping'); $objWriter->writeAttribute('val', $plotGroupingType); $objWriter->endElement(); @@ -1094,57 +1026,61 @@ class Chart extends WriterPart if ($groupType !== DataSeries::TYPE_LINECHART) { if (($groupType == DataSeries::TYPE_PIECHART) || ($groupType == DataSeries::TYPE_PIECHART_3D) || ($groupType == DataSeries::TYPE_DONUTCHART) || ($plotSeriesCount > 1)) { $objWriter->startElement('c:varyColors'); - $objWriter->writeAttribute('val', 1); + $objWriter->writeAttribute('val', '1'); $objWriter->endElement(); } else { $objWriter->startElement('c:varyColors'); - $objWriter->writeAttribute('val', 0); + $objWriter->writeAttribute('val', '0'); $objWriter->endElement(); } } } + $plotSeriesIdx = 0; foreach ($plotSeriesOrder as $plotSeriesIdx => $plotSeriesRef) { $objWriter->startElement('c:ser'); - $plotLabel = $plotGroup->getPlotLabelByIndex($plotSeriesIdx); - if ($plotLabel) { - $fillColor = $plotLabel->getFillColor(); - if ($fillColor !== null && !is_array($fillColor)) { - $objWriter->startElement('c:spPr'); - $objWriter->startElement('a:solidFill'); - $objWriter->startElement('a:srgbClr'); - $objWriter->writeAttribute('val', $fillColor); - $objWriter->endElement(); - $objWriter->endElement(); - $objWriter->endElement(); - } - } - $objWriter->startElement('c:idx'); - $objWriter->writeAttribute('val', $this->seriesIndex + $plotSeriesIdx); + $adder = array_key_exists(0, $plotSeriesOrder) ? $this->seriesIndex : 0; + $objWriter->writeAttribute('val', (string) ($adder + $plotSeriesIdx)); $objWriter->endElement(); $objWriter->startElement('c:order'); - $objWriter->writeAttribute('val', $this->seriesIndex + $plotSeriesRef); + $objWriter->writeAttribute('val', (string) ($adder + $plotSeriesRef)); $objWriter->endElement(); - // Values - $plotSeriesValues = $plotGroup->getPlotValuesByIndex($plotSeriesRef); - - if (($groupType == DataSeries::TYPE_PIECHART) || ($groupType == DataSeries::TYPE_PIECHART_3D) || ($groupType == DataSeries::TYPE_DONUTCHART)) { - $fillColorValues = $plotSeriesValues->getFillColor(); - if ($fillColorValues !== null && is_array($fillColorValues)) { - foreach ($plotSeriesValues->getDataValues() as $dataKey => $dataValue) { - $this->writePlotSeriesValuesElement($objWriter, $dataKey, ($fillColorValues[$dataKey] ?? 'FF9900')); - } - } else { - $this->writePlotSeriesValuesElement($objWriter); + $plotLabel = $plotGroup->getPlotLabelByIndex($plotSeriesIdx); + $labelFill = null; + if ($plotLabel && $groupType === DataSeries::TYPE_LINECHART) { + $labelFill = $plotLabel->getFillColorObject(); + $labelFill = ($labelFill instanceof ChartColor) ? $labelFill : null; + } + if ($plotLabel && $groupType !== DataSeries::TYPE_LINECHART) { + $fillColor = $plotLabel->getFillColorObject(); + if ($fillColor !== null && !is_array($fillColor) && $fillColor->isUsable()) { + $objWriter->startElement('c:spPr'); + $this->writeColor($objWriter, $fillColor); + $objWriter->endElement(); // c:spPr } } + // Values + $plotSeriesValues = $plotGroup->getPlotValuesByIndex($plotSeriesIdx); + + if ($plotSeriesValues !== false && in_array($groupType, self::CUSTOM_COLOR_TYPES, true)) { + $fillColorValues = $plotSeriesValues->getFillColorObject(); + if ($fillColorValues !== null && is_array($fillColorValues)) { + foreach ($plotSeriesValues->getDataValues() as $dataKey => $dataValue) { + $this->writePlotSeriesValuesElement($objWriter, $dataKey, $fillColorValues[$dataKey] ?? null); + } + } + } + if ($plotSeriesValues !== false && $plotSeriesValues->getLabelLayout()) { + $this->writeDataLabels($objWriter, $plotSeriesValues->getLabelLayout()); + } + // Labels - $plotSeriesLabel = $plotGroup->getPlotLabelByIndex($plotSeriesRef); + $plotSeriesLabel = $plotGroup->getPlotLabelByIndex($plotSeriesIdx); if ($plotSeriesLabel && ($plotSeriesLabel->getPointCount() > 0)) { $objWriter->startElement('c:tx'); $objWriter->startElement('c:strRef'); @@ -1154,35 +1090,56 @@ class Chart extends WriterPart } // Formatting for the points - if (($groupType == DataSeries::TYPE_LINECHART) || ($groupType == DataSeries::TYPE_STOCKCHART)) { - $plotLineWidth = 12700; - if ($plotSeriesValues) { - $plotLineWidth = $plotSeriesValues->getLineWidth(); - } - + if ( + $plotSeriesValues !== false + ) { $objWriter->startElement('c:spPr'); - $objWriter->startElement('a:ln'); - $objWriter->writeAttribute('w', $plotLineWidth); - if ($groupType == DataSeries::TYPE_STOCKCHART) { - $objWriter->startElement('a:noFill'); - $objWriter->endElement(); + $fillObject = $labelFill ?? $plotSeriesValues->getFillColorObject(); + $callLineStyles = true; + if ($fillObject instanceof ChartColor && $fillObject->isUsable()) { + if ($groupType === DataSeries::TYPE_LINECHART) { + $objWriter->startElement('a:ln'); + $callLineStyles = false; + } + $this->writeColor($objWriter, $fillObject); + if (!$callLineStyles) { + $objWriter->endElement(); // a:ln + } } - $objWriter->endElement(); - $objWriter->endElement(); + $nofill = $groupType === DataSeries::TYPE_STOCKCHART || (($groupType === DataSeries::TYPE_SCATTERCHART || $groupType === DataSeries::TYPE_LINECHART) && !$plotSeriesValues->getScatterLines()); + if ($callLineStyles) { + $this->writeLineStyles($objWriter, $plotSeriesValues, $nofill); + $this->writeEffects($objWriter, $plotSeriesValues); + } + $objWriter->endElement(); // c:spPr } if ($plotSeriesValues) { $plotSeriesMarker = $plotSeriesValues->getPointMarker(); - if ($plotSeriesMarker) { + $markerFillColor = $plotSeriesValues->getMarkerFillColor(); + $fillUsed = $markerFillColor->IsUsable(); + $markerBorderColor = $plotSeriesValues->getMarkerBorderColor(); + $borderUsed = $markerBorderColor->isUsable(); + if ($plotSeriesMarker || $fillUsed || $borderUsed) { $objWriter->startElement('c:marker'); $objWriter->startElement('c:symbol'); - $objWriter->writeAttribute('val', $plotSeriesMarker); + if ($plotSeriesMarker) { + $objWriter->writeAttribute('val', $plotSeriesMarker); + } $objWriter->endElement(); if ($plotSeriesMarker !== 'none') { $objWriter->startElement('c:size'); - $objWriter->writeAttribute('val', 3); - $objWriter->endElement(); + $objWriter->writeAttribute('val', (string) $plotSeriesValues->getPointSize()); + $objWriter->endElement(); // c:size + $objWriter->startElement('c:spPr'); + $this->writeColor($objWriter, $markerFillColor); + if ($borderUsed) { + $objWriter->startElement('a:ln'); + $this->writeColor($objWriter, $markerBorderColor); + $objWriter->endElement(); // a:ln + } + $objWriter->endElement(); // spPr } $objWriter->endElement(); @@ -1191,23 +1148,103 @@ class Chart extends WriterPart if (($groupType === DataSeries::TYPE_BARCHART) || ($groupType === DataSeries::TYPE_BARCHART_3D) || ($groupType === DataSeries::TYPE_BUBBLECHART)) { $objWriter->startElement('c:invertIfNegative'); - $objWriter->writeAttribute('val', 0); + $objWriter->writeAttribute('val', '0'); $objWriter->endElement(); } + // Trendlines + if ($plotSeriesValues !== false) { + foreach ($plotSeriesValues->getTrendLines() as $trendLine) { + $trendLineType = $trendLine->getTrendLineType(); + $order = $trendLine->getOrder(); + $period = $trendLine->getPeriod(); + $dispRSqr = $trendLine->getDispRSqr(); + $dispEq = $trendLine->getDispEq(); + $forward = $trendLine->getForward(); + $backward = $trendLine->getBackward(); + $intercept = $trendLine->getIntercept(); + $name = $trendLine->getName(); + $trendLineColor = $trendLine->getLineColor(); // ChartColor + + $objWriter->startElement('c:trendline'); // N.B. lowercase 'ell' + if ($name !== '') { + $objWriter->startElement('c:name'); + $objWriter->writeRawData($name); + $objWriter->endElement(); // c:name + } + $objWriter->startElement('c:spPr'); + + if (!$trendLineColor->isUsable()) { + // use dataSeriesValues line color as a backup if $trendLineColor is null + $dsvLineColor = $plotSeriesValues->getLineColor(); + if ($dsvLineColor->isUsable()) { + $trendLine + ->getLineColor() + ->setColorProperties($dsvLineColor->getValue(), $dsvLineColor->getAlpha(), $dsvLineColor->getType()); + } + } // otherwise, hope Excel does the right thing + + $this->writeLineStyles($objWriter, $trendLine, false); // suppress noFill + + $objWriter->endElement(); // spPr + + $objWriter->startElement('c:trendlineType'); // N.B lowercase 'ell' + $objWriter->writeAttribute('val', $trendLineType); + $objWriter->endElement(); // trendlineType + if ($backward !== 0.0) { + $objWriter->startElement('c:backward'); + $objWriter->writeAttribute('val', "$backward"); + $objWriter->endElement(); // c:backward + } + if ($forward !== 0.0) { + $objWriter->startElement('c:forward'); + $objWriter->writeAttribute('val', "$forward"); + $objWriter->endElement(); // c:forward + } + if ($intercept !== 0.0) { + $objWriter->startElement('c:intercept'); + $objWriter->writeAttribute('val', "$intercept"); + $objWriter->endElement(); // c:intercept + } + if ($trendLineType == TrendLine::TRENDLINE_POLYNOMIAL) { + $objWriter->startElement('c:order'); + $objWriter->writeAttribute('val', $order); + $objWriter->endElement(); // order + } + if ($trendLineType == TrendLine::TRENDLINE_MOVING_AVG) { + $objWriter->startElement('c:period'); + $objWriter->writeAttribute('val', $period); + $objWriter->endElement(); // period + } + $objWriter->startElement('c:dispRSqr'); + $objWriter->writeAttribute('val', $dispRSqr ? '1' : '0'); + $objWriter->endElement(); + $objWriter->startElement('c:dispEq'); + $objWriter->writeAttribute('val', $dispEq ? '1' : '0'); + $objWriter->endElement(); + if ($groupType === DataSeries::TYPE_SCATTERCHART || $groupType === DataSeries::TYPE_LINECHART) { + $objWriter->startElement('c:trendlineLbl'); + $objWriter->startElement('c:numFmt'); + $objWriter->writeAttribute('formatCode', 'General'); + $objWriter->writeAttribute('sourceLinked', '0'); + $objWriter->endElement(); // numFmt + $objWriter->endElement(); // trendlineLbl + } + + $objWriter->endElement(); // trendline + } + } // Category Labels - $plotSeriesCategory = $plotGroup->getPlotCategoryByIndex($plotSeriesRef); + $plotSeriesCategory = $plotGroup->getPlotCategoryByIndex($plotSeriesIdx); if ($plotSeriesCategory && ($plotSeriesCategory->getPointCount() > 0)) { $catIsMultiLevelSeries = $catIsMultiLevelSeries || $plotSeriesCategory->isMultiLevelSeries(); if (($groupType == DataSeries::TYPE_PIECHART) || ($groupType == DataSeries::TYPE_PIECHART_3D) || ($groupType == DataSeries::TYPE_DONUTCHART)) { - if ($plotGroup->getPlotStyle() !== null) { - $plotStyle = $plotGroup->getPlotStyle(); - if ($plotStyle) { - $objWriter->startElement('c:explosion'); - $objWriter->writeAttribute('val', 25); - $objWriter->endElement(); - } + $plotStyle = $plotGroup->getPlotStyle(); + if (is_numeric($plotStyle)) { + $objWriter->startElement('c:explosion'); + $objWriter->writeAttribute('val', $plotStyle); + $objWriter->endElement(); } } @@ -1217,7 +1254,14 @@ class Chart extends WriterPart $objWriter->startElement('c:cat'); } - $this->writePlotSeriesValues($plotSeriesCategory, $objWriter, $groupType, 'str'); + // xVals (Categories) are not always 'str' + // Test X-axis Label's Datatype to decide 'str' vs 'num' + $CategoryDatatype = $plotSeriesCategory->getDataType(); + if ($CategoryDatatype == DataSeriesValues::DATASERIES_TYPE_NUMBER) { + $this->writePlotSeriesValues($plotSeriesCategory, $objWriter, $groupType, 'num'); + } else { + $this->writePlotSeriesValues($plotSeriesCategory, $objWriter, $groupType, 'str'); + } $objWriter->endElement(); } @@ -1233,10 +1277,31 @@ class Chart extends WriterPart $this->writePlotSeriesValues($plotSeriesValues, $objWriter, $groupType, 'num'); $objWriter->endElement(); + if ($groupType === DataSeries::TYPE_SCATTERCHART && $plotGroup->getPlotStyle() === 'smoothMarker') { + $objWriter->startElement('c:smooth'); + $objWriter->writeAttribute('val', $plotSeriesValues->getSmoothLine() ? '1' : '0'); + $objWriter->endElement(); + } } if ($groupType === DataSeries::TYPE_BUBBLECHART) { - $this->writeBubbles($plotSeriesValues, $objWriter); + if (!empty($plotGroup->getPlotBubbleSizes()[$plotSeriesIdx])) { + $objWriter->startElement('c:bubbleSize'); + $this->writePlotSeriesValues( + $plotGroup->getPlotBubbleSizes()[$plotSeriesIdx], + $objWriter, + $groupType, + 'num' + ); + $objWriter->endElement(); + if ($plotSeriesValues !== false) { + $objWriter->startElement('c:bubble3D'); + $objWriter->writeAttribute('val', $plotSeriesValues->getBubble3D() ? '1' : '0'); + $objWriter->endElement(); + } + } elseif ($plotSeriesValues !== false) { + $this->writeBubbles($plotSeriesValues, $objWriter); + } } $objWriter->endElement(); @@ -1247,11 +1312,8 @@ class Chart extends WriterPart /** * Write Plot Series Label. - * - * @param DataSeriesValues $plotSeriesLabel - * @param XMLWriter $objWriter XML Writer */ - private function writePlotSeriesLabel($plotSeriesLabel, $objWriter) + private function writePlotSeriesLabel(?DataSeriesValues $plotSeriesLabel, XMLWriter $objWriter): void { if ($plotSeriesLabel === null) { return; @@ -1263,7 +1325,7 @@ class Chart extends WriterPart $objWriter->startElement('c:strCache'); $objWriter->startElement('c:ptCount'); - $objWriter->writeAttribute('val', $plotSeriesLabel->getPointCount()); + $objWriter->writeAttribute('val', (string) $plotSeriesLabel->getPointCount()); $objWriter->endElement(); foreach ($plotSeriesLabel->getDataValues() as $plotLabelKey => $plotLabelValue) { @@ -1281,12 +1343,10 @@ class Chart extends WriterPart /** * Write Plot Series Values. * - * @param DataSeriesValues $plotSeriesValues - * @param XMLWriter $objWriter XML Writer * @param string $groupType Type of plot for dataseries * @param string $dataType Datatype of series values */ - private function writePlotSeriesValues($plotSeriesValues, XMLWriter $objWriter, $groupType, $dataType = 'str') + private function writePlotSeriesValues(?DataSeriesValues $plotSeriesValues, XMLWriter $objWriter, $groupType, $dataType = 'str'): void { if ($plotSeriesValues === null) { return; @@ -1304,7 +1364,7 @@ class Chart extends WriterPart $objWriter->startElement('c:multiLvlStrCache'); $objWriter->startElement('c:ptCount'); - $objWriter->writeAttribute('val', $plotSeriesValues->getPointCount()); + $objWriter->writeAttribute('val', (string) $plotSeriesValues->getPointCount()); $objWriter->endElement(); for ($level = 0; $level < $levelCount; ++$level) { @@ -1335,23 +1395,26 @@ class Chart extends WriterPart $objWriter->writeRawData($plotSeriesValues->getDataSource()); $objWriter->endElement(); - $objWriter->startElement('c:' . $dataType . 'Cache'); + $count = $plotSeriesValues->getPointCount(); + $source = $plotSeriesValues->getDataSource(); + $values = $plotSeriesValues->getDataValues(); + if ($count > 1 || ($count === 1 && "=$source" !== (string) $values[0])) { + $objWriter->startElement('c:' . $dataType . 'Cache'); - if (($groupType != DataSeries::TYPE_PIECHART) && ($groupType != DataSeries::TYPE_PIECHART_3D) && ($groupType != DataSeries::TYPE_DONUTCHART)) { - if (($plotSeriesValues->getFormatCode() !== null) && ($plotSeriesValues->getFormatCode() !== '')) { - $objWriter->startElement('c:formatCode'); - $objWriter->writeRawData($plotSeriesValues->getFormatCode()); - $objWriter->endElement(); + if (($groupType != DataSeries::TYPE_PIECHART) && ($groupType != DataSeries::TYPE_PIECHART_3D) && ($groupType != DataSeries::TYPE_DONUTCHART)) { + if (($plotSeriesValues->getFormatCode() !== null) && ($plotSeriesValues->getFormatCode() !== '')) { + $objWriter->startElement('c:formatCode'); + $objWriter->writeRawData($plotSeriesValues->getFormatCode()); + $objWriter->endElement(); + } } - } - $objWriter->startElement('c:ptCount'); - $objWriter->writeAttribute('val', $plotSeriesValues->getPointCount()); - $objWriter->endElement(); + $objWriter->startElement('c:ptCount'); + $objWriter->writeAttribute('val', (string) $plotSeriesValues->getPointCount()); + $objWriter->endElement(); - $dataValues = $plotSeriesValues->getDataValues(); - if (!empty($dataValues)) { - if (is_array($dataValues)) { + $dataValues = $plotSeriesValues->getDataValues(); + if (!empty($dataValues)) { foreach ($dataValues as $plotSeriesKey => $plotSeriesValue) { $objWriter->startElement('c:pt'); $objWriter->writeAttribute('idx', $plotSeriesKey); @@ -1362,21 +1425,26 @@ class Chart extends WriterPart $objWriter->endElement(); } } + + $objWriter->endElement(); // *Cache } - $objWriter->endElement(); - - $objWriter->endElement(); + $objWriter->endElement(); // *Ref } } + private const CUSTOM_COLOR_TYPES = [ + DataSeries::TYPE_BARCHART, + DataSeries::TYPE_BARCHART_3D, + DataSeries::TYPE_PIECHART, + DataSeries::TYPE_PIECHART_3D, + DataSeries::TYPE_DONUTCHART, + ]; + /** * Write Bubble Chart Details. - * - * @param DataSeriesValues $plotSeriesValues - * @param XMLWriter $objWriter XML Writer */ - private function writeBubbles($plotSeriesValues, $objWriter) + private function writeBubbles(?DataSeriesValues $plotSeriesValues, XMLWriter $objWriter): void { if ($plotSeriesValues === null) { return; @@ -1390,20 +1458,18 @@ class Chart extends WriterPart $objWriter->endElement(); $objWriter->startElement('c:ptCount'); - $objWriter->writeAttribute('val', $plotSeriesValues->getPointCount()); + $objWriter->writeAttribute('val', (string) $plotSeriesValues->getPointCount()); $objWriter->endElement(); $dataValues = $plotSeriesValues->getDataValues(); if (!empty($dataValues)) { - if (is_array($dataValues)) { - foreach ($dataValues as $plotSeriesKey => $plotSeriesValue) { - $objWriter->startElement('c:pt'); - $objWriter->writeAttribute('idx', $plotSeriesKey); - $objWriter->startElement('c:v'); - $objWriter->writeRawData(1); - $objWriter->endElement(); - $objWriter->endElement(); - } + foreach ($dataValues as $plotSeriesKey => $plotSeriesValue) { + $objWriter->startElement('c:pt'); + $objWriter->writeAttribute('idx', $plotSeriesKey); + $objWriter->startElement('c:v'); + $objWriter->writeRawData('1'); + $objWriter->endElement(); + $objWriter->endElement(); } } @@ -1411,17 +1477,14 @@ class Chart extends WriterPart $objWriter->endElement(); $objWriter->startElement('c:bubble3D'); - $objWriter->writeAttribute('val', 0); + $objWriter->writeAttribute('val', $plotSeriesValues->getBubble3D() ? '1' : '0'); $objWriter->endElement(); } /** * Write Layout. - * - * @param XMLWriter $objWriter XML Writer - * @param Layout $layout */ - private function writeLayout(XMLWriter $objWriter, Layout $layout = null) + private function writeLayout(XMLWriter $objWriter, ?Layout $layout = null): void { $objWriter->startElement('c:layout'); @@ -1452,28 +1515,28 @@ class Chart extends WriterPart $x = $layout->getXPosition(); if ($x !== null) { $objWriter->startElement('c:x'); - $objWriter->writeAttribute('val', $x); + $objWriter->writeAttribute('val', "$x"); $objWriter->endElement(); } $y = $layout->getYPosition(); if ($y !== null) { $objWriter->startElement('c:y'); - $objWriter->writeAttribute('val', $y); + $objWriter->writeAttribute('val', "$y"); $objWriter->endElement(); } $w = $layout->getWidth(); if ($w !== null) { $objWriter->startElement('c:w'); - $objWriter->writeAttribute('val', $w); + $objWriter->writeAttribute('val', "$w"); $objWriter->endElement(); } $h = $layout->getHeight(); if ($h !== null) { $objWriter->startElement('c:h'); - $objWriter->writeAttribute('val', $h); + $objWriter->writeAttribute('val', "$h"); $objWriter->endElement(); } @@ -1485,17 +1548,15 @@ class Chart extends WriterPart /** * Write Alternate Content block. - * - * @param XMLWriter $objWriter XML Writer */ - private function writeAlternateContent($objWriter) + private function writeAlternateContent(XMLWriter $objWriter): void { $objWriter->startElement('mc:AlternateContent'); - $objWriter->writeAttribute('xmlns:mc', 'http://schemas.openxmlformats.org/markup-compatibility/2006'); + $objWriter->writeAttribute('xmlns:mc', Namespaces::COMPATIBILITY); $objWriter->startElement('mc:Choice'); - $objWriter->writeAttribute('xmlns:c14', 'http://schemas.microsoft.com/office/drawing/2007/8/2/chart'); $objWriter->writeAttribute('Requires', 'c14'); + $objWriter->writeAttribute('xmlns:c14', Namespaces::CHART_ALTERNATE); $objWriter->startElement('c14:style'); $objWriter->writeAttribute('val', '102'); @@ -1513,10 +1574,8 @@ class Chart extends WriterPart /** * Write Printer Settings. - * - * @param XMLWriter $objWriter XML Writer */ - private function writePrintSettings($objWriter) + private function writePrintSettings(XMLWriter $objWriter): void { $objWriter->startElement('c:printSettings'); @@ -1524,12 +1583,12 @@ class Chart extends WriterPart $objWriter->endElement(); $objWriter->startElement('c:pageMargins'); - $objWriter->writeAttribute('footer', 0.3); - $objWriter->writeAttribute('header', 0.3); - $objWriter->writeAttribute('r', 0.7); - $objWriter->writeAttribute('l', 0.7); - $objWriter->writeAttribute('t', 0.75); - $objWriter->writeAttribute('b', 0.75); + $objWriter->writeAttribute('footer', '0.3'); + $objWriter->writeAttribute('header', '0.3'); + $objWriter->writeAttribute('r', '0.7'); + $objWriter->writeAttribute('l', '0.7'); + $objWriter->writeAttribute('t', '0.75'); + $objWriter->writeAttribute('b', '0.75'); $objWriter->endElement(); $objWriter->startElement('c:pageSetup'); @@ -1538,4 +1597,177 @@ class Chart extends WriterPart $objWriter->endElement(); } + + private function writeEffects(XMLWriter $objWriter, Properties $yAxis): void + { + if ( + !empty($yAxis->getSoftEdgesSize()) + || !empty($yAxis->getShadowProperty('effect')) + || !empty($yAxis->getGlowProperty('size')) + ) { + $objWriter->startElement('a:effectLst'); + $this->writeGlow($objWriter, $yAxis); + $this->writeShadow($objWriter, $yAxis); + $this->writeSoftEdge($objWriter, $yAxis); + $objWriter->endElement(); // effectLst + } + } + + private function writeShadow(XMLWriter $objWriter, Properties $xAxis): void + { + if (empty($xAxis->getShadowProperty('effect'))) { + return; + } + /** @var string */ + $effect = $xAxis->getShadowProperty('effect'); + $objWriter->startElement("a:$effect"); + + if (is_numeric($xAxis->getShadowProperty('blur'))) { + $objWriter->writeAttribute('blurRad', Properties::pointsToXml((float) $xAxis->getShadowProperty('blur'))); + } + if (is_numeric($xAxis->getShadowProperty('distance'))) { + $objWriter->writeAttribute('dist', Properties::pointsToXml((float) $xAxis->getShadowProperty('distance'))); + } + if (is_numeric($xAxis->getShadowProperty('direction'))) { + $objWriter->writeAttribute('dir', Properties::angleToXml((float) $xAxis->getShadowProperty('direction'))); + } + $algn = $xAxis->getShadowProperty('algn'); + if (is_string($algn) && $algn !== '') { + $objWriter->writeAttribute('algn', $algn); + } + foreach (['sx', 'sy'] as $sizeType) { + $sizeValue = $xAxis->getShadowProperty(['size', $sizeType]); + if (is_numeric($sizeValue)) { + $objWriter->writeAttribute($sizeType, Properties::tenthOfPercentToXml((float) $sizeValue)); + } + } + foreach (['kx', 'ky'] as $sizeType) { + $sizeValue = $xAxis->getShadowProperty(['size', $sizeType]); + if (is_numeric($sizeValue)) { + $objWriter->writeAttribute($sizeType, Properties::angleToXml((float) $sizeValue)); + } + } + $rotWithShape = $xAxis->getShadowProperty('rotWithShape'); + if (is_numeric($rotWithShape)) { + $objWriter->writeAttribute('rotWithShape', (string) (int) $rotWithShape); + } + + $this->writeColor($objWriter, $xAxis->getShadowColorObject(), false); + + $objWriter->endElement(); + } + + private function writeGlow(XMLWriter $objWriter, Properties $yAxis): void + { + $size = $yAxis->getGlowProperty('size'); + if (empty($size)) { + return; + } + $objWriter->startElement('a:glow'); + $objWriter->writeAttribute('rad', Properties::pointsToXml((float) $size)); + $this->writeColor($objWriter, $yAxis->getGlowColorObject(), false); + $objWriter->endElement(); // glow + } + + private function writeSoftEdge(XMLWriter $objWriter, Properties $yAxis): void + { + $softEdgeSize = $yAxis->getSoftEdgesSize(); + if (empty($softEdgeSize)) { + return; + } + $objWriter->startElement('a:softEdge'); + $objWriter->writeAttribute('rad', Properties::pointsToXml((float) $softEdgeSize)); + $objWriter->endElement(); //end softEdge + } + + private function writeLineStyles(XMLWriter $objWriter, Properties $gridlines, bool $noFill = false): void + { + $objWriter->startElement('a:ln'); + $widthTemp = $gridlines->getLineStyleProperty('width'); + if (is_numeric($widthTemp)) { + $objWriter->writeAttribute('w', Properties::pointsToXml((float) $widthTemp)); + } + $this->writeNotEmpty($objWriter, 'cap', $gridlines->getLineStyleProperty('cap')); + $this->writeNotEmpty($objWriter, 'cmpd', $gridlines->getLineStyleProperty('compound')); + if ($noFill) { + $objWriter->startElement('a:noFill'); + $objWriter->endElement(); + } else { + $this->writeColor($objWriter, $gridlines->getLineColor()); + } + + $dash = $gridlines->getLineStyleProperty('dash'); + if (!empty($dash)) { + $objWriter->startElement('a:prstDash'); + $this->writeNotEmpty($objWriter, 'val', $dash); + $objWriter->endElement(); + } + + if ($gridlines->getLineStyleProperty('join') === 'miter') { + $objWriter->startElement('a:miter'); + $objWriter->writeAttribute('lim', '800000'); + $objWriter->endElement(); + } elseif ($gridlines->getLineStyleProperty('join') === 'bevel') { + $objWriter->startElement('a:bevel'); + $objWriter->endElement(); + } + + if ($gridlines->getLineStyleProperty(['arrow', 'head', 'type'])) { + $objWriter->startElement('a:headEnd'); + $objWriter->writeAttribute('type', $gridlines->getLineStyleProperty(['arrow', 'head', 'type'])); + $this->writeNotEmpty($objWriter, 'w', $gridlines->getLineStyleArrowWidth('head')); + $this->writeNotEmpty($objWriter, 'len', $gridlines->getLineStyleArrowLength('head')); + $objWriter->endElement(); + } + + if ($gridlines->getLineStyleProperty(['arrow', 'end', 'type'])) { + $objWriter->startElement('a:tailEnd'); + $objWriter->writeAttribute('type', $gridlines->getLineStyleProperty(['arrow', 'end', 'type'])); + $this->writeNotEmpty($objWriter, 'w', $gridlines->getLineStyleArrowWidth('end')); + $this->writeNotEmpty($objWriter, 'len', $gridlines->getLineStyleArrowLength('end')); + $objWriter->endElement(); + } + $objWriter->endElement(); //end ln + } + + private function writeNotEmpty(XMLWriter $objWriter, string $name, ?string $value): void + { + if ($value !== null && $value !== '') { + $objWriter->writeAttribute($name, $value); + } + } + + private function writeColor(XMLWriter $objWriter, ChartColor $chartColor, bool $solidFill = true): void + { + $type = $chartColor->getType(); + $value = $chartColor->getValue(); + if (!empty($type) && !empty($value)) { + if ($solidFill) { + $objWriter->startElement('a:solidFill'); + } + $objWriter->startElement("a:$type"); + $objWriter->writeAttribute('val', $value); + $alpha = $chartColor->getAlpha(); + if (is_numeric($alpha)) { + $objWriter->startElement('a:alpha'); + $objWriter->writeAttribute('val', ChartColor::alphaToXml((int) $alpha)); + $objWriter->endElement(); // a:alpha + } + $brightness = $chartColor->getBrightness(); + if (is_numeric($brightness)) { + $brightness = (int) $brightness; + $lumOff = 100 - $brightness; + $objWriter->startElement('a:lumMod'); + $objWriter->writeAttribute('val', ChartColor::alphaToXml($brightness)); + $objWriter->endElement(); // a:lumMod + $objWriter->startElement('a:lumOff'); + $objWriter->writeAttribute('val', ChartColor::alphaToXml($lumOff)); + $objWriter->endElement(); // a:lumOff + } + $objWriter->endElement(); //a:srgbClr/schemeClr/prstClr + if ($solidFill) { + $objWriter->endElement(); //a:solidFill + } + } + } } diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Comments.php b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Comments.php old mode 100755 new mode 100644 index a95298a..9197388 --- a/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Comments.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Comments.php @@ -4,6 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; use PhpOffice\PhpSpreadsheet\Comment; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; class Comments extends WriterPart @@ -11,13 +12,9 @@ class Comments extends WriterPart /** * Write comments to XML format. * - * @param \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet - * - * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception - * * @return string XML Output */ - public function writeComments(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet) + public function writeComments(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet) { // Create XML writer $objWriter = null; @@ -31,7 +28,7 @@ class Comments extends WriterPart $objWriter->startDocument('1.0', 'UTF-8', 'yes'); // Comments cache - $comments = $pWorksheet->getComments(); + $comments = $worksheet->getComments(); // Authors cache $authors = []; @@ -44,7 +41,7 @@ class Comments extends WriterPart // comments $objWriter->startElement('comments'); - $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'); + $objWriter->writeAttribute('xmlns', Namespaces::MAIN); // Loop through authors $objWriter->startElement('authors'); @@ -69,23 +66,20 @@ class Comments extends WriterPart /** * Write comment to XML format. * - * @param XMLWriter $objWriter XML Writer - * @param string $pCellReference Cell reference - * @param Comment $pComment Comment - * @param array $pAuthors Array of authors - * - * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception + * @param string $cellReference Cell reference + * @param Comment $comment Comment + * @param array $authors Array of authors */ - private function writeComment(XMLWriter $objWriter, $pCellReference, Comment $pComment, array $pAuthors) + private function writeComment(XMLWriter $objWriter, $cellReference, Comment $comment, array $authors): void { // comment $objWriter->startElement('comment'); - $objWriter->writeAttribute('ref', $pCellReference); - $objWriter->writeAttribute('authorId', $pAuthors[$pComment->getAuthor()]); + $objWriter->writeAttribute('ref', $cellReference); + $objWriter->writeAttribute('authorId', $authors[$comment->getAuthor()]); // text $objWriter->startElement('text'); - $this->getParentWriter()->getWriterPart('stringtable')->writeRichText($objWriter, $pComment->getText()); + $this->getParentWriter()->getWriterPartstringtable()->writeRichText($objWriter, $comment->getText()); $objWriter->endElement(); $objWriter->endElement(); @@ -94,13 +88,9 @@ class Comments extends WriterPart /** * Write VML comments to XML format. * - * @param \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet - * - * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception - * * @return string XML Output */ - public function writeVMLComments(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet) + public function writeVMLComments(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet) { // Create XML writer $objWriter = null; @@ -114,13 +104,13 @@ class Comments extends WriterPart $objWriter->startDocument('1.0', 'UTF-8', 'yes'); // Comments cache - $comments = $pWorksheet->getComments(); + $comments = $worksheet->getComments(); // xml $objWriter->startElement('xml'); - $objWriter->writeAttribute('xmlns:v', 'urn:schemas-microsoft-com:vml'); - $objWriter->writeAttribute('xmlns:o', 'urn:schemas-microsoft-com:office:office'); - $objWriter->writeAttribute('xmlns:x', 'urn:schemas-microsoft-com:office:excel'); + $objWriter->writeAttribute('xmlns:v', Namespaces::URN_VML); + $objWriter->writeAttribute('xmlns:o', Namespaces::URN_MSOFFICE); + $objWriter->writeAttribute('xmlns:x', Namespaces::URN_EXCEL); // o:shapelayout $objWriter->startElement('o:shapelayout'); @@ -168,29 +158,33 @@ class Comments extends WriterPart /** * Write VML comment to XML format. * - * @param XMLWriter $objWriter XML Writer - * @param string $pCellReference Cell reference, eg: 'A1' - * @param Comment $pComment Comment + * @param string $cellReference Cell reference, eg: 'A1' + * @param Comment $comment Comment */ - private function writeVMLComment(XMLWriter $objWriter, $pCellReference, Comment $pComment) + private function writeVMLComment(XMLWriter $objWriter, $cellReference, Comment $comment): void { // Metadata - [$column, $row] = Coordinate::coordinateFromString($pCellReference); - $column = Coordinate::columnIndexFromString($column); + [$column, $row] = Coordinate::indexesFromString($cellReference); $id = 1024 + $column + $row; - $id = substr($id, 0, 4); + $id = substr("$id", 0, 4); // v:shape $objWriter->startElement('v:shape'); $objWriter->writeAttribute('id', '_x0000_s' . $id); $objWriter->writeAttribute('type', '#_x0000_t202'); - $objWriter->writeAttribute('style', 'position:absolute;margin-left:' . $pComment->getMarginLeft() . ';margin-top:' . $pComment->getMarginTop() . ';width:' . $pComment->getWidth() . ';height:' . $pComment->getHeight() . ';z-index:1;visibility:' . ($pComment->getVisible() ? 'visible' : 'hidden')); - $objWriter->writeAttribute('fillcolor', '#' . $pComment->getFillColor()->getRGB()); + $objWriter->writeAttribute('style', 'position:absolute;margin-left:' . $comment->getMarginLeft() . ';margin-top:' . $comment->getMarginTop() . ';width:' . $comment->getWidth() . ';height:' . $comment->getHeight() . ';z-index:1;visibility:' . ($comment->getVisible() ? 'visible' : 'hidden')); + $objWriter->writeAttribute('fillcolor', '#' . $comment->getFillColor()->getRGB()); $objWriter->writeAttribute('o:insetmode', 'auto'); // v:fill $objWriter->startElement('v:fill'); - $objWriter->writeAttribute('color2', '#' . $pComment->getFillColor()->getRGB()); + $objWriter->writeAttribute('color2', '#' . $comment->getFillColor()->getRGB()); + if ($comment->hasBackgroundImage()) { + $bgImage = $comment->getBackgroundImage(); + $objWriter->writeAttribute('o:relid', 'rId' . $bgImage->getImageIndex()); + $objWriter->writeAttribute('o:title', $bgImage->getName()); + $objWriter->writeAttribute('type', 'frame'); + } $objWriter->endElement(); // v:shadow @@ -230,10 +224,10 @@ class Comments extends WriterPart $objWriter->writeElement('x:AutoFill', 'False'); // x:Row - $objWriter->writeElement('x:Row', ($row - 1)); + $objWriter->writeElement('x:Row', (string) ($row - 1)); // x:Column - $objWriter->writeElement('x:Column', ($column - 1)); + $objWriter->writeElement('x:Column', (string) ($column - 1)); $objWriter->endElement(); diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php old mode 100755 new mode 100644 index 6b22d71..73657fc --- a/PhpOffice/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php @@ -2,6 +2,7 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces; use PhpOffice\PhpSpreadsheet\Shared\File; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; use PhpOffice\PhpSpreadsheet\Spreadsheet; @@ -13,11 +14,8 @@ class ContentTypes extends WriterPart /** * Write content types to XML format. * - * @param Spreadsheet $spreadsheet * @param bool $includeCharts Flag indicating if we should include drawing details for charts * - * @throws WriterException - * * @return string XML Output */ public function writeContentTypes(Spreadsheet $spreadsheet, $includeCharts = false) @@ -35,7 +33,7 @@ class ContentTypes extends WriterPart // Types $objWriter->startElement('Types'); - $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/content-types'); + $objWriter->writeAttribute('xmlns', Namespaces::CONTENT_TYPES); // Theme $this->writeOverrideContentType($objWriter, '/xl/theme/theme1.xml', 'application/vnd.openxmlformats-officedocument.theme+xml'); @@ -88,6 +86,16 @@ class ContentTypes extends WriterPart // Shared strings $this->writeOverrideContentType($objWriter, '/xl/sharedStrings.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml'); + // Table + $table = 1; + for ($i = 0; $i < $sheetCount; ++$i) { + $tableCount = $spreadsheet->getSheet($i)->getTableCollection()->count(); + + for ($t = 1; $t <= $tableCount; ++$t) { + $this->writeOverrideContentType($objWriter, '/xl/tables/table' . $table++ . '.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml'); + } + } + // Add worksheet relationship content types $unparsedLoadedData = $spreadsheet->getUnparsedLoadedData(); $chart = 1; @@ -144,7 +152,7 @@ class ContentTypes extends WriterPart if ($spreadsheet->hasRibbonBinObjects()) { // Some additional objects in the ribbon ? // we need to write "Extension" but not already write for media content - $tabRibbonTypes = array_diff($spreadsheet->getRibbonBinObjects('types'), array_keys($aMediaContentTypes)); + $tabRibbonTypes = array_diff($spreadsheet->getRibbonBinObjects('types') ?? [], array_keys($aMediaContentTypes)); foreach ($tabRibbonTypes as $aRibbonType) { $mimeType = 'image/.' . $aRibbonType; //we wrote $mimeType like customUI Editor $this->writeDefaultContentType($objWriter, $aRibbonType, $mimeType); @@ -161,6 +169,23 @@ class ContentTypes extends WriterPart } } } + + if (count($spreadsheet->getSheet($i)->getComments()) > 0) { + foreach ($spreadsheet->getSheet($i)->getComments() as $comment) { + if (!$comment->hasBackgroundImage()) { + continue; + } + + $bgImage = $comment->getBackgroundImage(); + $bgImageExtentionKey = strtolower($bgImage->getImageFileExtensionForSave(false)); + + if (!isset($aMediaContentTypes[$bgImageExtentionKey])) { + $aMediaContentTypes[$bgImageExtentionKey] = $bgImage->getImageMimeType(); + + $this->writeDefaultContentType($objWriter, $bgImageExtentionKey, $aMediaContentTypes[$bgImageExtentionKey]); + } + } + } } // unparsed defaults @@ -186,39 +211,34 @@ class ContentTypes extends WriterPart /** * Get image mime type. * - * @param string $pFile Filename - * - * @throws WriterException + * @param string $filename Filename * * @return string Mime Type */ - private function getImageMimeType($pFile) + private function getImageMimeType($filename) { - if (File::fileExists($pFile)) { - $image = getimagesize($pFile); + if (File::fileExists($filename)) { + $image = getimagesize($filename); - return image_type_to_mime_type($image[2]); + return image_type_to_mime_type((is_array($image) && count($image) >= 3) ? $image[2] : 0); } - throw new WriterException("File $pFile does not exist"); + throw new WriterException("File $filename does not exist"); } /** * Write Default content type. * - * @param XMLWriter $objWriter XML Writer - * @param string $pPartname Part name - * @param string $pContentType Content type - * - * @throws WriterException + * @param string $partName Part name + * @param string $contentType Content type */ - private function writeDefaultContentType(XMLWriter $objWriter, $pPartname, $pContentType) + private function writeDefaultContentType(XMLWriter $objWriter, $partName, $contentType): void { - if ($pPartname != '' && $pContentType != '') { + if ($partName != '' && $contentType != '') { // Write content type $objWriter->startElement('Default'); - $objWriter->writeAttribute('Extension', $pPartname); - $objWriter->writeAttribute('ContentType', $pContentType); + $objWriter->writeAttribute('Extension', $partName); + $objWriter->writeAttribute('ContentType', $contentType); $objWriter->endElement(); } else { throw new WriterException('Invalid parameters passed.'); @@ -228,19 +248,16 @@ class ContentTypes extends WriterPart /** * Write Override content type. * - * @param XMLWriter $objWriter XML Writer - * @param string $pPartname Part name - * @param string $pContentType Content type - * - * @throws WriterException + * @param string $partName Part name + * @param string $contentType Content type */ - private function writeOverrideContentType(XMLWriter $objWriter, $pPartname, $pContentType) + private function writeOverrideContentType(XMLWriter $objWriter, $partName, $contentType): void { - if ($pPartname != '' && $pContentType != '') { + if ($partName != '' && $contentType != '') { // Write content type $objWriter->startElement('Override'); - $objWriter->writeAttribute('PartName', $pPartname); - $objWriter->writeAttribute('ContentType', $pContentType); + $objWriter->writeAttribute('PartName', $partName); + $objWriter->writeAttribute('ContentType', $contentType); $objWriter->endElement(); } else { throw new WriterException('Invalid parameters passed.'); diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php new file mode 100644 index 0000000..24752c9 --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php @@ -0,0 +1,244 @@ +objWriter = $objWriter; + $this->spreadsheet = $spreadsheet; + } + + public function write(): void + { + // Write defined names + $this->objWriter->startElement('definedNames'); + + // Named ranges + if (count($this->spreadsheet->getDefinedNames()) > 0) { + // Named ranges + $this->writeNamedRangesAndFormulae(); + } + + // Other defined names + $sheetCount = $this->spreadsheet->getSheetCount(); + for ($i = 0; $i < $sheetCount; ++$i) { + // NamedRange for autoFilter + $this->writeNamedRangeForAutofilter($this->spreadsheet->getSheet($i), $i); + + // NamedRange for Print_Titles + $this->writeNamedRangeForPrintTitles($this->spreadsheet->getSheet($i), $i); + + // NamedRange for Print_Area + $this->writeNamedRangeForPrintArea($this->spreadsheet->getSheet($i), $i); + } + + $this->objWriter->endElement(); + } + + /** + * Write defined names. + */ + private function writeNamedRangesAndFormulae(): void + { + // Loop named ranges + $definedNames = $this->spreadsheet->getDefinedNames(); + foreach ($definedNames as $definedName) { + $this->writeDefinedName($definedName); + } + } + + /** + * Write Defined Name for named range. + */ + private function writeDefinedName(DefinedName $definedName): void + { + // definedName for named range + $local = -1; + if ($definedName->getLocalOnly() && $definedName->getScope() !== null) { + try { + $local = $definedName->getScope()->getParent()->getIndex($definedName->getScope()); + } catch (Exception $e) { + // See issue 2266 - deleting sheet which contains + // defined names will cause Exception above. + return; + } + } + $this->objWriter->startElement('definedName'); + $this->objWriter->writeAttribute('name', $definedName->getName()); + if ($local >= 0) { + $this->objWriter->writeAttribute( + 'localSheetId', + "$local" + ); + } + + $definedRange = $this->getDefinedRange($definedName); + + $this->objWriter->writeRawData($definedRange); + + $this->objWriter->endElement(); + } + + /** + * Write Defined Name for autoFilter. + */ + private function writeNamedRangeForAutofilter(ActualWorksheet $worksheet, int $worksheetId = 0): void + { + // NamedRange for autoFilter + $autoFilterRange = $worksheet->getAutoFilter()->getRange(); + if (!empty($autoFilterRange)) { + $this->objWriter->startElement('definedName'); + $this->objWriter->writeAttribute('name', '_xlnm._FilterDatabase'); + $this->objWriter->writeAttribute('localSheetId', "$worksheetId"); + $this->objWriter->writeAttribute('hidden', '1'); + + // Create absolute coordinate and write as raw text + $range = Coordinate::splitRange($autoFilterRange); + $range = $range[0]; + // Strip any worksheet ref so we can make the cell ref absolute + [, $range[0]] = ActualWorksheet::extractSheetTitle($range[0], true); + + $range[0] = Coordinate::absoluteCoordinate($range[0]); + if (count($range) > 1) { + $range[1] = Coordinate::absoluteCoordinate($range[1]); + } + $range = implode(':', $range); + + $this->objWriter->writeRawData('\'' . str_replace("'", "''", $worksheet->getTitle()) . '\'!' . $range); + + $this->objWriter->endElement(); + } + } + + /** + * Write Defined Name for PrintTitles. + */ + private function writeNamedRangeForPrintTitles(ActualWorksheet $worksheet, int $worksheetId = 0): void + { + // NamedRange for PrintTitles + if ($worksheet->getPageSetup()->isColumnsToRepeatAtLeftSet() || $worksheet->getPageSetup()->isRowsToRepeatAtTopSet()) { + $this->objWriter->startElement('definedName'); + $this->objWriter->writeAttribute('name', '_xlnm.Print_Titles'); + $this->objWriter->writeAttribute('localSheetId', "$worksheetId"); + + // Setting string + $settingString = ''; + + // Columns to repeat + if ($worksheet->getPageSetup()->isColumnsToRepeatAtLeftSet()) { + $repeat = $worksheet->getPageSetup()->getColumnsToRepeatAtLeft(); + + $settingString .= '\'' . str_replace("'", "''", $worksheet->getTitle()) . '\'!$' . $repeat[0] . ':$' . $repeat[1]; + } + + // Rows to repeat + if ($worksheet->getPageSetup()->isRowsToRepeatAtTopSet()) { + if ($worksheet->getPageSetup()->isColumnsToRepeatAtLeftSet()) { + $settingString .= ','; + } + + $repeat = $worksheet->getPageSetup()->getRowsToRepeatAtTop(); + + $settingString .= '\'' . str_replace("'", "''", $worksheet->getTitle()) . '\'!$' . $repeat[0] . ':$' . $repeat[1]; + } + + $this->objWriter->writeRawData($settingString); + + $this->objWriter->endElement(); + } + } + + /** + * Write Defined Name for PrintTitles. + */ + private function writeNamedRangeForPrintArea(ActualWorksheet $worksheet, int $worksheetId = 0): void + { + // NamedRange for PrintArea + if ($worksheet->getPageSetup()->isPrintAreaSet()) { + $this->objWriter->startElement('definedName'); + $this->objWriter->writeAttribute('name', '_xlnm.Print_Area'); + $this->objWriter->writeAttribute('localSheetId', "$worksheetId"); + + // Print area + $printArea = Coordinate::splitRange($worksheet->getPageSetup()->getPrintArea()); + + $chunks = []; + foreach ($printArea as $printAreaRect) { + $printAreaRect[0] = Coordinate::absoluteReference($printAreaRect[0]); + $printAreaRect[1] = Coordinate::absoluteReference($printAreaRect[1]); + $chunks[] = '\'' . str_replace("'", "''", $worksheet->getTitle()) . '\'!' . implode(':', $printAreaRect); + } + + $this->objWriter->writeRawData(implode(',', $chunks)); + + $this->objWriter->endElement(); + } + } + + private function getDefinedRange(DefinedName $definedName): string + { + $definedRange = $definedName->getValue(); + $splitCount = preg_match_all( + '/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/mui', + $definedRange, + $splitRanges, + PREG_OFFSET_CAPTURE + ); + + $lengths = array_map('strlen', array_column($splitRanges[0], 0)); + $offsets = array_column($splitRanges[0], 1); + + $worksheets = $splitRanges[2]; + $columns = $splitRanges[6]; + $rows = $splitRanges[7]; + + while ($splitCount > 0) { + --$splitCount; + $length = $lengths[$splitCount]; + $offset = $offsets[$splitCount]; + $worksheet = $worksheets[$splitCount][0]; + $column = $columns[$splitCount][0]; + $row = $rows[$splitCount][0]; + + $newRange = ''; + if (empty($worksheet)) { + if (($offset === 0) || ($definedRange[$offset - 1] !== ':')) { + // We should have a worksheet + $ws = $definedName->getWorksheet(); + $worksheet = ($ws === null) ? null : $ws->getTitle(); + } + } else { + $worksheet = str_replace("''", "'", trim($worksheet, "'")); + } + + if (!empty($worksheet)) { + $newRange = "'" . str_replace("'", "''", $worksheet) . "'!"; + } + $newRange = "{$newRange}{$column}{$row}"; + + $definedRange = substr($definedRange, 0, $offset) . $newRange . substr($definedRange, $offset + $length); + } + + if (substr($definedRange, 0, 1) === '=') { + $definedRange = substr($definedRange, 1); + } + + return $definedRange; + } +} diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xlsx/DocProps.php b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/DocProps.php old mode 100755 new mode 100644 index 2a18d5c..8902826 --- a/PhpOffice/PhpSpreadsheet/Writer/Xlsx/DocProps.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/DocProps.php @@ -2,6 +2,9 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx; +use PhpOffice\PhpSpreadsheet\Document\Properties; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces; +use PhpOffice\PhpSpreadsheet\Shared\Date; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; use PhpOffice\PhpSpreadsheet\Spreadsheet; @@ -10,10 +13,6 @@ class DocProps extends WriterPart /** * Write docProps/app.xml to XML format. * - * @param Spreadsheet $spreadsheet - * - * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception - * * @return string XML Output */ public function writeDocPropsApp(Spreadsheet $spreadsheet) @@ -31,8 +30,8 @@ class DocProps extends WriterPart // Properties $objWriter->startElement('Properties'); - $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/officeDocument/2006/extended-properties'); - $objWriter->writeAttribute('xmlns:vt', 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes'); + $objWriter->writeAttribute('xmlns', Namespaces::EXTENDED_PROPERTIES); + $objWriter->writeAttribute('xmlns:vt', Namespaces::PROPERTIES_VTYPES); // Application $objWriter->writeElement('Application', 'Microsoft Excel'); @@ -58,7 +57,7 @@ class DocProps extends WriterPart // Variant $objWriter->startElement('vt:variant'); - $objWriter->writeElement('vt:i4', $spreadsheet->getSheetCount()); + $objWriter->writeElement('vt:i4', (string) $spreadsheet->getSheetCount()); $objWriter->endElement(); $objWriter->endElement(); @@ -70,7 +69,7 @@ class DocProps extends WriterPart // Vector $objWriter->startElement('vt:vector'); - $objWriter->writeAttribute('size', $spreadsheet->getSheetCount()); + $objWriter->writeAttribute('size', (string) $spreadsheet->getSheetCount()); $objWriter->writeAttribute('baseType', 'lpstr'); $sheetCount = $spreadsheet->getSheetCount(); @@ -109,10 +108,6 @@ class DocProps extends WriterPart /** * Write docProps/core.xml to XML format. * - * @param Spreadsheet $spreadsheet - * - * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception - * * @return string XML Output */ public function writeDocPropsCore(Spreadsheet $spreadsheet) @@ -130,11 +125,11 @@ class DocProps extends WriterPart // cp:coreProperties $objWriter->startElement('cp:coreProperties'); - $objWriter->writeAttribute('xmlns:cp', 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties'); - $objWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/'); - $objWriter->writeAttribute('xmlns:dcterms', 'http://purl.org/dc/terms/'); - $objWriter->writeAttribute('xmlns:dcmitype', 'http://purl.org/dc/dcmitype/'); - $objWriter->writeAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); + $objWriter->writeAttribute('xmlns:cp', Namespaces::CORE_PROPERTIES2); + $objWriter->writeAttribute('xmlns:dc', Namespaces::DC_ELEMENTS); + $objWriter->writeAttribute('xmlns:dcterms', Namespaces::DC_TERMS); + $objWriter->writeAttribute('xmlns:dcmitype', Namespaces::DC_DCMITYPE); + $objWriter->writeAttribute('xmlns:xsi', Namespaces::SCHEMA_INSTANCE); // dc:creator $objWriter->writeElement('dc:creator', $spreadsheet->getProperties()->getCreator()); @@ -145,13 +140,17 @@ class DocProps extends WriterPart // dcterms:created $objWriter->startElement('dcterms:created'); $objWriter->writeAttribute('xsi:type', 'dcterms:W3CDTF'); - $objWriter->writeRawData(date(DATE_W3C, $spreadsheet->getProperties()->getCreated())); + $created = $spreadsheet->getProperties()->getCreated(); + $date = Date::dateTimeFromTimestamp("$created"); + $objWriter->writeRawData($date->format(DATE_W3C)); $objWriter->endElement(); // dcterms:modified $objWriter->startElement('dcterms:modified'); $objWriter->writeAttribute('xsi:type', 'dcterms:W3CDTF'); - $objWriter->writeRawData(date(DATE_W3C, $spreadsheet->getProperties()->getModified())); + $created = $spreadsheet->getProperties()->getModified(); + $date = Date::dateTimeFromTimestamp("$created"); + $objWriter->writeRawData($date->format(DATE_W3C)); $objWriter->endElement(); // dc:title @@ -178,17 +177,13 @@ class DocProps extends WriterPart /** * Write docProps/custom.xml to XML format. * - * @param Spreadsheet $spreadsheet - * - * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception - * - * @return string XML Output + * @return null|string XML Output */ public function writeDocPropsCustom(Spreadsheet $spreadsheet) { $customPropertyList = $spreadsheet->getProperties()->getCustomProperties(); if (empty($customPropertyList)) { - return; + return null; } // Create XML writer @@ -204,8 +199,8 @@ class DocProps extends WriterPart // cp:coreProperties $objWriter->startElement('Properties'); - $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/officeDocument/2006/custom-properties'); - $objWriter->writeAttribute('xmlns:vt', 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes'); + $objWriter->writeAttribute('xmlns', Namespaces::CUSTOM_PROPERTIES); + $objWriter->writeAttribute('xmlns:vt', Namespaces::PROPERTIES_VTYPES); foreach ($customPropertyList as $key => $customProperty) { $propertyValue = $spreadsheet->getProperties()->getCustomPropertyValue($customProperty); @@ -213,25 +208,26 @@ class DocProps extends WriterPart $objWriter->startElement('property'); $objWriter->writeAttribute('fmtid', '{D5CDD505-2E9C-101B-9397-08002B2CF9AE}'); - $objWriter->writeAttribute('pid', $key + 2); + $objWriter->writeAttribute('pid', (string) ($key + 2)); $objWriter->writeAttribute('name', $customProperty); switch ($propertyType) { - case 'i': + case Properties::PROPERTY_TYPE_INTEGER: $objWriter->writeElement('vt:i4', $propertyValue); break; - case 'f': - $objWriter->writeElement('vt:r8', $propertyValue); + case Properties::PROPERTY_TYPE_FLOAT: + $objWriter->writeElement('vt:r8', sprintf('%F', $propertyValue)); break; - case 'b': + case Properties::PROPERTY_TYPE_BOOLEAN: $objWriter->writeElement('vt:bool', ($propertyValue) ? 'true' : 'false'); break; - case 'd': + case Properties::PROPERTY_TYPE_DATE: $objWriter->startElement('vt:filetime'); - $objWriter->writeRawData(date(DATE_W3C, $propertyValue)); + $date = Date::dateTimeFromTimestamp("$propertyValue"); + $objWriter->writeRawData($date->format(DATE_W3C)); $objWriter->endElement(); break; diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Drawing.php b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Drawing.php old mode 100755 new mode 100644 index 08256a1..d4f7f11 --- a/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Drawing.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Drawing.php @@ -3,6 +3,8 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces; +use PhpOffice\PhpSpreadsheet\Shared\Drawing as SharedDrawing; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing; @@ -14,14 +16,11 @@ class Drawing extends WriterPart /** * Write drawings to XML format. * - * @param \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet * @param bool $includeCharts Flag indicating if we should include drawing details for charts * - * @throws WriterException - * * @return string XML Output */ - public function writeDrawings(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet, $includeCharts = false) + public function writeDrawings(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet, $includeCharts = false) { // Create XML writer $objWriter = null; @@ -36,12 +35,12 @@ class Drawing extends WriterPart // xdr:wsDr $objWriter->startElement('xdr:wsDr'); - $objWriter->writeAttribute('xmlns:xdr', 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing'); - $objWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main'); + $objWriter->writeAttribute('xmlns:xdr', Namespaces::SPREADSHEET_DRAWING); + $objWriter->writeAttribute('xmlns:a', Namespaces::DRAWINGML); // Loop through images and write drawings $i = 1; - $iterator = $pWorksheet->getDrawingCollection()->getIterator(); + $iterator = $worksheet->getDrawingCollection()->getIterator(); while ($iterator->valid()) { /** @var BaseDrawing $pDrawing */ $pDrawing = $iterator->current(); @@ -55,19 +54,22 @@ class Drawing extends WriterPart } if ($includeCharts) { - $chartCount = $pWorksheet->getChartCount(); + $chartCount = $worksheet->getChartCount(); // Loop through charts and write the chart position if ($chartCount > 0) { for ($c = 0; $c < $chartCount; ++$c) { - $this->writeChart($objWriter, $pWorksheet->getChartByIndex($c), $c + $i); + $chart = $worksheet->getChartByIndex((string) $c); + if ($chart !== false) { + $this->writeChart($objWriter, $chart, $c + $i); + } } } } // unparsed AlternateContent - $unparsedLoadedData = $pWorksheet->getParent()->getUnparsedLoadedData(); - if (isset($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingAlternateContents'])) { - foreach ($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingAlternateContents'] as $drawingAlternateContent) { + $unparsedLoadedData = $worksheet->getParent()->getUnparsedLoadedData(); + if (isset($unparsedLoadedData['sheets'][$worksheet->getCodeName()]['drawingAlternateContents'])) { + foreach ($unparsedLoadedData['sheets'][$worksheet->getCodeName()]['drawingAlternateContents'] as $drawingAlternateContent) { $objWriter->writeRaw($drawingAlternateContent); } } @@ -81,38 +83,63 @@ class Drawing extends WriterPart /** * Write drawings to XML format. * - * @param XMLWriter $objWriter XML Writer - * @param \PhpOffice\PhpSpreadsheet\Chart\Chart $pChart - * @param int $pRelationId + * @param int $relationId */ - public function writeChart(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Chart\Chart $pChart, $pRelationId = -1) + public function writeChart(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Chart\Chart $chart, $relationId = -1): void { - $tl = $pChart->getTopLeftPosition(); - $tl['colRow'] = Coordinate::coordinateFromString($tl['cell']); - $br = $pChart->getBottomRightPosition(); - $br['colRow'] = Coordinate::coordinateFromString($br['cell']); + $tl = $chart->getTopLeftPosition(); + $tlColRow = Coordinate::indexesFromString($tl['cell']); + $br = $chart->getBottomRightPosition(); - $objWriter->startElement('xdr:twoCellAnchor'); + $isTwoCellAnchor = $br['cell'] !== ''; + if ($isTwoCellAnchor) { + $brColRow = Coordinate::indexesFromString($br['cell']); - $objWriter->startElement('xdr:from'); - $objWriter->writeElement('xdr:col', Coordinate::columnIndexFromString($tl['colRow'][0]) - 1); - $objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($tl['xOffset'])); - $objWriter->writeElement('xdr:row', $tl['colRow'][1] - 1); - $objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($tl['yOffset'])); - $objWriter->endElement(); - $objWriter->startElement('xdr:to'); - $objWriter->writeElement('xdr:col', Coordinate::columnIndexFromString($br['colRow'][0]) - 1); - $objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($br['xOffset'])); - $objWriter->writeElement('xdr:row', $br['colRow'][1] - 1); - $objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($br['yOffset'])); - $objWriter->endElement(); + $objWriter->startElement('xdr:twoCellAnchor'); + + $objWriter->startElement('xdr:from'); + $objWriter->writeElement('xdr:col', (string) ($tlColRow[0] - 1)); + $objWriter->writeElement('xdr:colOff', self::stringEmu($tl['xOffset'])); + $objWriter->writeElement('xdr:row', (string) ($tlColRow[1] - 1)); + $objWriter->writeElement('xdr:rowOff', self::stringEmu($tl['yOffset'])); + $objWriter->endElement(); + $objWriter->startElement('xdr:to'); + $objWriter->writeElement('xdr:col', (string) ($brColRow[0] - 1)); + $objWriter->writeElement('xdr:colOff', self::stringEmu($br['xOffset'])); + $objWriter->writeElement('xdr:row', (string) ($brColRow[1] - 1)); + $objWriter->writeElement('xdr:rowOff', self::stringEmu($br['yOffset'])); + $objWriter->endElement(); + } elseif ($chart->getOneCellAnchor()) { + $objWriter->startElement('xdr:oneCellAnchor'); + + $objWriter->startElement('xdr:from'); + $objWriter->writeElement('xdr:col', (string) ($tlColRow[0] - 1)); + $objWriter->writeElement('xdr:colOff', self::stringEmu($tl['xOffset'])); + $objWriter->writeElement('xdr:row', (string) ($tlColRow[1] - 1)); + $objWriter->writeElement('xdr:rowOff', self::stringEmu($tl['yOffset'])); + $objWriter->endElement(); + $objWriter->startElement('xdr:ext'); + $objWriter->writeAttribute('cx', self::stringEmu($br['xOffset'])); + $objWriter->writeAttribute('cy', self::stringEmu($br['yOffset'])); + $objWriter->endElement(); + } else { + $objWriter->startElement('xdr:absoluteAnchor'); + $objWriter->startElement('xdr:pos'); + $objWriter->writeAttribute('x', '0'); + $objWriter->writeAttribute('y', '0'); + $objWriter->endElement(); + $objWriter->startElement('xdr:ext'); + $objWriter->writeAttribute('cx', self::stringEmu($br['xOffset'])); + $objWriter->writeAttribute('cy', self::stringEmu($br['yOffset'])); + $objWriter->endElement(); + } $objWriter->startElement('xdr:graphicFrame'); $objWriter->writeAttribute('macro', ''); $objWriter->startElement('xdr:nvGraphicFramePr'); $objWriter->startElement('xdr:cNvPr'); - $objWriter->writeAttribute('name', 'Chart ' . $pRelationId); - $objWriter->writeAttribute('id', 1025 * $pRelationId); + $objWriter->writeAttribute('name', 'Chart ' . $relationId); + $objWriter->writeAttribute('id', (string) (1025 * $relationId)); $objWriter->endElement(); $objWriter->startElement('xdr:cNvGraphicFramePr'); $objWriter->startElement('a:graphicFrameLocks'); @@ -133,11 +160,11 @@ class Drawing extends WriterPart $objWriter->startElement('a:graphic'); $objWriter->startElement('a:graphicData'); - $objWriter->writeAttribute('uri', 'http://schemas.openxmlformats.org/drawingml/2006/chart'); + $objWriter->writeAttribute('uri', Namespaces::CHART); $objWriter->startElement('c:chart'); - $objWriter->writeAttribute('xmlns:c', 'http://schemas.openxmlformats.org/drawingml/2006/chart'); - $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); - $objWriter->writeAttribute('r:id', 'rId' . $pRelationId); + $objWriter->writeAttribute('xmlns:c', Namespaces::CHART); + $objWriter->writeAttribute('xmlns:r', Namespaces::SCHEMA_OFFICE_DOCUMENT); + $objWriter->writeAttribute('r:id', 'rId' . $relationId); $objWriter->endElement(); $objWriter->endElement(); $objWriter->endElement(); @@ -152,35 +179,58 @@ class Drawing extends WriterPart /** * Write drawings to XML format. * - * @param XMLWriter $objWriter XML Writer - * @param BaseDrawing $pDrawing - * @param int $pRelationId + * @param int $relationId * @param null|int $hlinkClickId - * - * @throws WriterException */ - public function writeDrawing(XMLWriter $objWriter, BaseDrawing $pDrawing, $pRelationId = -1, $hlinkClickId = null) + public function writeDrawing(XMLWriter $objWriter, BaseDrawing $drawing, $relationId = -1, $hlinkClickId = null): void { - if ($pRelationId >= 0) { - // xdr:oneCellAnchor - $objWriter->startElement('xdr:oneCellAnchor'); - // Image location - $aCoordinates = Coordinate::coordinateFromString($pDrawing->getCoordinates()); - $aCoordinates[0] = Coordinate::columnIndexFromString($aCoordinates[0]); + if ($relationId >= 0) { + $isTwoCellAnchor = $drawing->getCoordinates2() !== ''; + if ($isTwoCellAnchor) { + // xdr:twoCellAnchor + $objWriter->startElement('xdr:twoCellAnchor'); + if ($drawing->validEditAs()) { + $objWriter->writeAttribute('editAs', $drawing->getEditAs()); + } + // Image location + $aCoordinates = Coordinate::indexesFromString($drawing->getCoordinates()); + $aCoordinates2 = Coordinate::indexesFromString($drawing->getCoordinates2()); - // xdr:from - $objWriter->startElement('xdr:from'); - $objWriter->writeElement('xdr:col', $aCoordinates[0] - 1); - $objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getOffsetX())); - $objWriter->writeElement('xdr:row', $aCoordinates[1] - 1); - $objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getOffsetY())); - $objWriter->endElement(); + // xdr:from + $objWriter->startElement('xdr:from'); + $objWriter->writeElement('xdr:col', (string) ($aCoordinates[0] - 1)); + $objWriter->writeElement('xdr:colOff', self::stringEmu($drawing->getOffsetX())); + $objWriter->writeElement('xdr:row', (string) ($aCoordinates[1] - 1)); + $objWriter->writeElement('xdr:rowOff', self::stringEmu($drawing->getOffsetY())); + $objWriter->endElement(); - // xdr:ext - $objWriter->startElement('xdr:ext'); - $objWriter->writeAttribute('cx', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getWidth())); - $objWriter->writeAttribute('cy', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getHeight())); - $objWriter->endElement(); + // xdr:to + $objWriter->startElement('xdr:to'); + $objWriter->writeElement('xdr:col', (string) ($aCoordinates2[0] - 1)); + $objWriter->writeElement('xdr:colOff', self::stringEmu($drawing->getOffsetX2())); + $objWriter->writeElement('xdr:row', (string) ($aCoordinates2[1] - 1)); + $objWriter->writeElement('xdr:rowOff', self::stringEmu($drawing->getOffsetY2())); + $objWriter->endElement(); + } else { + // xdr:oneCellAnchor + $objWriter->startElement('xdr:oneCellAnchor'); + // Image location + $aCoordinates = Coordinate::indexesFromString($drawing->getCoordinates()); + + // xdr:from + $objWriter->startElement('xdr:from'); + $objWriter->writeElement('xdr:col', (string) ($aCoordinates[0] - 1)); + $objWriter->writeElement('xdr:colOff', self::stringEmu($drawing->getOffsetX())); + $objWriter->writeElement('xdr:row', (string) ($aCoordinates[1] - 1)); + $objWriter->writeElement('xdr:rowOff', self::stringEmu($drawing->getOffsetY())); + $objWriter->endElement(); + + // xdr:ext + $objWriter->startElement('xdr:ext'); + $objWriter->writeAttribute('cx', self::stringEmu($drawing->getWidth())); + $objWriter->writeAttribute('cy', self::stringEmu($drawing->getHeight())); + $objWriter->endElement(); + } // xdr:pic $objWriter->startElement('xdr:pic'); @@ -190,9 +240,9 @@ class Drawing extends WriterPart // xdr:cNvPr $objWriter->startElement('xdr:cNvPr'); - $objWriter->writeAttribute('id', $pRelationId); - $objWriter->writeAttribute('name', $pDrawing->getName()); - $objWriter->writeAttribute('descr', $pDrawing->getDescription()); + $objWriter->writeAttribute('id', (string) $relationId); + $objWriter->writeAttribute('name', $drawing->getName()); + $objWriter->writeAttribute('descr', $drawing->getDescription()); //a:hlinkClick $this->writeHyperLinkDrawing($objWriter, $hlinkClickId); @@ -216,8 +266,8 @@ class Drawing extends WriterPart // a:blip $objWriter->startElement('a:blip'); - $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); - $objWriter->writeAttribute('r:embed', 'rId' . $pRelationId); + $objWriter->writeAttribute('xmlns:r', Namespaces::SCHEMA_OFFICE_DOCUMENT); + $objWriter->writeAttribute('r:embed', 'rId' . $relationId); $objWriter->endElement(); // a:stretch @@ -232,7 +282,13 @@ class Drawing extends WriterPart // a:xfrm $objWriter->startElement('a:xfrm'); - $objWriter->writeAttribute('rot', \PhpOffice\PhpSpreadsheet\Shared\Drawing::degreesToAngle($pDrawing->getRotation())); + $objWriter->writeAttribute('rot', (string) SharedDrawing::degreesToAngle($drawing->getRotation())); + if ($isTwoCellAnchor) { + $objWriter->startElement('a:ext'); + $objWriter->writeAttribute('cx', self::stringEmu($drawing->getWidth())); + $objWriter->writeAttribute('cy', self::stringEmu($drawing->getHeight())); + $objWriter->endElement(); + } $objWriter->endElement(); // a:prstGeom @@ -244,25 +300,25 @@ class Drawing extends WriterPart $objWriter->endElement(); - if ($pDrawing->getShadow()->getVisible()) { + if ($drawing->getShadow()->getVisible()) { // a:effectLst $objWriter->startElement('a:effectLst'); // a:outerShdw $objWriter->startElement('a:outerShdw'); - $objWriter->writeAttribute('blurRad', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getShadow()->getBlurRadius())); - $objWriter->writeAttribute('dist', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getShadow()->getDistance())); - $objWriter->writeAttribute('dir', \PhpOffice\PhpSpreadsheet\Shared\Drawing::degreesToAngle($pDrawing->getShadow()->getDirection())); - $objWriter->writeAttribute('algn', $pDrawing->getShadow()->getAlignment()); + $objWriter->writeAttribute('blurRad', self::stringEmu($drawing->getShadow()->getBlurRadius())); + $objWriter->writeAttribute('dist', self::stringEmu($drawing->getShadow()->getDistance())); + $objWriter->writeAttribute('dir', (string) SharedDrawing::degreesToAngle($drawing->getShadow()->getDirection())); + $objWriter->writeAttribute('algn', $drawing->getShadow()->getAlignment()); $objWriter->writeAttribute('rotWithShape', '0'); // a:srgbClr $objWriter->startElement('a:srgbClr'); - $objWriter->writeAttribute('val', $pDrawing->getShadow()->getColor()->getRGB()); + $objWriter->writeAttribute('val', $drawing->getShadow()->getColor()->getRGB()); // a:alpha $objWriter->startElement('a:alpha'); - $objWriter->writeAttribute('val', $pDrawing->getShadow()->getAlpha() * 1000); + $objWriter->writeAttribute('val', (string) ($drawing->getShadow()->getAlpha() * 1000)); $objWriter->endElement(); $objWriter->endElement(); @@ -287,13 +343,9 @@ class Drawing extends WriterPart /** * Write VML header/footer images to XML format. * - * @param \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet - * - * @throws WriterException - * * @return string XML Output */ - public function writeVMLHeaderFooterImages(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet) + public function writeVMLHeaderFooterImages(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet) { // Create XML writer $objWriter = null; @@ -307,13 +359,13 @@ class Drawing extends WriterPart $objWriter->startDocument('1.0', 'UTF-8', 'yes'); // Header/footer images - $images = $pWorksheet->getHeaderFooter()->getImages(); + $images = $worksheet->getHeaderFooter()->getImages(); // xml $objWriter->startElement('xml'); - $objWriter->writeAttribute('xmlns:v', 'urn:schemas-microsoft-com:vml'); - $objWriter->writeAttribute('xmlns:o', 'urn:schemas-microsoft-com:office:office'); - $objWriter->writeAttribute('xmlns:x', 'urn:schemas-microsoft-com:office:excel'); + $objWriter->writeAttribute('xmlns:v', Namespaces::URN_VML); + $objWriter->writeAttribute('xmlns:o', Namespaces::URN_MSOFFICE); + $objWriter->writeAttribute('xmlns:x', Namespaces::URN_EXCEL); // o:shapelayout $objWriter->startElement('o:shapelayout'); @@ -436,33 +488,31 @@ class Drawing extends WriterPart /** * Write VML comment to XML format. * - * @param XMLWriter $objWriter XML Writer - * @param string $pReference Reference - * @param HeaderFooterDrawing $pImage Image + * @param string $reference Reference */ - private function writeVMLHeaderFooterImage(XMLWriter $objWriter, $pReference, HeaderFooterDrawing $pImage) + private function writeVMLHeaderFooterImage(XMLWriter $objWriter, $reference, HeaderFooterDrawing $image): void { // Calculate object id - preg_match('{(\d+)}', md5($pReference), $m); - $id = 1500 + (substr($m[1], 0, 2) * 1); + preg_match('{(\d+)}', md5($reference), $m); + $id = 1500 + ((int) substr($m[1], 0, 2) * 1); // Calculate offset - $width = $pImage->getWidth(); - $height = $pImage->getHeight(); - $marginLeft = $pImage->getOffsetX(); - $marginTop = $pImage->getOffsetY(); + $width = $image->getWidth(); + $height = $image->getHeight(); + $marginLeft = $image->getOffsetX(); + $marginTop = $image->getOffsetY(); // v:shape $objWriter->startElement('v:shape'); - $objWriter->writeAttribute('id', $pReference); + $objWriter->writeAttribute('id', $reference); $objWriter->writeAttribute('o:spid', '_x0000_s' . $id); $objWriter->writeAttribute('type', '#_x0000_t75'); $objWriter->writeAttribute('style', "position:absolute;margin-left:{$marginLeft}px;margin-top:{$marginTop}px;width:{$width}px;height:{$height}px;z-index:1"); // v:imagedata $objWriter->startElement('v:imagedata'); - $objWriter->writeAttribute('o:relid', 'rId' . $pReference); - $objWriter->writeAttribute('o:title', $pImage->getName()); + $objWriter->writeAttribute('o:relid', 'rId' . $reference); + $objWriter->writeAttribute('o:title', $image->getName()); $objWriter->endElement(); // o:lock @@ -477,9 +527,7 @@ class Drawing extends WriterPart /** * Get an array of all drawings. * - * @param Spreadsheet $spreadsheet - * - * @return \PhpOffice\PhpSpreadsheet\Worksheet\Drawing[] All drawings in PhpSpreadsheet + * @return BaseDrawing[] All drawings in PhpSpreadsheet */ public function allDrawings(Spreadsheet $spreadsheet) { @@ -502,18 +550,22 @@ class Drawing extends WriterPart } /** - * @param XMLWriter $objWriter * @param null|int $hlinkClickId */ - private function writeHyperLinkDrawing(XMLWriter $objWriter, $hlinkClickId) + private function writeHyperLinkDrawing(XMLWriter $objWriter, $hlinkClickId): void { if ($hlinkClickId === null) { return; } $objWriter->startElement('a:hlinkClick'); - $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $objWriter->writeAttribute('xmlns:r', Namespaces::SCHEMA_OFFICE_DOCUMENT); $objWriter->writeAttribute('r:id', 'rId' . $hlinkClickId); $objWriter->endElement(); } + + private static function stringEmu(int $pixelValue): string + { + return (string) SharedDrawing::pixelsToEMU($pixelValue); + } } diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xlsx/FunctionPrefix.php b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/FunctionPrefix.php new file mode 100644 index 0000000..ecc247d --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/FunctionPrefix.php @@ -0,0 +1,194 @@ +startElement('Relationships'); - $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships'); + $objWriter->writeAttribute('xmlns', Namespaces::RELATIONSHIPS); $customPropertyList = $spreadsheet->getProperties()->getCustomProperties(); if (!empty($customPropertyList)) { @@ -41,7 +39,7 @@ class Rels extends WriterPart $this->writeRelationship( $objWriter, 4, - 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties', + Namespaces::RELATIONSHIPS_CUSTOM_PROPERTIES, 'docProps/custom.xml' ); } @@ -50,7 +48,7 @@ class Rels extends WriterPart $this->writeRelationship( $objWriter, 3, - 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties', + Namespaces::RELATIONSHIPS_EXTENDED_PROPERTIES, 'docProps/app.xml' ); @@ -58,7 +56,7 @@ class Rels extends WriterPart $this->writeRelationship( $objWriter, 2, - 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties', + Namespaces::CORE_PROPERTIES, 'docProps/core.xml' ); @@ -66,16 +64,17 @@ class Rels extends WriterPart $this->writeRelationship( $objWriter, 1, - 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument', + Namespaces::OFFICE_DOCUMENT, 'xl/workbook.xml' ); // a custom UI in workbook ? + $target = $spreadsheet->getRibbonXMLData('target'); if ($spreadsheet->hasRibbon()) { $this->writeRelationShip( $objWriter, 5, - 'http://schemas.microsoft.com/office/2006/relationships/ui/extensibility', - $spreadsheet->getRibbonXMLData('target') + Namespaces::EXTENSIBILITY, + is_string($target) ? $target : '' ); } @@ -87,10 +86,6 @@ class Rels extends WriterPart /** * Write workbook relationships to XML format. * - * @param Spreadsheet $spreadsheet - * - * @throws WriterException - * * @return string XML Output */ public function writeWorkbookRelationships(Spreadsheet $spreadsheet) @@ -108,13 +103,13 @@ class Rels extends WriterPart // Relationships $objWriter->startElement('Relationships'); - $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships'); + $objWriter->writeAttribute('xmlns', Namespaces::RELATIONSHIPS); // Relationship styles.xml $this->writeRelationship( $objWriter, 1, - 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles', + Namespaces::STYLES, 'styles.xml' ); @@ -122,7 +117,7 @@ class Rels extends WriterPart $this->writeRelationship( $objWriter, 2, - 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme', + Namespaces::THEME2, 'theme/theme1.xml' ); @@ -130,7 +125,7 @@ class Rels extends WriterPart $this->writeRelationship( $objWriter, 3, - 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings', + Namespaces::SHARED_STRINGS, 'sharedStrings.xml' ); @@ -140,7 +135,7 @@ class Rels extends WriterPart $this->writeRelationship( $objWriter, ($i + 1 + 3), - 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet', + Namespaces::WORKSHEET, 'worksheets/sheet' . ($i + 1) . '.xml' ); } @@ -150,7 +145,7 @@ class Rels extends WriterPart $this->writeRelationShip( $objWriter, ($i + 1 + 3), - 'http://schemas.microsoft.com/office/2006/relationships/vbaProject', + Namespaces::VBA, 'vbaProject.bin' ); ++$i; //increment i if needed for an another relation @@ -168,15 +163,13 @@ class Rels extends WriterPart * rId1 - Drawings * rId_hyperlink_x - Hyperlinks * - * @param \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet - * @param int $pWorksheetId + * @param int $worksheetId * @param bool $includeCharts Flag indicating if we should write charts - * - * @throws WriterException + * @param int $tableRef Table ID * * @return string XML Output */ - public function writeWorksheetRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet, $pWorksheetId = 1, $includeCharts = false) + public function writeWorksheetRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet, $worksheetId = 1, $includeCharts = false, $tableRef = 1) { // Create XML writer $objWriter = null; @@ -191,46 +184,51 @@ class Rels extends WriterPart // Relationships $objWriter->startElement('Relationships'); - $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships'); + $objWriter->writeAttribute('xmlns', Namespaces::RELATIONSHIPS); // Write drawing relationships? - $d = 0; $drawingOriginalIds = []; - $unparsedLoadedData = $pWorksheet->getParent()->getUnparsedLoadedData(); - if (isset($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingOriginalIds'])) { - $drawingOriginalIds = $unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingOriginalIds']; + $unparsedLoadedData = $worksheet->getParent()->getUnparsedLoadedData(); + if (isset($unparsedLoadedData['sheets'][$worksheet->getCodeName()]['drawingOriginalIds'])) { + $drawingOriginalIds = $unparsedLoadedData['sheets'][$worksheet->getCodeName()]['drawingOriginalIds']; } if ($includeCharts) { - $charts = $pWorksheet->getChartCollection(); + $charts = $worksheet->getChartCollection(); } else { $charts = []; } - if (($pWorksheet->getDrawingCollection()->count() > 0) || (count($charts) > 0) || $drawingOriginalIds) { - $relPath = '../drawings/drawing' . $pWorksheetId . '.xml'; - $rId = ++$d; + if (($worksheet->getDrawingCollection()->count() > 0) || (count($charts) > 0) || $drawingOriginalIds) { + $rId = 1; + // Use original $relPath to get original $rId. + // Take first. In future can be overwritten. + // (! synchronize with \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Worksheet::writeDrawings) + reset($drawingOriginalIds); + $relPath = key($drawingOriginalIds); if (isset($drawingOriginalIds[$relPath])) { $rId = (int) (substr($drawingOriginalIds[$relPath], 3)); } + // Generate new $relPath to write drawing relationship + $relPath = '../drawings/drawing' . $worksheetId . '.xml'; $this->writeRelationship( $objWriter, $rId, - 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing', + Namespaces::RELATIONSHIPS_DRAWING, $relPath ); } // Write hyperlink relationships? $i = 1; - foreach ($pWorksheet->getHyperlinkCollection() as $hyperlink) { + foreach ($worksheet->getHyperlinkCollection() as $hyperlink) { if (!$hyperlink->isInternal()) { $this->writeRelationship( $objWriter, '_hyperlink_' . $i, - 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink', + Namespaces::HYPERLINK, $hyperlink->getUrl(), 'External' ); @@ -241,71 +239,83 @@ class Rels extends WriterPart // Write comments relationship? $i = 1; - if (count($pWorksheet->getComments()) > 0) { + if (count($worksheet->getComments()) > 0 || isset($unparsedLoadedData['sheets'][$worksheet->getCodeName()]['legacyDrawing'])) { $this->writeRelationship( $objWriter, '_comments_vml' . $i, - 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing', - '../drawings/vmlDrawing' . $pWorksheetId . '.vml' + Namespaces::VML, + '../drawings/vmlDrawing' . $worksheetId . '.vml' ); + } + if (count($worksheet->getComments()) > 0) { $this->writeRelationship( $objWriter, '_comments' . $i, - 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments', - '../comments' . $pWorksheetId . '.xml' + Namespaces::COMMENTS, + '../comments' . $worksheetId . '.xml' + ); + } + + // Write Table + $tableCount = $worksheet->getTableCollection()->count(); + for ($i = 1; $i <= $tableCount; ++$i) { + $this->writeRelationship( + $objWriter, + '_table_' . $i, + Namespaces::RELATIONSHIPS_TABLE, + '../tables/table' . $tableRef++ . '.xml' ); } // Write header/footer relationship? $i = 1; - if (count($pWorksheet->getHeaderFooter()->getImages()) > 0) { + if (count($worksheet->getHeaderFooter()->getImages()) > 0) { $this->writeRelationship( $objWriter, '_headerfooter_vml' . $i, - 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing', - '../drawings/vmlDrawingHF' . $pWorksheetId . '.vml' + Namespaces::VML, + '../drawings/vmlDrawingHF' . $worksheetId . '.vml' ); } - $this->writeUnparsedRelationship($pWorksheet, $objWriter, 'ctrlProps', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp'); - $this->writeUnparsedRelationship($pWorksheet, $objWriter, 'vmlDrawings', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing'); - $this->writeUnparsedRelationship($pWorksheet, $objWriter, 'printerSettings', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerSettings'); + $this->writeUnparsedRelationship($worksheet, $objWriter, 'ctrlProps', Namespaces::RELATIONSHIPS_CTRLPROP); + $this->writeUnparsedRelationship($worksheet, $objWriter, 'vmlDrawings', Namespaces::VML); + $this->writeUnparsedRelationship($worksheet, $objWriter, 'printerSettings', Namespaces::RELATIONSHIPS_PRINTER_SETTINGS); $objWriter->endElement(); return $objWriter->getData(); } - private function writeUnparsedRelationship(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet, XMLWriter $objWriter, $relationship, $type) + private function writeUnparsedRelationship(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet, XMLWriter $objWriter, string $relationship, string $type): void { - $unparsedLoadedData = $pWorksheet->getParent()->getUnparsedLoadedData(); - if (!isset($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()][$relationship])) { + $unparsedLoadedData = $worksheet->getParent()->getUnparsedLoadedData(); + if (!isset($unparsedLoadedData['sheets'][$worksheet->getCodeName()][$relationship])) { return; } - foreach ($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()][$relationship] as $rId => $value) { - $this->writeRelationship( - $objWriter, - $rId, - $type, - $value['relFilePath'] - ); + foreach ($unparsedLoadedData['sheets'][$worksheet->getCodeName()][$relationship] as $rId => $value) { + if (substr($rId, 0, 17) !== '_headerfooter_vml') { + $this->writeRelationship( + $objWriter, + $rId, + $type, + $value['relFilePath'] + ); + } } } /** * Write drawing relationships to XML format. * - * @param \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet - * @param int &$chartRef Chart ID + * @param int $chartRef Chart ID * @param bool $includeCharts Flag indicating if we should write charts * - * @throws WriterException - * * @return string XML Output */ - public function writeDrawingRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet, &$chartRef, $includeCharts = false) + public function writeDrawingRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet, &$chartRef, $includeCharts = false) { // Create XML writer $objWriter = null; @@ -320,22 +330,23 @@ class Rels extends WriterPart // Relationships $objWriter->startElement('Relationships'); - $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships'); + $objWriter->writeAttribute('xmlns', Namespaces::RELATIONSHIPS); // Loop through images and write relationships $i = 1; - $iterator = $pWorksheet->getDrawingCollection()->getIterator(); + $iterator = $worksheet->getDrawingCollection()->getIterator(); while ($iterator->valid()) { - if ($iterator->current() instanceof \PhpOffice\PhpSpreadsheet\Worksheet\Drawing - || $iterator->current() instanceof MemoryDrawing) { + $drawing = $iterator->current(); + if ( + $drawing instanceof \PhpOffice\PhpSpreadsheet\Worksheet\Drawing + || $drawing instanceof MemoryDrawing + ) { // Write relationship for image drawing - /** @var \PhpOffice\PhpSpreadsheet\Worksheet\Drawing $drawing */ - $drawing = $iterator->current(); $this->writeRelationship( $objWriter, $i, - 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', - '../media/' . str_replace(' ', '', $drawing->getIndexedFilename()) + Namespaces::IMAGE, + '../media/' . $drawing->getIndexedFilename() ); $i = $this->writeDrawingHyperLink($objWriter, $drawing, $i); @@ -347,13 +358,13 @@ class Rels extends WriterPart if ($includeCharts) { // Loop through charts and write relationships - $chartCount = $pWorksheet->getChartCount(); + $chartCount = $worksheet->getChartCount(); if ($chartCount > 0) { for ($c = 0; $c < $chartCount; ++$c) { $this->writeRelationship( $objWriter, $i++, - 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart', + Namespaces::RELATIONSHIPS_CHART, '../charts/chart' . ++$chartRef . '.xml' ); } @@ -368,13 +379,9 @@ class Rels extends WriterPart /** * Write header/footer drawing relationships to XML format. * - * @param \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet - * - * @throws WriterException - * * @return string XML Output */ - public function writeHeaderFooterDrawingRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet) + public function writeHeaderFooterDrawingRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet) { // Create XML writer $objWriter = null; @@ -389,15 +396,15 @@ class Rels extends WriterPart // Relationships $objWriter->startElement('Relationships'); - $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships'); + $objWriter->writeAttribute('xmlns', Namespaces::RELATIONSHIPS); // Loop through images and write relationships - foreach ($pWorksheet->getHeaderFooter()->getImages() as $key => $value) { + foreach ($worksheet->getHeaderFooter()->getImages() as $key => $value) { // Write relationship for image drawing $this->writeRelationship( $objWriter, $key, - 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image', + Namespaces::IMAGE, '../media/' . $value->getIndexedFilename() ); } @@ -407,28 +414,62 @@ class Rels extends WriterPart return $objWriter->getData(); } + public function writeVMLDrawingRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $worksheet): string + { + // Create XML writer + $objWriter = null; + if ($this->getParentWriter()->getUseDiskCaching()) { + $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + } else { + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + } + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // Relationships + $objWriter->startElement('Relationships'); + $objWriter->writeAttribute('xmlns', Namespaces::RELATIONSHIPS); + + // Loop through images and write relationships + foreach ($worksheet->getComments() as $comment) { + if (!$comment->hasBackgroundImage()) { + continue; + } + + $bgImage = $comment->getBackgroundImage(); + $this->writeRelationship( + $objWriter, + $bgImage->getImageIndex(), + Namespaces::IMAGE, + '../media/' . $bgImage->getMediaFilename() + ); + } + + $objWriter->endElement(); + + return $objWriter->getData(); + } + /** * Write Override content type. * - * @param XMLWriter $objWriter XML Writer - * @param int $pId Relationship ID. rId will be prepended! - * @param string $pType Relationship type - * @param string $pTarget Relationship target - * @param string $pTargetMode Relationship target mode - * - * @throws WriterException + * @param int|string $id Relationship ID. rId will be prepended! + * @param string $type Relationship type + * @param string $target Relationship target + * @param string $targetMode Relationship target mode */ - private function writeRelationship(XMLWriter $objWriter, $pId, $pType, $pTarget, $pTargetMode = '') + private function writeRelationship(XMLWriter $objWriter, $id, $type, $target, $targetMode = ''): void { - if ($pType != '' && $pTarget != '') { + if ($type != '' && $target != '') { // Write relationship $objWriter->startElement('Relationship'); - $objWriter->writeAttribute('Id', 'rId' . $pId); - $objWriter->writeAttribute('Type', $pType); - $objWriter->writeAttribute('Target', $pTarget); + $objWriter->writeAttribute('Id', 'rId' . $id); + $objWriter->writeAttribute('Type', $type); + $objWriter->writeAttribute('Target', $target); - if ($pTargetMode != '') { - $objWriter->writeAttribute('TargetMode', $pTargetMode); + if ($targetMode != '') { + $objWriter->writeAttribute('TargetMode', $targetMode); } $objWriter->endElement(); @@ -437,16 +478,7 @@ class Rels extends WriterPart } } - /** - * @param $objWriter - * @param \PhpOffice\PhpSpreadsheet\Worksheet\Drawing $drawing - * @param $i - * - * @throws WriterException - * - * @return int - */ - private function writeDrawingHyperLink($objWriter, $drawing, $i) + private function writeDrawingHyperLink(XMLWriter $objWriter, BaseDrawing $drawing, int $i): int { if ($drawing->getHyperlink() === null) { return $i; @@ -456,7 +488,7 @@ class Rels extends WriterPart $this->writeRelationship( $objWriter, $i, - 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink', + Namespaces::HYPERLINK, $drawing->getHyperlink()->getUrl(), $drawing->getHyperlink()->getTypeHyperlink() ); diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xlsx/RelsRibbon.php b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/RelsRibbon.php old mode 100755 new mode 100644 index 8a0cfe3..e231972 --- a/PhpOffice/PhpSpreadsheet/Writer/Xlsx/RelsRibbon.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/RelsRibbon.php @@ -2,6 +2,7 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; use PhpOffice\PhpSpreadsheet\Spreadsheet; @@ -10,10 +11,6 @@ class RelsRibbon extends WriterPart /** * Write relationships for additional objects of custom UI (ribbon). * - * @param Spreadsheet $spreadsheet - * - * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception - * * @return string XML Output */ public function writeRibbonRelationships(Spreadsheet $spreadsheet) @@ -31,13 +28,13 @@ class RelsRibbon extends WriterPart // Relationships $objWriter->startElement('Relationships'); - $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships'); + $objWriter->writeAttribute('xmlns', Namespaces::RELATIONSHIPS); $localRels = $spreadsheet->getRibbonBinObjects('names'); if (is_array($localRels)) { foreach ($localRels as $aId => $aTarget) { $objWriter->startElement('Relationship'); $objWriter->writeAttribute('Id', $aId); - $objWriter->writeAttribute('Type', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image'); + $objWriter->writeAttribute('Type', Namespaces::IMAGE); $objWriter->writeAttribute('Target', $aTarget); $objWriter->endElement(); } diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xlsx/RelsVBA.php b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/RelsVBA.php old mode 100755 new mode 100644 index 01ad38d..d5b6060 --- a/PhpOffice/PhpSpreadsheet/Writer/Xlsx/RelsVBA.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/RelsVBA.php @@ -2,21 +2,17 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; -use PhpOffice\PhpSpreadsheet\Spreadsheet; class RelsVBA extends WriterPart { /** * Write relationships for a signed VBA Project. * - * @param Spreadsheet $spreadsheet - * - * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception - * * @return string XML Output */ - public function writeVBARelationships(Spreadsheet $spreadsheet) + public function writeVBARelationships() { // Create XML writer $objWriter = null; @@ -31,10 +27,10 @@ class RelsVBA extends WriterPart // Relationships $objWriter->startElement('Relationships'); - $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships'); + $objWriter->writeAttribute('xmlns', Namespaces::RELATIONSHIPS); $objWriter->startElement('Relationship'); $objWriter->writeAttribute('Id', 'rId1'); - $objWriter->writeAttribute('Type', 'http://schemas.microsoft.com/office/2006/relationships/vbaProjectSignature'); + $objWriter->writeAttribute('Type', Namespaces::VBA_SIGNATURE); $objWriter->writeAttribute('Target', 'vbaProjectSignature.bin'); $objWriter->endElement(); $objWriter->endElement(); diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xlsx/StringTable.php b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/StringTable.php old mode 100755 new mode 100644 index d86c6b8..7f62393 --- a/PhpOffice/PhpSpreadsheet/Writer/Xlsx/StringTable.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/StringTable.php @@ -2,53 +2,57 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx; +use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Cell\DataType; +use PhpOffice\PhpSpreadsheet\Chart\ChartColor; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces; use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\RichText\Run; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; -use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; -use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException; +use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet as ActualWorksheet; class StringTable extends WriterPart { /** * Create worksheet stringtable. * - * @param Worksheet $pSheet Worksheet - * @param string[] $pExistingTable Existing table to eventually merge with + * @param string[] $existingTable Existing table to eventually merge with * * @return string[] String table for worksheet */ - public function createStringTable(Worksheet $pSheet, $pExistingTable = null) + public function createStringTable(ActualWorksheet $worksheet, $existingTable = null) { // Create string lookup table $aStringTable = []; - $cellCollection = null; - $aFlippedStringTable = null; // For faster lookup // Is an existing table given? - if (($pExistingTable !== null) && is_array($pExistingTable)) { - $aStringTable = $pExistingTable; + if (($existingTable !== null) && is_array($existingTable)) { + $aStringTable = $existingTable; } // Fill index array $aFlippedStringTable = $this->flipStringTable($aStringTable); // Loop through cells - foreach ($pSheet->getCoordinates() as $coordinate) { - $cell = $pSheet->getCell($coordinate); + foreach ($worksheet->getCellCollection()->getCoordinates() as $coordinate) { + /** @var Cell $cell */ + $cell = $worksheet->getCellCollection()->get($coordinate); $cellValue = $cell->getValue(); - if (!is_object($cellValue) && + if ( + !is_object($cellValue) && ($cellValue !== null) && $cellValue !== '' && - !isset($aFlippedStringTable[\is_float($cellValue) ? (int) $cellValue : $cellValue]) && - ($cell->getDataType() == DataType::TYPE_STRING || $cell->getDataType() == DataType::TYPE_STRING2 || $cell->getDataType() == DataType::TYPE_NULL)) { + ($cell->getDataType() == DataType::TYPE_STRING || $cell->getDataType() == DataType::TYPE_STRING2 || $cell->getDataType() == DataType::TYPE_NULL) && + !isset($aFlippedStringTable[$cellValue]) + ) { $aStringTable[] = $cellValue; $aFlippedStringTable[$cellValue] = true; - } elseif ($cellValue instanceof RichText && + } elseif ( + $cellValue instanceof RichText && ($cellValue !== null) && - !isset($aFlippedStringTable[$cellValue->getHashCode()])) { + !isset($aFlippedStringTable[$cellValue->getHashCode()]) + ) { $aStringTable[] = $cellValue; $aFlippedStringTable[$cellValue->getHashCode()] = true; } @@ -60,13 +64,11 @@ class StringTable extends WriterPart /** * Write string table to XML format. * - * @param string[] $pStringTable - * - * @throws WriterException + * @param (string|RichText)[] $stringTable * * @return string XML Output */ - public function writeStringTable(array $pStringTable) + public function writeStringTable(array $stringTable) { // Create XML writer $objWriter = null; @@ -81,14 +83,14 @@ class StringTable extends WriterPart // String table $objWriter->startElement('sst'); - $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'); - $objWriter->writeAttribute('uniqueCount', count($pStringTable)); + $objWriter->writeAttribute('xmlns', Namespaces::MAIN); + $objWriter->writeAttribute('uniqueCount', (string) count($stringTable)); // Loop through string table - foreach ($pStringTable as $textElement) { + foreach ($stringTable as $textElement) { $objWriter->startElement('si'); - if (!$textElement instanceof RichText) { + if (!($textElement instanceof RichText)) { $textToWrite = StringHelper::controlCharacterPHP2OOXML($textElement); $objWriter->startElement('t'); if ($textToWrite !== trim($textToWrite)) { @@ -96,7 +98,7 @@ class StringTable extends WriterPart } $objWriter->writeRawData($textToWrite); $objWriter->endElement(); - } elseif ($textElement instanceof RichText) { + } else { $this->writeRichText($objWriter, $textElement); } @@ -111,31 +113,31 @@ class StringTable extends WriterPart /** * Write Rich Text. * - * @param XMLWriter $objWriter XML Writer - * @param RichText $pRichText Rich text * @param string $prefix Optional Namespace prefix */ - public function writeRichText(XMLWriter $objWriter, RichText $pRichText, $prefix = null) + public function writeRichText(XMLWriter $objWriter, RichText $richText, $prefix = null): void { if ($prefix !== null) { $prefix .= ':'; } // Loop through rich text elements - $elements = $pRichText->getRichTextElements(); + $elements = $richText->getRichTextElements(); foreach ($elements as $element) { // r $objWriter->startElement($prefix . 'r'); // rPr - if ($element instanceof Run) { + if ($element instanceof Run && $element->getFont() !== null) { // rPr $objWriter->startElement($prefix . 'rPr'); // rFont - $objWriter->startElement($prefix . 'rFont'); - $objWriter->writeAttribute('val', $element->getFont()->getName()); - $objWriter->endElement(); + if ($element->getFont()->getName() !== null) { + $objWriter->startElement($prefix . 'rFont'); + $objWriter->writeAttribute('val', $element->getFont()->getName()); + $objWriter->endElement(); + } // Bold $objWriter->startElement($prefix . 'b'); @@ -164,19 +166,25 @@ class StringTable extends WriterPart $objWriter->endElement(); // Color - $objWriter->startElement($prefix . 'color'); - $objWriter->writeAttribute('rgb', $element->getFont()->getColor()->getARGB()); - $objWriter->endElement(); + if ($element->getFont()->getColor()->getARGB() !== null) { + $objWriter->startElement($prefix . 'color'); + $objWriter->writeAttribute('rgb', $element->getFont()->getColor()->getARGB()); + $objWriter->endElement(); + } // Size - $objWriter->startElement($prefix . 'sz'); - $objWriter->writeAttribute('val', $element->getFont()->getSize()); - $objWriter->endElement(); + if ($element->getFont()->getSize() !== null) { + $objWriter->startElement($prefix . 'sz'); + $objWriter->writeAttribute('val', (string) $element->getFont()->getSize()); + $objWriter->endElement(); + } // Underline - $objWriter->startElement($prefix . 'u'); - $objWriter->writeAttribute('val', $element->getFont()->getUnderline()); - $objWriter->endElement(); + if ($element->getFont()->getUnderline() !== null) { + $objWriter->startElement($prefix . 'u'); + $objWriter->writeAttribute('val', $element->getFont()->getUnderline()); + $objWriter->endElement(); + } $objWriter->endElement(); } @@ -194,57 +202,86 @@ class StringTable extends WriterPart /** * Write Rich Text. * - * @param XMLWriter $objWriter XML Writer - * @param RichText|string $pRichText text string or Rich text + * @param RichText|string $richText text string or Rich text * @param string $prefix Optional Namespace prefix */ - public function writeRichTextForCharts(XMLWriter $objWriter, $pRichText = null, $prefix = null) + public function writeRichTextForCharts(XMLWriter $objWriter, $richText = null, $prefix = ''): void { - if (!$pRichText instanceof RichText) { - $textRun = $pRichText; - $pRichText = new RichText(); - $pRichText->createTextRun($textRun); + if (!($richText instanceof RichText)) { + $textRun = $richText; + $richText = new RichText(); + $run = $richText->createTextRun($textRun ?? ''); + $run->setFont(null); } - if ($prefix !== null) { + if ($prefix !== '') { $prefix .= ':'; } // Loop through rich text elements - $elements = $pRichText->getRichTextElements(); + $elements = $richText->getRichTextElements(); foreach ($elements as $element) { // r $objWriter->startElement($prefix . 'r'); + if ($element->getFont() !== null) { + // rPr + $objWriter->startElement($prefix . 'rPr'); + $size = $element->getFont()->getSize(); + if (is_numeric($size)) { + $objWriter->writeAttribute('sz', (string) (int) ($size * 100)); + } - // rPr - $objWriter->startElement($prefix . 'rPr'); + // Bold + $objWriter->writeAttribute('b', ($element->getFont()->getBold() ? '1' : '0')); + // Italic + $objWriter->writeAttribute('i', ($element->getFont()->getItalic() ? '1' : '0')); + // Underline + $underlineType = $element->getFont()->getUnderline(); + switch ($underlineType) { + case 'single': + $underlineType = 'sng'; - // Bold - $objWriter->writeAttribute('b', ($element->getFont()->getBold() ? 1 : 0)); - // Italic - $objWriter->writeAttribute('i', ($element->getFont()->getItalic() ? 1 : 0)); - // Underline - $underlineType = $element->getFont()->getUnderline(); - switch ($underlineType) { - case 'single': - $underlineType = 'sng'; + break; + case 'double': + $underlineType = 'dbl'; - break; - case 'double': - $underlineType = 'dbl'; + break; + } + if ($underlineType !== null) { + $objWriter->writeAttribute('u', $underlineType); + } + // Strikethrough + $objWriter->writeAttribute('strike', ($element->getFont()->getStriketype() ?: 'noStrike')); + // Superscript/subscript + if ($element->getFont()->getBaseLine()) { + $objWriter->writeAttribute('baseline', (string) $element->getFont()->getBaseLine()); + } - break; + // Color + $this->writeChartTextColor($objWriter, $element->getFont()->getChartColor(), $prefix); + + // Underscore Color + $this->writeChartTextColor($objWriter, $element->getFont()->getUnderlineColor(), $prefix, 'uFill'); + + // fontName + if ($element->getFont()->getLatin()) { + $objWriter->startElement($prefix . 'latin'); + $objWriter->writeAttribute('typeface', $element->getFont()->getLatin()); + $objWriter->endElement(); + } + if ($element->getFont()->getEastAsian()) { + $objWriter->startElement($prefix . 'ea'); + $objWriter->writeAttribute('typeface', $element->getFont()->getEastAsian()); + $objWriter->endElement(); + } + if ($element->getFont()->getComplexScript()) { + $objWriter->startElement($prefix . 'cs'); + $objWriter->writeAttribute('typeface', $element->getFont()->getComplexScript()); + $objWriter->endElement(); + } + + $objWriter->endElement(); } - $objWriter->writeAttribute('u', $underlineType); - // Strikethrough - $objWriter->writeAttribute('strike', ($element->getFont()->getStrikethrough() ? 'sngStrike' : 'noStrike')); - - // rFont - $objWriter->startElement($prefix . 'latin'); - $objWriter->writeAttribute('typeface', $element->getFont()->getName()); - $objWriter->endElement(); - - $objWriter->endElement(); // t $objWriter->startElement($prefix . 't'); @@ -255,6 +292,33 @@ class StringTable extends WriterPart } } + private function writeChartTextColor(XMLWriter $objWriter, ?ChartColor $underlineColor, string $prefix, ?string $openTag = ''): void + { + if ($underlineColor !== null) { + $type = $underlineColor->getType(); + $value = $underlineColor->getValue(); + if (!empty($type) && !empty($value)) { + if ($openTag !== '') { + $objWriter->startElement($prefix . $openTag); + } + $objWriter->startElement($prefix . 'solidFill'); + $objWriter->startElement($prefix . $type); + $objWriter->writeAttribute('val', $value); + $alpha = $underlineColor->getAlpha(); + if (is_numeric($alpha)) { + $objWriter->startElement('a:alpha'); + $objWriter->writeAttribute('val', ChartColor::alphaToXml((int) $alpha)); + $objWriter->endElement(); + } + $objWriter->endElement(); // srgbClr/schemeClr/prstClr + $objWriter->endElement(); // solidFill + if ($openTag !== '') { + $objWriter->endElement(); // uFill + } + } + } + } + /** * Flip string table (for index searching). * diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Style.php b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Style.php old mode 100755 new mode 100644 index 16e800e..b914664 --- a/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Style.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Style.php @@ -2,9 +2,11 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Style\Alignment; use PhpOffice\PhpSpreadsheet\Style\Border; use PhpOffice\PhpSpreadsheet\Style\Borders; use PhpOffice\PhpSpreadsheet\Style\Conditional; @@ -18,10 +20,6 @@ class Style extends WriterPart /** * Write styles to XML format. * - * @param Spreadsheet $spreadsheet - * - * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception - * * @return string XML Output */ public function writeStyles(Spreadsheet $spreadsheet) @@ -40,11 +38,11 @@ class Style extends WriterPart // styleSheet $objWriter->startElement('styleSheet'); $objWriter->writeAttribute('xml:space', 'preserve'); - $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'); + $objWriter->writeAttribute('xmlns', Namespaces::MAIN); // numFmts $objWriter->startElement('numFmts'); - $objWriter->writeAttribute('count', $this->getParentWriter()->getNumFmtHashTable()->count()); + $objWriter->writeAttribute('count', (string) $this->getParentWriter()->getNumFmtHashTable()->count()); // numFmt for ($i = 0; $i < $this->getParentWriter()->getNumFmtHashTable()->count(); ++$i) { @@ -55,54 +53,63 @@ class Style extends WriterPart // fonts $objWriter->startElement('fonts'); - $objWriter->writeAttribute('count', $this->getParentWriter()->getFontHashTable()->count()); + $objWriter->writeAttribute('count', (string) $this->getParentWriter()->getFontHashTable()->count()); // font for ($i = 0; $i < $this->getParentWriter()->getFontHashTable()->count(); ++$i) { - $this->writeFont($objWriter, $this->getParentWriter()->getFontHashTable()->getByIndex($i)); + $thisfont = $this->getParentWriter()->getFontHashTable()->getByIndex($i); + if ($thisfont !== null) { + $this->writeFont($objWriter, $thisfont); + } } $objWriter->endElement(); // fills $objWriter->startElement('fills'); - $objWriter->writeAttribute('count', $this->getParentWriter()->getFillHashTable()->count()); + $objWriter->writeAttribute('count', (string) $this->getParentWriter()->getFillHashTable()->count()); // fill for ($i = 0; $i < $this->getParentWriter()->getFillHashTable()->count(); ++$i) { - $this->writeFill($objWriter, $this->getParentWriter()->getFillHashTable()->getByIndex($i)); + $thisfill = $this->getParentWriter()->getFillHashTable()->getByIndex($i); + if ($thisfill !== null) { + $this->writeFill($objWriter, $thisfill); + } } $objWriter->endElement(); // borders $objWriter->startElement('borders'); - $objWriter->writeAttribute('count', $this->getParentWriter()->getBordersHashTable()->count()); + $objWriter->writeAttribute('count', (string) $this->getParentWriter()->getBordersHashTable()->count()); // border for ($i = 0; $i < $this->getParentWriter()->getBordersHashTable()->count(); ++$i) { - $this->writeBorder($objWriter, $this->getParentWriter()->getBordersHashTable()->getByIndex($i)); + $thisborder = $this->getParentWriter()->getBordersHashTable()->getByIndex($i); + if ($thisborder !== null) { + $this->writeBorder($objWriter, $thisborder); + } } $objWriter->endElement(); // cellStyleXfs $objWriter->startElement('cellStyleXfs'); - $objWriter->writeAttribute('count', 1); + $objWriter->writeAttribute('count', '1'); // xf $objWriter->startElement('xf'); - $objWriter->writeAttribute('numFmtId', 0); - $objWriter->writeAttribute('fontId', 0); - $objWriter->writeAttribute('fillId', 0); - $objWriter->writeAttribute('borderId', 0); + $objWriter->writeAttribute('numFmtId', '0'); + $objWriter->writeAttribute('fontId', '0'); + $objWriter->writeAttribute('fillId', '0'); + $objWriter->writeAttribute('borderId', '0'); $objWriter->endElement(); $objWriter->endElement(); // cellXfs $objWriter->startElement('cellXfs'); - $objWriter->writeAttribute('count', count($spreadsheet->getCellXfCollection())); + $objWriter->writeAttribute('count', (string) count($spreadsheet->getCellXfCollection())); // xf foreach ($spreadsheet->getCellXfCollection() as $cellXf) { @@ -113,24 +120,27 @@ class Style extends WriterPart // cellStyles $objWriter->startElement('cellStyles'); - $objWriter->writeAttribute('count', 1); + $objWriter->writeAttribute('count', '1'); // cellStyle $objWriter->startElement('cellStyle'); $objWriter->writeAttribute('name', 'Normal'); - $objWriter->writeAttribute('xfId', 0); - $objWriter->writeAttribute('builtinId', 0); + $objWriter->writeAttribute('xfId', '0'); + $objWriter->writeAttribute('builtinId', '0'); $objWriter->endElement(); $objWriter->endElement(); // dxfs $objWriter->startElement('dxfs'); - $objWriter->writeAttribute('count', $this->getParentWriter()->getStylesConditionalHashTable()->count()); + $objWriter->writeAttribute('count', (string) $this->getParentWriter()->getStylesConditionalHashTable()->count()); // dxf for ($i = 0; $i < $this->getParentWriter()->getStylesConditionalHashTable()->count(); ++$i) { - $this->writeCellStyleDxf($objWriter, $this->getParentWriter()->getStylesConditionalHashTable()->getByIndex($i)->getStyle()); + $thisstyle = $this->getParentWriter()->getStylesConditionalHashTable()->getByIndex($i); + if ($thisstyle !== null) { + $this->writeCellStyleDxf($objWriter, $thisstyle->getStyle()); + } } $objWriter->endElement(); @@ -149,47 +159,45 @@ class Style extends WriterPart /** * Write Fill. - * - * @param XMLWriter $objWriter XML Writer - * @param Fill $pFill Fill style */ - private function writeFill(XMLWriter $objWriter, Fill $pFill) + private function writeFill(XMLWriter $objWriter, Fill $fill): void { // Check if this is a pattern type or gradient type - if ($pFill->getFillType() === Fill::FILL_GRADIENT_LINEAR || - $pFill->getFillType() === Fill::FILL_GRADIENT_PATH) { + if ( + $fill->getFillType() === Fill::FILL_GRADIENT_LINEAR || + $fill->getFillType() === Fill::FILL_GRADIENT_PATH + ) { // Gradient fill - $this->writeGradientFill($objWriter, $pFill); - } elseif ($pFill->getFillType() !== null) { + $this->writeGradientFill($objWriter, $fill); + } elseif ($fill->getFillType() !== null) { // Pattern fill - $this->writePatternFill($objWriter, $pFill); + $this->writePatternFill($objWriter, $fill); } } /** * Write Gradient Fill. - * - * @param XMLWriter $objWriter XML Writer - * @param Fill $pFill Fill style */ - private function writeGradientFill(XMLWriter $objWriter, Fill $pFill) + private function writeGradientFill(XMLWriter $objWriter, Fill $fill): void { // fill $objWriter->startElement('fill'); // gradientFill $objWriter->startElement('gradientFill'); - $objWriter->writeAttribute('type', $pFill->getFillType()); - $objWriter->writeAttribute('degree', $pFill->getRotation()); + $objWriter->writeAttribute('type', (string) $fill->getFillType()); + $objWriter->writeAttribute('degree', (string) $fill->getRotation()); // stop $objWriter->startElement('stop'); $objWriter->writeAttribute('position', '0'); // color - $objWriter->startElement('color'); - $objWriter->writeAttribute('rgb', $pFill->getStartColor()->getARGB()); - $objWriter->endElement(); + if ($fill->getStartColor()->getARGB() !== null) { + $objWriter->startElement('color'); + $objWriter->writeAttribute('rgb', $fill->getStartColor()->getARGB()); + $objWriter->endElement(); + } $objWriter->endElement(); @@ -198,9 +206,11 @@ class Style extends WriterPart $objWriter->writeAttribute('position', '1'); // color - $objWriter->startElement('color'); - $objWriter->writeAttribute('rgb', $pFill->getEndColor()->getARGB()); - $objWriter->endElement(); + if ($fill->getEndColor()->getARGB() !== null) { + $objWriter->startElement('color'); + $objWriter->writeAttribute('rgb', $fill->getEndColor()->getARGB()); + $objWriter->endElement(); + } $objWriter->endElement(); @@ -209,34 +219,38 @@ class Style extends WriterPart $objWriter->endElement(); } + private static function writePatternColors(Fill $fill): bool + { + if ($fill->getFillType() === Fill::FILL_NONE) { + return false; + } + + return $fill->getFillType() === Fill::FILL_SOLID || $fill->getColorsChanged(); + } + /** * Write Pattern Fill. - * - * @param XMLWriter $objWriter XML Writer - * @param Fill $pFill Fill style */ - private function writePatternFill(XMLWriter $objWriter, Fill $pFill) + private function writePatternFill(XMLWriter $objWriter, Fill $fill): void { // fill $objWriter->startElement('fill'); // patternFill $objWriter->startElement('patternFill'); - $objWriter->writeAttribute('patternType', $pFill->getFillType()); + $objWriter->writeAttribute('patternType', (string) $fill->getFillType()); - if ($pFill->getFillType() !== Fill::FILL_NONE) { + if (self::writePatternColors($fill)) { // fgColor - if ($pFill->getStartColor()->getARGB()) { + if ($fill->getStartColor()->getARGB()) { $objWriter->startElement('fgColor'); - $objWriter->writeAttribute('rgb', $pFill->getStartColor()->getARGB()); + $objWriter->writeAttribute('rgb', $fill->getStartColor()->getARGB()); $objWriter->endElement(); } - } - if ($pFill->getFillType() !== Fill::FILL_NONE) { // bgColor - if ($pFill->getEndColor()->getARGB()) { + if ($fill->getEndColor()->getARGB()) { $objWriter->startElement('bgColor'); - $objWriter->writeAttribute('rgb', $pFill->getEndColor()->getARGB()); + $objWriter->writeAttribute('rgb', $fill->getEndColor()->getARGB()); $objWriter->endElement(); } } @@ -248,11 +262,8 @@ class Style extends WriterPart /** * Write Font. - * - * @param XMLWriter $objWriter XML Writer - * @param Font $pFont Font style */ - private function writeFont(XMLWriter $objWriter, Font $pFont) + private function writeFont(XMLWriter $objWriter, Font $font): void { // font $objWriter->startElement('font'); @@ -263,62 +274,62 @@ class Style extends WriterPart // Bold. We explicitly write this element also when false (like MS Office Excel 2007 does // for conditional formatting). Otherwise it will apparently not be picked up in conditional // formatting style dialog - if ($pFont->getBold() !== null) { + if ($font->getBold() !== null) { $objWriter->startElement('b'); - $objWriter->writeAttribute('val', $pFont->getBold() ? '1' : '0'); + $objWriter->writeAttribute('val', $font->getBold() ? '1' : '0'); $objWriter->endElement(); } // Italic - if ($pFont->getItalic() !== null) { + if ($font->getItalic() !== null) { $objWriter->startElement('i'); - $objWriter->writeAttribute('val', $pFont->getItalic() ? '1' : '0'); + $objWriter->writeAttribute('val', $font->getItalic() ? '1' : '0'); $objWriter->endElement(); } // Strikethrough - if ($pFont->getStrikethrough() !== null) { + if ($font->getStrikethrough() !== null) { $objWriter->startElement('strike'); - $objWriter->writeAttribute('val', $pFont->getStrikethrough() ? '1' : '0'); + $objWriter->writeAttribute('val', $font->getStrikethrough() ? '1' : '0'); $objWriter->endElement(); } // Underline - if ($pFont->getUnderline() !== null) { + if ($font->getUnderline() !== null) { $objWriter->startElement('u'); - $objWriter->writeAttribute('val', $pFont->getUnderline()); + $objWriter->writeAttribute('val', $font->getUnderline()); $objWriter->endElement(); } // Superscript / subscript - if ($pFont->getSuperscript() === true || $pFont->getSubscript() === true) { + if ($font->getSuperscript() === true || $font->getSubscript() === true) { $objWriter->startElement('vertAlign'); - if ($pFont->getSuperscript() === true) { + if ($font->getSuperscript() === true) { $objWriter->writeAttribute('val', 'superscript'); - } elseif ($pFont->getSubscript() === true) { + } elseif ($font->getSubscript() === true) { $objWriter->writeAttribute('val', 'subscript'); } $objWriter->endElement(); } // Size - if ($pFont->getSize() !== null) { + if ($font->getSize() !== null) { $objWriter->startElement('sz'); - $objWriter->writeAttribute('val', StringHelper::formatNumber($pFont->getSize())); + $objWriter->writeAttribute('val', StringHelper::formatNumber($font->getSize())); $objWriter->endElement(); } // Foreground color - if ($pFont->getColor()->getARGB() !== null) { + if ($font->getColor()->getARGB() !== null) { $objWriter->startElement('color'); - $objWriter->writeAttribute('rgb', $pFont->getColor()->getARGB()); + $objWriter->writeAttribute('rgb', $font->getColor()->getARGB()); $objWriter->endElement(); } // Name - if ($pFont->getName() !== null) { + if ($font->getName() !== null) { $objWriter->startElement('name'); - $objWriter->writeAttribute('val', $pFont->getName()); + $objWriter->writeAttribute('val', $font->getName()); $objWriter->endElement(); } @@ -327,16 +338,13 @@ class Style extends WriterPart /** * Write Border. - * - * @param XMLWriter $objWriter XML Writer - * @param Borders $pBorders Borders style */ - private function writeBorder(XMLWriter $objWriter, Borders $pBorders) + private function writeBorder(XMLWriter $objWriter, Borders $borders): void { // Write border $objWriter->startElement('border'); // Diagonal? - switch ($pBorders->getDiagonalDirection()) { + switch ($borders->getDiagonalDirection()) { case Borders::DIAGONAL_UP: $objWriter->writeAttribute('diagonalUp', 'true'); $objWriter->writeAttribute('diagonalDown', 'false'); @@ -355,84 +363,87 @@ class Style extends WriterPart } // BorderPr - $this->writeBorderPr($objWriter, 'left', $pBorders->getLeft()); - $this->writeBorderPr($objWriter, 'right', $pBorders->getRight()); - $this->writeBorderPr($objWriter, 'top', $pBorders->getTop()); - $this->writeBorderPr($objWriter, 'bottom', $pBorders->getBottom()); - $this->writeBorderPr($objWriter, 'diagonal', $pBorders->getDiagonal()); + $this->writeBorderPr($objWriter, 'left', $borders->getLeft()); + $this->writeBorderPr($objWriter, 'right', $borders->getRight()); + $this->writeBorderPr($objWriter, 'top', $borders->getTop()); + $this->writeBorderPr($objWriter, 'bottom', $borders->getBottom()); + $this->writeBorderPr($objWriter, 'diagonal', $borders->getDiagonal()); $objWriter->endElement(); } + /** @var mixed */ + private static $scrutinizerFalse = false; + /** * Write Cell Style Xf. - * - * @param XMLWriter $objWriter XML Writer - * @param \PhpOffice\PhpSpreadsheet\Style\Style $pStyle Style - * @param Spreadsheet $spreadsheet Workbook - * - * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception */ - private function writeCellStyleXf(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Style\Style $pStyle, Spreadsheet $spreadsheet) + private function writeCellStyleXf(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Style\Style $style, Spreadsheet $spreadsheet): void { // xf $objWriter->startElement('xf'); - $objWriter->writeAttribute('xfId', 0); - $objWriter->writeAttribute('fontId', (int) $this->getParentWriter()->getFontHashTable()->getIndexForHashCode($pStyle->getFont()->getHashCode())); - if ($pStyle->getQuotePrefix()) { - $objWriter->writeAttribute('quotePrefix', 1); + $objWriter->writeAttribute('xfId', '0'); + $objWriter->writeAttribute('fontId', (string) (int) $this->getParentWriter()->getFontHashTable()->getIndexForHashCode($style->getFont()->getHashCode())); + if ($style->getQuotePrefix()) { + $objWriter->writeAttribute('quotePrefix', '1'); } - if ($pStyle->getNumberFormat()->getBuiltInFormatCode() === false) { - $objWriter->writeAttribute('numFmtId', (int) ($this->getParentWriter()->getNumFmtHashTable()->getIndexForHashCode($pStyle->getNumberFormat()->getHashCode()) + 164)); + if ($style->getNumberFormat()->getBuiltInFormatCode() === self::$scrutinizerFalse) { + $objWriter->writeAttribute('numFmtId', (string) (int) ($this->getParentWriter()->getNumFmtHashTable()->getIndexForHashCode($style->getNumberFormat()->getHashCode()) + 164)); } else { - $objWriter->writeAttribute('numFmtId', (int) $pStyle->getNumberFormat()->getBuiltInFormatCode()); + $objWriter->writeAttribute('numFmtId', (string) (int) $style->getNumberFormat()->getBuiltInFormatCode()); } - $objWriter->writeAttribute('fillId', (int) $this->getParentWriter()->getFillHashTable()->getIndexForHashCode($pStyle->getFill()->getHashCode())); - $objWriter->writeAttribute('borderId', (int) $this->getParentWriter()->getBordersHashTable()->getIndexForHashCode($pStyle->getBorders()->getHashCode())); + $objWriter->writeAttribute('fillId', (string) (int) $this->getParentWriter()->getFillHashTable()->getIndexForHashCode($style->getFill()->getHashCode())); + $objWriter->writeAttribute('borderId', (string) (int) $this->getParentWriter()->getBordersHashTable()->getIndexForHashCode($style->getBorders()->getHashCode())); // Apply styles? - $objWriter->writeAttribute('applyFont', ($spreadsheet->getDefaultStyle()->getFont()->getHashCode() != $pStyle->getFont()->getHashCode()) ? '1' : '0'); - $objWriter->writeAttribute('applyNumberFormat', ($spreadsheet->getDefaultStyle()->getNumberFormat()->getHashCode() != $pStyle->getNumberFormat()->getHashCode()) ? '1' : '0'); - $objWriter->writeAttribute('applyFill', ($spreadsheet->getDefaultStyle()->getFill()->getHashCode() != $pStyle->getFill()->getHashCode()) ? '1' : '0'); - $objWriter->writeAttribute('applyBorder', ($spreadsheet->getDefaultStyle()->getBorders()->getHashCode() != $pStyle->getBorders()->getHashCode()) ? '1' : '0'); - $objWriter->writeAttribute('applyAlignment', ($spreadsheet->getDefaultStyle()->getAlignment()->getHashCode() != $pStyle->getAlignment()->getHashCode()) ? '1' : '0'); - if ($pStyle->getProtection()->getLocked() != Protection::PROTECTION_INHERIT || $pStyle->getProtection()->getHidden() != Protection::PROTECTION_INHERIT) { + $objWriter->writeAttribute('applyFont', ($spreadsheet->getDefaultStyle()->getFont()->getHashCode() != $style->getFont()->getHashCode()) ? '1' : '0'); + $objWriter->writeAttribute('applyNumberFormat', ($spreadsheet->getDefaultStyle()->getNumberFormat()->getHashCode() != $style->getNumberFormat()->getHashCode()) ? '1' : '0'); + $objWriter->writeAttribute('applyFill', ($spreadsheet->getDefaultStyle()->getFill()->getHashCode() != $style->getFill()->getHashCode()) ? '1' : '0'); + $objWriter->writeAttribute('applyBorder', ($spreadsheet->getDefaultStyle()->getBorders()->getHashCode() != $style->getBorders()->getHashCode()) ? '1' : '0'); + $objWriter->writeAttribute('applyAlignment', ($spreadsheet->getDefaultStyle()->getAlignment()->getHashCode() != $style->getAlignment()->getHashCode()) ? '1' : '0'); + if ($style->getProtection()->getLocked() != Protection::PROTECTION_INHERIT || $style->getProtection()->getHidden() != Protection::PROTECTION_INHERIT) { $objWriter->writeAttribute('applyProtection', 'true'); } // alignment $objWriter->startElement('alignment'); - $objWriter->writeAttribute('horizontal', $pStyle->getAlignment()->getHorizontal()); - $objWriter->writeAttribute('vertical', $pStyle->getAlignment()->getVertical()); + $vertical = Alignment::VERTICAL_ALIGNMENT_FOR_XLSX[$style->getAlignment()->getVertical()] ?? ''; + $horizontal = Alignment::HORIZONTAL_ALIGNMENT_FOR_XLSX[$style->getAlignment()->getHorizontal()] ?? ''; + if ($horizontal !== '') { + $objWriter->writeAttribute('horizontal', $horizontal); + } + if ($vertical !== '') { + $objWriter->writeAttribute('vertical', $vertical); + } $textRotation = 0; - if ($pStyle->getAlignment()->getTextRotation() >= 0) { - $textRotation = $pStyle->getAlignment()->getTextRotation(); - } elseif ($pStyle->getAlignment()->getTextRotation() < 0) { - $textRotation = 90 - $pStyle->getAlignment()->getTextRotation(); + if ($style->getAlignment()->getTextRotation() >= 0) { + $textRotation = $style->getAlignment()->getTextRotation(); + } else { + $textRotation = 90 - $style->getAlignment()->getTextRotation(); } - $objWriter->writeAttribute('textRotation', $textRotation); + $objWriter->writeAttribute('textRotation', (string) $textRotation); - $objWriter->writeAttribute('wrapText', ($pStyle->getAlignment()->getWrapText() ? 'true' : 'false')); - $objWriter->writeAttribute('shrinkToFit', ($pStyle->getAlignment()->getShrinkToFit() ? 'true' : 'false')); + $objWriter->writeAttribute('wrapText', ($style->getAlignment()->getWrapText() ? 'true' : 'false')); + $objWriter->writeAttribute('shrinkToFit', ($style->getAlignment()->getShrinkToFit() ? 'true' : 'false')); - if ($pStyle->getAlignment()->getIndent() > 0) { - $objWriter->writeAttribute('indent', $pStyle->getAlignment()->getIndent()); + if ($style->getAlignment()->getIndent() > 0) { + $objWriter->writeAttribute('indent', (string) $style->getAlignment()->getIndent()); } - if ($pStyle->getAlignment()->getReadOrder() > 0) { - $objWriter->writeAttribute('readingOrder', $pStyle->getAlignment()->getReadOrder()); + if ($style->getAlignment()->getReadOrder() > 0) { + $objWriter->writeAttribute('readingOrder', (string) $style->getAlignment()->getReadOrder()); } $objWriter->endElement(); // protection - if ($pStyle->getProtection()->getLocked() != Protection::PROTECTION_INHERIT || $pStyle->getProtection()->getHidden() != Protection::PROTECTION_INHERIT) { + if ($style->getProtection()->getLocked() != Protection::PROTECTION_INHERIT || $style->getProtection()->getHidden() != Protection::PROTECTION_INHERIT) { $objWriter->startElement('protection'); - if ($pStyle->getProtection()->getLocked() != Protection::PROTECTION_INHERIT) { - $objWriter->writeAttribute('locked', ($pStyle->getProtection()->getLocked() == Protection::PROTECTION_PROTECTED ? 'true' : 'false')); + if ($style->getProtection()->getLocked() != Protection::PROTECTION_INHERIT) { + $objWriter->writeAttribute('locked', ($style->getProtection()->getLocked() == Protection::PROTECTION_PROTECTED ? 'true' : 'false')); } - if ($pStyle->getProtection()->getHidden() != Protection::PROTECTION_INHERIT) { - $objWriter->writeAttribute('hidden', ($pStyle->getProtection()->getHidden() == Protection::PROTECTION_PROTECTED ? 'true' : 'false')); + if ($style->getProtection()->getHidden() != Protection::PROTECTION_INHERIT) { + $objWriter->writeAttribute('hidden', ($style->getProtection()->getHidden() == Protection::PROTECTION_PROTECTED ? 'true' : 'false')); } $objWriter->endElement(); } @@ -442,59 +453,64 @@ class Style extends WriterPart /** * Write Cell Style Dxf. - * - * @param XMLWriter $objWriter XML Writer - * @param \PhpOffice\PhpSpreadsheet\Style\Style $pStyle Style */ - private function writeCellStyleDxf(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Style\Style $pStyle) + private function writeCellStyleDxf(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Style\Style $style): void { // dxf $objWriter->startElement('dxf'); // font - $this->writeFont($objWriter, $pStyle->getFont()); + $this->writeFont($objWriter, $style->getFont()); // numFmt - $this->writeNumFmt($objWriter, $pStyle->getNumberFormat()); + $this->writeNumFmt($objWriter, $style->getNumberFormat()); // fill - $this->writeFill($objWriter, $pStyle->getFill()); + $this->writeFill($objWriter, $style->getFill()); // alignment $objWriter->startElement('alignment'); - if ($pStyle->getAlignment()->getHorizontal() !== null) { - $objWriter->writeAttribute('horizontal', $pStyle->getAlignment()->getHorizontal()); + $horizontal = Alignment::HORIZONTAL_ALIGNMENT_FOR_XLSX[$style->getAlignment()->getHorizontal()] ?? ''; + if ($horizontal) { + $objWriter->writeAttribute('horizontal', $horizontal); } - if ($pStyle->getAlignment()->getVertical() !== null) { - $objWriter->writeAttribute('vertical', $pStyle->getAlignment()->getVertical()); + $vertical = Alignment::VERTICAL_ALIGNMENT_FOR_XLSX[$style->getAlignment()->getVertical()] ?? ''; + if ($vertical) { + $objWriter->writeAttribute('vertical', $vertical); } - if ($pStyle->getAlignment()->getTextRotation() !== null) { + if ($style->getAlignment()->getTextRotation() !== null) { $textRotation = 0; - if ($pStyle->getAlignment()->getTextRotation() >= 0) { - $textRotation = $pStyle->getAlignment()->getTextRotation(); - } elseif ($pStyle->getAlignment()->getTextRotation() < 0) { - $textRotation = 90 - $pStyle->getAlignment()->getTextRotation(); + if ($style->getAlignment()->getTextRotation() >= 0) { + $textRotation = $style->getAlignment()->getTextRotation(); + } else { + $textRotation = 90 - $style->getAlignment()->getTextRotation(); } - $objWriter->writeAttribute('textRotation', $textRotation); + $objWriter->writeAttribute('textRotation', (string) $textRotation); } $objWriter->endElement(); // border - $this->writeBorder($objWriter, $pStyle->getBorders()); + $this->writeBorder($objWriter, $style->getBorders()); // protection - if (($pStyle->getProtection()->getLocked() !== null) || ($pStyle->getProtection()->getHidden() !== null)) { - if ($pStyle->getProtection()->getLocked() !== Protection::PROTECTION_INHERIT || - $pStyle->getProtection()->getHidden() !== Protection::PROTECTION_INHERIT) { + if ((!empty($style->getProtection()->getLocked())) || (!empty($style->getProtection()->getHidden()))) { + if ( + $style->getProtection()->getLocked() !== Protection::PROTECTION_INHERIT || + $style->getProtection()->getHidden() !== Protection::PROTECTION_INHERIT + ) { $objWriter->startElement('protection'); - if (($pStyle->getProtection()->getLocked() !== null) && - ($pStyle->getProtection()->getLocked() !== Protection::PROTECTION_INHERIT)) { - $objWriter->writeAttribute('locked', ($pStyle->getProtection()->getLocked() == Protection::PROTECTION_PROTECTED ? 'true' : 'false')); + if ( + ($style->getProtection()->getLocked() !== null) && + ($style->getProtection()->getLocked() !== Protection::PROTECTION_INHERIT) + ) { + $objWriter->writeAttribute('locked', ($style->getProtection()->getLocked() == Protection::PROTECTION_PROTECTED ? 'true' : 'false')); } - if (($pStyle->getProtection()->getHidden() !== null) && - ($pStyle->getProtection()->getHidden() !== Protection::PROTECTION_INHERIT)) { - $objWriter->writeAttribute('hidden', ($pStyle->getProtection()->getHidden() == Protection::PROTECTION_PROTECTED ? 'true' : 'false')); + if ( + ($style->getProtection()->getHidden() !== null) && + ($style->getProtection()->getHidden() !== Protection::PROTECTION_INHERIT) + ) { + $objWriter->writeAttribute('hidden', ($style->getProtection()->getHidden() == Protection::PROTECTION_PROTECTED ? 'true' : 'false')); } $objWriter->endElement(); } @@ -506,42 +522,40 @@ class Style extends WriterPart /** * Write BorderPr. * - * @param XMLWriter $objWriter XML Writer - * @param string $pName Element name - * @param Border $pBorder Border style + * @param string $name Element name */ - private function writeBorderPr(XMLWriter $objWriter, $pName, Border $pBorder) + private function writeBorderPr(XMLWriter $objWriter, $name, Border $border): void { // Write BorderPr - if ($pBorder->getBorderStyle() != Border::BORDER_NONE) { - $objWriter->startElement($pName); - $objWriter->writeAttribute('style', $pBorder->getBorderStyle()); + if ($border->getBorderStyle() != Border::BORDER_NONE) { + $objWriter->startElement($name); + $objWriter->writeAttribute('style', $border->getBorderStyle()); // color - $objWriter->startElement('color'); - $objWriter->writeAttribute('rgb', $pBorder->getColor()->getARGB()); - $objWriter->endElement(); + if ($border->getColor()->getARGB() !== null) { + $objWriter->startElement('color'); + $objWriter->writeAttribute('rgb', $border->getColor()->getARGB()); + $objWriter->endElement(); - $objWriter->endElement(); + $objWriter->endElement(); + } } } /** * Write NumberFormat. * - * @param XMLWriter $objWriter XML Writer - * @param NumberFormat $pNumberFormat Number Format - * @param int $pId Number Format identifier + * @param int $id Number Format identifier */ - private function writeNumFmt(XMLWriter $objWriter, NumberFormat $pNumberFormat, $pId = 0) + private function writeNumFmt(XMLWriter $objWriter, ?NumberFormat $numberFormat, $id = 0): void { // Translate formatcode - $formatCode = $pNumberFormat->getFormatCode(); + $formatCode = ($numberFormat === null) ? null : $numberFormat->getFormatCode(); // numFmt if ($formatCode !== null) { $objWriter->startElement('numFmt'); - $objWriter->writeAttribute('numFmtId', ($pId + 164)); + $objWriter->writeAttribute('numFmtId', (string) ($id + 164)); $objWriter->writeAttribute('formatCode', $formatCode); $objWriter->endElement(); } @@ -550,8 +564,6 @@ class Style extends WriterPart /** * Get an array of all styles. * - * @param Spreadsheet $spreadsheet - * * @return \PhpOffice\PhpSpreadsheet\Style\Style[] All styles in PhpSpreadsheet */ public function allStyles(Spreadsheet $spreadsheet) @@ -562,8 +574,6 @@ class Style extends WriterPart /** * Get an array of all conditional styles. * - * @param Spreadsheet $spreadsheet - * * @return Conditional[] All conditional styles in PhpSpreadsheet */ public function allConditionalStyles(Spreadsheet $spreadsheet) @@ -586,8 +596,6 @@ class Style extends WriterPart /** * Get an array of all fills. * - * @param Spreadsheet $spreadsheet - * * @return Fill[] All fills in PhpSpreadsheet */ public function allFills(Spreadsheet $spreadsheet) @@ -618,8 +626,6 @@ class Style extends WriterPart /** * Get an array of all fonts. * - * @param Spreadsheet $spreadsheet - * * @return Font[] All fonts in PhpSpreadsheet */ public function allFonts(Spreadsheet $spreadsheet) @@ -641,8 +647,6 @@ class Style extends WriterPart /** * Get an array of all borders. * - * @param Spreadsheet $spreadsheet - * * @return Borders[] All borders in PhpSpreadsheet */ public function allBorders(Spreadsheet $spreadsheet) @@ -664,8 +668,6 @@ class Style extends WriterPart /** * Get an array of all number formats. * - * @param Spreadsheet $spreadsheet - * * @return NumberFormat[] All number formats in PhpSpreadsheet */ public function allNumberFormats(Spreadsheet $spreadsheet) diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Table.php b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Table.php new file mode 100644 index 0000000..2c1468b --- /dev/null +++ b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Table.php @@ -0,0 +1,115 @@ +getParentWriter()->getUseDiskCaching()) { + $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory()); + } else { + $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY); + } + + // XML header + $objWriter->startDocument('1.0', 'UTF-8', 'yes'); + + // Table + $name = 'Table' . $tableRef; + $range = $table->getRange(); + + $objWriter->startElement('table'); + $objWriter->writeAttribute('xml:space', 'preserve'); + $objWriter->writeAttribute('xmlns', Namespaces::MAIN); + $objWriter->writeAttribute('id', (string) $tableRef); + $objWriter->writeAttribute('name', $name); + $objWriter->writeAttribute('displayName', $table->getName() ?: $name); + $objWriter->writeAttribute('ref', $range); + $objWriter->writeAttribute('headerRowCount', $table->getShowHeaderRow() ? '1' : '0'); + $objWriter->writeAttribute('totalsRowCount', $table->getShowTotalsRow() ? '1' : '0'); + + // Table Boundaries + [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($table->getRange()); + + // Table Auto Filter + if ($table->getShowHeaderRow() && $table->getAllowFilter() === true) { + $objWriter->startElement('autoFilter'); + $objWriter->writeAttribute('ref', $range); + $objWriter->endElement(); + foreach (range($rangeStart[0], $rangeEnd[0]) as $offset => $columnIndex) { + $column = $table->getColumnByOffset($offset); + + if (!$column->getShowFilterButton()) { + $objWriter->startElement('filterColumn'); + $objWriter->writeAttribute('colId', (string) $offset); + $objWriter->writeAttribute('hiddenButton', '1'); + $objWriter->endElement(); + } else { + $column = $table->getAutoFilter()->getColumnByOffset($offset); + AutoFilter::writeAutoFilterColumn($objWriter, $column, $offset); + } + } + } + + // Table Columns + $objWriter->startElement('tableColumns'); + $objWriter->writeAttribute('count', (string) ($rangeEnd[0] - $rangeStart[0] + 1)); + foreach (range($rangeStart[0], $rangeEnd[0]) as $offset => $columnIndex) { + $worksheet = $table->getWorksheet(); + if (!$worksheet) { + continue; + } + + $column = $table->getColumnByOffset($offset); + $cell = $worksheet->getCell([$columnIndex, $rangeStart[1]]); + + $objWriter->startElement('tableColumn'); + $objWriter->writeAttribute('id', (string) ($offset + 1)); + $objWriter->writeAttribute('name', $table->getShowHeaderRow() ? $cell->getValue() : 'Column' . ($offset + 1)); + + if ($table->getShowTotalsRow()) { + if ($column->getTotalsRowLabel()) { + $objWriter->writeAttribute('totalsRowLabel', $column->getTotalsRowLabel()); + } + if ($column->getTotalsRowFunction()) { + $objWriter->writeAttribute('totalsRowFunction', $column->getTotalsRowFunction()); + } + } + if ($column->getColumnFormula()) { + $objWriter->writeElement('calculatedColumnFormula', $column->getColumnFormula()); + } + + $objWriter->endElement(); + } + $objWriter->endElement(); + + // Table Styles + $objWriter->startElement('tableStyleInfo'); + $objWriter->writeAttribute('name', $table->getStyle()->getTheme()); + $objWriter->writeAttribute('showFirstColumn', $table->getStyle()->getShowFirstColumn() ? '1' : '0'); + $objWriter->writeAttribute('showLastColumn', $table->getStyle()->getShowLastColumn() ? '1' : '0'); + $objWriter->writeAttribute('showRowStripes', $table->getStyle()->getShowRowStripes() ? '1' : '0'); + $objWriter->writeAttribute('showColumnStripes', $table->getStyle()->getShowColumnStripes() ? '1' : '0'); + $objWriter->endElement(); + + $objWriter->endElement(); + + // Return + return $objWriter->getData(); + } +} diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Theme.php b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Theme.php old mode 100755 new mode 100644 index f5f8dc0..9ff29d4 --- a/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Theme.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Theme.php @@ -2,20 +2,15 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; -use PhpOffice\PhpSpreadsheet\Spreadsheet; -/** - * @category PhpSpreadsheet - * - * @copyright Copyright (c) 2006 - 2016 PhpSpreadsheet (https://github.com/PHPOffice/PhpSpreadsheet) - */ class Theme extends WriterPart { /** * Map of Major fonts to write. * - * @var array of string + * @var string[] */ private static $majorFonts = [ 'Jpan' => 'MS Pゴシック', @@ -53,7 +48,7 @@ class Theme extends WriterPart /** * Map of Minor fonts to write. * - * @var array of string + * @var string[] */ private static $minorFonts = [ 'Jpan' => 'MS Pゴシック', @@ -91,7 +86,7 @@ class Theme extends WriterPart /** * Map of core colours. * - * @var array of string + * @var string[] */ private static $colourScheme = [ 'dk2' => '1F497D', @@ -109,13 +104,9 @@ class Theme extends WriterPart /** * Write theme to XML format. * - * @param Spreadsheet $spreadsheet - * - * @throws \PhpOffice\PhpSpreadsheet\Writer\Exception - * * @return string XML Output */ - public function writeTheme(Spreadsheet $spreadsheet) + public function writeTheme() { // Create XML writer $objWriter = null; @@ -130,7 +121,7 @@ class Theme extends WriterPart // a:theme $objWriter->startElement('a:theme'); - $objWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main'); + $objWriter->writeAttribute('xmlns:a', Namespaces::DRAWINGML); $objWriter->writeAttribute('name', 'Office Theme'); // a:themeElements @@ -793,13 +784,9 @@ class Theme extends WriterPart /** * Write fonts to XML format. * - * @param XMLWriter $objWriter - * @param string $latinFont - * @param array of string $fontSet - * - * @return string XML Output + * @param string[] $fontSet */ - private function writeFonts($objWriter, $latinFont, $fontSet) + private function writeFonts(XMLWriter $objWriter, string $latinFont, array $fontSet): void { // a:latin $objWriter->startElement('a:latin'); @@ -826,12 +813,8 @@ class Theme extends WriterPart /** * Write colour scheme to XML format. - * - * @param XMLWriter $objWriter - * - * @return string XML Output */ - private function writeColourScheme($objWriter) + private function writeColourScheme(XMLWriter $objWriter): void { foreach (self::$colourScheme as $colourName => $colourValue) { $objWriter->startElement('a:' . $colourName); diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Workbook.php b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Workbook.php old mode 100755 new mode 100644 index fd93674..f2a8678 --- a/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Workbook.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Workbook.php @@ -2,24 +2,20 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx; -use PhpOffice\PhpSpreadsheet\Cell\Coordinate; -use PhpOffice\PhpSpreadsheet\NamedRange; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces; use PhpOffice\PhpSpreadsheet\Shared\Date; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; use PhpOffice\PhpSpreadsheet\Spreadsheet; -use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException; +use PhpOffice\PhpSpreadsheet\Writer\Xlsx\DefinedNames as DefinedNamesWriter; class Workbook extends WriterPart { /** * Write workbook to XML format. * - * @param Spreadsheet $spreadsheet * @param bool $recalcRequired Indicate whether formulas should be recalculated before writing * - * @throws WriterException - * * @return string XML Output */ public function writeWorkbook(Spreadsheet $spreadsheet, $recalcRequired = false) @@ -37,8 +33,8 @@ class Workbook extends WriterPart // workbook $objWriter->startElement('workbook'); $objWriter->writeAttribute('xml:space', 'preserve'); - $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'); - $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $objWriter->writeAttribute('xmlns', Namespaces::MAIN); + $objWriter->writeAttribute('xmlns:r', Namespaces::SCHEMA_OFFICE_DOCUMENT); // fileVersion $this->writeFileVersion($objWriter); @@ -58,7 +54,7 @@ class Workbook extends WriterPart $this->writeSheets($objWriter, $spreadsheet); // definedNames - $this->writeDefinedNames($objWriter, $spreadsheet); + (new DefinedNamesWriter($objWriter, $spreadsheet))->write(); // calcPr $this->writeCalcPr($objWriter, $recalcRequired); @@ -71,10 +67,8 @@ class Workbook extends WriterPart /** * Write file version. - * - * @param XMLWriter $objWriter XML Writer */ - private function writeFileVersion(XMLWriter $objWriter) + private function writeFileVersion(XMLWriter $objWriter): void { $objWriter->startElement('fileVersion'); $objWriter->writeAttribute('appName', 'xl'); @@ -86,10 +80,8 @@ class Workbook extends WriterPart /** * Write WorkbookPr. - * - * @param XMLWriter $objWriter XML Writer */ - private function writeWorkbookPr(XMLWriter $objWriter) + private function writeWorkbookPr(XMLWriter $objWriter): void { $objWriter->startElement('workbookPr'); @@ -104,11 +96,8 @@ class Workbook extends WriterPart /** * Write BookViews. - * - * @param XMLWriter $objWriter XML Writer - * @param Spreadsheet $spreadsheet */ - private function writeBookViews(XMLWriter $objWriter, Spreadsheet $spreadsheet) + private function writeBookViews(XMLWriter $objWriter, Spreadsheet $spreadsheet): void { // bookViews $objWriter->startElement('bookViews'); @@ -116,14 +105,14 @@ class Workbook extends WriterPart // workbookView $objWriter->startElement('workbookView'); - $objWriter->writeAttribute('activeTab', $spreadsheet->getActiveSheetIndex()); + $objWriter->writeAttribute('activeTab', (string) $spreadsheet->getActiveSheetIndex()); $objWriter->writeAttribute('autoFilterDateGrouping', ($spreadsheet->getAutoFilterDateGrouping() ? 'true' : 'false')); - $objWriter->writeAttribute('firstSheet', $spreadsheet->getFirstSheetIndex()); + $objWriter->writeAttribute('firstSheet', (string) $spreadsheet->getFirstSheetIndex()); $objWriter->writeAttribute('minimized', ($spreadsheet->getMinimized() ? 'true' : 'false')); $objWriter->writeAttribute('showHorizontalScroll', ($spreadsheet->getShowHorizontalScroll() ? 'true' : 'false')); $objWriter->writeAttribute('showSheetTabs', ($spreadsheet->getShowSheetTabs() ? 'true' : 'false')); $objWriter->writeAttribute('showVerticalScroll', ($spreadsheet->getShowVerticalScroll() ? 'true' : 'false')); - $objWriter->writeAttribute('tabRatio', $spreadsheet->getTabRatio()); + $objWriter->writeAttribute('tabRatio', (string) $spreadsheet->getTabRatio()); $objWriter->writeAttribute('visibility', $spreadsheet->getVisibility()); $objWriter->endElement(); @@ -133,11 +122,8 @@ class Workbook extends WriterPart /** * Write WorkbookProtection. - * - * @param XMLWriter $objWriter XML Writer - * @param Spreadsheet $spreadsheet */ - private function writeWorkbookProtection(XMLWriter $objWriter, Spreadsheet $spreadsheet) + private function writeWorkbookProtection(XMLWriter $objWriter, Spreadsheet $spreadsheet): void { if ($spreadsheet->getSecurity()->isSecurityEnabled()) { $objWriter->startElement('workbookProtection'); @@ -160,10 +146,9 @@ class Workbook extends WriterPart /** * Write calcPr. * - * @param XMLWriter $objWriter XML Writer * @param bool $recalcRequired Indicate whether formulas should be recalculated before writing */ - private function writeCalcPr(XMLWriter $objWriter, $recalcRequired = true) + private function writeCalcPr(XMLWriter $objWriter, $recalcRequired = true): void { $objWriter->startElement('calcPr'); @@ -173,22 +158,17 @@ class Workbook extends WriterPart $objWriter->writeAttribute('calcId', '999999'); $objWriter->writeAttribute('calcMode', 'auto'); // fullCalcOnLoad isn't needed if we've recalculating for the save - $objWriter->writeAttribute('calcCompleted', ($recalcRequired) ? 1 : 0); - $objWriter->writeAttribute('fullCalcOnLoad', ($recalcRequired) ? 0 : 1); - $objWriter->writeAttribute('forceFullCalc', ($recalcRequired) ? 0 : 1); + $objWriter->writeAttribute('calcCompleted', ($recalcRequired) ? '1' : '0'); + $objWriter->writeAttribute('fullCalcOnLoad', ($recalcRequired) ? '0' : '1'); + $objWriter->writeAttribute('forceFullCalc', ($recalcRequired) ? '0' : '1'); $objWriter->endElement(); } /** * Write sheets. - * - * @param XMLWriter $objWriter XML Writer - * @param Spreadsheet $spreadsheet - * - * @throws WriterException */ - private function writeSheets(XMLWriter $objWriter, Spreadsheet $spreadsheet) + private function writeSheets(XMLWriter $objWriter, Spreadsheet $spreadsheet): void { // Write sheets $objWriter->startElement('sheets'); @@ -210,217 +190,25 @@ class Workbook extends WriterPart /** * Write sheet. * - * @param XMLWriter $objWriter XML Writer - * @param string $pSheetname Sheet name - * @param int $pSheetId Sheet id - * @param int $pRelId Relationship ID + * @param string $worksheetName Sheet name + * @param int $worksheetId Sheet id + * @param int $relId Relationship ID * @param string $sheetState Sheet state (visible, hidden, veryHidden) - * - * @throws WriterException */ - private function writeSheet(XMLWriter $objWriter, $pSheetname, $pSheetId = 1, $pRelId = 1, $sheetState = 'visible') + private function writeSheet(XMLWriter $objWriter, $worksheetName, $worksheetId = 1, $relId = 1, $sheetState = 'visible'): void { - if ($pSheetname != '') { + if ($worksheetName != '') { // Write sheet $objWriter->startElement('sheet'); - $objWriter->writeAttribute('name', $pSheetname); - $objWriter->writeAttribute('sheetId', $pSheetId); + $objWriter->writeAttribute('name', $worksheetName); + $objWriter->writeAttribute('sheetId', (string) $worksheetId); if ($sheetState !== 'visible' && $sheetState != '') { $objWriter->writeAttribute('state', $sheetState); } - $objWriter->writeAttribute('r:id', 'rId' . $pRelId); + $objWriter->writeAttribute('r:id', 'rId' . $relId); $objWriter->endElement(); } else { throw new WriterException('Invalid parameters passed.'); } } - - /** - * Write Defined Names. - * - * @param XMLWriter $objWriter XML Writer - * @param Spreadsheet $spreadsheet - * - * @throws WriterException - */ - private function writeDefinedNames(XMLWriter $objWriter, Spreadsheet $spreadsheet) - { - // Write defined names - $objWriter->startElement('definedNames'); - - // Named ranges - if (count($spreadsheet->getNamedRanges()) > 0) { - // Named ranges - $this->writeNamedRanges($objWriter, $spreadsheet); - } - - // Other defined names - $sheetCount = $spreadsheet->getSheetCount(); - for ($i = 0; $i < $sheetCount; ++$i) { - // definedName for autoFilter - $this->writeDefinedNameForAutofilter($objWriter, $spreadsheet->getSheet($i), $i); - - // definedName for Print_Titles - $this->writeDefinedNameForPrintTitles($objWriter, $spreadsheet->getSheet($i), $i); - - // definedName for Print_Area - $this->writeDefinedNameForPrintArea($objWriter, $spreadsheet->getSheet($i), $i); - } - - $objWriter->endElement(); - } - - /** - * Write named ranges. - * - * @param XMLWriter $objWriter XML Writer - * @param Spreadsheet $spreadsheet - * - * @throws WriterException - */ - private function writeNamedRanges(XMLWriter $objWriter, Spreadsheet $spreadsheet) - { - // Loop named ranges - $namedRanges = $spreadsheet->getNamedRanges(); - foreach ($namedRanges as $namedRange) { - $this->writeDefinedNameForNamedRange($objWriter, $namedRange); - } - } - - /** - * Write Defined Name for named range. - * - * @param XMLWriter $objWriter XML Writer - * @param NamedRange $pNamedRange - */ - private function writeDefinedNameForNamedRange(XMLWriter $objWriter, NamedRange $pNamedRange) - { - // definedName for named range - $objWriter->startElement('definedName'); - $objWriter->writeAttribute('name', $pNamedRange->getName()); - if ($pNamedRange->getLocalOnly()) { - $objWriter->writeAttribute('localSheetId', $pNamedRange->getScope()->getParent()->getIndex($pNamedRange->getScope())); - } - - // Create absolute coordinate and write as raw text - $range = Coordinate::splitRange($pNamedRange->getRange()); - $iMax = count($range); - for ($i = 0; $i < $iMax; ++$i) { - $range[$i][0] = '\'' . str_replace("'", "''", $pNamedRange->getWorksheet()->getTitle()) . '\'!' . Coordinate::absoluteReference($range[$i][0]); - if (isset($range[$i][1])) { - $range[$i][1] = Coordinate::absoluteReference($range[$i][1]); - } - } - $range = Coordinate::buildRange($range); - - $objWriter->writeRawData($range); - - $objWriter->endElement(); - } - - /** - * Write Defined Name for autoFilter. - * - * @param XMLWriter $objWriter XML Writer - * @param Worksheet $pSheet - * @param int $pSheetId - */ - private function writeDefinedNameForAutofilter(XMLWriter $objWriter, Worksheet $pSheet, $pSheetId = 0) - { - // definedName for autoFilter - $autoFilterRange = $pSheet->getAutoFilter()->getRange(); - if (!empty($autoFilterRange)) { - $objWriter->startElement('definedName'); - $objWriter->writeAttribute('name', '_xlnm._FilterDatabase'); - $objWriter->writeAttribute('localSheetId', $pSheetId); - $objWriter->writeAttribute('hidden', '1'); - - // Create absolute coordinate and write as raw text - $range = Coordinate::splitRange($autoFilterRange); - $range = $range[0]; - // Strip any worksheet ref so we can make the cell ref absolute - [$ws, $range[0]] = Worksheet::extractSheetTitle($range[0], true); - - $range[0] = Coordinate::absoluteCoordinate($range[0]); - $range[1] = Coordinate::absoluteCoordinate($range[1]); - $range = implode(':', $range); - - $objWriter->writeRawData('\'' . str_replace("'", "''", $pSheet->getTitle()) . '\'!' . $range); - - $objWriter->endElement(); - } - } - - /** - * Write Defined Name for PrintTitles. - * - * @param XMLWriter $objWriter XML Writer - * @param Worksheet $pSheet - * @param int $pSheetId - */ - private function writeDefinedNameForPrintTitles(XMLWriter $objWriter, Worksheet $pSheet, $pSheetId = 0) - { - // definedName for PrintTitles - if ($pSheet->getPageSetup()->isColumnsToRepeatAtLeftSet() || $pSheet->getPageSetup()->isRowsToRepeatAtTopSet()) { - $objWriter->startElement('definedName'); - $objWriter->writeAttribute('name', '_xlnm.Print_Titles'); - $objWriter->writeAttribute('localSheetId', $pSheetId); - - // Setting string - $settingString = ''; - - // Columns to repeat - if ($pSheet->getPageSetup()->isColumnsToRepeatAtLeftSet()) { - $repeat = $pSheet->getPageSetup()->getColumnsToRepeatAtLeft(); - - $settingString .= '\'' . str_replace("'", "''", $pSheet->getTitle()) . '\'!$' . $repeat[0] . ':$' . $repeat[1]; - } - - // Rows to repeat - if ($pSheet->getPageSetup()->isRowsToRepeatAtTopSet()) { - if ($pSheet->getPageSetup()->isColumnsToRepeatAtLeftSet()) { - $settingString .= ','; - } - - $repeat = $pSheet->getPageSetup()->getRowsToRepeatAtTop(); - - $settingString .= '\'' . str_replace("'", "''", $pSheet->getTitle()) . '\'!$' . $repeat[0] . ':$' . $repeat[1]; - } - - $objWriter->writeRawData($settingString); - - $objWriter->endElement(); - } - } - - /** - * Write Defined Name for PrintTitles. - * - * @param XMLWriter $objWriter XML Writer - * @param Worksheet $pSheet - * @param int $pSheetId - */ - private function writeDefinedNameForPrintArea(XMLWriter $objWriter, Worksheet $pSheet, $pSheetId = 0) - { - // definedName for PrintArea - if ($pSheet->getPageSetup()->isPrintAreaSet()) { - $objWriter->startElement('definedName'); - $objWriter->writeAttribute('name', '_xlnm.Print_Area'); - $objWriter->writeAttribute('localSheetId', $pSheetId); - - // Print area - $printArea = Coordinate::splitRange($pSheet->getPageSetup()->getPrintArea()); - - $chunks = []; - foreach ($printArea as $printAreaRect) { - $printAreaRect[0] = Coordinate::absoluteReference($printAreaRect[0]); - $printAreaRect[1] = Coordinate::absoluteReference($printAreaRect[1]); - $chunks[] = '\'' . str_replace("'", "''", $pSheet->getTitle()) . '\'!' . implode(':', $printAreaRect); - } - - $objWriter->writeRawData(implode(',', $chunks)); - - $objWriter->endElement(); - } - } } diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Worksheet.php old mode 100755 new mode 100644 index 07bf39b..966526f --- a/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Worksheet.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/Worksheet.php @@ -2,37 +2,31 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx; +use PhpOffice\PhpSpreadsheet\Calculation\Information\ErrorValue; use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; +use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces; use PhpOffice\PhpSpreadsheet\RichText\RichText; +use PhpOffice\PhpSpreadsheet\Settings; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Shared\XMLWriter; use PhpOffice\PhpSpreadsheet\Style\Conditional; -use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column; -use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\Column\Rule; +use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalDataBar; +use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\ConditionalFormattingRuleExtension; use PhpOffice\PhpSpreadsheet\Worksheet\SheetView; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet as PhpspreadsheetWorksheet; -use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException; -/** - * @category PhpSpreadsheet - * - * @copyright Copyright (c) 2006 - 2015 PhpSpreadsheet (https://github.com/PHPOffice/PhpSpreadsheet) - */ class Worksheet extends WriterPart { /** * Write worksheet to XML format. * - * @param PhpspreadsheetWorksheet $pSheet - * @param string[] $pStringTable + * @param string[] $stringTable * @param bool $includeCharts Flag indicating if we should write charts * - * @throws WriterException - * * @return string XML Output */ - public function writeWorksheet(PhpspreadsheetWorksheet $pSheet, $pStringTable = null, $includeCharts = false) + public function writeWorksheet(PhpspreadsheetWorksheet $worksheet, $stringTable = [], $includeCharts = false) { // Create XML writer $objWriter = null; @@ -48,80 +42,88 @@ class Worksheet extends WriterPart // Worksheet $objWriter->startElement('worksheet'); $objWriter->writeAttribute('xml:space', 'preserve'); - $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'); - $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'); + $objWriter->writeAttribute('xmlns', Namespaces::MAIN); + $objWriter->writeAttribute('xmlns:r', Namespaces::SCHEMA_OFFICE_DOCUMENT); - $objWriter->writeAttribute('xmlns:xdr', 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing'); - $objWriter->writeAttribute('xmlns:x14', 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/main'); - $objWriter->writeAttribute('xmlns:mc', 'http://schemas.openxmlformats.org/markup-compatibility/2006'); + $objWriter->writeAttribute('xmlns:xdr', Namespaces::SPREADSHEET_DRAWING); + $objWriter->writeAttribute('xmlns:x14', Namespaces::DATA_VALIDATIONS1); + $objWriter->writeAttribute('xmlns:xm', Namespaces::DATA_VALIDATIONS2); + $objWriter->writeAttribute('xmlns:mc', Namespaces::COMPATIBILITY); $objWriter->writeAttribute('mc:Ignorable', 'x14ac'); - $objWriter->writeAttribute('xmlns:x14ac', 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac'); + $objWriter->writeAttribute('xmlns:x14ac', Namespaces::SPREADSHEETML_AC); // sheetPr - $this->writeSheetPr($objWriter, $pSheet); + $this->writeSheetPr($objWriter, $worksheet); // Dimension - $this->writeDimension($objWriter, $pSheet); + $this->writeDimension($objWriter, $worksheet); // sheetViews - $this->writeSheetViews($objWriter, $pSheet); + $this->writeSheetViews($objWriter, $worksheet); // sheetFormatPr - $this->writeSheetFormatPr($objWriter, $pSheet); + $this->writeSheetFormatPr($objWriter, $worksheet); // cols - $this->writeCols($objWriter, $pSheet); + $this->writeCols($objWriter, $worksheet); // sheetData - $this->writeSheetData($objWriter, $pSheet, $pStringTable); + $this->writeSheetData($objWriter, $worksheet, $stringTable); // sheetProtection - $this->writeSheetProtection($objWriter, $pSheet); + $this->writeSheetProtection($objWriter, $worksheet); // protectedRanges - $this->writeProtectedRanges($objWriter, $pSheet); + $this->writeProtectedRanges($objWriter, $worksheet); // autoFilter - $this->writeAutoFilter($objWriter, $pSheet); + $this->writeAutoFilter($objWriter, $worksheet); // mergeCells - $this->writeMergeCells($objWriter, $pSheet); + $this->writeMergeCells($objWriter, $worksheet); // conditionalFormatting - $this->writeConditionalFormatting($objWriter, $pSheet); + $this->writeConditionalFormatting($objWriter, $worksheet); // dataValidations - $this->writeDataValidations($objWriter, $pSheet); + $this->writeDataValidations($objWriter, $worksheet); // hyperlinks - $this->writeHyperlinks($objWriter, $pSheet); + $this->writeHyperlinks($objWriter, $worksheet); // Print options - $this->writePrintOptions($objWriter, $pSheet); + $this->writePrintOptions($objWriter, $worksheet); // Page margins - $this->writePageMargins($objWriter, $pSheet); + $this->writePageMargins($objWriter, $worksheet); // Page setup - $this->writePageSetup($objWriter, $pSheet); + $this->writePageSetup($objWriter, $worksheet); // Header / footer - $this->writeHeaderFooter($objWriter, $pSheet); + $this->writeHeaderFooter($objWriter, $worksheet); // Breaks - $this->writeBreaks($objWriter, $pSheet); + $this->writeBreaks($objWriter, $worksheet); // Drawings and/or Charts - $this->writeDrawings($objWriter, $pSheet, $includeCharts); + $this->writeDrawings($objWriter, $worksheet, $includeCharts); // LegacyDrawing - $this->writeLegacyDrawing($objWriter, $pSheet); + $this->writeLegacyDrawing($objWriter, $worksheet); // LegacyDrawingHF - $this->writeLegacyDrawingHF($objWriter, $pSheet); + $this->writeLegacyDrawingHF($objWriter, $worksheet); // AlternateContent - $this->writeAlternateContent($objWriter, $pSheet); + $this->writeAlternateContent($objWriter, $worksheet); + + // Table + $this->writeTable($objWriter, $worksheet); + + // ConditionalFormattingRuleExtensionList + // (Must be inserted last. Not insert last, an Excel parse error will occur) + $this->writeExtLst($objWriter, $worksheet); $objWriter->endElement(); @@ -131,42 +133,49 @@ class Worksheet extends WriterPart /** * Write SheetPr. - * - * @param XMLWriter $objWriter XML Writer - * @param PhpspreadsheetWorksheet $pSheet Worksheet */ - private function writeSheetPr(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet) + private function writeSheetPr(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void { // sheetPr $objWriter->startElement('sheetPr'); - if ($pSheet->getParent()->hasMacros()) { + if ($worksheet->getParent()->hasMacros()) { //if the workbook have macros, we need to have codeName for the sheet - if (!$pSheet->hasCodeName()) { - $pSheet->setCodeName($pSheet->getTitle()); + if (!$worksheet->hasCodeName()) { + $worksheet->setCodeName($worksheet->getTitle()); } - $objWriter->writeAttribute('codeName', $pSheet->getCodeName()); + self::writeAttributeNotNull($objWriter, 'codeName', $worksheet->getCodeName()); } - $autoFilterRange = $pSheet->getAutoFilter()->getRange(); + $autoFilterRange = $worksheet->getAutoFilter()->getRange(); if (!empty($autoFilterRange)) { - $objWriter->writeAttribute('filterMode', 1); - $pSheet->getAutoFilter()->showHideRows(); + $objWriter->writeAttribute('filterMode', '1'); + if (!$worksheet->getAutoFilter()->getEvaluated()) { + $worksheet->getAutoFilter()->showHideRows(); + } + } + $tables = $worksheet->getTableCollection(); + if (count($tables)) { + foreach ($tables as $table) { + if (!$table->getAutoFilter()->getEvaluated()) { + $table->getAutoFilter()->showHideRows(); + } + } } // tabColor - if ($pSheet->isTabColorSet()) { + if ($worksheet->isTabColorSet()) { $objWriter->startElement('tabColor'); - $objWriter->writeAttribute('rgb', $pSheet->getTabColor()->getARGB()); + $objWriter->writeAttribute('rgb', $worksheet->getTabColor()->getARGB() ?? ''); $objWriter->endElement(); } // outlinePr $objWriter->startElement('outlinePr'); - $objWriter->writeAttribute('summaryBelow', ($pSheet->getShowSummaryBelow() ? '1' : '0')); - $objWriter->writeAttribute('summaryRight', ($pSheet->getShowSummaryRight() ? '1' : '0')); + $objWriter->writeAttribute('summaryBelow', ($worksheet->getShowSummaryBelow() ? '1' : '0')); + $objWriter->writeAttribute('summaryRight', ($worksheet->getShowSummaryRight() ? '1' : '0')); $objWriter->endElement(); // pageSetUpPr - if ($pSheet->getPageSetup()->getFitToPage()) { + if ($worksheet->getPageSetup()->getFitToPage()) { $objWriter->startElement('pageSetUpPr'); $objWriter->writeAttribute('fitToPage', '1'); $objWriter->endElement(); @@ -177,34 +186,26 @@ class Worksheet extends WriterPart /** * Write Dimension. - * - * @param XMLWriter $objWriter XML Writer - * @param PhpspreadsheetWorksheet $pSheet Worksheet */ - private function writeDimension(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet) + private function writeDimension(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void { // dimension $objWriter->startElement('dimension'); - $objWriter->writeAttribute('ref', $pSheet->calculateWorksheetDimension()); + $objWriter->writeAttribute('ref', $worksheet->calculateWorksheetDimension()); $objWriter->endElement(); } /** * Write SheetViews. - * - * @param XMLWriter $objWriter XML Writer - * @param PhpspreadsheetWorksheet $pSheet Worksheet - * - * @throws WriterException */ - private function writeSheetViews(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet) + private function writeSheetViews(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void { // sheetViews $objWriter->startElement('sheetViews'); // Sheet selected? $sheetSelected = false; - if ($this->getParentWriter()->getSpreadsheet()->getIndex($pSheet) == $this->getParentWriter()->getSpreadsheet()->getActiveSheetIndex()) { + if ($this->getParentWriter()->getSpreadsheet()->getIndex($worksheet) == $this->getParentWriter()->getSpreadsheet()->getActiveSheetIndex()) { $sheetSelected = true; } @@ -214,63 +215,65 @@ class Worksheet extends WriterPart $objWriter->writeAttribute('workbookViewId', '0'); // Zoom scales - if ($pSheet->getSheetView()->getZoomScale() != 100) { - $objWriter->writeAttribute('zoomScale', $pSheet->getSheetView()->getZoomScale()); + if ($worksheet->getSheetView()->getZoomScale() != 100) { + $objWriter->writeAttribute('zoomScale', (string) $worksheet->getSheetView()->getZoomScale()); } - if ($pSheet->getSheetView()->getZoomScaleNormal() != 100) { - $objWriter->writeAttribute('zoomScaleNormal', $pSheet->getSheetView()->getZoomScaleNormal()); + if ($worksheet->getSheetView()->getZoomScaleNormal() != 100) { + $objWriter->writeAttribute('zoomScaleNormal', (string) $worksheet->getSheetView()->getZoomScaleNormal()); + } + + // Show zeros (Excel also writes this attribute only if set to false) + if ($worksheet->getSheetView()->getShowZeros() === false) { + $objWriter->writeAttribute('showZeros', '0'); } // View Layout Type - if ($pSheet->getSheetView()->getView() !== SheetView::SHEETVIEW_NORMAL) { - $objWriter->writeAttribute('view', $pSheet->getSheetView()->getView()); + if ($worksheet->getSheetView()->getView() !== SheetView::SHEETVIEW_NORMAL) { + $objWriter->writeAttribute('view', $worksheet->getSheetView()->getView()); } // Gridlines - if ($pSheet->getShowGridlines()) { + if ($worksheet->getShowGridlines()) { $objWriter->writeAttribute('showGridLines', 'true'); } else { $objWriter->writeAttribute('showGridLines', 'false'); } // Row and column headers - if ($pSheet->getShowRowColHeaders()) { + if ($worksheet->getShowRowColHeaders()) { $objWriter->writeAttribute('showRowColHeaders', '1'); } else { $objWriter->writeAttribute('showRowColHeaders', '0'); } // Right-to-left - if ($pSheet->getRightToLeft()) { + if ($worksheet->getRightToLeft()) { $objWriter->writeAttribute('rightToLeft', 'true'); } - $activeCell = $pSheet->getActiveCell(); - $sqref = $pSheet->getSelectedCells(); + $topLeftCell = $worksheet->getTopLeftCell(); + $activeCell = $worksheet->getActiveCell(); + $sqref = $worksheet->getSelectedCells(); // Pane $pane = ''; - if ($pSheet->getFreezePane()) { - [$xSplit, $ySplit] = Coordinate::coordinateFromString($pSheet->getFreezePane()); + if ($worksheet->getFreezePane()) { + [$xSplit, $ySplit] = Coordinate::coordinateFromString($worksheet->getFreezePane()); $xSplit = Coordinate::columnIndexFromString($xSplit); --$xSplit; --$ySplit; - $topLeftCell = $pSheet->getTopLeftCell(); - $activeCell = $topLeftCell; - $sqref = $topLeftCell; - // pane $pane = 'topRight'; $objWriter->startElement('pane'); if ($xSplit > 0) { - $objWriter->writeAttribute('xSplit', $xSplit); + $objWriter->writeAttribute('xSplit', "$xSplit"); } if ($ySplit > 0) { $objWriter->writeAttribute('ySplit', $ySplit); $pane = ($xSplit > 0) ? 'bottomRight' : 'bottomLeft'; } - $objWriter->writeAttribute('topLeftCell', $topLeftCell); + self::writeAttributeNotNull($objWriter, 'topLeftCell', $topLeftCell); $objWriter->writeAttribute('activePane', $pane); $objWriter->writeAttribute('state', 'frozen'); $objWriter->endElement(); @@ -284,6 +287,8 @@ class Worksheet extends WriterPart $objWriter->writeAttribute('pane', 'bottomLeft'); $objWriter->endElement(); } + } else { + self::writeAttributeNotNull($objWriter, 'topLeftCell', $topLeftCell); } // Selection @@ -304,75 +309,68 @@ class Worksheet extends WriterPart /** * Write SheetFormatPr. - * - * @param XMLWriter $objWriter XML Writer - * @param PhpspreadsheetWorksheet $pSheet Worksheet */ - private function writeSheetFormatPr(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet) + private function writeSheetFormatPr(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void { // sheetFormatPr $objWriter->startElement('sheetFormatPr'); // Default row height - if ($pSheet->getDefaultRowDimension()->getRowHeight() >= 0) { + if ($worksheet->getDefaultRowDimension()->getRowHeight() >= 0) { $objWriter->writeAttribute('customHeight', 'true'); - $objWriter->writeAttribute('defaultRowHeight', StringHelper::formatNumber($pSheet->getDefaultRowDimension()->getRowHeight())); + $objWriter->writeAttribute('defaultRowHeight', StringHelper::formatNumber($worksheet->getDefaultRowDimension()->getRowHeight())); } else { $objWriter->writeAttribute('defaultRowHeight', '14.4'); } // Set Zero Height row - if ((string) $pSheet->getDefaultRowDimension()->getZeroHeight() === '1' || - strtolower((string) $pSheet->getDefaultRowDimension()->getZeroHeight()) == 'true') { + if ($worksheet->getDefaultRowDimension()->getZeroHeight()) { $objWriter->writeAttribute('zeroHeight', '1'); } // Default column width - if ($pSheet->getDefaultColumnDimension()->getWidth() >= 0) { - $objWriter->writeAttribute('defaultColWidth', StringHelper::formatNumber($pSheet->getDefaultColumnDimension()->getWidth())); + if ($worksheet->getDefaultColumnDimension()->getWidth() >= 0) { + $objWriter->writeAttribute('defaultColWidth', StringHelper::formatNumber($worksheet->getDefaultColumnDimension()->getWidth())); } // Outline level - row $outlineLevelRow = 0; - foreach ($pSheet->getRowDimensions() as $dimension) { + foreach ($worksheet->getRowDimensions() as $dimension) { if ($dimension->getOutlineLevel() > $outlineLevelRow) { $outlineLevelRow = $dimension->getOutlineLevel(); } } - $objWriter->writeAttribute('outlineLevelRow', (int) $outlineLevelRow); + $objWriter->writeAttribute('outlineLevelRow', (string) (int) $outlineLevelRow); // Outline level - column $outlineLevelCol = 0; - foreach ($pSheet->getColumnDimensions() as $dimension) { + foreach ($worksheet->getColumnDimensions() as $dimension) { if ($dimension->getOutlineLevel() > $outlineLevelCol) { $outlineLevelCol = $dimension->getOutlineLevel(); } } - $objWriter->writeAttribute('outlineLevelCol', (int) $outlineLevelCol); + $objWriter->writeAttribute('outlineLevelCol', (string) (int) $outlineLevelCol); $objWriter->endElement(); } /** * Write Cols. - * - * @param XMLWriter $objWriter XML Writer - * @param PhpspreadsheetWorksheet $pSheet Worksheet */ - private function writeCols(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet) + private function writeCols(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void { // cols - if (count($pSheet->getColumnDimensions()) > 0) { + if (count($worksheet->getColumnDimensions()) > 0) { $objWriter->startElement('cols'); - $pSheet->calculateColumnWidths(); + $worksheet->calculateColumnWidths(); // Loop through column dimensions - foreach ($pSheet->getColumnDimensions() as $colDimension) { + foreach ($worksheet->getColumnDimensions() as $colDimension) { // col $objWriter->startElement('col'); - $objWriter->writeAttribute('min', Coordinate::columnIndexFromString($colDimension->getColumnIndex())); - $objWriter->writeAttribute('max', Coordinate::columnIndexFromString($colDimension->getColumnIndex())); + $objWriter->writeAttribute('min', (string) Coordinate::columnIndexFromString($colDimension->getColumnIndex())); + $objWriter->writeAttribute('max', (string) Coordinate::columnIndexFromString($colDimension->getColumnIndex())); if ($colDimension->getWidth() < 0) { // No width set, apply default of 10 @@ -393,7 +391,7 @@ class Worksheet extends WriterPart } // Custom width? - if ($colDimension->getWidth() != $pSheet->getDefaultColumnDimension()->getWidth()) { + if ($colDimension->getWidth() != $worksheet->getDefaultColumnDimension()->getWidth()) { $objWriter->writeAttribute('customWidth', 'true'); } @@ -404,11 +402,11 @@ class Worksheet extends WriterPart // Outline level if ($colDimension->getOutlineLevel() > 0) { - $objWriter->writeAttribute('outlineLevel', $colDimension->getOutlineLevel()); + $objWriter->writeAttribute('outlineLevel', (string) $colDimension->getOutlineLevel()); } // Style - $objWriter->writeAttribute('style', $colDimension->getXfIndex()); + $objWriter->writeAttribute('style', (string) $colDimension->getXfIndex()); $objWriter->endElement(); } @@ -419,135 +417,330 @@ class Worksheet extends WriterPart /** * Write SheetProtection. - * - * @param XMLWriter $objWriter XML Writer - * @param PhpspreadsheetWorksheet $pSheet Worksheet */ - private function writeSheetProtection(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet) + private function writeSheetProtection(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void { + $protection = $worksheet->getProtection(); + if (!$protection->isProtectionEnabled()) { + return; + } // sheetProtection $objWriter->startElement('sheetProtection'); - if ($pSheet->getProtection()->getPassword() !== '') { - $objWriter->writeAttribute('password', $pSheet->getProtection()->getPassword()); + if ($protection->getAlgorithm()) { + $objWriter->writeAttribute('algorithmName', $protection->getAlgorithm()); + $objWriter->writeAttribute('hashValue', $protection->getPassword()); + $objWriter->writeAttribute('saltValue', $protection->getSalt()); + $objWriter->writeAttribute('spinCount', (string) $protection->getSpinCount()); + } elseif ($protection->getPassword() !== '') { + $objWriter->writeAttribute('password', $protection->getPassword()); } - $objWriter->writeAttribute('sheet', ($pSheet->getProtection()->getSheet() ? 'true' : 'false')); - $objWriter->writeAttribute('objects', ($pSheet->getProtection()->getObjects() ? 'true' : 'false')); - $objWriter->writeAttribute('scenarios', ($pSheet->getProtection()->getScenarios() ? 'true' : 'false')); - $objWriter->writeAttribute('formatCells', ($pSheet->getProtection()->getFormatCells() ? 'true' : 'false')); - $objWriter->writeAttribute('formatColumns', ($pSheet->getProtection()->getFormatColumns() ? 'true' : 'false')); - $objWriter->writeAttribute('formatRows', ($pSheet->getProtection()->getFormatRows() ? 'true' : 'false')); - $objWriter->writeAttribute('insertColumns', ($pSheet->getProtection()->getInsertColumns() ? 'true' : 'false')); - $objWriter->writeAttribute('insertRows', ($pSheet->getProtection()->getInsertRows() ? 'true' : 'false')); - $objWriter->writeAttribute('insertHyperlinks', ($pSheet->getProtection()->getInsertHyperlinks() ? 'true' : 'false')); - $objWriter->writeAttribute('deleteColumns', ($pSheet->getProtection()->getDeleteColumns() ? 'true' : 'false')); - $objWriter->writeAttribute('deleteRows', ($pSheet->getProtection()->getDeleteRows() ? 'true' : 'false')); - $objWriter->writeAttribute('selectLockedCells', ($pSheet->getProtection()->getSelectLockedCells() ? 'true' : 'false')); - $objWriter->writeAttribute('sort', ($pSheet->getProtection()->getSort() ? 'true' : 'false')); - $objWriter->writeAttribute('autoFilter', ($pSheet->getProtection()->getAutoFilter() ? 'true' : 'false')); - $objWriter->writeAttribute('pivotTables', ($pSheet->getProtection()->getPivotTables() ? 'true' : 'false')); - $objWriter->writeAttribute('selectUnlockedCells', ($pSheet->getProtection()->getSelectUnlockedCells() ? 'true' : 'false')); + self::writeProtectionAttribute($objWriter, 'sheet', $protection->getSheet()); + self::writeProtectionAttribute($objWriter, 'objects', $protection->getObjects()); + self::writeProtectionAttribute($objWriter, 'scenarios', $protection->getScenarios()); + self::writeProtectionAttribute($objWriter, 'formatCells', $protection->getFormatCells()); + self::writeProtectionAttribute($objWriter, 'formatColumns', $protection->getFormatColumns()); + self::writeProtectionAttribute($objWriter, 'formatRows', $protection->getFormatRows()); + self::writeProtectionAttribute($objWriter, 'insertColumns', $protection->getInsertColumns()); + self::writeProtectionAttribute($objWriter, 'insertRows', $protection->getInsertRows()); + self::writeProtectionAttribute($objWriter, 'insertHyperlinks', $protection->getInsertHyperlinks()); + self::writeProtectionAttribute($objWriter, 'deleteColumns', $protection->getDeleteColumns()); + self::writeProtectionAttribute($objWriter, 'deleteRows', $protection->getDeleteRows()); + self::writeProtectionAttribute($objWriter, 'sort', $protection->getSort()); + self::writeProtectionAttribute($objWriter, 'autoFilter', $protection->getAutoFilter()); + self::writeProtectionAttribute($objWriter, 'pivotTables', $protection->getPivotTables()); + self::writeProtectionAttribute($objWriter, 'selectLockedCells', $protection->getSelectLockedCells()); + self::writeProtectionAttribute($objWriter, 'selectUnlockedCells', $protection->getSelectUnlockedCells()); $objWriter->endElement(); } - /** - * Write ConditionalFormatting. - * - * @param XMLWriter $objWriter XML Writer - * @param PhpspreadsheetWorksheet $pSheet Worksheet - * - * @throws WriterException - */ - private function writeConditionalFormatting(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet) + private static function writeProtectionAttribute(XMLWriter $objWriter, string $name, ?bool $value): void { - // Conditional id - $id = 1; + if ($value === true) { + $objWriter->writeAttribute($name, '1'); + } elseif ($value === false) { + $objWriter->writeAttribute($name, '0'); + } + } - // Loop through styles in the current worksheet - foreach ($pSheet->getConditionalStylesCollection() as $cellCoordinate => $conditionalStyles) { - foreach ($conditionalStyles as $conditional) { - // WHY was this again? - // if ($this->getParentWriter()->getStylesConditionalHashTable()->getIndexForHashCode($conditional->getHashCode()) == '') { - // continue; - // } - if ($conditional->getConditionType() != Conditional::CONDITION_NONE) { - // conditionalFormatting - $objWriter->startElement('conditionalFormatting'); - $objWriter->writeAttribute('sqref', $cellCoordinate); + private static function writeAttributeIf(XMLWriter $objWriter, ?bool $condition, string $attr, string $val): void + { + if ($condition) { + $objWriter->writeAttribute($attr, $val); + } + } - // cfRule - $objWriter->startElement('cfRule'); - $objWriter->writeAttribute('type', $conditional->getConditionType()); - $objWriter->writeAttribute('dxfId', $this->getParentWriter()->getStylesConditionalHashTable()->getIndexForHashCode($conditional->getHashCode())); - $objWriter->writeAttribute('priority', $id++); + private static function writeAttributeNotNull(XMLWriter $objWriter, string $attr, ?string $val): void + { + if ($val !== null) { + $objWriter->writeAttribute($attr, $val); + } + } - if (($conditional->getConditionType() == Conditional::CONDITION_CELLIS || $conditional->getConditionType() == Conditional::CONDITION_CONTAINSTEXT) - && $conditional->getOperatorType() != Conditional::OPERATOR_NONE) { - $objWriter->writeAttribute('operator', $conditional->getOperatorType()); - } + private static function writeElementIf(XMLWriter $objWriter, bool $condition, string $attr, string $val): void + { + if ($condition) { + $objWriter->writeElement($attr, $val); + } + } - if ($conditional->getConditionType() == Conditional::CONDITION_CONTAINSTEXT - && $conditional->getText() !== null) { - $objWriter->writeAttribute('text', $conditional->getText()); - } - - if ($conditional->getStopIfTrue()) { - $objWriter->writeAttribute('stopIfTrue', '1'); - } - - if ($conditional->getConditionType() == Conditional::CONDITION_CONTAINSTEXT - && $conditional->getOperatorType() == Conditional::OPERATOR_CONTAINSTEXT - && $conditional->getText() !== null) { - $objWriter->writeElement('formula', 'NOT(ISERROR(SEARCH("' . $conditional->getText() . '",' . $cellCoordinate . ')))'); - } elseif ($conditional->getConditionType() == Conditional::CONDITION_CONTAINSTEXT - && $conditional->getOperatorType() == Conditional::OPERATOR_BEGINSWITH - && $conditional->getText() !== null) { - $objWriter->writeElement('formula', 'LEFT(' . $cellCoordinate . ',' . strlen($conditional->getText()) . ')="' . $conditional->getText() . '"'); - } elseif ($conditional->getConditionType() == Conditional::CONDITION_CONTAINSTEXT - && $conditional->getOperatorType() == Conditional::OPERATOR_ENDSWITH - && $conditional->getText() !== null) { - $objWriter->writeElement('formula', 'RIGHT(' . $cellCoordinate . ',' . strlen($conditional->getText()) . ')="' . $conditional->getText() . '"'); - } elseif ($conditional->getConditionType() == Conditional::CONDITION_CONTAINSTEXT - && $conditional->getOperatorType() == Conditional::OPERATOR_NOTCONTAINS - && $conditional->getText() !== null) { - $objWriter->writeElement('formula', 'ISERROR(SEARCH("' . $conditional->getText() . '",' . $cellCoordinate . '))'); - } elseif ($conditional->getConditionType() == Conditional::CONDITION_CELLIS - || $conditional->getConditionType() == Conditional::CONDITION_CONTAINSTEXT - || $conditional->getConditionType() == Conditional::CONDITION_EXPRESSION) { - foreach ($conditional->getConditions() as $formula) { - // Formula - $objWriter->writeElement('formula', $formula); - } - } elseif ($conditional->getConditionType() == Conditional::CONDITION_CONTAINSBLANKS) { - // formula copied from ms xlsx xml source file - $objWriter->writeElement('formula', 'LEN(TRIM(' . $cellCoordinate . '))=0'); - } - - $objWriter->endElement(); - - $objWriter->endElement(); + private static function writeOtherCondElements(XMLWriter $objWriter, Conditional $conditional, string $cellCoordinate): void + { + $conditions = $conditional->getConditions(); + if ( + $conditional->getConditionType() == Conditional::CONDITION_CELLIS + || $conditional->getConditionType() == Conditional::CONDITION_EXPRESSION + || !empty($conditions) + ) { + foreach ($conditions as $formula) { + // Formula + if (is_bool($formula)) { + $formula = $formula ? 'TRUE' : 'FALSE'; } + $objWriter->writeElement('formula', FunctionPrefix::addFunctionPrefix("$formula")); + } + } else { + if ($conditional->getConditionType() == Conditional::CONDITION_CONTAINSBLANKS) { + // formula copied from ms xlsx xml source file + $objWriter->writeElement('formula', 'LEN(TRIM(' . $cellCoordinate . '))=0'); + } elseif ($conditional->getConditionType() == Conditional::CONDITION_NOTCONTAINSBLANKS) { + // formula copied from ms xlsx xml source file + $objWriter->writeElement('formula', 'LEN(TRIM(' . $cellCoordinate . '))>0'); + } elseif ($conditional->getConditionType() == Conditional::CONDITION_CONTAINSERRORS) { + // formula copied from ms xlsx xml source file + $objWriter->writeElement('formula', 'ISERROR(' . $cellCoordinate . ')'); + } elseif ($conditional->getConditionType() == Conditional::CONDITION_NOTCONTAINSERRORS) { + // formula copied from ms xlsx xml source file + $objWriter->writeElement('formula', 'NOT(ISERROR(' . $cellCoordinate . '))'); + } + } + } + + private static function writeTimePeriodCondElements(XMLWriter $objWriter, Conditional $conditional, string $cellCoordinate): void + { + $txt = $conditional->getText(); + if (!empty($txt)) { + $objWriter->writeAttribute('timePeriod', $txt); + if (empty($conditional->getConditions())) { + if ($conditional->getOperatorType() == Conditional::TIMEPERIOD_TODAY) { + $objWriter->writeElement('formula', 'FLOOR(' . $cellCoordinate . ')=TODAY()'); + } elseif ($conditional->getOperatorType() == Conditional::TIMEPERIOD_TOMORROW) { + $objWriter->writeElement('formula', 'FLOOR(' . $cellCoordinate . ')=TODAY()+1'); + } elseif ($conditional->getOperatorType() == Conditional::TIMEPERIOD_YESTERDAY) { + $objWriter->writeElement('formula', 'FLOOR(' . $cellCoordinate . ')=TODAY()-1'); + } elseif ($conditional->getOperatorType() == Conditional::TIMEPERIOD_LAST_7_DAYS) { + $objWriter->writeElement('formula', 'AND(TODAY()-FLOOR(' . $cellCoordinate . ',1)<=6,FLOOR(' . $cellCoordinate . ',1)<=TODAY())'); + } elseif ($conditional->getOperatorType() == Conditional::TIMEPERIOD_LAST_WEEK) { + $objWriter->writeElement('formula', 'AND(TODAY()-ROUNDDOWN(' . $cellCoordinate . ',0)>=(WEEKDAY(TODAY())),TODAY()-ROUNDDOWN(' . $cellCoordinate . ',0)<(WEEKDAY(TODAY())+7))'); + } elseif ($conditional->getOperatorType() == Conditional::TIMEPERIOD_THIS_WEEK) { + $objWriter->writeElement('formula', 'AND(TODAY()-ROUNDDOWN(' . $cellCoordinate . ',0)<=WEEKDAY(TODAY())-1,ROUNDDOWN(' . $cellCoordinate . ',0)-TODAY()<=7-WEEKDAY(TODAY()))'); + } elseif ($conditional->getOperatorType() == Conditional::TIMEPERIOD_NEXT_WEEK) { + $objWriter->writeElement('formula', 'AND(ROUNDDOWN(' . $cellCoordinate . ',0)-TODAY()>(7-WEEKDAY(TODAY())),ROUNDDOWN(' . $cellCoordinate . ',0)-TODAY()<(15-WEEKDAY(TODAY())))'); + } elseif ($conditional->getOperatorType() == Conditional::TIMEPERIOD_LAST_MONTH) { + $objWriter->writeElement('formula', 'AND(MONTH(' . $cellCoordinate . ')=MONTH(EDATE(TODAY(),0-1)),YEAR(' . $cellCoordinate . ')=YEAR(EDATE(TODAY(),0-1)))'); + } elseif ($conditional->getOperatorType() == Conditional::TIMEPERIOD_THIS_MONTH) { + $objWriter->writeElement('formula', 'AND(MONTH(' . $cellCoordinate . ')=MONTH(TODAY()),YEAR(' . $cellCoordinate . ')=YEAR(TODAY()))'); + } elseif ($conditional->getOperatorType() == Conditional::TIMEPERIOD_NEXT_MONTH) { + $objWriter->writeElement('formula', 'AND(MONTH(' . $cellCoordinate . ')=MONTH(EDATE(TODAY(),0+1)),YEAR(' . $cellCoordinate . ')=YEAR(EDATE(TODAY(),0+1)))'); + } + } else { + $objWriter->writeElement('formula', (string) ($conditional->getConditions()[0])); + } + } + } + + private static function writeTextCondElements(XMLWriter $objWriter, Conditional $conditional, string $cellCoordinate): void + { + $txt = $conditional->getText(); + if (!empty($txt)) { + $objWriter->writeAttribute('text', $txt); + if (empty($conditional->getConditions())) { + if ($conditional->getOperatorType() == Conditional::OPERATOR_CONTAINSTEXT) { + $objWriter->writeElement('formula', 'NOT(ISERROR(SEARCH("' . $txt . '",' . $cellCoordinate . ')))'); + } elseif ($conditional->getOperatorType() == Conditional::OPERATOR_BEGINSWITH) { + $objWriter->writeElement('formula', 'LEFT(' . $cellCoordinate . ',LEN("' . $txt . '"))="' . $txt . '"'); + } elseif ($conditional->getOperatorType() == Conditional::OPERATOR_ENDSWITH) { + $objWriter->writeElement('formula', 'RIGHT(' . $cellCoordinate . ',LEN("' . $txt . '"))="' . $txt . '"'); + } elseif ($conditional->getOperatorType() == Conditional::OPERATOR_NOTCONTAINS) { + $objWriter->writeElement('formula', 'ISERROR(SEARCH("' . $txt . '",' . $cellCoordinate . '))'); + } + } else { + $objWriter->writeElement('formula', (string) ($conditional->getConditions()[0])); + } + } + } + + private static function writeExtConditionalFormattingElements(XMLWriter $objWriter, ConditionalFormattingRuleExtension $ruleExtension): void + { + $prefix = 'x14'; + $objWriter->startElementNs($prefix, 'conditionalFormatting', null); + + $objWriter->startElementNs($prefix, 'cfRule', null); + $objWriter->writeAttribute('type', $ruleExtension->getCfRule()); + $objWriter->writeAttribute('id', $ruleExtension->getId()); + $objWriter->startElementNs($prefix, 'dataBar', null); + $dataBar = $ruleExtension->getDataBarExt(); + foreach ($dataBar->getXmlAttributes() as $attrKey => $val) { + $objWriter->writeAttribute($attrKey, $val); + } + $minCfvo = $dataBar->getMinimumConditionalFormatValueObject(); + if ($minCfvo !== null) { + $objWriter->startElementNs($prefix, 'cfvo', null); + $objWriter->writeAttribute('type', $minCfvo->getType()); + if ($minCfvo->getCellFormula()) { + $objWriter->writeElement('xm:f', $minCfvo->getCellFormula()); + } + $objWriter->endElement(); //end cfvo + } + + $maxCfvo = $dataBar->getMaximumConditionalFormatValueObject(); + if ($maxCfvo !== null) { + $objWriter->startElementNs($prefix, 'cfvo', null); + $objWriter->writeAttribute('type', $maxCfvo->getType()); + if ($maxCfvo->getCellFormula()) { + $objWriter->writeElement('xm:f', $maxCfvo->getCellFormula()); + } + $objWriter->endElement(); //end cfvo + } + + foreach ($dataBar->getXmlElements() as $elmKey => $elmAttr) { + $objWriter->startElementNs($prefix, $elmKey, null); + foreach ($elmAttr as $attrKey => $attrVal) { + $objWriter->writeAttribute($attrKey, $attrVal); + } + $objWriter->endElement(); //end elmKey + } + $objWriter->endElement(); //end dataBar + $objWriter->endElement(); //end cfRule + $objWriter->writeElement('xm:sqref', $ruleExtension->getSqref()); + $objWriter->endElement(); //end conditionalFormatting + } + + private static function writeDataBarElements(XMLWriter $objWriter, ?ConditionalDataBar $dataBar): void + { + if ($dataBar) { + $objWriter->startElement('dataBar'); + self::writeAttributeIf($objWriter, null !== $dataBar->getShowValue(), 'showValue', $dataBar->getShowValue() ? '1' : '0'); + + $minCfvo = $dataBar->getMinimumConditionalFormatValueObject(); + if ($minCfvo) { + $objWriter->startElement('cfvo'); + self::writeAttributeIf($objWriter, $minCfvo->getType(), 'type', (string) $minCfvo->getType()); + self::writeAttributeIf($objWriter, $minCfvo->getValue(), 'val', (string) $minCfvo->getValue()); + $objWriter->endElement(); + } + $maxCfvo = $dataBar->getMaximumConditionalFormatValueObject(); + if ($maxCfvo) { + $objWriter->startElement('cfvo'); + self::writeAttributeIf($objWriter, $maxCfvo->getType(), 'type', (string) $maxCfvo->getType()); + self::writeAttributeIf($objWriter, $maxCfvo->getValue(), 'val', (string) $maxCfvo->getValue()); + $objWriter->endElement(); + } + if ($dataBar->getColor()) { + $objWriter->startElement('color'); + $objWriter->writeAttribute('rgb', $dataBar->getColor()); + $objWriter->endElement(); + } + $objWriter->endElement(); // end dataBar + + if ($dataBar->getConditionalFormattingRuleExt()) { + $objWriter->startElement('extLst'); + $extension = $dataBar->getConditionalFormattingRuleExt(); + $objWriter->startElement('ext'); + $objWriter->writeAttribute('uri', '{B025F937-C7B1-47D3-B67F-A62EFF666E3E}'); + $objWriter->startElementNs('x14', 'id', null); + $objWriter->text($extension->getId()); + $objWriter->endElement(); + $objWriter->endElement(); + $objWriter->endElement(); //end extLst } } } /** - * Write DataValidations. - * - * @param XMLWriter $objWriter XML Writer - * @param PhpspreadsheetWorksheet $pSheet Worksheet + * Write ConditionalFormatting. */ - private function writeDataValidations(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet) + private function writeConditionalFormatting(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void + { + // Conditional id + $id = 1; + + // Loop through styles in the current worksheet + foreach ($worksheet->getConditionalStylesCollection() as $cellCoordinate => $conditionalStyles) { + $objWriter->startElement('conditionalFormatting'); + $objWriter->writeAttribute('sqref', $cellCoordinate); + + foreach ($conditionalStyles as $conditional) { + // WHY was this again? + // if ($this->getParentWriter()->getStylesConditionalHashTable()->getIndexForHashCode($conditional->getHashCode()) == '') { + // continue; + // } + // cfRule + $objWriter->startElement('cfRule'); + $objWriter->writeAttribute('type', $conditional->getConditionType()); + self::writeAttributeIf( + $objWriter, + ($conditional->getConditionType() != Conditional::CONDITION_DATABAR), + 'dxfId', + (string) $this->getParentWriter()->getStylesConditionalHashTable()->getIndexForHashCode($conditional->getHashCode()) + ); + $objWriter->writeAttribute('priority', (string) $id++); + + self::writeAttributeif( + $objWriter, + ( + $conditional->getConditionType() === Conditional::CONDITION_CELLIS + || $conditional->getConditionType() === Conditional::CONDITION_CONTAINSTEXT + || $conditional->getConditionType() === Conditional::CONDITION_NOTCONTAINSTEXT + || $conditional->getConditionType() === Conditional::CONDITION_BEGINSWITH + || $conditional->getConditionType() === Conditional::CONDITION_ENDSWITH + ) && $conditional->getOperatorType() !== Conditional::OPERATOR_NONE, + 'operator', + $conditional->getOperatorType() + ); + + self::writeAttributeIf($objWriter, $conditional->getStopIfTrue(), 'stopIfTrue', '1'); + + $cellRange = Coordinate::splitRange(str_replace('$', '', strtoupper($cellCoordinate))); + [$topLeftCell] = $cellRange[0]; + + if ( + $conditional->getConditionType() === Conditional::CONDITION_CONTAINSTEXT + || $conditional->getConditionType() === Conditional::CONDITION_NOTCONTAINSTEXT + || $conditional->getConditionType() === Conditional::CONDITION_BEGINSWITH + || $conditional->getConditionType() === Conditional::CONDITION_ENDSWITH + ) { + self::writeTextCondElements($objWriter, $conditional, $topLeftCell); + } elseif ($conditional->getConditionType() === Conditional::CONDITION_TIMEPERIOD) { + self::writeTimePeriodCondElements($objWriter, $conditional, $topLeftCell); + } else { + self::writeOtherCondElements($objWriter, $conditional, $topLeftCell); + } + + // + self::writeDataBarElements($objWriter, $conditional->getDataBar()); + + $objWriter->endElement(); //end cfRule + } + + $objWriter->endElement(); //end conditionalFormatting + } + } + + /** + * Write DataValidations. + */ + private function writeDataValidations(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void { // Datavalidation collection - $dataValidationCollection = $pSheet->getDataValidationCollection(); + $dataValidationCollection = $worksheet->getDataValidationCollection(); // Write data validations? if (!empty($dataValidationCollection)) { $dataValidationCollection = Coordinate::mergeRangesInCollection($dataValidationCollection); $objWriter->startElement('dataValidations'); - $objWriter->writeAttribute('count', count($dataValidationCollection)); + $objWriter->writeAttribute('count', (string) count($dataValidationCollection)); foreach ($dataValidationCollection as $coordinate => $dv) { $objWriter->startElement('dataValidation'); @@ -582,7 +775,7 @@ class Worksheet extends WriterPart $objWriter->writeAttribute('prompt', $dv->getPrompt()); } - $objWriter->writeAttribute('sqref', $coordinate); + $objWriter->writeAttribute('sqref', $dv->getSqref() ?? $coordinate); if ($dv->getFormula1() !== '') { $objWriter->writeElement('formula1', $dv->getFormula1()); @@ -600,14 +793,11 @@ class Worksheet extends WriterPart /** * Write Hyperlinks. - * - * @param XMLWriter $objWriter XML Writer - * @param PhpspreadsheetWorksheet $pSheet Worksheet */ - private function writeHyperlinks(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet) + private function writeHyperlinks(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void { // Hyperlink collection - $hyperlinkCollection = $pSheet->getHyperlinkCollection(); + $hyperlinkCollection = $worksheet->getHyperlinkCollection(); // Relation ID $relationId = 1; @@ -641,18 +831,15 @@ class Worksheet extends WriterPart /** * Write ProtectedRanges. - * - * @param XMLWriter $objWriter XML Writer - * @param PhpspreadsheetWorksheet $pSheet Worksheet */ - private function writeProtectedRanges(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet) + private function writeProtectedRanges(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void { - if (count($pSheet->getProtectedCells()) > 0) { + if (count($worksheet->getProtectedCells()) > 0) { // protectedRanges $objWriter->startElement('protectedRanges'); // Loop protectedRanges - foreach ($pSheet->getProtectedCells() as $protectedCell => $passwordHash) { + foreach ($worksheet->getProtectedCells() as $protectedCell => $passwordHash) { // protectedRange $objWriter->startElement('protectedRange'); $objWriter->writeAttribute('name', 'p' . md5($protectedCell)); @@ -669,18 +856,15 @@ class Worksheet extends WriterPart /** * Write MergeCells. - * - * @param XMLWriter $objWriter XML Writer - * @param PhpspreadsheetWorksheet $pSheet Worksheet */ - private function writeMergeCells(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet) + private function writeMergeCells(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void { - if (count($pSheet->getMergeCells()) > 0) { + if (count($worksheet->getMergeCells()) > 0) { // mergeCells $objWriter->startElement('mergeCells'); // Loop mergeCells - foreach ($pSheet->getMergeCells() as $mergeCell) { + foreach ($worksheet->getMergeCells() as $mergeCell) { // mergeCell $objWriter->startElement('mergeCell'); $objWriter->writeAttribute('ref', $mergeCell); @@ -693,23 +877,20 @@ class Worksheet extends WriterPart /** * Write PrintOptions. - * - * @param XMLWriter $objWriter XML Writer - * @param PhpspreadsheetWorksheet $pSheet Worksheet */ - private function writePrintOptions(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet) + private function writePrintOptions(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void { // printOptions $objWriter->startElement('printOptions'); - $objWriter->writeAttribute('gridLines', ($pSheet->getPrintGridlines() ? 'true' : 'false')); + $objWriter->writeAttribute('gridLines', ($worksheet->getPrintGridlines() ? 'true' : 'false')); $objWriter->writeAttribute('gridLinesSet', 'true'); - if ($pSheet->getPageSetup()->getHorizontalCentered()) { + if ($worksheet->getPageSetup()->getHorizontalCentered()) { $objWriter->writeAttribute('horizontalCentered', 'true'); } - if ($pSheet->getPageSetup()->getVerticalCentered()) { + if ($worksheet->getPageSetup()->getVerticalCentered()) { $objWriter->writeAttribute('verticalCentered', 'true'); } @@ -718,147 +899,79 @@ class Worksheet extends WriterPart /** * Write PageMargins. - * - * @param XMLWriter $objWriter XML Writer - * @param PhpspreadsheetWorksheet $pSheet Worksheet */ - private function writePageMargins(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet) + private function writePageMargins(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void { // pageMargins $objWriter->startElement('pageMargins'); - $objWriter->writeAttribute('left', StringHelper::formatNumber($pSheet->getPageMargins()->getLeft())); - $objWriter->writeAttribute('right', StringHelper::formatNumber($pSheet->getPageMargins()->getRight())); - $objWriter->writeAttribute('top', StringHelper::formatNumber($pSheet->getPageMargins()->getTop())); - $objWriter->writeAttribute('bottom', StringHelper::formatNumber($pSheet->getPageMargins()->getBottom())); - $objWriter->writeAttribute('header', StringHelper::formatNumber($pSheet->getPageMargins()->getHeader())); - $objWriter->writeAttribute('footer', StringHelper::formatNumber($pSheet->getPageMargins()->getFooter())); + $objWriter->writeAttribute('left', StringHelper::formatNumber($worksheet->getPageMargins()->getLeft())); + $objWriter->writeAttribute('right', StringHelper::formatNumber($worksheet->getPageMargins()->getRight())); + $objWriter->writeAttribute('top', StringHelper::formatNumber($worksheet->getPageMargins()->getTop())); + $objWriter->writeAttribute('bottom', StringHelper::formatNumber($worksheet->getPageMargins()->getBottom())); + $objWriter->writeAttribute('header', StringHelper::formatNumber($worksheet->getPageMargins()->getHeader())); + $objWriter->writeAttribute('footer', StringHelper::formatNumber($worksheet->getPageMargins()->getFooter())); $objWriter->endElement(); } /** * Write AutoFilter. - * - * @param XMLWriter $objWriter XML Writer - * @param PhpspreadsheetWorksheet $pSheet Worksheet */ - private function writeAutoFilter(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet) + private function writeAutoFilter(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void { - $autoFilterRange = $pSheet->getAutoFilter()->getRange(); - if (!empty($autoFilterRange)) { - // autoFilter - $objWriter->startElement('autoFilter'); + AutoFilter::writeAutoFilter($objWriter, $worksheet); + } - // Strip any worksheet reference from the filter coordinates - $range = Coordinate::splitRange($autoFilterRange); - $range = $range[0]; - // Strip any worksheet ref - [$ws, $range[0]] = PhpspreadsheetWorksheet::extractSheetTitle($range[0], true); - $range = implode(':', $range); + /** + * Write Table. + */ + private function writeTable(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void + { + $tableCount = $worksheet->getTableCollection()->count(); - $objWriter->writeAttribute('ref', str_replace('$', '', $range)); + $objWriter->startElement('tableParts'); + $objWriter->writeAttribute('count', (string) $tableCount); - $columns = $pSheet->getAutoFilter()->getColumns(); - if (count($columns) > 0) { - foreach ($columns as $columnID => $column) { - $rules = $column->getRules(); - if (count($rules) > 0) { - $objWriter->startElement('filterColumn'); - $objWriter->writeAttribute('colId', $pSheet->getAutoFilter()->getColumnOffset($columnID)); - - $objWriter->startElement($column->getFilterType()); - if ($column->getJoin() == Column::AUTOFILTER_COLUMN_JOIN_AND) { - $objWriter->writeAttribute('and', 1); - } - - foreach ($rules as $rule) { - if (($column->getFilterType() === Column::AUTOFILTER_FILTERTYPE_FILTER) && - ($rule->getOperator() === Rule::AUTOFILTER_COLUMN_RULE_EQUAL) && - ($rule->getValue() === '')) { - // Filter rule for Blanks - $objWriter->writeAttribute('blank', 1); - } elseif ($rule->getRuleType() === Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER) { - // Dynamic Filter Rule - $objWriter->writeAttribute('type', $rule->getGrouping()); - $val = $column->getAttribute('val'); - if ($val !== null) { - $objWriter->writeAttribute('val', $val); - } - $maxVal = $column->getAttribute('maxVal'); - if ($maxVal !== null) { - $objWriter->writeAttribute('maxVal', $maxVal); - } - } elseif ($rule->getRuleType() === Rule::AUTOFILTER_RULETYPE_TOPTENFILTER) { - // Top 10 Filter Rule - $objWriter->writeAttribute('val', $rule->getValue()); - $objWriter->writeAttribute('percent', (($rule->getOperator() === Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT) ? '1' : '0')); - $objWriter->writeAttribute('top', (($rule->getGrouping() === Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP) ? '1' : '0')); - } else { - // Filter, DateGroupItem or CustomFilter - $objWriter->startElement($rule->getRuleType()); - - if ($rule->getOperator() !== Rule::AUTOFILTER_COLUMN_RULE_EQUAL) { - $objWriter->writeAttribute('operator', $rule->getOperator()); - } - if ($rule->getRuleType() === Rule::AUTOFILTER_RULETYPE_DATEGROUP) { - // Date Group filters - foreach ($rule->getValue() as $key => $value) { - if ($value > '') { - $objWriter->writeAttribute($key, $value); - } - } - $objWriter->writeAttribute('dateTimeGrouping', $rule->getGrouping()); - } else { - $objWriter->writeAttribute('val', $rule->getValue()); - } - - $objWriter->endElement(); - } - } - - $objWriter->endElement(); - - $objWriter->endElement(); - } - } - } + for ($t = 1; $t <= $tableCount; ++$t) { + $objWriter->startElement('tablePart'); + $objWriter->writeAttribute('r:id', 'rId_table_' . $t); $objWriter->endElement(); } + + $objWriter->endElement(); } /** * Write PageSetup. - * - * @param XMLWriter $objWriter XML Writer - * @param PhpspreadsheetWorksheet $pSheet Worksheet */ - private function writePageSetup(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet) + private function writePageSetup(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void { // pageSetup $objWriter->startElement('pageSetup'); - $objWriter->writeAttribute('paperSize', $pSheet->getPageSetup()->getPaperSize()); - $objWriter->writeAttribute('orientation', $pSheet->getPageSetup()->getOrientation()); + $objWriter->writeAttribute('paperSize', (string) $worksheet->getPageSetup()->getPaperSize()); + $objWriter->writeAttribute('orientation', $worksheet->getPageSetup()->getOrientation()); - if ($pSheet->getPageSetup()->getScale() !== null) { - $objWriter->writeAttribute('scale', $pSheet->getPageSetup()->getScale()); + if ($worksheet->getPageSetup()->getScale() !== null) { + $objWriter->writeAttribute('scale', (string) $worksheet->getPageSetup()->getScale()); } - if ($pSheet->getPageSetup()->getFitToHeight() !== null) { - $objWriter->writeAttribute('fitToHeight', $pSheet->getPageSetup()->getFitToHeight()); + if ($worksheet->getPageSetup()->getFitToHeight() !== null) { + $objWriter->writeAttribute('fitToHeight', (string) $worksheet->getPageSetup()->getFitToHeight()); } else { $objWriter->writeAttribute('fitToHeight', '0'); } - if ($pSheet->getPageSetup()->getFitToWidth() !== null) { - $objWriter->writeAttribute('fitToWidth', $pSheet->getPageSetup()->getFitToWidth()); + if ($worksheet->getPageSetup()->getFitToWidth() !== null) { + $objWriter->writeAttribute('fitToWidth', (string) $worksheet->getPageSetup()->getFitToWidth()); } else { $objWriter->writeAttribute('fitToWidth', '0'); } - if ($pSheet->getPageSetup()->getFirstPageNumber() !== null) { - $objWriter->writeAttribute('firstPageNumber', $pSheet->getPageSetup()->getFirstPageNumber()); + if (!empty($worksheet->getPageSetup()->getFirstPageNumber())) { + $objWriter->writeAttribute('firstPageNumber', (string) $worksheet->getPageSetup()->getFirstPageNumber()); $objWriter->writeAttribute('useFirstPageNumber', '1'); } + $objWriter->writeAttribute('pageOrder', $worksheet->getPageSetup()->getPageOrder()); - $getUnparsedLoadedData = $pSheet->getParent()->getUnparsedLoadedData(); - if (isset($getUnparsedLoadedData['sheets'][$pSheet->getCodeName()]['pageSetupRelId'])) { - $objWriter->writeAttribute('r:id', $getUnparsedLoadedData['sheets'][$pSheet->getCodeName()]['pageSetupRelId']); + $getUnparsedLoadedData = $worksheet->getParent()->getUnparsedLoadedData(); + if (isset($getUnparsedLoadedData['sheets'][$worksheet->getCodeName()]['pageSetupRelId'])) { + $objWriter->writeAttribute('r:id', $getUnparsedLoadedData['sheets'][$worksheet->getCodeName()]['pageSetupRelId']); } $objWriter->endElement(); @@ -866,40 +979,34 @@ class Worksheet extends WriterPart /** * Write Header / Footer. - * - * @param XMLWriter $objWriter XML Writer - * @param PhpspreadsheetWorksheet $pSheet Worksheet */ - private function writeHeaderFooter(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet) + private function writeHeaderFooter(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void { // headerFooter $objWriter->startElement('headerFooter'); - $objWriter->writeAttribute('differentOddEven', ($pSheet->getHeaderFooter()->getDifferentOddEven() ? 'true' : 'false')); - $objWriter->writeAttribute('differentFirst', ($pSheet->getHeaderFooter()->getDifferentFirst() ? 'true' : 'false')); - $objWriter->writeAttribute('scaleWithDoc', ($pSheet->getHeaderFooter()->getScaleWithDocument() ? 'true' : 'false')); - $objWriter->writeAttribute('alignWithMargins', ($pSheet->getHeaderFooter()->getAlignWithMargins() ? 'true' : 'false')); + $objWriter->writeAttribute('differentOddEven', ($worksheet->getHeaderFooter()->getDifferentOddEven() ? 'true' : 'false')); + $objWriter->writeAttribute('differentFirst', ($worksheet->getHeaderFooter()->getDifferentFirst() ? 'true' : 'false')); + $objWriter->writeAttribute('scaleWithDoc', ($worksheet->getHeaderFooter()->getScaleWithDocument() ? 'true' : 'false')); + $objWriter->writeAttribute('alignWithMargins', ($worksheet->getHeaderFooter()->getAlignWithMargins() ? 'true' : 'false')); - $objWriter->writeElement('oddHeader', $pSheet->getHeaderFooter()->getOddHeader()); - $objWriter->writeElement('oddFooter', $pSheet->getHeaderFooter()->getOddFooter()); - $objWriter->writeElement('evenHeader', $pSheet->getHeaderFooter()->getEvenHeader()); - $objWriter->writeElement('evenFooter', $pSheet->getHeaderFooter()->getEvenFooter()); - $objWriter->writeElement('firstHeader', $pSheet->getHeaderFooter()->getFirstHeader()); - $objWriter->writeElement('firstFooter', $pSheet->getHeaderFooter()->getFirstFooter()); + $objWriter->writeElement('oddHeader', $worksheet->getHeaderFooter()->getOddHeader()); + $objWriter->writeElement('oddFooter', $worksheet->getHeaderFooter()->getOddFooter()); + $objWriter->writeElement('evenHeader', $worksheet->getHeaderFooter()->getEvenHeader()); + $objWriter->writeElement('evenFooter', $worksheet->getHeaderFooter()->getEvenFooter()); + $objWriter->writeElement('firstHeader', $worksheet->getHeaderFooter()->getFirstHeader()); + $objWriter->writeElement('firstFooter', $worksheet->getHeaderFooter()->getFirstFooter()); $objWriter->endElement(); } /** * Write Breaks. - * - * @param XMLWriter $objWriter XML Writer - * @param PhpspreadsheetWorksheet $pSheet Worksheet */ - private function writeBreaks(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet) + private function writeBreaks(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void { // Get row and column breaks $aRowBreaks = []; $aColumnBreaks = []; - foreach ($pSheet->getBreaks() as $cell => $breakType) { + foreach ($worksheet->getBreaks() as $cell => $breakType) { if ($breakType == PhpspreadsheetWorksheet::BREAK_ROW) { $aRowBreaks[] = $cell; } elseif ($breakType == PhpspreadsheetWorksheet::BREAK_COLUMN) { @@ -910,8 +1017,8 @@ class Worksheet extends WriterPart // rowBreaks if (!empty($aRowBreaks)) { $objWriter->startElement('rowBreaks'); - $objWriter->writeAttribute('count', count($aRowBreaks)); - $objWriter->writeAttribute('manualBreakCount', count($aRowBreaks)); + $objWriter->writeAttribute('count', (string) count($aRowBreaks)); + $objWriter->writeAttribute('manualBreakCount', (string) count($aRowBreaks)); foreach ($aRowBreaks as $cell) { $coords = Coordinate::coordinateFromString($cell); @@ -928,14 +1035,14 @@ class Worksheet extends WriterPart // Second, write column breaks if (!empty($aColumnBreaks)) { $objWriter->startElement('colBreaks'); - $objWriter->writeAttribute('count', count($aColumnBreaks)); - $objWriter->writeAttribute('manualBreakCount', count($aColumnBreaks)); + $objWriter->writeAttribute('count', (string) count($aColumnBreaks)); + $objWriter->writeAttribute('manualBreakCount', (string) count($aColumnBreaks)); foreach ($aColumnBreaks as $cell) { $coords = Coordinate::coordinateFromString($cell); $objWriter->startElement('brk'); - $objWriter->writeAttribute('id', Coordinate::columnIndexFromString($coords[0]) - 1); + $objWriter->writeAttribute('id', (string) (Coordinate::columnIndexFromString($coords[0]) - 1)); $objWriter->writeAttribute('man', '1'); $objWriter->endElement(); } @@ -947,111 +1054,213 @@ class Worksheet extends WriterPart /** * Write SheetData. * - * @param XMLWriter $objWriter XML Writer - * @param PhpspreadsheetWorksheet $pSheet Worksheet - * @param string[] $pStringTable String table - * - * @throws WriterException + * @param string[] $stringTable String table */ - private function writeSheetData(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet, array $pStringTable) + private function writeSheetData(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet, array $stringTable): void { // Flipped stringtable, for faster index searching - $aFlippedStringTable = $this->getParentWriter()->getWriterPart('stringtable')->flipStringTable($pStringTable); + $aFlippedStringTable = $this->getParentWriter()->getWriterPartstringtable()->flipStringTable($stringTable); // sheetData $objWriter->startElement('sheetData'); // Get column count - $colCount = Coordinate::columnIndexFromString($pSheet->getHighestColumn()); + $colCount = Coordinate::columnIndexFromString($worksheet->getHighestColumn()); // Highest row number - $highestRow = $pSheet->getHighestRow(); + $highestRow = $worksheet->getHighestRow(); - // Loop through cells + // Loop through cells building a comma-separated list of the columns in each row + // This is a trade-off between the memory usage that is required for a full array of columns, + // and execution speed + /** @var array $cellsByRow */ $cellsByRow = []; - foreach ($pSheet->getCoordinates() as $coordinate) { - $cellAddress = Coordinate::coordinateFromString($coordinate); - $cellsByRow[$cellAddress[1]][] = $coordinate; + foreach ($worksheet->getCoordinates() as $coordinate) { + [$column, $row] = Coordinate::coordinateFromString($coordinate); + $cellsByRow[$row] = $cellsByRow[$row] ?? ''; + $cellsByRow[$row] .= "{$column},"; } $currentRow = 0; while ($currentRow++ < $highestRow) { - // Get row dimension - $rowDimension = $pSheet->getRowDimension($currentRow); + $isRowSet = isset($cellsByRow[$currentRow]); + if ($isRowSet || $worksheet->rowDimensionExists($currentRow)) { + // Get row dimension + $rowDimension = $worksheet->getRowDimension($currentRow); - // Write current row? - $writeCurrentRow = isset($cellsByRow[$currentRow]) || $rowDimension->getRowHeight() >= 0 || $rowDimension->getVisible() == false || $rowDimension->getCollapsed() == true || $rowDimension->getOutlineLevel() > 0 || $rowDimension->getXfIndex() !== null; + // Write current row? + $writeCurrentRow = $isRowSet || $rowDimension->getRowHeight() >= 0 || $rowDimension->getVisible() === false || $rowDimension->getCollapsed() === true || $rowDimension->getOutlineLevel() > 0 || $rowDimension->getXfIndex() !== null; - if ($writeCurrentRow) { - // Start a new row - $objWriter->startElement('row'); - $objWriter->writeAttribute('r', $currentRow); - $objWriter->writeAttribute('spans', '1:' . $colCount); + if ($writeCurrentRow) { + // Start a new row + $objWriter->startElement('row'); + $objWriter->writeAttribute('r', "$currentRow"); + $objWriter->writeAttribute('spans', '1:' . $colCount); - // Row dimensions - if ($rowDimension->getRowHeight() >= 0) { - $objWriter->writeAttribute('customHeight', '1'); - $objWriter->writeAttribute('ht', StringHelper::formatNumber($rowDimension->getRowHeight())); - } - - // Row visibility - if (!$rowDimension->getVisible() === true) { - $objWriter->writeAttribute('hidden', 'true'); - } - - // Collapsed - if ($rowDimension->getCollapsed() === true) { - $objWriter->writeAttribute('collapsed', 'true'); - } - - // Outline level - if ($rowDimension->getOutlineLevel() > 0) { - $objWriter->writeAttribute('outlineLevel', $rowDimension->getOutlineLevel()); - } - - // Style - if ($rowDimension->getXfIndex() !== null) { - $objWriter->writeAttribute('s', $rowDimension->getXfIndex()); - $objWriter->writeAttribute('customFormat', '1'); - } - - // Write cells - if (isset($cellsByRow[$currentRow])) { - foreach ($cellsByRow[$currentRow] as $cellAddress) { - // Write cell - $this->writeCell($objWriter, $pSheet, $cellAddress, $aFlippedStringTable); + // Row dimensions + if ($rowDimension->getRowHeight() >= 0) { + $objWriter->writeAttribute('customHeight', '1'); + $objWriter->writeAttribute('ht', StringHelper::formatNumber($rowDimension->getRowHeight())); } - } - // End row - $objWriter->endElement(); + // Row visibility + if (!$rowDimension->getVisible() === true) { + $objWriter->writeAttribute('hidden', 'true'); + } + + // Collapsed + if ($rowDimension->getCollapsed() === true) { + $objWriter->writeAttribute('collapsed', 'true'); + } + + // Outline level + if ($rowDimension->getOutlineLevel() > 0) { + $objWriter->writeAttribute('outlineLevel', (string) $rowDimension->getOutlineLevel()); + } + + // Style + if ($rowDimension->getXfIndex() !== null) { + $objWriter->writeAttribute('s', (string) $rowDimension->getXfIndex()); + $objWriter->writeAttribute('customFormat', '1'); + } + + // Write cells + if (isset($cellsByRow[$currentRow])) { + // We have a comma-separated list of column names (with a trailing entry); split to an array + $columnsInRow = explode(',', $cellsByRow[$currentRow]); + array_pop($columnsInRow); + foreach ($columnsInRow as $column) { + // Write cell + $this->writeCell($objWriter, $worksheet, "{$column}{$currentRow}", $aFlippedStringTable); + } + } + + // End row + $objWriter->endElement(); + } } } $objWriter->endElement(); } + /** + * @param RichText|string $cellValue + */ + private function writeCellInlineStr(XMLWriter $objWriter, string $mappedType, $cellValue): void + { + $objWriter->writeAttribute('t', $mappedType); + if (!$cellValue instanceof RichText) { + $objWriter->startElement('is'); + $objWriter->writeElement( + 't', + StringHelper::controlCharacterPHP2OOXML(htmlspecialchars($cellValue, Settings::htmlEntityFlags())) + ); + $objWriter->endElement(); + } else { + $objWriter->startElement('is'); + $this->getParentWriter()->getWriterPartstringtable()->writeRichText($objWriter, $cellValue); + $objWriter->endElement(); + } + } + + /** + * @param RichText|string $cellValue + * @param string[] $flippedStringTable + */ + private function writeCellString(XMLWriter $objWriter, string $mappedType, $cellValue, array $flippedStringTable): void + { + $objWriter->writeAttribute('t', $mappedType); + if (!$cellValue instanceof RichText) { + self::writeElementIf($objWriter, isset($flippedStringTable[$cellValue]), 'v', $flippedStringTable[$cellValue] ?? ''); + } else { + $objWriter->writeElement('v', $flippedStringTable[$cellValue->getHashCode()]); + } + } + + /** + * @param float|int $cellValue + */ + private function writeCellNumeric(XMLWriter $objWriter, $cellValue): void + { + //force a decimal to be written if the type is float + if (is_float($cellValue)) { + // force point as decimal separator in case current locale uses comma + $cellValue = str_replace(',', '.', (string) $cellValue); + if (strpos($cellValue, '.') === false) { + $cellValue = $cellValue . '.0'; + } + } + $objWriter->writeElement('v', "$cellValue"); + } + + private function writeCellBoolean(XMLWriter $objWriter, string $mappedType, bool $cellValue): void + { + $objWriter->writeAttribute('t', $mappedType); + $objWriter->writeElement('v', $cellValue ? '1' : '0'); + } + + private function writeCellError(XMLWriter $objWriter, string $mappedType, string $cellValue, string $formulaerr = '#NULL!'): void + { + $objWriter->writeAttribute('t', $mappedType); + $cellIsFormula = substr($cellValue, 0, 1) === '='; + self::writeElementIf($objWriter, $cellIsFormula, 'f', FunctionPrefix::addFunctionPrefixStripEquals($cellValue)); + $objWriter->writeElement('v', $cellIsFormula ? $formulaerr : $cellValue); + } + + private function writeCellFormula(XMLWriter $objWriter, string $cellValue, Cell $cell): void + { + $calculatedValue = $this->getParentWriter()->getPreCalculateFormulas() ? $cell->getCalculatedValue() : $cellValue; + if (is_string($calculatedValue)) { + if (ErrorValue::isError($calculatedValue)) { + $this->writeCellError($objWriter, 'e', $cellValue, $calculatedValue); + + return; + } + $objWriter->writeAttribute('t', 'str'); + $calculatedValue = StringHelper::controlCharacterPHP2OOXML($calculatedValue); + } elseif (is_bool($calculatedValue)) { + $objWriter->writeAttribute('t', 'b'); + $calculatedValue = (int) $calculatedValue; + } + + $attributes = $cell->getFormulaAttributes(); + if (($attributes['t'] ?? null) === 'array') { + $objWriter->startElement('f'); + $objWriter->writeAttribute('t', 'array'); + $objWriter->writeAttribute('ref', $cell->getCoordinate()); + $objWriter->writeAttribute('aca', '1'); + $objWriter->writeAttribute('ca', '1'); + $objWriter->text(substr($cellValue, 1)); + $objWriter->endElement(); + } else { + $objWriter->writeElement('f', FunctionPrefix::addFunctionPrefixStripEquals($cellValue)); + self::writeElementIf( + $objWriter, + $this->getParentWriter()->getOffice2003Compatibility() === false, + 'v', + ($this->getParentWriter()->getPreCalculateFormulas() && !is_array($calculatedValue) && substr($calculatedValue ?? '', 0, 1) !== '#') + ? StringHelper::formatNumber($calculatedValue) : '0' + ); + } + } + /** * Write Cell. * - * @param XMLWriter $objWriter XML Writer - * @param PhpspreadsheetWorksheet $pSheet Worksheet - * @param Cell $pCellAddress Cell Address - * @param string[] $pFlippedStringTable String table (flipped), for faster index searching - * - * @throws WriterException + * @param string $cellAddress Cell Address + * @param string[] $flippedStringTable String table (flipped), for faster index searching */ - private function writeCell(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet, $pCellAddress, array $pFlippedStringTable) + private function writeCell(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet, string $cellAddress, array $flippedStringTable): void { // Cell - $pCell = $pSheet->getCell($pCellAddress); + $pCell = $worksheet->getCell($cellAddress); $objWriter->startElement('c'); - $objWriter->writeAttribute('r', $pCellAddress); + $objWriter->writeAttribute('r', $cellAddress); // Sheet styles - if ($pCell->getXfIndex() != '') { - $objWriter->writeAttribute('s', $pCell->getXfIndex()); - } + $xfi = $pCell->getXfIndex(); + self::writeAttributeIf($objWriter, (bool) $xfi, 's', "$xfi"); // If cell value is supplied, write cell value $cellValue = $pCell->getValue(); @@ -1059,94 +1268,30 @@ class Worksheet extends WriterPart // Map type $mappedType = $pCell->getDataType(); - // Write data type depending on its type - switch (strtolower($mappedType)) { - case 'inlinestr': // Inline string - case 's': // String - case 'b': // Boolean - $objWriter->writeAttribute('t', $mappedType); - - break; - case 'f': // Formula - $calculatedValue = ($this->getParentWriter()->getPreCalculateFormulas()) ? - $pCell->getCalculatedValue() : $cellValue; - if (is_string($calculatedValue)) { - $objWriter->writeAttribute('t', 'str'); - } elseif (is_bool($calculatedValue)) { - $objWriter->writeAttribute('t', 'b'); - } - - break; - case 'e': // Error - $objWriter->writeAttribute('t', $mappedType); - } - // Write data depending on its type switch (strtolower($mappedType)) { case 'inlinestr': // Inline string - if (!$cellValue instanceof RichText) { - $objWriter->writeElement('t', StringHelper::controlCharacterPHP2OOXML(htmlspecialchars($cellValue))); - } elseif ($cellValue instanceof RichText) { - $objWriter->startElement('is'); - $this->getParentWriter()->getWriterPart('stringtable')->writeRichText($objWriter, $cellValue); - $objWriter->endElement(); - } + $this->writeCellInlineStr($objWriter, $mappedType, $cellValue); break; case 's': // String - if (!$cellValue instanceof RichText) { - if (isset($pFlippedStringTable[$cellValue])) { - $objWriter->writeElement('v', $pFlippedStringTable[$cellValue]); - } - } elseif ($cellValue instanceof RichText) { - $objWriter->writeElement('v', $pFlippedStringTable[$cellValue->getHashCode()]); - } + $this->writeCellString($objWriter, $mappedType, $cellValue, $flippedStringTable); break; case 'f': // Formula - $attributes = $pCell->getFormulaAttributes(); - if ($attributes['t'] === 'array') { - $objWriter->startElement('f'); - $objWriter->writeAttribute('t', 'array'); - $objWriter->writeAttribute('ref', $pCellAddress); - $objWriter->writeAttribute('aca', '1'); - $objWriter->writeAttribute('ca', '1'); - $objWriter->text(substr($cellValue, 1)); - $objWriter->endElement(); - } else { - $objWriter->writeElement('f', substr($cellValue, 1)); - } - if ($this->getParentWriter()->getOffice2003Compatibility() === false) { - if ($this->getParentWriter()->getPreCalculateFormulas()) { - if (!is_array($calculatedValue) && substr($calculatedValue, 0, 1) !== '#') { - $objWriter->writeElement('v', StringHelper::formatNumber($calculatedValue)); - } else { - $objWriter->writeElement('v', '0'); - } - } else { - $objWriter->writeElement('v', '0'); - } - } + $this->writeCellFormula($objWriter, $cellValue, $pCell); break; case 'n': // Numeric - // force point as decimal separator in case current locale uses comma - $objWriter->writeElement('v', str_replace(',', '.', $cellValue)); + $this->writeCellNumeric($objWriter, $cellValue); break; case 'b': // Boolean - $objWriter->writeElement('v', ($cellValue ? '1' : '0')); + $this->writeCellBoolean($objWriter, $mappedType, $cellValue); break; case 'e': // Error - if (substr($cellValue, 0, 1) === '=') { - $objWriter->writeElement('f', substr($cellValue, 1)); - $objWriter->writeElement('v', substr($cellValue, 1)); - } else { - $objWriter->writeElement('v', $cellValue); - } - - break; + $this->writeCellError($objWriter, $mappedType, $cellValue); } } @@ -1156,16 +1301,14 @@ class Worksheet extends WriterPart /** * Write Drawings. * - * @param XMLWriter $objWriter XML Writer - * @param PhpspreadsheetWorksheet $pSheet Worksheet * @param bool $includeCharts Flag indicating if we should include drawing details for charts */ - private function writeDrawings(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet, $includeCharts = false) + private function writeDrawings(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet, $includeCharts = false): void { - $unparsedLoadedData = $pSheet->getParent()->getUnparsedLoadedData(); - $hasUnparsedDrawing = isset($unparsedLoadedData['sheets'][$pSheet->getCodeName()]['drawingOriginalIds']); - $chartCount = ($includeCharts) ? $pSheet->getChartCollection()->count() : 0; - if ($chartCount == 0 && $pSheet->getDrawingCollection()->count() == 0 && !$hasUnparsedDrawing) { + $unparsedLoadedData = $worksheet->getParent()->getUnparsedLoadedData(); + $hasUnparsedDrawing = isset($unparsedLoadedData['sheets'][$worksheet->getCodeName()]['drawingOriginalIds']); + $chartCount = ($includeCharts) ? $worksheet->getChartCollection()->count() : 0; + if ($chartCount == 0 && $worksheet->getDrawingCollection()->count() == 0 && !$hasUnparsedDrawing) { return; } @@ -1173,9 +1316,10 @@ class Worksheet extends WriterPart $objWriter->startElement('drawing'); $rId = 'rId1'; - if (isset($unparsedLoadedData['sheets'][$pSheet->getCodeName()]['drawingOriginalIds'])) { - $drawingOriginalIds = $unparsedLoadedData['sheets'][$pSheet->getCodeName()]['drawingOriginalIds']; + if (isset($unparsedLoadedData['sheets'][$worksheet->getCodeName()]['drawingOriginalIds'])) { + $drawingOriginalIds = $unparsedLoadedData['sheets'][$worksheet->getCodeName()]['drawingOriginalIds']; // take first. In future can be overriten + // (! synchronize with \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Rels::writeWorksheetRelationships) $rId = reset($drawingOriginalIds); } @@ -1185,14 +1329,12 @@ class Worksheet extends WriterPart /** * Write LegacyDrawing. - * - * @param XMLWriter $objWriter XML Writer - * @param PhpspreadsheetWorksheet $pSheet Worksheet */ - private function writeLegacyDrawing(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet) + private function writeLegacyDrawing(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void { // If sheet contains comments, add the relationships - if (count($pSheet->getComments()) > 0) { + $unparsedLoadedData = $worksheet->getParent()->getUnparsedLoadedData(); + if (count($worksheet->getComments()) > 0 || isset($unparsedLoadedData['sheets'][$worksheet->getCodeName()]['legacyDrawing'])) { $objWriter->startElement('legacyDrawing'); $objWriter->writeAttribute('r:id', 'rId_comments_vml1'); $objWriter->endElement(); @@ -1201,28 +1343,59 @@ class Worksheet extends WriterPart /** * Write LegacyDrawingHF. - * - * @param XMLWriter $objWriter XML Writer - * @param PhpspreadsheetWorksheet $pSheet Worksheet */ - private function writeLegacyDrawingHF(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet) + private function writeLegacyDrawingHF(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void { // If sheet contains images, add the relationships - if (count($pSheet->getHeaderFooter()->getImages()) > 0) { + if (count($worksheet->getHeaderFooter()->getImages()) > 0) { $objWriter->startElement('legacyDrawingHF'); $objWriter->writeAttribute('r:id', 'rId_headerfooter_vml1'); $objWriter->endElement(); } } - private function writeAlternateContent(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet) + private function writeAlternateContent(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void { - if (empty($pSheet->getParent()->getUnparsedLoadedData()['sheets'][$pSheet->getCodeName()]['AlternateContents'])) { + if (empty($worksheet->getParent()->getUnparsedLoadedData()['sheets'][$worksheet->getCodeName()]['AlternateContents'])) { return; } - foreach ($pSheet->getParent()->getUnparsedLoadedData()['sheets'][$pSheet->getCodeName()]['AlternateContents'] as $alternateContent) { + foreach ($worksheet->getParent()->getUnparsedLoadedData()['sheets'][$worksheet->getCodeName()]['AlternateContents'] as $alternateContent) { $objWriter->writeRaw($alternateContent); } } + + /** + * write + * only implementation conditionalFormattings. + * + * @url https://docs.microsoft.com/en-us/openspecs/office_standards/ms-xlsx/07d607af-5618-4ca2-b683-6a78dc0d9627 + */ + private function writeExtLst(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void + { + $conditionalFormattingRuleExtList = []; + foreach ($worksheet->getConditionalStylesCollection() as $cellCoordinate => $conditionalStyles) { + /** @var Conditional $conditional */ + foreach ($conditionalStyles as $conditional) { + $dataBar = $conditional->getDataBar(); + if ($dataBar && $dataBar->getConditionalFormattingRuleExt()) { + $conditionalFormattingRuleExtList[] = $dataBar->getConditionalFormattingRuleExt(); + } + } + } + + if (count($conditionalFormattingRuleExtList) > 0) { + $conditionalFormattingRuleExtNsPrefix = 'x14'; + $objWriter->startElement('extLst'); + $objWriter->startElement('ext'); + $objWriter->writeAttribute('uri', '{78C0D931-6437-407d-A8EE-F0AAD7539E65}'); + $objWriter->startElementNs($conditionalFormattingRuleExtNsPrefix, 'conditionalFormattings', null); + foreach ($conditionalFormattingRuleExtList as $extension) { + self::writeExtConditionalFormattingElements($objWriter, $extension); + } + $objWriter->endElement(); //end conditionalFormattings + $objWriter->endElement(); //end ext + $objWriter->endElement(); //end extLst + } + } } diff --git a/PhpOffice/PhpSpreadsheet/Writer/Xlsx/WriterPart.php b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/WriterPart.php old mode 100755 new mode 100644 index 7119512..0bfb356 --- a/PhpOffice/PhpSpreadsheet/Writer/Xlsx/WriterPart.php +++ b/PhpOffice/PhpSpreadsheet/Writer/Xlsx/WriterPart.php @@ -25,11 +25,9 @@ abstract class WriterPart /** * Set parent Xlsx object. - * - * @param Xlsx $pWriter */ - public function __construct(Xlsx $pWriter) + public function __construct(Xlsx $writer) { - $this->parentWriter = $pWriter; + $this->parentWriter = $writer; } } diff --git a/PhpOffice/PhpWord/COPYING b/PhpOffice/PhpWord/COPYING deleted file mode 100755 index 94a9ed0..0000000 --- a/PhpOffice/PhpWord/COPYING +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/PhpOffice/PhpWord/COPYING.LESSER b/PhpOffice/PhpWord/COPYING.LESSER deleted file mode 100755 index 65c5ca8..0000000 --- a/PhpOffice/PhpWord/COPYING.LESSER +++ /dev/null @@ -1,165 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. diff --git a/PhpOffice/PhpWord/Collection/AbstractCollection.php b/PhpOffice/PhpWord/Collection/AbstractCollection.php old mode 100755 new mode 100644 index 899ec28..225b361 --- a/PhpOffice/PhpWord/Collection/AbstractCollection.php +++ b/PhpOffice/PhpWord/Collection/AbstractCollection.php @@ -11,28 +11,28 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Collection; /** - * Collection abstract class + * Collection abstract class. * * @since 0.10.0 */ abstract class AbstractCollection { /** - * Items + * Items. * * @var \PhpOffice\PhpWord\Element\AbstractContainer[] */ - private $items = array(); + private $items = []; /** - * Get items + * Get items. * * @return \PhpOffice\PhpWord\Element\AbstractContainer[] */ @@ -42,9 +42,10 @@ abstract class AbstractCollection } /** - * Get item by index + * Get item by index. * * @param int $index + * * @return \PhpOffice\PhpWord\Element\AbstractContainer */ public function getItem($index) @@ -62,7 +63,7 @@ abstract class AbstractCollection * @param int $index * @param \PhpOffice\PhpWord\Element\AbstractContainer $item */ - public function setItem($index, $item) + public function setItem($index, $item): void { if (array_key_exists($index, $this->items)) { $this->items[$index] = $item; @@ -70,9 +71,10 @@ abstract class AbstractCollection } /** - * Add new item + * Add new item. * * @param \PhpOffice\PhpWord\Element\AbstractContainer $item + * * @return int */ public function addItem($item) @@ -84,7 +86,7 @@ abstract class AbstractCollection } /** - * Get item count + * Get item count. * * @return int */ diff --git a/PhpOffice/PhpWord/Collection/Bookmarks.php b/PhpOffice/PhpWord/Collection/Bookmarks.php old mode 100755 new mode 100644 index b5ffd5f..e7d9b4a --- a/PhpOffice/PhpWord/Collection/Bookmarks.php +++ b/PhpOffice/PhpWord/Collection/Bookmarks.php @@ -11,14 +11,14 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Collection; /** - * Bookmarks collection + * Bookmarks collection. * * @since 0.12.0 */ diff --git a/PhpOffice/PhpWord/Collection/Charts.php b/PhpOffice/PhpWord/Collection/Charts.php old mode 100755 new mode 100644 index aa807d1..bb63a13 --- a/PhpOffice/PhpWord/Collection/Charts.php +++ b/PhpOffice/PhpWord/Collection/Charts.php @@ -11,14 +11,14 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Collection; /** - * Charts collection + * Charts collection. * * @since 0.12.0 */ diff --git a/PhpOffice/PhpWord/Collection/Comments.php b/PhpOffice/PhpWord/Collection/Comments.php old mode 100755 new mode 100644 index b6c02d3..8c6b577 --- a/PhpOffice/PhpWord/Collection/Comments.php +++ b/PhpOffice/PhpWord/Collection/Comments.php @@ -11,14 +11,14 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Collection; /** - * Comments collection + * Comments collection. * * @since 0.12.0 */ diff --git a/PhpOffice/PhpWord/Collection/Endnotes.php b/PhpOffice/PhpWord/Collection/Endnotes.php old mode 100755 new mode 100644 index db01b40..362b258 --- a/PhpOffice/PhpWord/Collection/Endnotes.php +++ b/PhpOffice/PhpWord/Collection/Endnotes.php @@ -11,14 +11,14 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Collection; /** - * Endnotes collection + * Endnotes collection. * * @since 0.10.0 */ diff --git a/PhpOffice/PhpWord/Collection/Footnotes.php b/PhpOffice/PhpWord/Collection/Footnotes.php old mode 100755 new mode 100644 index a0a31ca..76eae3e --- a/PhpOffice/PhpWord/Collection/Footnotes.php +++ b/PhpOffice/PhpWord/Collection/Footnotes.php @@ -11,14 +11,14 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Collection; /** - * Footnotes collection + * Footnotes collection. * * @since 0.10.0 */ diff --git a/PhpOffice/PhpWord/Collection/Titles.php b/PhpOffice/PhpWord/Collection/Titles.php old mode 100755 new mode 100644 index 1ea58ec..7b79577 --- a/PhpOffice/PhpWord/Collection/Titles.php +++ b/PhpOffice/PhpWord/Collection/Titles.php @@ -11,14 +11,14 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Collection; /** - * Titles collection + * Titles collection. * * @since 0.10.0 */ diff --git a/PhpOffice/PhpWord/ComplexType/FootnoteProperties.php b/PhpOffice/PhpWord/ComplexType/FootnoteProperties.php old mode 100755 new mode 100644 index e42c9f9..2e7743c --- a/PhpOffice/PhpWord/ComplexType/FootnoteProperties.php +++ b/PhpOffice/PhpWord/ComplexType/FootnoteProperties.php @@ -11,16 +11,17 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\ComplexType; +use InvalidArgumentException; use PhpOffice\PhpWord\SimpleType\NumberFormat; /** - * Footnote properties + * Footnote properties. * * @see http://www.datypic.com/sc/ooxml/e-w_footnotePr-1.html */ @@ -36,35 +37,35 @@ final class FootnoteProperties const POSITION_DOC_END = 'docEnd'; /** - * Footnote Positioning Location + * Footnote Positioning Location. * * @var string */ private $pos; /** - * Footnote Numbering Format w:numFmt, one of PhpOffice\PhpWord\SimpleType\NumberFormat + * Footnote Numbering Format w:numFmt, one of PhpOffice\PhpWord\SimpleType\NumberFormat. * * @var string */ private $numFmt; /** - * Footnote and Endnote Numbering Starting Value + * Footnote and Endnote Numbering Starting Value. * * @var float */ private $numStart; /** - * Footnote and Endnote Numbering Restart Location + * Footnote and Endnote Numbering Restart Location. * * @var string */ private $numRestart; /** - * Get the Footnote Positioning Location + * Get the Footnote Positioning Location. * * @return string */ @@ -74,32 +75,32 @@ final class FootnoteProperties } /** - * Set the Footnote Positioning Location (pageBottom, beneathText, sectEnd, docEnd) + * Set the Footnote Positioning Location (pageBottom, beneathText, sectEnd, docEnd). * * @param string $pos - * @throws \InvalidArgumentException + * * @return self */ public function setPos($pos) { - $position = array( + $position = [ self::POSITION_PAGE_BOTTOM, self::POSITION_BENEATH_TEXT, self::POSITION_SECTION_END, self::POSITION_DOC_END, - ); + ]; if (in_array($pos, $position)) { $this->pos = $pos; } else { - throw new \InvalidArgumentException('Invalid value, on of ' . implode(', ', $position) . ' possible'); + throw new InvalidArgumentException('Invalid value, on of ' . implode(', ', $position) . ' possible'); } return $this; } /** - * Get the Footnote Numbering Format + * Get the Footnote Numbering Format. * * @return string */ @@ -109,9 +110,10 @@ final class FootnoteProperties } /** - * Set the Footnote Numbering Format + * Set the Footnote Numbering Format. * * @param string $numFmt One of NumberFormat + * * @return self */ public function setNumFmt($numFmt) @@ -123,7 +125,7 @@ final class FootnoteProperties } /** - * Get the Footnote Numbering Format + * Get the Footnote Numbering Format. * * @return float */ @@ -133,9 +135,10 @@ final class FootnoteProperties } /** - * Set the Footnote Numbering Format + * Set the Footnote Numbering Format. * * @param float $numStart + * * @return self */ public function setNumStart($numStart) @@ -146,7 +149,7 @@ final class FootnoteProperties } /** - * Get the Footnote and Endnote Numbering Starting Value + * Get the Footnote and Endnote Numbering Starting Value. * * @return string */ @@ -156,24 +159,24 @@ final class FootnoteProperties } /** - * Set the Footnote and Endnote Numbering Starting Value (continuous, eachSect, eachPage) + * Set the Footnote and Endnote Numbering Starting Value (continuous, eachSect, eachPage). * * @param string $numRestart - * @throws \InvalidArgumentException + * * @return self */ public function setNumRestart($numRestart) { - $restartNumbers = array( + $restartNumbers = [ self::RESTART_NUMBER_CONTINUOUS, self::RESTART_NUMBER_EACH_SECTION, self::RESTART_NUMBER_EACH_PAGE, - ); + ]; if (in_array($numRestart, $restartNumbers)) { $this->numRestart = $numRestart; } else { - throw new \InvalidArgumentException('Invalid value, on of ' . implode(', ', $restartNumbers) . ' possible'); + throw new InvalidArgumentException('Invalid value, on of ' . implode(', ', $restartNumbers) . ' possible'); } return $this; diff --git a/PhpOffice/PhpWord/ComplexType/ProofState.php b/PhpOffice/PhpWord/ComplexType/ProofState.php old mode 100755 new mode 100644 index 4f8dafe..37039cb --- a/PhpOffice/PhpWord/ComplexType/ProofState.php +++ b/PhpOffice/PhpWord/ComplexType/ProofState.php @@ -11,48 +11,50 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\ComplexType; +use InvalidArgumentException; + /** - * Spelling and Grammatical Checking State + * Spelling and Grammatical Checking State. * * @see http://www.datypic.com/sc/ooxml/e-w_proofState-1.html */ final class ProofState { /** - * Check Completed + * Check Completed. */ const CLEAN = 'clean'; /** - * Check Not Completed + * Check Not Completed. */ const DIRTY = 'dirty'; /** - * Spell Checking State + * Spell Checking State. * * @var string */ private $spelling; /** - * Grammatical Checking State + * Grammatical Checking State. * * @var string */ private $grammar; /** - * Set the Spell Checking State (dirty or clean) + * Set the Spell Checking State (dirty or clean). * * @param string $spelling - * @throws \InvalidArgumentException + * * @return self */ public function setSpelling($spelling) @@ -60,14 +62,14 @@ final class ProofState if ($spelling == self::CLEAN || $spelling == self::DIRTY) { $this->spelling = $spelling; } else { - throw new \InvalidArgumentException('Invalid value, dirty or clean possible'); + throw new InvalidArgumentException('Invalid value, dirty or clean possible'); } return $this; } /** - * Get the Spell Checking State + * Get the Spell Checking State. * * @return string */ @@ -77,10 +79,10 @@ final class ProofState } /** - * Set the Grammatical Checking State (dirty or clean) + * Set the Grammatical Checking State (dirty or clean). * * @param string $grammar - * @throws \InvalidArgumentException + * * @return self */ public function setGrammar($grammar) @@ -88,14 +90,14 @@ final class ProofState if ($grammar == self::CLEAN || $grammar == self::DIRTY) { $this->grammar = $grammar; } else { - throw new \InvalidArgumentException('Invalid value, dirty or clean possible'); + throw new InvalidArgumentException('Invalid value, dirty or clean possible'); } return $this; } /** - * Get the Grammatical Checking State + * Get the Grammatical Checking State. * * @return string */ diff --git a/PhpOffice/PhpWord/ComplexType/TblWidth.php b/PhpOffice/PhpWord/ComplexType/TblWidth.php old mode 100755 new mode 100644 index 0d1a241..6a9368f --- a/PhpOffice/PhpWord/ComplexType/TblWidth.php +++ b/PhpOffice/PhpWord/ComplexType/TblWidth.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ diff --git a/PhpOffice/PhpWord/ComplexType/TrackChangesView.php b/PhpOffice/PhpWord/ComplexType/TrackChangesView.php old mode 100755 new mode 100644 index 92ea05e..170c560 --- a/PhpOffice/PhpWord/ComplexType/TrackChangesView.php +++ b/PhpOffice/PhpWord/ComplexType/TrackChangesView.php @@ -11,56 +11,56 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\ComplexType; /** - * Visibility of Annotation Types + * Visibility of Annotation Types. * * @see http://www.datypic.com/sc/ooxml/e-w_revisionView-1.html */ final class TrackChangesView { /** - * Display Visual Indicator Of Markup Area + * Display Visual Indicator Of Markup Area. * * @var bool */ private $markup; /** - * Display Comments + * Display Comments. * * @var bool */ private $comments; /** - * Display Content Revisions + * Display Content Revisions. * * @var bool */ private $insDel; /** - * Display Formatting Revisions + * Display Formatting Revisions. * * @var bool */ private $formatting; /** - * Display Ink Annotations + * Display Ink Annotations. * * @var bool */ private $inkAnnotations; /** - * Get Display Visual Indicator Of Markup Area + * Get Display Visual Indicator Of Markup Area. * * @return bool True if markup is shown */ @@ -70,18 +70,18 @@ final class TrackChangesView } /** - * Set Display Visual Indicator Of Markup Area + * Set Display Visual Indicator Of Markup Area. * * @param bool $markup * Set to true to show markup */ - public function setMarkup($markup) + public function setMarkup($markup): void { $this->markup = $markup === null ? true : $markup; } /** - * Get Display Comments + * Get Display Comments. * * @return bool True if comments are shown */ @@ -91,18 +91,18 @@ final class TrackChangesView } /** - * Set Display Comments + * Set Display Comments. * * @param bool $comments * Set to true to show comments */ - public function setComments($comments) + public function setComments($comments): void { $this->comments = $comments === null ? true : $comments; } /** - * Get Display Content Revisions + * Get Display Content Revisions. * * @return bool True if content revisions are shown */ @@ -112,18 +112,18 @@ final class TrackChangesView } /** - * Set Display Content Revisions + * Set Display Content Revisions. * * @param bool $insDel * Set to true to show content revisions */ - public function setInsDel($insDel) + public function setInsDel($insDel): void { $this->insDel = $insDel === null ? true : $insDel; } /** - * Get Display Formatting Revisions + * Get Display Formatting Revisions. * * @return bool True if formatting revisions are shown */ @@ -133,18 +133,18 @@ final class TrackChangesView } /** - * Set Display Formatting Revisions + * Set Display Formatting Revisions. * - * @param bool|null $formatting + * @param null|bool $formatting * Set to true to show formatting revisions */ - public function setFormatting($formatting = null) + public function setFormatting($formatting = null): void { $this->formatting = $formatting === null ? true : $formatting; } /** - * Get Display Ink Annotations + * Get Display Ink Annotations. * * @return bool True if ink annotations are shown */ @@ -154,12 +154,12 @@ final class TrackChangesView } /** - * Set Display Ink Annotations + * Set Display Ink Annotations. * * @param bool $inkAnnotations * Set to true to show ink annotations */ - public function setInkAnnotations($inkAnnotations) + public function setInkAnnotations($inkAnnotations): void { $this->inkAnnotations = $inkAnnotations === null ? true : $inkAnnotations; } diff --git a/PhpOffice/PhpWord/Element/AbstractContainer.php b/PhpOffice/PhpWord/Element/AbstractContainer.php old mode 100755 new mode 100644 index 0c773db..726e204 --- a/PhpOffice/PhpWord/Element/AbstractContainer.php +++ b/PhpOffice/PhpWord/Element/AbstractContainer.php @@ -11,14 +11,17 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Element; +use BadMethodCallException; +use ReflectionClass; + /** - * Container abstract class + * Container abstract class. * * @method Text addText(string $text, mixed $fStyle = null, mixed $pStyle = null) * @method TextRun addTextRun(mixed $pStyle = null) @@ -44,7 +47,6 @@ namespace PhpOffice\PhpWord\Element; * @method Chart addChart(string $type, array $categories, array $values, array $style = null, $seriesName = null) * @method FormField addFormField(string $type, mixed $fStyle = null, mixed $pStyle = null) * @method SDT addSDT(string $type) - * * @method \PhpOffice\PhpWord\Element\OLEObject addObject(string $source, mixed $style = null) deprecated, use addOLEObject instead * * @since 0.10.0 @@ -52,21 +54,21 @@ namespace PhpOffice\PhpWord\Element; abstract class AbstractContainer extends AbstractElement { /** - * Elements collection + * Elements collection. * * @var \PhpOffice\PhpWord\Element\AbstractElement[] */ - protected $elements = array(); + protected $elements = []; /** - * Container type Section|Header|Footer|Footnote|Endnote|Cell|TextRun|TextBox|ListItemRun|TrackChange + * Container type Section|Header|Footer|Footnote|Endnote|Cell|TextRun|TextBox|ListItemRun|TrackChange. * * @var string */ protected $container; /** - * Magic method to catch all 'addElement' variation + * Magic method to catch all 'addElement' variation. * * This removes addText, addTextRun, etc. When adding new element, we have to * add the model in the class docblock with `@method`. @@ -75,18 +77,19 @@ abstract class AbstractContainer extends AbstractElement * * @param mixed $function * @param mixed $args + * * @return \PhpOffice\PhpWord\Element\AbstractElement */ public function __call($function, $args) { - $elements = array( + $elements = [ 'Text', 'TextRun', 'Bookmark', 'Link', 'PreserveText', 'TextBreak', 'ListItem', 'ListItemRun', 'Table', 'Image', 'Object', 'OLEObject', 'Footnote', 'Endnote', 'CheckBox', 'TextBox', 'Field', 'Line', 'Shape', 'Title', 'TOC', 'PageBreak', 'Chart', 'FormField', 'SDT', 'Comment', - ); - $functions = array(); + ]; + $functions = []; foreach ($elements as $element) { $functions['add' . strtolower($element)] = $element == 'Object' ? 'OLEObject' : $element; } @@ -99,17 +102,18 @@ abstract class AbstractContainer extends AbstractElement // Special case for TextBreak // @todo Remove the `$count` parameter in 1.0.0 to make this element similiar to other elements? if ($element == 'TextBreak') { - list($count, $fontStyle, $paragraphStyle) = array_pad($args, 3, null); + [$count, $fontStyle, $paragraphStyle] = array_pad($args, 3, null); if ($count === null) { $count = 1; } - for ($i = 1; $i <= $count; $i++) { + for ($i = 1; $i <= $count; ++$i) { $this->addElement($element, $fontStyle, $paragraphStyle); } } else { // All other elements array_unshift($args, $element); // Prepend element name to the beginning of args array - return call_user_func_array(array($this, 'addElement'), $args); + + return call_user_func_array([$this, 'addElement'], $args); } } @@ -117,11 +121,12 @@ abstract class AbstractContainer extends AbstractElement } /** - * Add element + * Add element. * * Each element has different number of parameters passed * * @param string $elementName + * * @return \PhpOffice\PhpWord\Element\AbstractElement */ protected function addElement($elementName) @@ -131,13 +136,13 @@ abstract class AbstractContainer extends AbstractElement // Get arguments $args = func_get_args(); - $withoutP = in_array($this->container, array('TextRun', 'Footnote', 'Endnote', 'ListItemRun', 'Field')); + $withoutP = in_array($this->container, ['TextRun', 'Footnote', 'Endnote', 'ListItemRun', 'Field']); if ($withoutP && ($elementName == 'Text' || $elementName == 'PreserveText')) { $args[3] = null; // Remove paragraph style for texts in textrun } // Create element using reflection - $reflection = new \ReflectionClass($elementClass); + $reflection = new ReflectionClass($elementClass); $elementArgs = $args; array_shift($elementArgs); // Shift the $elementName off the beginning of array @@ -155,7 +160,7 @@ abstract class AbstractContainer extends AbstractElement } /** - * Get all elements + * Get all elements. * * @return \PhpOffice\PhpWord\Element\AbstractElement[] */ @@ -165,10 +170,11 @@ abstract class AbstractContainer extends AbstractElement } /** - * Returns the element at the requested position + * Returns the element at the requested position. * * @param int $index - * @return \PhpOffice\PhpWord\Element\AbstractElement|null + * + * @return null|\PhpOffice\PhpWord\Element\AbstractElement */ public function getElement($index) { @@ -180,11 +186,11 @@ abstract class AbstractContainer extends AbstractElement } /** - * Removes the element at requested index + * Removes the element at requested index. * * @param int|\PhpOffice\PhpWord\Element\AbstractElement $toRemove */ - public function removeElement($toRemove) + public function removeElement($toRemove): void { if (is_int($toRemove) && array_key_exists($toRemove, $this->elements)) { unset($this->elements[$toRemove]); @@ -200,7 +206,7 @@ abstract class AbstractContainer extends AbstractElement } /** - * Count elements + * Count elements. * * @return int */ @@ -210,59 +216,58 @@ abstract class AbstractContainer extends AbstractElement } /** - * Check if a method is allowed for the current container + * Check if a method is allowed for the current container. * * @param string $method * - * @throws \BadMethodCallException * @return bool */ private function checkValidity($method) { - $generalContainers = array( + $generalContainers = [ 'Section', 'Header', 'Footer', 'Footnote', 'Endnote', 'Cell', 'TextRun', 'TextBox', 'ListItemRun', 'TrackChange', - ); + ]; - $validContainers = array( - 'Text' => $generalContainers, - 'Bookmark' => $generalContainers, - 'Link' => $generalContainers, - 'TextBreak' => $generalContainers, - 'Image' => $generalContainers, - 'OLEObject' => $generalContainers, - 'Field' => $generalContainers, - 'Line' => $generalContainers, - 'Shape' => $generalContainers, - 'FormField' => $generalContainers, - 'SDT' => $generalContainers, - 'TrackChange' => $generalContainers, - 'TextRun' => array('Section', 'Header', 'Footer', 'Cell', 'TextBox', 'TrackChange', 'ListItemRun'), - 'ListItem' => array('Section', 'Header', 'Footer', 'Cell', 'TextBox'), - 'ListItemRun' => array('Section', 'Header', 'Footer', 'Cell', 'TextBox'), - 'Table' => array('Section', 'Header', 'Footer', 'Cell', 'TextBox'), - 'CheckBox' => array('Section', 'Header', 'Footer', 'Cell', 'TextRun'), - 'TextBox' => array('Section', 'Header', 'Footer', 'Cell'), - 'Footnote' => array('Section', 'TextRun', 'Cell', 'ListItemRun'), - 'Endnote' => array('Section', 'TextRun', 'Cell'), - 'PreserveText' => array('Section', 'Header', 'Footer', 'Cell'), - 'Title' => array('Section', 'Cell'), - 'TOC' => array('Section'), - 'PageBreak' => array('Section'), - 'Chart' => array('Section', 'Cell'), - ); + $validContainers = [ + 'Text' => $generalContainers, + 'Bookmark' => $generalContainers, + 'Link' => $generalContainers, + 'TextBreak' => $generalContainers, + 'Image' => $generalContainers, + 'OLEObject' => $generalContainers, + 'Field' => $generalContainers, + 'Line' => $generalContainers, + 'Shape' => $generalContainers, + 'FormField' => $generalContainers, + 'SDT' => $generalContainers, + 'TrackChange' => $generalContainers, + 'TextRun' => ['Section', 'Header', 'Footer', 'Cell', 'TextBox', 'TrackChange', 'ListItemRun'], + 'ListItem' => ['Section', 'Header', 'Footer', 'Cell', 'TextBox'], + 'ListItemRun' => ['Section', 'Header', 'Footer', 'Cell', 'TextBox'], + 'Table' => ['Section', 'Header', 'Footer', 'Cell', 'TextBox'], + 'CheckBox' => ['Section', 'Header', 'Footer', 'Cell', 'TextRun'], + 'TextBox' => ['Section', 'Header', 'Footer', 'Cell'], + 'Footnote' => ['Section', 'TextRun', 'Cell', 'ListItemRun'], + 'Endnote' => ['Section', 'TextRun', 'Cell'], + 'PreserveText' => ['Section', 'Header', 'Footer', 'Cell'], + 'Title' => ['Section', 'Cell'], + 'TOC' => ['Section'], + 'PageBreak' => ['Section'], + 'Chart' => ['Section', 'Cell'], + ]; // Special condition, e.g. preservetext can only exists in cell when // the cell is located in header or footer - $validSubcontainers = array( - 'PreserveText' => array(array('Cell'), array('Header', 'Footer', 'Section')), - 'Footnote' => array(array('Cell', 'TextRun'), array('Section')), - 'Endnote' => array(array('Cell', 'TextRun'), array('Section')), - ); + $validSubcontainers = [ + 'PreserveText' => [['Cell'], ['Header', 'Footer', 'Section']], + 'Footnote' => [['Cell', 'TextRun'], ['Section']], + 'Endnote' => [['Cell', 'TextRun'], ['Section']], + ]; // Check if a method is valid for current container if (isset($validContainers[$method])) { if (!in_array($this->container, $validContainers[$method])) { - throw new \BadMethodCallException("Cannot add {$method} in {$this->container}."); + throw new BadMethodCallException("Cannot add {$method} in {$this->container}."); } } @@ -273,43 +278,11 @@ abstract class AbstractContainer extends AbstractElement $allowedDocParts = $rules[1]; foreach ($containers as $container) { if ($this->container == $container && !in_array($this->getDocPart(), $allowedDocParts)) { - throw new \BadMethodCallException("Cannot add {$method} in {$this->container}."); + throw new BadMethodCallException("Cannot add {$method} in {$this->container}."); } } } return true; } - - /** - * Create textrun element - * - * @deprecated 0.10.0 - * - * @param mixed $paragraphStyle - * - * @return \PhpOffice\PhpWord\Element\TextRun - * - * @codeCoverageIgnore - */ - public function createTextRun($paragraphStyle = null) - { - return $this->addTextRun($paragraphStyle); - } - - /** - * Create footnote element - * - * @deprecated 0.10.0 - * - * @param mixed $paragraphStyle - * - * @return \PhpOffice\PhpWord\Element\Footnote - * - * @codeCoverageIgnore - */ - public function createFootnote($paragraphStyle = null) - { - return $this->addFootnote($paragraphStyle); - } } diff --git a/PhpOffice/PhpWord/Element/AbstractElement.php b/PhpOffice/PhpWord/Element/AbstractElement.php old mode 100755 new mode 100644 index 46372b7..3b59d06 --- a/PhpOffice/PhpWord/Element/AbstractElement.php +++ b/PhpOffice/PhpWord/Element/AbstractElement.php @@ -11,38 +11,40 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Element; +use DateTime; +use InvalidArgumentException; use PhpOffice\PhpWord\Media; use PhpOffice\PhpWord\PhpWord; /** - * Element abstract class + * Element abstract class. * * @since 0.10.0 */ abstract class AbstractElement { /** - * PhpWord object + * PhpWord object. * * @var \PhpOffice\PhpWord\PhpWord */ protected $phpWord; /** - * Section Id + * Section Id. * * @var int */ protected $sectionId; /** - * Document part type: Section|Header|Footer|Footnote|Endnote + * Document part type: Section|Header|Footer|Footnote|Endnote. * * Used by textrun and cell container to determine where the element is * located because it will affect the availability of other element, @@ -53,7 +55,7 @@ abstract class AbstractElement protected $docPart = 'Section'; /** - * Document part Id + * Document part Id. * * For header and footer, this will be = ($sectionId - 1) * 3 + $index * because the max number of header/footer in every page is 3, i.e. @@ -64,28 +66,28 @@ abstract class AbstractElement protected $docPartId = 1; /** - * Index of element in the elements collection (start with 1) + * Index of element in the elements collection (start with 1). * * @var int */ protected $elementIndex = 1; /** - * Unique Id for element + * Unique Id for element. * * @var string */ protected $elementId; /** - * Relation Id + * Relation Id. * * @var int */ protected $relationId; /** - * Depth of table container nested level; Primarily used for RTF writer/reader + * Depth of table container nested level; Primarily used for RTF writer/reader. * * 0 = Not in a table; 1 = in a table; 2 = in a table inside another table, etc. * @@ -94,56 +96,56 @@ abstract class AbstractElement private $nestedLevel = 0; /** - * A reference to the parent + * A reference to the parent. * - * @var AbstractElement|null + * @var null|AbstractElement */ private $parent; /** - * changed element info + * changed element info. * * @var TrackChange */ private $trackChange; /** - * Parent container type + * Parent container type. * * @var string */ private $parentContainer; /** - * Has media relation flag; true for Link, Image, and Object + * Has media relation flag; true for Link, Image, and Object. * * @var bool */ protected $mediaRelation = false; /** - * Is part of collection; true for Title, Footnote, Endnote, Chart, and Comment + * Is part of collection; true for Title, Footnote, Endnote, Chart, and Comment. * * @var bool */ protected $collectionRelation = false; /** - * The start position for the linked comment + * The start position for the linked comment. * * @var Comment */ protected $commentRangeStart; /** - * The end position for the linked comment + * The end position for the linked comment. * * @var Comment */ protected $commentRangeEnd; /** - * Get PhpWord + * Get PhpWord. * * @return \PhpOffice\PhpWord\PhpWord */ @@ -157,13 +159,13 @@ abstract class AbstractElement * * @param \PhpOffice\PhpWord\PhpWord $phpWord */ - public function setPhpWord(PhpWord $phpWord = null) + public function setPhpWord(?PhpWord $phpWord = null): void { $this->phpWord = $phpWord; } /** - * Get section number + * Get section number. * * @return int */ @@ -178,14 +180,14 @@ abstract class AbstractElement * @param string $docPart * @param int $docPartId */ - public function setDocPart($docPart, $docPartId = 1) + public function setDocPart($docPart, $docPartId = 1): void { $this->docPart = $docPart; $this->docPartId = $docPartId; } /** - * Get doc part + * Get doc part. * * @return string */ @@ -195,7 +197,7 @@ abstract class AbstractElement } /** - * Get doc part Id + * Get doc part Id. * * @return int */ @@ -205,7 +207,7 @@ abstract class AbstractElement } /** - * Return media element (image, object, link) container name + * Return media element (image, object, link) container name. * * @return string section|headerx|footerx|footnote|endnote */ @@ -220,7 +222,7 @@ abstract class AbstractElement } /** - * Get element index + * Get element index. * * @return int */ @@ -234,13 +236,13 @@ abstract class AbstractElement * * @param int $value */ - public function setElementIndex($value) + public function setElementIndex($value): void { $this->elementIndex = $value; } /** - * Get element unique ID + * Get element unique ID. * * @return string */ @@ -252,13 +254,13 @@ abstract class AbstractElement /** * Set element unique ID from 6 first digit of md5. */ - public function setElementId() + public function setElementId(): void { - $this->elementId = substr(md5(rand()), 0, 6); + $this->elementId = substr(md5(mt_rand()), 0, 6); } /** - * Get relation Id + * Get relation Id. * * @return int */ @@ -272,13 +274,13 @@ abstract class AbstractElement * * @param int $value */ - public function setRelationId($value) + public function setRelationId($value): void { $this->relationId = $value; } /** - * Get nested level + * Get nested level. * * @return int */ @@ -288,7 +290,7 @@ abstract class AbstractElement } /** - * Get comment start + * Get comment start. * * @return Comment */ @@ -298,21 +300,19 @@ abstract class AbstractElement } /** - * Set comment start - * - * @param Comment $value + * Set comment start. */ - public function setCommentRangeStart(Comment $value) + public function setCommentRangeStart(Comment $value): void { if ($this instanceof Comment) { - throw new \InvalidArgumentException('Cannot set a Comment on a Comment'); + throw new InvalidArgumentException('Cannot set a Comment on a Comment'); } $this->commentRangeStart = $value; $this->commentRangeStart->setStartElement($this); } /** - * Get comment end + * Get comment end. * * @return Comment */ @@ -322,23 +322,21 @@ abstract class AbstractElement } /** - * Set comment end - * - * @param Comment $value + * Set comment end. */ - public function setCommentRangeEnd(Comment $value) + public function setCommentRangeEnd(Comment $value): void { if ($this instanceof Comment) { - throw new \InvalidArgumentException('Cannot set a Comment on a Comment'); + throw new InvalidArgumentException('Cannot set a Comment on a Comment'); } $this->commentRangeEnd = $value; $this->commentRangeEnd->setEndElement($this); } /** - * Get parent element + * Get parent element. * - * @return AbstractElement|null + * @return null|AbstractElement */ public function getParent() { @@ -346,13 +344,13 @@ abstract class AbstractElement } /** - * Set parent container + * Set parent container. * * Passed parameter should be a container, except for Table (contain Row) and Row (contain Cell) * * @param \PhpOffice\PhpWord\Element\AbstractElement $container */ - public function setParentContainer(self $container) + public function setParentContainer(self $container): void { $this->parentContainer = substr(get_class($container), strrpos(get_class($container), '\\') + 1); $this->parent = $container; @@ -360,7 +358,7 @@ abstract class AbstractElement // Set nested level $this->nestedLevel = $container->getNestedLevel(); if ($this->parentContainer == 'Cell') { - $this->nestedLevel++; + ++$this->nestedLevel; } // Set phpword @@ -376,18 +374,18 @@ abstract class AbstractElement } /** - * Set relation Id for media elements (link, image, object; legacy of OOXML) + * Set relation Id for media elements (link, image, object; legacy of OOXML). * * - Image element needs to be passed to Media object * - Icon needs to be set for Object element */ - private function setMediaRelation() + private function setMediaRelation(): void { if (!$this instanceof Link && !$this instanceof Image && !$this instanceof OLEObject) { return; } - $elementName = substr(get_class($this), strrpos(get_class($this), '\\') + 1); + $elementName = substr(static::class, strrpos(static::class, '\\') + 1); if ($elementName == 'OLEObject') { $elementName = 'Object'; } @@ -410,10 +408,10 @@ abstract class AbstractElement /** * Set relation Id for elements that will be registered in the Collection subnamespaces. */ - private function setCollectionRelation() + private function setCollectionRelation(): void { if ($this->collectionRelation === true && $this->phpWord instanceof PhpWord) { - $elementName = substr(get_class($this), strrpos(get_class($this), '\\') + 1); + $elementName = substr(static::class, strrpos(static::class, '\\') + 1); $addMethod = "add{$elementName}"; $rId = $this->phpWord->$addMethod($this); $this->setRelationId($rId); @@ -421,7 +419,7 @@ abstract class AbstractElement } /** - * Check if element is located in Section doc part (as opposed to Header/Footer) + * Check if element is located in Section doc part (as opposed to Header/Footer). * * @return bool */ @@ -431,16 +429,17 @@ abstract class AbstractElement } /** - * Set new style value + * Set new style value. * * @param mixed $styleObject Style object * @param mixed $styleValue Style value * @param bool $returnObject Always return object + * * @return mixed */ protected function setNewStyle($styleObject, $styleValue = null, $returnObject = false) { - if (!is_null($styleValue) && is_array($styleValue)) { + if (null !== $styleValue && is_array($styleValue)) { $styleObject->setStyleByArray($styleValue); $style = $styleObject; } else { @@ -451,17 +450,15 @@ abstract class AbstractElement } /** - * Sets the trackChange information - * - * @param TrackChange $trackChange + * Sets the trackChange information. */ - public function setTrackChange(TrackChange $trackChange) + public function setTrackChange(TrackChange $trackChange): void { $this->trackChange = $trackChange; } /** - * Gets the trackChange information + * Gets the trackChange information. * * @return TrackChange */ @@ -471,33 +468,32 @@ abstract class AbstractElement } /** - * Set changed + * Set changed. * * @param string $type INSERTED|DELETED * @param string $author - * @param null|int|\DateTime $date allways in UTC + * @param null|DateTime|int $date allways in UTC */ - public function setChangeInfo($type, $author, $date = null) + public function setChangeInfo($type, $author, $date = null): void { $this->trackChange = new TrackChange($type, $author, $date); } /** - * Set enum value + * Set enum value. * - * @param string|null $value + * @param null|string $value * @param string[] $enum - * @param string|null $default + * @param null|string $default * - * @throws \InvalidArgumentException - * @return string|null + * @return null|string * * @todo Merge with the same method in AbstractStyle */ - protected function setEnumVal($value = null, $enum = array(), $default = null) + protected function setEnumVal($value = null, $enum = [], $default = null) { if ($value !== null && trim($value) != '' && !empty($enum) && !in_array($value, $enum)) { - throw new \InvalidArgumentException("Invalid style value: {$value}"); + throw new InvalidArgumentException("Invalid style value: {$value}"); } elseif ($value === null || trim($value) == '') { $value = $default; } diff --git a/PhpOffice/PhpWord/Element/Bookmark.php b/PhpOffice/PhpWord/Element/Bookmark.php old mode 100755 new mode 100644 index 16b020d..4fe3d0f --- a/PhpOffice/PhpWord/Element/Bookmark.php +++ b/PhpOffice/PhpWord/Element/Bookmark.php @@ -11,45 +11,45 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Element; -use PhpOffice\Common\Text as CommonText; +use PhpOffice\PhpWord\Shared\Text as SharedText; /** - * Bookmark element + * Bookmark element. */ class Bookmark extends AbstractElement { /** - * Bookmark Name + * Bookmark Name. * * @var string */ private $name; /** - * Is part of collection + * Is part of collection. * * @var bool */ protected $collectionRelation = true; /** - * Create a new Bookmark Element + * Create a new Bookmark Element. * * @param string $name */ public function __construct($name = '') { - $this->name = CommonText::toUTF8($name); + $this->name = SharedText::toUTF8($name); } /** - * Get Bookmark name + * Get Bookmark name. * * @return string */ diff --git a/PhpOffice/PhpWord/Element/Cell.php b/PhpOffice/PhpWord/Element/Cell.php old mode 100755 new mode 100644 index 68f5df6..f07d246 --- a/PhpOffice/PhpWord/Element/Cell.php +++ b/PhpOffice/PhpWord/Element/Cell.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -20,7 +20,7 @@ namespace PhpOffice\PhpWord\Element; use PhpOffice\PhpWord\Style\Cell as CellStyle; /** - * Table cell element + * Table cell element. */ class Cell extends AbstractContainer { @@ -30,21 +30,21 @@ class Cell extends AbstractContainer protected $container = 'Cell'; /** - * Cell width + * Cell width. * * @var int */ - private $width = null; + private $width; /** - * Cell style + * Cell style. * * @var \PhpOffice\PhpWord\Style\Cell */ private $style; /** - * Create new instance + * Create new instance. * * @param int $width * @param array|\PhpOffice\PhpWord\Style\Cell $style @@ -56,7 +56,7 @@ class Cell extends AbstractContainer } /** - * Get cell style + * Get cell style. * * @return \PhpOffice\PhpWord\Style\Cell */ @@ -66,7 +66,7 @@ class Cell extends AbstractContainer } /** - * Get cell width + * Get cell width. * * @return int */ diff --git a/PhpOffice/PhpWord/Element/Chart.php b/PhpOffice/PhpWord/Element/Chart.php old mode 100755 new mode 100644 index 92152c8..f2277c4 --- a/PhpOffice/PhpWord/Element/Chart.php +++ b/PhpOffice/PhpWord/Element/Chart.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -20,42 +20,42 @@ namespace PhpOffice\PhpWord\Element; use PhpOffice\PhpWord\Style\Chart as ChartStyle; /** - * Chart element + * Chart element. * * @since 0.12.0 */ class Chart extends AbstractElement { /** - * Is part of collection + * Is part of collection. * * @var bool */ protected $collectionRelation = true; /** - * Type + * Type. * * @var string */ private $type = 'pie'; /** - * Series + * Series. * * @var array */ - private $series = array(); + private $series = []; /** - * Chart style + * Chart style. * * @var \PhpOffice\PhpWord\Style\Chart */ private $style; /** - * Create new instance + * Create new instance. * * @param string $type * @param array $categories @@ -71,7 +71,7 @@ class Chart extends AbstractElement } /** - * Get type + * Get type. * * @return string */ @@ -85,30 +85,30 @@ class Chart extends AbstractElement * * @param string $value */ - public function setType($value) + public function setType($value): void { - $enum = array('pie', 'doughnut', 'line', 'bar', 'stacked_bar', 'percent_stacked_bar', 'column', 'stacked_column', 'percent_stacked_column', 'area', 'radar', 'scatter'); + $enum = ['pie', 'doughnut', 'line', 'bar', 'stacked_bar', 'percent_stacked_bar', 'column', 'stacked_column', 'percent_stacked_column', 'area', 'radar', 'scatter']; $this->type = $this->setEnumVal($value, $enum, 'pie'); } /** - * Add series + * Add series. * * @param array $categories * @param array $values * @param null|mixed $name */ - public function addSeries($categories, $values, $name = null) + public function addSeries($categories, $values, $name = null): void { - $this->series[] = array( + $this->series[] = [ 'categories' => $categories, - 'values' => $values, - 'name' => $name, - ); + 'values' => $values, + 'name' => $name, + ]; } /** - * Get series + * Get series. * * @return array */ @@ -118,7 +118,7 @@ class Chart extends AbstractElement } /** - * Get chart style + * Get chart style. * * @return \PhpOffice\PhpWord\Style\Chart */ diff --git a/PhpOffice/PhpWord/Element/CheckBox.php b/PhpOffice/PhpWord/Element/CheckBox.php old mode 100755 new mode 100644 index f3e8717..80b17a8 --- a/PhpOffice/PhpWord/Element/CheckBox.php +++ b/PhpOffice/PhpWord/Element/CheckBox.php @@ -11,30 +11,30 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Element; -use PhpOffice\Common\Text as CommonText; +use PhpOffice\PhpWord\Shared\Text as SharedText; /** - * Check box element + * Check box element. * * @since 0.10.0 */ class CheckBox extends Text { /** - * Name content + * Name content. * * @var string */ private $name; /** - * Create new instance + * Create new instance. * * @param string $name * @param string $text @@ -48,20 +48,21 @@ class CheckBox extends Text } /** - * Set name content + * Set name content. * * @param string $name + * * @return self */ public function setName($name) { - $this->name = CommonText::toUTF8($name); + $this->name = SharedText::toUTF8($name); return $this; } /** - * Get name content + * Get name content. * * @return string */ diff --git a/PhpOffice/PhpWord/Element/Comment.php b/PhpOffice/PhpWord/Element/Comment.php old mode 100755 new mode 100644 index 96ad15e..6972f82 --- a/PhpOffice/PhpWord/Element/Comment.php +++ b/PhpOffice/PhpWord/Element/Comment.php @@ -11,51 +11,54 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Element; +use DateTime; + /** - * Comment element + * Comment element. + * * @see http://datypic.com/sc/ooxml/t-w_CT_Comment.html */ class Comment extends TrackChange { /** - * Initials + * Initials. * * @var string */ private $initials; /** - * The Element where this comment starts + * The Element where this comment starts. * * @var AbstractElement */ private $startElement; /** - * The Element where this comment ends + * The Element where this comment ends. * * @var AbstractElement */ private $endElement; /** - * Is part of collection + * Is part of collection. * * @var bool */ protected $collectionRelation = true; /** - * Create a new Comment Element + * Create a new Comment Element. * * @param string $author - * @param null|\DateTime $date + * @param null|DateTime $date * @param string $initials */ public function __construct($author, $date = null, $initials = null) @@ -65,7 +68,7 @@ class Comment extends TrackChange } /** - * Get Initials + * Get Initials. * * @return string */ @@ -75,11 +78,11 @@ class Comment extends TrackChange } /** - * Sets the element where this comment starts + * Sets the element where this comment starts. * * @param \PhpOffice\PhpWord\Element\AbstractElement $value */ - public function setStartElement(AbstractElement $value) + public function setStartElement(AbstractElement $value): void { $this->startElement = $value; if ($value->getCommentRangeStart() == null) { @@ -88,7 +91,7 @@ class Comment extends TrackChange } /** - * Get the element where this comment starts + * Get the element where this comment starts. * * @return \PhpOffice\PhpWord\Element\AbstractElement */ @@ -98,11 +101,11 @@ class Comment extends TrackChange } /** - * Sets the element where this comment ends + * Sets the element where this comment ends. * * @param \PhpOffice\PhpWord\Element\AbstractElement $value */ - public function setEndElement(AbstractElement $value) + public function setEndElement(AbstractElement $value): void { $this->endElement = $value; if ($value->getCommentRangeEnd() == null) { @@ -111,7 +114,7 @@ class Comment extends TrackChange } /** - * Get the element where this comment ends + * Get the element where this comment ends. * * @return \PhpOffice\PhpWord\Element\AbstractElement */ diff --git a/PhpOffice/PhpWord/Element/Endnote.php b/PhpOffice/PhpWord/Element/Endnote.php old mode 100755 new mode 100644 index b962719..2888fde --- a/PhpOffice/PhpWord/Element/Endnote.php +++ b/PhpOffice/PhpWord/Element/Endnote.php @@ -11,14 +11,14 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Element; /** - * Endnote element + * Endnote element. * * @since 0.10.0 */ @@ -30,9 +30,9 @@ class Endnote extends Footnote protected $container = 'Endnote'; /** - * Create new instance + * Create new instance. * - * @param string|array|\PhpOffice\PhpWord\Style\Paragraph $paragraphStyle + * @param array|\PhpOffice\PhpWord\Style\Paragraph|string $paragraphStyle */ public function __construct($paragraphStyle = null) { diff --git a/PhpOffice/PhpWord/Element/Field.php b/PhpOffice/PhpWord/Element/Field.php old mode 100755 new mode 100644 index 3d1503f..a912f3c --- a/PhpOffice/PhpWord/Element/Field.php +++ b/PhpOffice/PhpWord/Element/Field.php @@ -11,16 +11,17 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Element; +use InvalidArgumentException; use PhpOffice\PhpWord\Style\Font; /** - * Field element + * Field element. * * @since 0.11.0 * @see http://www.schemacentral.com/sc/ooxml/t-w_CT_SimpleField.html @@ -29,103 +30,104 @@ class Field extends AbstractElement { /** * Field properties and options. Depending on type, a field can have different properties - * and options + * and options. * * @var array */ - protected $fieldsArray = array( - 'PAGE' => array( - 'properties' => array( - 'format' => array('Arabic', 'ArabicDash', 'alphabetic', 'ALPHABETIC', 'roman', 'ROMAN'), - ), - 'options' => array('PreserveFormat'), - ), - 'NUMPAGES' => array( - 'properties' => array( - 'format' => array('Arabic', 'ArabicDash', 'CardText', 'DollarText', 'Ordinal', 'OrdText', - 'alphabetic', 'ALPHABETIC', 'roman', 'ROMAN', 'Caps', 'FirstCap', 'Lower', 'Upper', ), - 'numformat' => array('0', '0,00', '#.##0', '#.##0,00', '€ #.##0,00(€ #.##0,00)', '0%', '0,00%'), - ), - 'options' => array('PreserveFormat'), - ), - 'DATE' => array( - 'properties' => array( - 'dateformat' => array( - /* Generic formats */ + protected $fieldsArray = [ + 'PAGE' => [ + 'properties' => [ + 'format' => ['Arabic', 'ArabicDash', 'alphabetic', 'ALPHABETIC', 'roman', 'ROMAN'], + ], + 'options' => ['PreserveFormat'], + ], + 'NUMPAGES' => [ + 'properties' => [ + 'format' => ['Arabic', 'ArabicDash', 'CardText', 'DollarText', 'Ordinal', 'OrdText', + 'alphabetic', 'ALPHABETIC', 'roman', 'ROMAN', 'Caps', 'FirstCap', 'Lower', 'Upper', ], + 'numformat' => ['0', '0,00', '#.##0', '#.##0,00', '€ #.##0,00(€ #.##0,00)', '0%', '0,00%'], + ], + 'options' => ['PreserveFormat'], + ], + 'DATE' => [ + 'properties' => [ + 'dateformat' => [ + // Generic formats 'yyyy-MM-dd', 'yyyy-MM', 'MMM-yy', 'MMM-yyyy', 'h:mm am/pm', 'h:mm:ss am/pm', 'HH:mm', 'HH:mm:ss', - /* Day-Month-Year formats */ + // Day-Month-Year formats 'dddd d MMMM yyyy', 'd MMMM yyyy', 'd-MMM-yy', 'd MMM. yy', 'd-M-yy', 'd-M-yy h:mm', 'd-M-yy h:mm:ss', 'd-M-yy h:mm am/pm', 'd-M-yy h:mm:ss am/pm', 'd-M-yy HH:mm', 'd-M-yy HH:mm:ss', 'd/M/yy', 'd/M/yy h:mm', 'd/M/yy h:mm:ss', 'd/M/yy h:mm am/pm', 'd/M/yy h:mm:ss am/pm', 'd/M/yy HH:mm', 'd/M/yy HH:mm:ss', 'd-M-yyyy', 'd-M-yyyy h:mm', 'd-M-yyyy h:mm:ss', 'd-M-yyyy h:mm am/pm', 'd-M-yyyy h:mm:ss am/pm', 'd-M-yyyy HH:mm', 'd-M-yyyy HH:mm:ss', 'd/M/yyyy', 'd/M/yyyy h:mm', 'd/M/yyyy h:mm:ss', 'd/M/yyyy h:mm am/pm', 'd/M/yyyy h:mm:ss am/pm', 'd/M/yyyy HH:mm', 'd/M/yyyy HH:mm:ss', - /* Month-Day-Year formats */ + // Month-Day-Year formats 'dddd, MMMM d yyyy', 'MMMM d yyyy', 'MMM-d-yy', 'MMM. d yy', 'M-d-yy', 'M-d-yy h:mm', 'M-d-yy h:mm:ss', 'M-d-yy h:mm am/pm', 'M-d-yy h:mm:ss am/pm', 'M-d-yy HH:mm', 'M-d-yy HH:mm:ss', 'M/d/yy', 'M/d/yy h:mm', 'M/d/yy h:mm:ss', 'M/d/yy h:mm am/pm', 'M/d/yy h:mm:ss am/pm', 'M/d/yy HH:mm', 'M/d/yy HH:mm:ss', 'M-d-yyyy', 'M-d-yyyy h:mm', 'M-d-yyyy h:mm:ss', 'M-d-yyyy h:mm am/pm', 'M-d-yyyy h:mm:ss am/pm', 'M-d-yyyy HH:mm', 'M-d-yyyy HH:mm:ss', 'M/d/yyyy', 'M/d/yyyy h:mm', 'M/d/yyyy h:mm:ss', 'M/d/yyyy h:mm am/pm', 'M/d/yyyy h:mm:ss am/pm', 'M/d/yyyy HH:mm', 'M/d/yyyy HH:mm:ss', - ), - ), - 'options' => array('PreserveFormat', 'LunarCalendar', 'SakaEraCalendar', 'LastUsedFormat'), - ), - 'MACROBUTTON' => array( - 'properties' => array('macroname' => ''), - ), - 'XE' => array( - 'properties' => array(), - 'options' => array('Bold', 'Italic'), - ), - 'INDEX' => array( - 'properties' => array(), - 'options' => array('PreserveFormat'), - ), - 'STYLEREF' => array( - 'properties' => array('StyleIdentifier' => ''), - 'options' => array('PreserveFormat'), - ), - ); + ], + ], + 'options' => ['PreserveFormat', 'LunarCalendar', 'SakaEraCalendar', 'LastUsedFormat'], + ], + 'MACROBUTTON' => [ + 'properties' => ['macroname' => ''], + ], + 'XE' => [ + 'properties' => [], + 'options' => ['Bold', 'Italic'], + ], + 'INDEX' => [ + 'properties' => [], + 'options' => ['PreserveFormat'], + ], + 'STYLEREF' => [ + 'properties' => ['StyleIdentifier' => ''], + 'options' => ['PreserveFormat'], + ], + ]; /** - * Field type + * Field type. * * @var string */ protected $type; /** - * Field text + * Field text. * - * @var TextRun|string + * @var string|TextRun */ protected $text; /** - * Field properties + * Field properties. * * @var array */ - protected $properties = array(); + protected $properties = []; /** - * Field options + * Field options. * * @var array */ - protected $options = array(); + protected $options = []; /** - * Font style + * Font style. * - * @var string|\PhpOffice\PhpWord\Style\Font + * @var \PhpOffice\PhpWord\Style\Font|string */ protected $fontStyle; /** - * Set Font style + * Set Font style. * - * @param string|array|\PhpOffice\PhpWord\Style\Font $style - * @return string|\PhpOffice\PhpWord\Style\Font + * @param array|\PhpOffice\PhpWord\Style\Font|string $style + * + * @return \PhpOffice\PhpWord\Style\Font|string */ public function setFontStyle($style = null) { @@ -144,9 +146,9 @@ class Field extends AbstractElement } /** - * Get Font style + * Get Font style. * - * @return string|\PhpOffice\PhpWord\Style\Font + * @return \PhpOffice\PhpWord\Style\Font|string */ public function getFontStyle() { @@ -154,15 +156,15 @@ class Field extends AbstractElement } /** - * Create a new Field Element + * Create a new Field Element. * * @param string $type * @param array $properties * @param array $options - * @param TextRun|string|null $text - * @param string|array|\PhpOffice\PhpWord\Style\Font $fontStyle + * @param null|string|TextRun $text + * @param array|\PhpOffice\PhpWord\Style\Font|string $fontStyle */ - public function __construct($type = null, $properties = array(), $options = array(), $text = null, $fontStyle = null) + public function __construct($type = null, $properties = [], $options = [], $text = null, $fontStyle = null) { $this->setType($type); $this->setProperties($properties); @@ -172,11 +174,10 @@ class Field extends AbstractElement } /** - * Set Field type + * Set Field type. * * @param string $type * - * @throws \InvalidArgumentException * @return string */ public function setType($type = null) @@ -185,7 +186,7 @@ class Field extends AbstractElement if (isset($this->fieldsArray[$type])) { $this->type = $type; } else { - throw new \InvalidArgumentException("Invalid type '$type'"); + throw new InvalidArgumentException("Invalid type '$type'"); } } @@ -193,7 +194,7 @@ class Field extends AbstractElement } /** - * Get Field type + * Get Field type. * * @return string */ @@ -203,19 +204,18 @@ class Field extends AbstractElement } /** - * Set Field properties + * Set Field properties. * * @param array $properties * - * @throws \InvalidArgumentException * @return self */ - public function setProperties($properties = array()) + public function setProperties($properties = []) { if (is_array($properties)) { foreach (array_keys($properties) as $propkey) { if (!(isset($this->fieldsArray[$this->type]['properties'][$propkey]))) { - throw new \InvalidArgumentException("Invalid property '$propkey'"); + throw new InvalidArgumentException("Invalid property '$propkey'"); } } $this->properties = array_merge($this->properties, $properties); @@ -225,7 +225,7 @@ class Field extends AbstractElement } /** - * Get Field properties + * Get Field properties. * * @return array */ @@ -235,19 +235,18 @@ class Field extends AbstractElement } /** - * Set Field options + * Set Field options. * * @param array $options * - * @throws \InvalidArgumentException * @return self */ - public function setOptions($options = array()) + public function setOptions($options = []) { if (is_array($options)) { foreach (array_keys($options) as $optionkey) { if (!(isset($this->fieldsArray[$this->type]['options'][$optionkey])) && substr($optionkey, 0, 1) !== '\\') { - throw new \InvalidArgumentException("Invalid option '$optionkey', possible values are " . implode(', ', $this->fieldsArray[$this->type]['options'])); + throw new InvalidArgumentException("Invalid option '$optionkey', possible values are " . implode(', ', $this->fieldsArray[$this->type]['options'])); } } $this->options = array_merge($this->options, $options); @@ -257,7 +256,7 @@ class Field extends AbstractElement } /** - * Get Field properties + * Get Field properties. * * @return array */ @@ -267,11 +266,10 @@ class Field extends AbstractElement } /** - * Set Field text + * Set Field text. * * @param string|TextRun $text * - * @throws \InvalidArgumentException * @return null|string|TextRun */ public function setText($text = null) @@ -280,7 +278,7 @@ class Field extends AbstractElement if (is_string($text) || $text instanceof TextRun) { $this->text = $text; } else { - throw new \InvalidArgumentException('Invalid text'); + throw new InvalidArgumentException('Invalid text'); } } @@ -288,7 +286,7 @@ class Field extends AbstractElement } /** - * Get Field text + * Get Field text. * * @return string|TextRun */ diff --git a/PhpOffice/PhpWord/Element/Footer.php b/PhpOffice/PhpWord/Element/Footer.php old mode 100755 new mode 100644 index 0290d7c..a9c48f7 --- a/PhpOffice/PhpWord/Element/Footer.php +++ b/PhpOffice/PhpWord/Element/Footer.php @@ -11,21 +11,22 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Element; /** - * Footer element + * Footer element. */ class Footer extends AbstractContainer { /** - * Header/footer types constants + * Header/footer types constants. * * @var string + * * @see http://www.datypic.com/sc/ooxml/t-w_ST_HdrFtr.html Header or Footer Type */ const AUTO = 'default'; // default and odd pages @@ -38,14 +39,14 @@ class Footer extends AbstractContainer protected $container = 'Footer'; /** - * Header type + * Header type. * * @var string */ protected $type = self::AUTO; /** - * Create new instance + * Create new instance. * * @param int $sectionId * @param int $containerId @@ -65,18 +66,19 @@ class Footer extends AbstractContainer * * @param string $value */ - public function setType($value = self::AUTO) + public function setType($value = self::AUTO): void { - if (!in_array($value, array(self::AUTO, self::FIRST, self::EVEN))) { + if (!in_array($value, [self::AUTO, self::FIRST, self::EVEN])) { $value = self::AUTO; } $this->type = $value; } /** - * Get type + * Get type. * * @return string + * * @since 0.10.0 */ public function getType() @@ -85,7 +87,7 @@ class Footer extends AbstractContainer } /** - * Reset type to default + * Reset type to default. * * @return string */ @@ -95,7 +97,7 @@ class Footer extends AbstractContainer } /** - * First page only header + * First page only header. * * @return string */ @@ -105,7 +107,7 @@ class Footer extends AbstractContainer } /** - * Even numbered pages only + * Even numbered pages only. * * @return string */ diff --git a/PhpOffice/PhpWord/Element/Footnote.php b/PhpOffice/PhpWord/Element/Footnote.php old mode 100755 new mode 100644 index 90aabcc..2c1bd26 --- a/PhpOffice/PhpWord/Element/Footnote.php +++ b/PhpOffice/PhpWord/Element/Footnote.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -27,23 +27,23 @@ class Footnote extends AbstractContainer protected $container = 'Footnote'; /** - * Paragraph style + * Paragraph style. * - * @var string|\PhpOffice\PhpWord\Style\Paragraph + * @var \PhpOffice\PhpWord\Style\Paragraph|string */ protected $paragraphStyle; /** - * Is part of collection + * Is part of collection. * * @var bool */ protected $collectionRelation = true; /** - * Create new instance + * Create new instance. * - * @param string|array|\PhpOffice\PhpWord\Style\Paragraph $paragraphStyle + * @param array|\PhpOffice\PhpWord\Style\Paragraph|string $paragraphStyle */ public function __construct($paragraphStyle = null) { @@ -52,38 +52,12 @@ class Footnote extends AbstractContainer } /** - * Get paragraph style + * Get paragraph style. * - * @return string|\PhpOffice\PhpWord\Style\Paragraph + * @return \PhpOffice\PhpWord\Style\Paragraph|string */ public function getParagraphStyle() { return $this->paragraphStyle; } - - /** - * Get Footnote Reference ID - * - * @deprecated 0.10.0 - * @codeCoverageIgnore - * - * @return int - */ - public function getReferenceId() - { - return $this->getRelationId(); - } - - /** - * Set Footnote Reference ID - * - * @deprecated 0.10.0 - * @codeCoverageIgnore - * - * @param int $rId - */ - public function setReferenceId($rId) - { - $this->setRelationId($rId); - } } diff --git a/PhpOffice/PhpWord/Element/FormField.php b/PhpOffice/PhpWord/Element/FormField.php old mode 100755 new mode 100644 index f937df5..23cded7 --- a/PhpOffice/PhpWord/Element/FormField.php +++ b/PhpOffice/PhpWord/Element/FormField.php @@ -11,14 +11,14 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Element; /** - * Form field element + * Form field element. * * @since 0.12.0 * @see http://www.datypic.com/sc/ooxml/t-w_CT_FFData.html @@ -26,46 +26,46 @@ namespace PhpOffice\PhpWord\Element; class FormField extends Text { /** - * Form field type: textinput|checkbox|dropdown + * Form field type: textinput|checkbox|dropdown. * * @var string */ private $type = 'textinput'; /** - * Form field name + * Form field name. * - * @var string|bool|int + * @var bool|int|string */ private $name; /** - * Default value + * Default value. * * - TextInput: string * - CheckBox: bool * - DropDown: int Index of entries (zero based) * - * @var string|bool|int + * @var bool|int|string */ private $default; /** - * Value + * Value. * - * @var string|bool|int + * @var bool|int|string */ private $value; /** - * Dropdown entries + * Dropdown entries. * * @var array */ - private $entries = array(); + private $entries = []; /** - * Create new instance + * Create new instance. * * @param string $type * @param mixed $fontStyle @@ -78,7 +78,7 @@ class FormField extends Text } /** - * Get type + * Get type. * * @return string */ @@ -88,21 +88,22 @@ class FormField extends Text } /** - * Set type + * Set type. * * @param string $value + * * @return self */ public function setType($value) { - $enum = array('textinput', 'checkbox', 'dropdown'); + $enum = ['textinput', 'checkbox', 'dropdown']; $this->type = $this->setEnumVal($value, $enum, $this->type); return $this; } /** - * Get name + * Get name. * * @return string */ @@ -112,9 +113,10 @@ class FormField extends Text } /** - * Set name + * Set name. + * + * @param bool|int|string $value * - * @param string|bool|int $value * @return self */ public function setName($value) @@ -125,9 +127,9 @@ class FormField extends Text } /** - * Get default + * Get default. * - * @return string|bool|int + * @return bool|int|string */ public function getDefault() { @@ -135,9 +137,10 @@ class FormField extends Text } /** - * Set default + * Set default. + * + * @param bool|int|string $value * - * @param string|bool|int $value * @return self */ public function setDefault($value) @@ -148,9 +151,9 @@ class FormField extends Text } /** - * Get value + * Get value. * - * @return string|bool|int + * @return bool|int|string */ public function getValue() { @@ -158,9 +161,10 @@ class FormField extends Text } /** - * Set value + * Set value. + * + * @param bool|int|string $value * - * @param string|bool|int $value * @return self */ public function setValue($value) @@ -171,7 +175,7 @@ class FormField extends Text } /** - * Get entries + * Get entries. * * @return array */ @@ -181,9 +185,10 @@ class FormField extends Text } /** - * Set entries + * Set entries. * * @param array $value + * * @return self */ public function setEntries($value) diff --git a/PhpOffice/PhpWord/Element/Header.php b/PhpOffice/PhpWord/Element/Header.php old mode 100755 new mode 100644 index 8a01946..9a87241 --- a/PhpOffice/PhpWord/Element/Header.php +++ b/PhpOffice/PhpWord/Element/Header.php @@ -11,14 +11,14 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Element; /** - * Header element + * Header element. */ class Header extends Footer { @@ -28,10 +28,11 @@ class Header extends Footer protected $container = 'Header'; /** - * Add a Watermark Element + * Add a Watermark Element. * * @param string $src * @param mixed $style + * * @return Image */ public function addWatermark($src, $style = null) diff --git a/PhpOffice/PhpWord/Element/Image.php b/PhpOffice/PhpWord/Element/Image.php old mode 100755 new mode 100644 index bae87ff..15c6997 --- a/PhpOffice/PhpWord/Element/Image.php +++ b/PhpOffice/PhpWord/Element/Image.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -25,12 +25,12 @@ use PhpOffice\PhpWord\Shared\ZipArchive; use PhpOffice\PhpWord\Style\Image as ImageStyle; /** - * Image element + * Image element. */ class Image extends AbstractElement { /** - * Image source type constants + * Image source type constants. */ const SOURCE_LOCAL = 'local'; // Local images const SOURCE_GD = 'gd'; // Generated using GD @@ -38,106 +38,103 @@ class Image extends AbstractElement const SOURCE_STRING = 'string'; // Image from string /** - * Image source + * Image source. * * @var string */ private $source; /** - * Source type: local|gd|archive + * Source type: local|gd|archive. * * @var string */ private $sourceType; /** - * Image style + * Image style. * * @var ImageStyle */ private $style; /** - * Is watermark + * Is watermark. * * @var bool */ private $watermark; /** - * Name of image + * Name of image. * * @var string */ private $name; /** - * Image type + * Image type. * * @var string */ private $imageType; /** - * Image create function + * Image create function. * * @var string */ private $imageCreateFunc; /** - * Image function + * Image function. * * @var string */ private $imageFunc; /** - * Image extension + * Image extension. * * @var string */ private $imageExtension; /** - * Is memory image + * Is memory image. * * @var bool */ private $memoryImage; /** - * Image target file name + * Image target file name. * * @var string */ private $target; /** - * Image media index + * Image media index. * * @var int */ private $mediaIndex; /** - * Has media relation flag; true for Link, Image, and Object + * Has media relation flag; true for Link, Image, and Object. * * @var bool */ protected $mediaRelation = true; /** - * Create new image element + * Create new image element. * * @param string $source * @param mixed $style * @param bool $watermark * @param string $name - * - * @throws \PhpOffice\PhpWord\Exception\InvalidImageException - * @throws \PhpOffice\PhpWord\Exception\UnsupportedImageTypeException */ public function __construct($source, $style = null, $watermark = false, $name = null) { @@ -150,7 +147,7 @@ class Image extends AbstractElement } /** - * Get Image style + * Get Image style. * * @return ImageStyle */ @@ -160,7 +157,7 @@ class Image extends AbstractElement } /** - * Get image source + * Get image source. * * @return string */ @@ -170,7 +167,7 @@ class Image extends AbstractElement } /** - * Get image source type + * Get image source type. * * @return string */ @@ -180,17 +177,17 @@ class Image extends AbstractElement } /** - * Sets the image name + * Sets the image name. * * @param string $value */ - public function setName($value) + public function setName($value): void { $this->name = $value; } /** - * Get image name + * Get image name. * * @return null|string */ @@ -200,7 +197,7 @@ class Image extends AbstractElement } /** - * Get image media ID + * Get image media ID. * * @return string */ @@ -210,7 +207,7 @@ class Image extends AbstractElement } /** - * Get is watermark + * Get is watermark. * * @return bool */ @@ -220,17 +217,17 @@ class Image extends AbstractElement } /** - * Set is watermark + * Set is watermark. * * @param bool $value */ - public function setIsWatermark($value) + public function setIsWatermark($value): void { $this->watermark = $value; } /** - * Get image type + * Get image type. * * @return string */ @@ -240,7 +237,7 @@ class Image extends AbstractElement } /** - * Get image create function + * Get image create function. * * @return string */ @@ -250,7 +247,7 @@ class Image extends AbstractElement } /** - * Get image function + * Get image function. * * @return string */ @@ -260,7 +257,7 @@ class Image extends AbstractElement } /** - * Get image extension + * Get image extension. * * @return string */ @@ -270,7 +267,7 @@ class Image extends AbstractElement } /** - * Get is memory image + * Get is memory image. * * @return bool */ @@ -280,7 +277,7 @@ class Image extends AbstractElement } /** - * Get target file name + * Get target file name. * * @return string */ @@ -294,13 +291,13 @@ class Image extends AbstractElement * * @param string $value */ - public function setTarget($value) + public function setTarget($value): void { $this->target = $value; } /** - * Get media index + * Get media index. * * @return int */ @@ -314,16 +311,18 @@ class Image extends AbstractElement * * @param int $value */ - public function setMediaIndex($value) + public function setMediaIndex($value): void { $this->mediaIndex = $value; } /** - * Get image string data + * Get image string data. * * @param bool $base64 - * @return string|null + * + * @return null|string + * * @since 0.11.0 */ public function getImageStringData($base64 = false) @@ -338,7 +337,7 @@ class Image extends AbstractElement // Return null if not found if ($this->sourceType == self::SOURCE_ARCHIVE) { $source = substr($source, 6); - list($zipFilename, $imageFilename) = explode('#', $source); + [$zipFilename, $imageFilename] = explode('#', $source); $zip = new ZipArchive(); if ($zip->open($zipFilename) !== false) { @@ -398,11 +397,8 @@ class Image extends AbstractElement /** * Check memory image, supported type, image functions, and proportional width/height. - * - * @throws \PhpOffice\PhpWord\Exception\InvalidImageException - * @throws \PhpOffice\PhpWord\Exception\UnsupportedImageTypeException */ - private function checkImage() + private function checkImage(): void { $this->setSourceType(); @@ -410,19 +406,19 @@ class Image extends AbstractElement if ($this->sourceType == self::SOURCE_ARCHIVE) { $imageData = $this->getArchiveImageSize($this->source); } elseif ($this->sourceType == self::SOURCE_STRING) { - $imageData = $this->getStringImageSize($this->source); + $imageData = @getimagesizefromstring($this->source); } else { $imageData = @getimagesize($this->source); } if (!is_array($imageData)) { throw new InvalidImageException(sprintf('Invalid image: %s', $this->source)); } - list($actualWidth, $actualHeight, $imageType) = $imageData; + [$actualWidth, $actualHeight, $imageType] = $imageData; // Check image type support - $supportedTypes = array(IMAGETYPE_JPEG, IMAGETYPE_GIF, IMAGETYPE_PNG); + $supportedTypes = [IMAGETYPE_JPEG, IMAGETYPE_GIF, IMAGETYPE_PNG]; if ($this->sourceType != self::SOURCE_GD && $this->sourceType != self::SOURCE_STRING) { - $supportedTypes = array_merge($supportedTypes, array(IMAGETYPE_BMP, IMAGETYPE_TIFF_II, IMAGETYPE_TIFF_MM)); + $supportedTypes = array_merge($supportedTypes, [IMAGETYPE_BMP, IMAGETYPE_TIFF_II, IMAGETYPE_TIFF_MM]); } if (!in_array($imageType, $supportedTypes)) { throw new UnsupportedImageTypeException(); @@ -437,7 +433,7 @@ class Image extends AbstractElement /** * Set source type. */ - private function setSourceType() + private function setSourceType(): void { if (stripos(strrev($this->source), strrev('.php')) === 0) { $this->memoryImage = true; @@ -454,7 +450,7 @@ class Image extends AbstractElement } else { $this->sourceType = self::SOURCE_GD; } - } elseif (@file_exists($this->source)) { + } elseif ((strpos($this->source, chr(0)) === false) && @file_exists($this->source)) { $this->memoryImage = false; $this->sourceType = self::SOURCE_LOCAL; } else { @@ -464,21 +460,19 @@ class Image extends AbstractElement } /** - * Get image size from archive + * Get image size from archive. * * @since 0.12.0 Throws CreateTemporaryFileException. * * @param string $source * - * @throws \PhpOffice\PhpWord\Exception\CreateTemporaryFileException - * - * @return array|null + * @return null|array */ private function getArchiveImageSize($source) { $imageData = null; $source = substr($source, 6); - list($zipFilename, $imageFilename) = explode('#', $source); + [$zipFilename, $imageFilename] = explode('#', $source); $tempFilename = tempnam(Settings::getTempDir(), 'PHPWordImage'); if (false === $tempFilename) { @@ -501,55 +495,40 @@ class Image extends AbstractElement return $imageData; } - /** - * get image size from string - * - * @param string $source - * - * @codeCoverageIgnore this method is just a replacement for getimagesizefromstring which exists only as of PHP 5.4 - */ - private function getStringImageSize($source) - { - $result = false; - if (!function_exists('getimagesizefromstring')) { - $uri = 'data://application/octet-stream;base64,' . base64_encode($source); - $result = @getimagesize($uri); - } else { - $result = @getimagesizefromstring($source); - } - - return $result; - } - /** * Set image functions and extensions. */ - private function setFunctions() + private function setFunctions(): void { switch ($this->imageType) { case 'image/png': $this->imageCreateFunc = $this->sourceType == self::SOURCE_STRING ? 'imagecreatefromstring' : 'imagecreatefrompng'; $this->imageFunc = 'imagepng'; $this->imageExtension = 'png'; + break; case 'image/gif': $this->imageCreateFunc = $this->sourceType == self::SOURCE_STRING ? 'imagecreatefromstring' : 'imagecreatefromgif'; $this->imageFunc = 'imagegif'; $this->imageExtension = 'gif'; + break; case 'image/jpeg': case 'image/jpg': $this->imageCreateFunc = $this->sourceType == self::SOURCE_STRING ? 'imagecreatefromstring' : 'imagecreatefromjpeg'; $this->imageFunc = 'imagejpeg'; $this->imageExtension = 'jpg'; + break; case 'image/bmp': case 'image/x-ms-bmp': $this->imageType = 'image/bmp'; $this->imageExtension = 'bmp'; + break; case 'image/tiff': $this->imageExtension = 'tif'; + break; } } @@ -560,7 +539,7 @@ class Image extends AbstractElement * @param int $actualWidth * @param int $actualHeight */ - private function setProportionalSize($actualWidth, $actualHeight) + private function setProportionalSize($actualWidth, $actualHeight): void { $styleWidth = $this->style->getWidth(); $styleHeight = $this->style->getHeight(); @@ -575,28 +554,4 @@ class Image extends AbstractElement } } } - - /** - * Get is watermark - * - * @deprecated 0.10.0 - * - * @codeCoverageIgnore - */ - public function getIsWatermark() - { - return $this->isWatermark(); - } - - /** - * Get is memory image - * - * @deprecated 0.10.0 - * - * @codeCoverageIgnore - */ - public function getIsMemImage() - { - return $this->isMemImage(); - } } diff --git a/PhpOffice/PhpWord/Element/Line.php b/PhpOffice/PhpWord/Element/Line.php old mode 100755 new mode 100644 index 7e40b94..7659a11 --- a/PhpOffice/PhpWord/Element/Line.php +++ b/PhpOffice/PhpWord/Element/Line.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -20,19 +20,19 @@ namespace PhpOffice\PhpWord\Element; use PhpOffice\PhpWord\Style\Line as LineStyle; /** - * Line element + * Line element. */ class Line extends AbstractElement { /** - * Line style + * Line style. * * @var \PhpOffice\PhpWord\Style\Line */ private $style; /** - * Create new line element + * Create new line element. * * @param mixed $style */ @@ -42,7 +42,7 @@ class Line extends AbstractElement } /** - * Get line style + * Get line style. * * @return \PhpOffice\PhpWord\Style\Line */ diff --git a/PhpOffice/PhpWord/Element/Link.php b/PhpOffice/PhpWord/Element/Link.php old mode 100755 new mode 100644 index 2bec32d..0e6ada4 --- a/PhpOffice/PhpWord/Element/Link.php +++ b/PhpOffice/PhpWord/Element/Link.php @@ -11,65 +11,65 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Element; -use PhpOffice\Common\Text as CommonText; +use PhpOffice\PhpWord\Shared\Text as SharedText; use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWord\Style\Paragraph; /** - * Link element + * Link element. */ class Link extends AbstractElement { /** - * Link source + * Link source. * * @var string */ private $source; /** - * Link text + * Link text. * * @var string */ private $text; /** - * Font style + * Font style. * - * @var string|\PhpOffice\PhpWord\Style\Font + * @var \PhpOffice\PhpWord\Style\Font|string */ private $fontStyle; /** - * Paragraph style + * Paragraph style. * - * @var string|\PhpOffice\PhpWord\Style\Paragraph + * @var \PhpOffice\PhpWord\Style\Paragraph|string */ private $paragraphStyle; /** - * Has media relation flag; true for Link, Image, and Object + * Has media relation flag; true for Link, Image, and Object. * * @var bool */ protected $mediaRelation = true; /** - * Has internal flag - anchor to internal bookmark + * Has internal flag - anchor to internal bookmark. * * @var bool */ protected $internal = false; /** - * Create a new Link Element + * Create a new Link Element. * * @param string $source * @param string $text @@ -79,15 +79,15 @@ class Link extends AbstractElement */ public function __construct($source, $text = null, $fontStyle = null, $paragraphStyle = null, $internal = false) { - $this->source = CommonText::toUTF8($source); - $this->text = is_null($text) ? $this->source : CommonText::toUTF8($text); + $this->source = SharedText::toUTF8($source); + $this->text = null === $text ? $this->source : SharedText::toUTF8($text); $this->fontStyle = $this->setNewStyle(new Font('text'), $fontStyle); $this->paragraphStyle = $this->setNewStyle(new Paragraph(), $paragraphStyle); $this->internal = $internal; } /** - * Get link source + * Get link source. * * @return string */ @@ -97,7 +97,7 @@ class Link extends AbstractElement } /** - * Get link text + * Get link text. * * @return string */ @@ -107,9 +107,9 @@ class Link extends AbstractElement } /** - * Get Text style + * Get Text style. * - * @return string|\PhpOffice\PhpWord\Style\Font + * @return \PhpOffice\PhpWord\Style\Font|string */ public function getFontStyle() { @@ -117,9 +117,9 @@ class Link extends AbstractElement } /** - * Get Paragraph style + * Get Paragraph style. * - * @return string|\PhpOffice\PhpWord\Style\Paragraph + * @return \PhpOffice\PhpWord\Style\Paragraph|string */ public function getParagraphStyle() { @@ -127,49 +127,7 @@ class Link extends AbstractElement } /** - * Get link target - * - * @deprecated 0.12.0 - * - * @return string - * - * @codeCoverageIgnore - */ - public function getTarget() - { - return $this->source; - } - - /** - * Get Link source - * - * @deprecated 0.10.0 - * - * @return string - * - * @codeCoverageIgnore - */ - public function getLinkSrc() - { - return $this->getSource(); - } - - /** - * Get Link name - * - * @deprecated 0.10.0 - * - * @return string - * - * @codeCoverageIgnore - */ - public function getLinkName() - { - return $this->getText(); - } - - /** - * is internal + * is internal. * * @return bool */ diff --git a/PhpOffice/PhpWord/Element/ListItem.php b/PhpOffice/PhpWord/Element/ListItem.php old mode 100755 new mode 100644 index 8b064c4..36484d9 --- a/PhpOffice/PhpWord/Element/ListItem.php +++ b/PhpOffice/PhpWord/Element/ListItem.php @@ -11,57 +11,57 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Element; -use PhpOffice\Common\Text as CommonText; +use PhpOffice\PhpWord\Shared\Text as SharedText; use PhpOffice\PhpWord\Style\ListItem as ListItemStyle; /** - * List item element + * List item element. */ class ListItem extends AbstractElement { /** - * Element style + * Element style. * * @var \PhpOffice\PhpWord\Style\ListItem */ private $style; /** - * Text object + * Text object. * * @var \PhpOffice\PhpWord\Element\Text */ private $textObject; /** - * Depth + * Depth. * * @var int */ private $depth; /** - * Create a new ListItem + * Create a new ListItem. * * @param string $text * @param int $depth * @param mixed $fontStyle - * @param array|string|null $listStyle + * @param null|array|string $listStyle * @param mixed $paragraphStyle */ public function __construct($text, $depth = 0, $fontStyle = null, $listStyle = null, $paragraphStyle = null) { - $this->textObject = new Text(CommonText::toUTF8($text), $fontStyle, $paragraphStyle); + $this->textObject = new Text(SharedText::toUTF8($text), $fontStyle, $paragraphStyle); $this->depth = $depth; // Version >= 0.10.0 will pass numbering style name. Older version will use old method - if (!is_null($listStyle) && is_string($listStyle)) { + if (null !== $listStyle && is_string($listStyle)) { $this->style = new ListItemStyle($listStyle); // @codeCoverageIgnore } else { $this->style = $this->setNewStyle(new ListItemStyle(), $listStyle, true); @@ -69,7 +69,7 @@ class ListItem extends AbstractElement } /** - * Get style + * Get style. * * @return \PhpOffice\PhpWord\Style\ListItem */ @@ -79,7 +79,7 @@ class ListItem extends AbstractElement } /** - * Get Text object + * Get Text object. * * @return \PhpOffice\PhpWord\Element\Text */ @@ -89,7 +89,7 @@ class ListItem extends AbstractElement } /** - * Get depth + * Get depth. * * @return int */ @@ -99,9 +99,10 @@ class ListItem extends AbstractElement } /** - * Get text + * Get text. * * @return string + * * @since 0.11.0 */ public function getText() diff --git a/PhpOffice/PhpWord/Element/ListItemRun.php b/PhpOffice/PhpWord/Element/ListItemRun.php old mode 100755 new mode 100644 index 6e48a69..69274a5 --- a/PhpOffice/PhpWord/Element/ListItemRun.php +++ b/PhpOffice/PhpWord/Element/ListItemRun.php @@ -1,26 +1,26 @@ depth = $depth; // Version >= 0.10.0 will pass numbering style name. Older version will use old method - if (!is_null($listStyle) && is_string($listStyle)) { + if (null !== $listStyle && is_string($listStyle)) { $this->style = new ListItemStyle($listStyle); } else { $this->style = $this->setNewStyle(new ListItemStyle(), $listStyle, true); diff --git a/PhpOffice/PhpWord/Element/OLEObject.php b/PhpOffice/PhpWord/Element/OLEObject.php old mode 100755 new mode 100644 index 1a17b74..df7396c --- a/PhpOffice/PhpWord/Element/OLEObject.php +++ b/PhpOffice/PhpWord/Element/OLEObject.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -21,56 +21,54 @@ use PhpOffice\PhpWord\Exception\InvalidObjectException; use PhpOffice\PhpWord\Style\Image as ImageStyle; /** - * OLEObject element + * OLEObject element. */ class OLEObject extends AbstractElement { /** - * Ole-Object Src + * Ole-Object Src. * * @var string */ private $source; /** - * Image Style + * Image Style. * * @var \PhpOffice\PhpWord\Style\Image */ private $style; /** - * Icon + * Icon. * * @var string */ private $icon; /** - * Image Relation ID + * Image Relation ID. * * @var int */ private $imageRelationId; /** - * Has media relation flag; true for Link, Image, and Object + * Has media relation flag; true for Link, Image, and Object. * * @var bool */ protected $mediaRelation = true; /** - * Create a new Ole-Object Element + * Create a new Ole-Object Element. * * @param string $source * @param mixed $style - * - * @throws \PhpOffice\PhpWord\Exception\InvalidObjectException */ public function __construct($source, $style = null) { - $supportedTypes = array('xls', 'doc', 'ppt', 'xlsx', 'docx', 'pptx'); + $supportedTypes = ['xls', 'doc', 'ppt', 'xlsx', 'docx', 'pptx']; $pathInfo = pathinfo($source); if (file_exists($source) && in_array($pathInfo['extension'], $supportedTypes)) { @@ -90,7 +88,7 @@ class OLEObject extends AbstractElement } /** - * Get object source + * Get object source. * * @return string */ @@ -100,7 +98,7 @@ class OLEObject extends AbstractElement } /** - * Get object style + * Get object style. * * @return \PhpOffice\PhpWord\Style\Image */ @@ -110,7 +108,7 @@ class OLEObject extends AbstractElement } /** - * Get object icon + * Get object icon. * * @return string */ @@ -120,7 +118,7 @@ class OLEObject extends AbstractElement } /** - * Get image relation ID + * Get image relation ID. * * @return int */ @@ -134,36 +132,8 @@ class OLEObject extends AbstractElement * * @param int $rId */ - public function setImageRelationId($rId) + public function setImageRelationId($rId): void { $this->imageRelationId = $rId; } - - /** - * Get Object ID - * - * @deprecated 0.10.0 - * - * @return int - * - * @codeCoverageIgnore - */ - public function getObjectId() - { - return $this->relationId + 1325353440; - } - - /** - * Set Object ID - * - * @deprecated 0.10.0 - * - * @param int $objId - * - * @codeCoverageIgnore - */ - public function setObjectId($objId) - { - $this->relationId = $objId; - } } diff --git a/PhpOffice/PhpWord/Element/PageBreak.php b/PhpOffice/PhpWord/Element/PageBreak.php old mode 100755 new mode 100644 index 1e2ada8..02f5989 --- a/PhpOffice/PhpWord/Element/PageBreak.php +++ b/PhpOffice/PhpWord/Element/PageBreak.php @@ -11,19 +11,19 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Element; /** - * Page break element + * Page break element. */ class PageBreak extends AbstractElement { /** - * Create new page break + * Create new page break. */ public function __construct() { diff --git a/PhpOffice/PhpWord/Element/PreserveText.php b/PhpOffice/PhpWord/Element/PreserveText.php old mode 100755 new mode 100644 index 374f1a9..19f468b --- a/PhpOffice/PhpWord/Element/PreserveText.php +++ b/PhpOffice/PhpWord/Element/PreserveText.php @@ -11,44 +11,44 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Element; -use PhpOffice\Common\Text as CommonText; +use PhpOffice\PhpWord\Shared\Text as SharedText; use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWord\Style\Paragraph; /** - * Preserve text/field element + * Preserve text/field element. */ class PreserveText extends AbstractElement { /** - * Text content + * Text content. * - * @var string|array + * @var array|string */ private $text; /** - * Text style + * Text style. * - * @var string|\PhpOffice\PhpWord\Style\Font + * @var \PhpOffice\PhpWord\Style\Font|string */ private $fontStyle; /** - * Paragraph style + * Paragraph style. * - * @var string|\PhpOffice\PhpWord\Style\Paragraph + * @var \PhpOffice\PhpWord\Style\Paragraph|string */ private $paragraphStyle; /** - * Create a new Preserve Text Element + * Create a new Preserve Text Element. * * @param string $text * @param mixed $fontStyle @@ -59,17 +59,17 @@ class PreserveText extends AbstractElement $this->fontStyle = $this->setNewStyle(new Font('text'), $fontStyle); $this->paragraphStyle = $this->setNewStyle(new Paragraph(), $paragraphStyle); - $this->text = CommonText::toUTF8($text); - $matches = preg_split('/({.*?})/', $this->text, null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + $this->text = SharedText::toUTF8($text); + $matches = preg_split('/({.*?})/', $this->text ?? '', -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); if (isset($matches[0])) { $this->text = $matches; } } /** - * Get Text style + * Get Text style. * - * @return string|\PhpOffice\PhpWord\Style\Font + * @return \PhpOffice\PhpWord\Style\Font|string */ public function getFontStyle() { @@ -77,9 +77,9 @@ class PreserveText extends AbstractElement } /** - * Get Paragraph style + * Get Paragraph style. * - * @return string|\PhpOffice\PhpWord\Style\Paragraph + * @return \PhpOffice\PhpWord\Style\Paragraph|string */ public function getParagraphStyle() { @@ -87,9 +87,9 @@ class PreserveText extends AbstractElement } /** - * Get Text content + * Get Text content. * - * @return string|array + * @return array|string */ public function getText() { diff --git a/PhpOffice/PhpWord/Element/Row.php b/PhpOffice/PhpWord/Element/Row.php old mode 100755 new mode 100644 index da4dfe5..d2f0eca --- a/PhpOffice/PhpWord/Element/Row.php +++ b/PhpOffice/PhpWord/Element/Row.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -20,35 +20,35 @@ namespace PhpOffice\PhpWord\Element; use PhpOffice\PhpWord\Style\Row as RowStyle; /** - * Table row element + * Table row element. * * @since 0.8.0 */ class Row extends AbstractElement { /** - * Row height + * Row height. * * @var int */ - private $height = null; + private $height; /** - * Row style + * Row style. * * @var \PhpOffice\PhpWord\Style\Row */ private $style; /** - * Row cells + * Row cells. * * @var \PhpOffice\PhpWord\Element\Cell[] */ - private $cells = array(); + private $cells = []; /** - * Create a new table row + * Create a new table row. * * @param int $height * @param mixed $style @@ -60,10 +60,11 @@ class Row extends AbstractElement } /** - * Add a cell + * Add a cell. * * @param int $width * @param mixed $style + * * @return \PhpOffice\PhpWord\Element\Cell */ public function addCell($width = null, $style = null) @@ -76,7 +77,7 @@ class Row extends AbstractElement } /** - * Get all cells + * Get all cells. * * @return \PhpOffice\PhpWord\Element\Cell[] */ @@ -86,7 +87,7 @@ class Row extends AbstractElement } /** - * Get row style + * Get row style. * * @return \PhpOffice\PhpWord\Style\Row */ @@ -96,7 +97,7 @@ class Row extends AbstractElement } /** - * Get row height + * Get row height. * * @return int */ diff --git a/PhpOffice/PhpWord/Element/SDT.php b/PhpOffice/PhpWord/Element/SDT.php old mode 100755 new mode 100644 index 5548f76..b7ff31e --- a/PhpOffice/PhpWord/Element/SDT.php +++ b/PhpOffice/PhpWord/Element/SDT.php @@ -11,56 +11,56 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Element; /** - * Structured document tag (SDT) element + * Structured document tag (SDT) element. * * @since 0.12.0 */ class SDT extends Text { /** - * Form field type: comboBox|dropDownList|date + * Form field type: comboBox|dropDownList|date. * * @var string */ private $type; /** - * Value + * Value. * - * @var string|bool|int + * @var bool|int|string */ private $value; /** - * CheckBox/DropDown list entries + * CheckBox/DropDown list entries. * * @var array */ - private $listItems = array(); + private $listItems = []; /** - * Alias + * Alias. * * @var string */ private $alias; /** - * Tag + * Tag. * * @var string */ private $tag; /** - * Create new instance + * Create new instance. * * @param string $type * @param mixed $fontStyle @@ -73,7 +73,7 @@ class SDT extends Text } /** - * Get type + * Get type. * * @return string */ @@ -83,23 +83,24 @@ class SDT extends Text } /** - * Set type + * Set type. * * @param string $value + * * @return self */ public function setType($value) { - $enum = array('plainText', 'comboBox', 'dropDownList', 'date'); + $enum = ['plainText', 'comboBox', 'dropDownList', 'date']; $this->type = $this->setEnumVal($value, $enum, 'comboBox'); return $this; } /** - * Get value + * Get value. * - * @return string|bool|int + * @return bool|int|string */ public function getValue() { @@ -107,9 +108,10 @@ class SDT extends Text } /** - * Set value + * Set value. + * + * @param bool|int|string $value * - * @param string|bool|int $value * @return self */ public function setValue($value) @@ -120,7 +122,7 @@ class SDT extends Text } /** - * Get listItems + * Get listItems. * * @return array */ @@ -130,9 +132,10 @@ class SDT extends Text } /** - * Set listItems + * Set listItems. * * @param array $value + * * @return self */ public function setListItems($value) @@ -143,7 +146,7 @@ class SDT extends Text } /** - * Get tag + * Get tag. * * @return string */ @@ -153,9 +156,10 @@ class SDT extends Text } /** - * Set tag + * Set tag. * * @param string $tag + * * @return self */ public function setTag($tag) @@ -166,7 +170,7 @@ class SDT extends Text } /** - * Get alias + * Get alias. * * @return string */ @@ -176,9 +180,10 @@ class SDT extends Text } /** - * Set alias + * Set alias. * * @param string $alias + * * @return self */ public function setAlias($alias) diff --git a/PhpOffice/PhpWord/Element/Section.php b/PhpOffice/PhpWord/Element/Section.php old mode 100755 new mode 100644 index b6da9f3..d77645f --- a/PhpOffice/PhpWord/Element/Section.php +++ b/PhpOffice/PhpWord/Element/Section.php @@ -11,12 +11,13 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Element; +use Exception; use PhpOffice\PhpWord\ComplexType\FootnoteProperties; use PhpOffice\PhpWord\Style\Section as SectionStyle; @@ -28,35 +29,35 @@ class Section extends AbstractContainer protected $container = 'Section'; /** - * Section style + * Section style. * * @var \PhpOffice\PhpWord\Style\Section */ private $style; /** - * Section headers, indexed from 1, not zero + * Section headers, indexed from 1, not zero. * * @var Header[] */ - private $headers = array(); + private $headers = []; /** - * Section footers, indexed from 1, not zero + * Section footers, indexed from 1, not zero. * * @var Footer[] */ - private $footers = array(); + private $footers = []; /** - * The properties for the footnote of this section + * The properties for the footnote of this section. * * @var FootnoteProperties */ private $footnoteProperties; /** - * Create new instance + * Create new instance. * * @param int $sectionCount * @param null|array|\PhpOffice\PhpWord\Style $style @@ -76,15 +77,15 @@ class Section extends AbstractContainer * * @param array $style */ - public function setStyle($style = null) + public function setStyle($style = null): void { - if (!is_null($style) && is_array($style)) { + if (null !== $style && is_array($style)) { $this->style->setStyleByArray($style); } } /** - * Get section style + * Get section style. * * @return \PhpOffice\PhpWord\Style\Section */ @@ -94,7 +95,7 @@ class Section extends AbstractContainer } /** - * Add header + * Add header. * * @since 0.10.0 * @@ -108,7 +109,7 @@ class Section extends AbstractContainer } /** - * Add footer + * Add footer. * * @since 0.10.0 * @@ -122,7 +123,7 @@ class Section extends AbstractContainer } /** - * Get header elements + * Get header elements. * * @return Header[] */ @@ -132,7 +133,7 @@ class Section extends AbstractContainer } /** - * Get footer elements + * Get footer elements. * * @return Footer[] */ @@ -142,7 +143,7 @@ class Section extends AbstractContainer } /** - * Get the footnote properties + * Get the footnote properties. * * @return FootnoteProperties */ @@ -152,23 +153,11 @@ class Section extends AbstractContainer } /** - * Get the footnote properties - * - * @deprecated Use the `getFootnoteProperties` method instead - * - * @return FootnoteProperties - */ - public function getFootnotePropoperties() - { - return $this->footnoteProperties; - } - - /** - * Set the footnote properties + * Set the footnote properties. * * @param FootnoteProperties $footnoteProperties */ - public function setFootnoteProperties(FootnoteProperties $footnoteProperties = null) + public function setFootnoteProperties(?FootnoteProperties $footnoteProperties = null): void { $this->footnoteProperties = $footnoteProperties; } @@ -198,25 +187,23 @@ class Section extends AbstractContainer } /** - * Add header/footer + * Add header/footer. * * @since 0.10.0 * * @param string $type * @param bool $header * - * @throws \Exception - * - * @return Header|Footer + * @return Footer|Header */ private function addHeaderFooter($type = Header::AUTO, $header = true) { - $containerClass = substr(get_class($this), 0, strrpos(get_class($this), '\\')) . '\\' . + $containerClass = substr(static::class, 0, strrpos(static::class, '\\')) . '\\' . ($header ? 'Header' : 'Footer'); $collectionArray = $header ? 'headers' : 'footers'; $collection = &$this->$collectionArray; - if (in_array($type, array(Header::AUTO, Header::FIRST, Header::EVEN))) { + if (in_array($type, [Header::AUTO, Header::FIRST, Header::EVEN])) { $index = count($collection); /** @var \PhpOffice\PhpWord\Element\AbstractContainer $container Type hint */ $container = new $containerClass($this->sectionId, ++$index, $type); @@ -226,80 +213,7 @@ class Section extends AbstractContainer return $container; } - throw new \Exception('Invalid header/footer type.'); - } - /** - * Set section style - * - * @deprecated 0.12.0 - * - * @param array $settings - * - * @codeCoverageIgnore - */ - public function setSettings($settings = null) - { - $this->setStyle($settings); - } - - /** - * Get section style - * - * @deprecated 0.12.0 - * - * @return \PhpOffice\PhpWord\Style\Section - * - * @codeCoverageIgnore - */ - public function getSettings() - { - return $this->getStyle(); - } - - /** - * Create header - * - * @deprecated 0.10.0 - * - * @return Header - * - * @codeCoverageIgnore - */ - public function createHeader() - { - return $this->addHeader(); - } - - /** - * Create footer - * - * @deprecated 0.10.0 - * - * @return Footer - * - * @codeCoverageIgnore - */ - public function createFooter() - { - return $this->addFooter(); - } - - /** - * Get footer - * - * @deprecated 0.10.0 - * - * @return Footer - * - * @codeCoverageIgnore - */ - public function getFooter() - { - if (empty($this->footers)) { - return null; - } - - return $this->footers[1]; + throw new Exception('Invalid header/footer type.'); } } diff --git a/PhpOffice/PhpWord/Element/Shape.php b/PhpOffice/PhpWord/Element/Shape.php old mode 100755 new mode 100644 index d143c9b..930ed8a --- a/PhpOffice/PhpWord/Element/Shape.php +++ b/PhpOffice/PhpWord/Element/Shape.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -20,28 +20,28 @@ namespace PhpOffice\PhpWord\Element; use PhpOffice\PhpWord\Style\Shape as ShapeStyle; /** - * Shape element + * Shape element. * * @since 0.12.0 */ class Shape extends AbstractElement { /** - * Shape type arc|curve|line|polyline|rect|oval + * Shape type arc|curve|line|polyline|rect|oval. * * @var string */ private $type; /** - * Shape style + * Shape style. * * @var \PhpOffice\PhpWord\Style\Shape */ private $style; /** - * Create new instance + * Create new instance. * * @param string $type * @param mixed $style @@ -53,7 +53,7 @@ class Shape extends AbstractElement } /** - * Get type + * Get type. * * @return string */ @@ -63,21 +63,22 @@ class Shape extends AbstractElement } /** - * Set pattern + * Set pattern. * * @param string $value + * * @return self */ public function setType($value = null) { - $enum = array('arc', 'curve', 'line', 'polyline', 'rect', 'oval'); + $enum = ['arc', 'curve', 'line', 'polyline', 'rect', 'oval']; $this->type = $this->setEnumVal($value, $enum, null); return $this; } /** - * Get shape style + * Get shape style. * * @return \PhpOffice\PhpWord\Style\Shape */ diff --git a/PhpOffice/PhpWord/Element/TOC.php b/PhpOffice/PhpWord/Element/TOC.php old mode 100755 new mode 100644 index c51d0e6..320c3e9 --- a/PhpOffice/PhpWord/Element/TOC.php +++ b/PhpOffice/PhpWord/Element/TOC.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -22,40 +22,40 @@ use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWord\Style\TOC as TOCStyle; /** - * Table of contents + * Table of contents. */ class TOC extends AbstractElement { /** - * TOC style + * TOC style. * * @var \PhpOffice\PhpWord\Style\TOC */ - private $TOCStyle; + private $tocStyle; /** - * Font style + * Font style. * * @var \PhpOffice\PhpWord\Style\Font|string */ private $fontStyle; /** - * Min title depth to show + * Min title depth to show. * * @var int */ private $minDepth = 1; /** - * Max title depth to show + * Max title depth to show. * * @var int */ private $maxDepth = 9; /** - * Create a new Table-of-Contents Element + * Create a new Table-of-Contents Element. * * @param mixed $fontStyle * @param array $tocStyle @@ -64,13 +64,13 @@ class TOC extends AbstractElement */ public function __construct($fontStyle = null, $tocStyle = null, $minDepth = 1, $maxDepth = 9) { - $this->TOCStyle = new TOCStyle(); + $this->tocStyle = new TOCStyle(); - if (!is_null($tocStyle) && is_array($tocStyle)) { - $this->TOCStyle->setStyleByArray($tocStyle); + if (null !== $tocStyle && is_array($tocStyle)) { + $this->tocStyle->setStyleByArray($tocStyle); } - if (!is_null($fontStyle) && is_array($fontStyle)) { + if (null !== $fontStyle && is_array($fontStyle)) { $this->fontStyle = new Font(); $this->fontStyle->setStyleByArray($fontStyle); } else { @@ -82,14 +82,14 @@ class TOC extends AbstractElement } /** - * Get all titles + * Get all titles. * * @return array */ public function getTitles() { if (!$this->phpWord instanceof PhpWord) { - return array(); + return []; } $titles = $this->phpWord->getTitles()->getItems(); @@ -108,17 +108,17 @@ class TOC extends AbstractElement } /** - * Get TOC Style + * Get TOC Style. * * @return \PhpOffice\PhpWord\Style\TOC */ public function getStyleTOC() { - return $this->TOCStyle; + return $this->tocStyle; } /** - * Get Font Style + * Get Font Style. * * @return \PhpOffice\PhpWord\Style\Font|string */ @@ -132,13 +132,13 @@ class TOC extends AbstractElement * * @param int $value */ - public function setMaxDepth($value) + public function setMaxDepth($value): void { $this->maxDepth = $value; } /** - * Get Max Depth + * Get Max Depth. * * @return int Max depth of titles */ @@ -152,13 +152,13 @@ class TOC extends AbstractElement * * @param int $value */ - public function setMinDepth($value) + public function setMinDepth($value): void { $this->minDepth = $value; } /** - * Get Min Depth + * Get Min Depth. * * @return int Min depth of titles */ diff --git a/PhpOffice/PhpWord/Element/Table.php b/PhpOffice/PhpWord/Element/Table.php old mode 100755 new mode 100644 index 44fe374..308e7bc --- a/PhpOffice/PhpWord/Element/Table.php +++ b/PhpOffice/PhpWord/Element/Table.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -20,33 +20,33 @@ namespace PhpOffice\PhpWord\Element; use PhpOffice\PhpWord\Style\Table as TableStyle; /** - * Table element + * Table element. */ class Table extends AbstractElement { /** - * Table style + * Table style. * * @var \PhpOffice\PhpWord\Style\Table */ private $style; /** - * Table rows + * Table rows. * * @var \PhpOffice\PhpWord\Element\Row[] */ - private $rows = array(); + private $rows = []; /** - * Table width + * Table width. * * @var int */ - private $width = null; + private $width; /** - * Create a new table + * Create a new table. * * @param mixed $style */ @@ -56,10 +56,11 @@ class Table extends AbstractElement } /** - * Add a row + * Add a row. * * @param int $height * @param mixed $style + * * @return \PhpOffice\PhpWord\Element\Row */ public function addRow($height = null, $style = null) @@ -72,10 +73,11 @@ class Table extends AbstractElement } /** - * Add a cell + * Add a cell. * * @param int $width * @param mixed $style + * * @return \PhpOffice\PhpWord\Element\Cell */ public function addCell($width = null, $style = null) @@ -88,7 +90,7 @@ class Table extends AbstractElement } /** - * Get all rows + * Get all rows. * * @return \PhpOffice\PhpWord\Element\Row[] */ @@ -98,7 +100,7 @@ class Table extends AbstractElement } /** - * Get table style + * Get table style. * * @return \PhpOffice\PhpWord\Style\Table */ @@ -108,7 +110,7 @@ class Table extends AbstractElement } /** - * Get table width + * Get table width. * * @return int */ @@ -122,13 +124,13 @@ class Table extends AbstractElement * * @param int $width */ - public function setWidth($width) + public function setWidth($width): void { $this->width = $width; } /** - * Get column count + * Get column count. * * @return int */ @@ -137,7 +139,7 @@ class Table extends AbstractElement $columnCount = 0; $rowCount = count($this->rows); - for ($i = 0; $i < $rowCount; $i++) { + for ($i = 0; $i < $rowCount; ++$i) { /** @var \PhpOffice\PhpWord\Element\Row $row Type hint */ $row = $this->rows[$i]; $cellCount = count($row->getCells()); @@ -150,20 +152,20 @@ class Table extends AbstractElement } /** - * The first declared cell width for each column + * The first declared cell width for each column. * * @return int[] */ public function findFirstDefinedCellWidths() { - $cellWidths = array(); + $cellWidths = []; foreach ($this->rows as $row) { $cells = $row->getCells(); if (count($cells) <= count($cellWidths)) { continue; } - $cellWidths = array(); + $cellWidths = []; foreach ($cells as $cell) { $cellWidths[] = $cell->getWidth(); } diff --git a/PhpOffice/PhpWord/Element/Text.php b/PhpOffice/PhpWord/Element/Text.php old mode 100755 new mode 100644 index f4d7f08..6a11e82 --- a/PhpOffice/PhpWord/Element/Text.php +++ b/PhpOffice/PhpWord/Element/Text.php @@ -11,44 +11,44 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Element; -use PhpOffice\Common\Text as CommonText; +use PhpOffice\PhpWord\Shared\Text as SharedText; use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWord\Style\Paragraph; /** - * Text element + * Text element. */ class Text extends AbstractElement { /** - * Text content + * Text content. * * @var string */ protected $text; /** - * Text style + * Text style. * - * @var string|\PhpOffice\PhpWord\Style\Font + * @var \PhpOffice\PhpWord\Style\Font|string */ protected $fontStyle; /** - * Paragraph style + * Paragraph style. * - * @var string|\PhpOffice\PhpWord\Style\Paragraph + * @var \PhpOffice\PhpWord\Style\Paragraph|string */ protected $paragraphStyle; /** - * Create a new Text Element + * Create a new Text Element. * * @param string $text * @param mixed $fontStyle @@ -62,11 +62,12 @@ class Text extends AbstractElement } /** - * Set Text style + * Set Text style. * - * @param string|array|\PhpOffice\PhpWord\Style\Font $style - * @param string|array|\PhpOffice\PhpWord\Style\Paragraph $paragraphStyle - * @return string|\PhpOffice\PhpWord\Style\Font + * @param array|\PhpOffice\PhpWord\Style\Font|string $style + * @param array|\PhpOffice\PhpWord\Style\Paragraph|string $paragraphStyle + * + * @return \PhpOffice\PhpWord\Style\Font|string */ public function setFontStyle($style = null, $paragraphStyle = null) { @@ -87,9 +88,9 @@ class Text extends AbstractElement } /** - * Get Text style + * Get Text style. * - * @return string|\PhpOffice\PhpWord\Style\Font + * @return \PhpOffice\PhpWord\Style\Font|string */ public function getFontStyle() { @@ -97,10 +98,11 @@ class Text extends AbstractElement } /** - * Set Paragraph style + * Set Paragraph style. * - * @param string|array|\PhpOffice\PhpWord\Style\Paragraph $style - * @return string|\PhpOffice\PhpWord\Style\Paragraph + * @param array|\PhpOffice\PhpWord\Style\Paragraph|string $style + * + * @return \PhpOffice\PhpWord\Style\Paragraph|string */ public function setParagraphStyle($style = null) { @@ -119,9 +121,9 @@ class Text extends AbstractElement } /** - * Get Paragraph style + * Get Paragraph style. * - * @return string|\PhpOffice\PhpWord\Style\Paragraph + * @return \PhpOffice\PhpWord\Style\Paragraph|string */ public function getParagraphStyle() { @@ -129,20 +131,21 @@ class Text extends AbstractElement } /** - * Set text content + * Set text content. * * @param string $text + * * @return self */ public function setText($text) { - $this->text = CommonText::toUTF8($text); + $this->text = SharedText::toUTF8($text); return $this; } /** - * Get Text content + * Get Text content. * * @return string */ diff --git a/PhpOffice/PhpWord/Element/TextBox.php b/PhpOffice/PhpWord/Element/TextBox.php old mode 100755 new mode 100644 index b9f274d..7472f4b --- a/PhpOffice/PhpWord/Element/TextBox.php +++ b/PhpOffice/PhpWord/Element/TextBox.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -20,7 +20,7 @@ namespace PhpOffice\PhpWord\Element; use PhpOffice\PhpWord\Style\TextBox as TextBoxStyle; /** - * TextBox element + * TextBox element. * * @since 0.11.0 */ @@ -32,14 +32,14 @@ class TextBox extends AbstractContainer protected $container = 'TextBox'; /** - * TextBox style + * TextBox style. * * @var \PhpOffice\PhpWord\Style\TextBox */ private $style; /** - * Create a new textbox + * Create a new textbox. * * @param mixed $style */ @@ -49,7 +49,7 @@ class TextBox extends AbstractContainer } /** - * Get textbox style + * Get textbox style. * * @return \PhpOffice\PhpWord\Style\TextBox */ diff --git a/PhpOffice/PhpWord/Element/TextBreak.php b/PhpOffice/PhpWord/Element/TextBreak.php old mode 100755 new mode 100644 index 385fec5..088503a --- a/PhpOffice/PhpWord/Element/TextBreak.php +++ b/PhpOffice/PhpWord/Element/TextBreak.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -21,46 +21,47 @@ use PhpOffice\PhpWord\Style\Font; use PhpOffice\PhpWord\Style\Paragraph; /** - * Text break element + * Text break element. */ class TextBreak extends AbstractElement { /** - * Paragraph style + * Paragraph style. * - * @var string|\PhpOffice\PhpWord\Style\Paragraph + * @var \PhpOffice\PhpWord\Style\Paragraph|string */ - private $paragraphStyle = null; + private $paragraphStyle; /** - * Text style + * Text style. * - * @var string|\PhpOffice\PhpWord\Style\Font + * @var \PhpOffice\PhpWord\Style\Font|string */ - private $fontStyle = null; + private $fontStyle; /** - * Create a new TextBreak Element + * Create a new TextBreak Element. * * @param mixed $fontStyle * @param mixed $paragraphStyle */ public function __construct($fontStyle = null, $paragraphStyle = null) { - if (!is_null($paragraphStyle)) { + if (null !== $paragraphStyle) { $paragraphStyle = $this->setParagraphStyle($paragraphStyle); } - if (!is_null($fontStyle)) { + if (null !== $fontStyle) { $this->setFontStyle($fontStyle, $paragraphStyle); } } /** - * Set Text style + * Set Text style. * * @param mixed $style * @param mixed $paragraphStyle - * @return string|\PhpOffice\PhpWord\Style\Font + * + * @return \PhpOffice\PhpWord\Style\Font|string */ public function setFontStyle($style = null, $paragraphStyle = null) { @@ -79,9 +80,9 @@ class TextBreak extends AbstractElement } /** - * Get Text style + * Get Text style. * - * @return string|\PhpOffice\PhpWord\Style\Font + * @return \PhpOffice\PhpWord\Style\Font|string */ public function getFontStyle() { @@ -89,10 +90,11 @@ class TextBreak extends AbstractElement } /** - * Set Paragraph style + * Set Paragraph style. * - * @param string|array|\PhpOffice\PhpWord\Style\Paragraph $style - * @return string|\PhpOffice\PhpWord\Style\Paragraph + * @param array|\PhpOffice\PhpWord\Style\Paragraph|string $style + * + * @return \PhpOffice\PhpWord\Style\Paragraph|string */ public function setParagraphStyle($style = null) { @@ -109,9 +111,9 @@ class TextBreak extends AbstractElement } /** - * Get Paragraph style + * Get Paragraph style. * - * @return string|\PhpOffice\PhpWord\Style\Paragraph + * @return \PhpOffice\PhpWord\Style\Paragraph|string */ public function getParagraphStyle() { @@ -119,12 +121,12 @@ class TextBreak extends AbstractElement } /** - * Has font/paragraph style defined + * Has font/paragraph style defined. * * @return bool */ public function hasStyle() { - return !is_null($this->fontStyle) || !is_null($this->paragraphStyle); + return null !== $this->fontStyle || null !== $this->paragraphStyle; } } diff --git a/PhpOffice/PhpWord/Element/TextRun.php b/PhpOffice/PhpWord/Element/TextRun.php old mode 100755 new mode 100644 index 9af55d4..fc87275 --- a/PhpOffice/PhpWord/Element/TextRun.php +++ b/PhpOffice/PhpWord/Element/TextRun.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -20,7 +20,7 @@ namespace PhpOffice\PhpWord\Element; use PhpOffice\PhpWord\Style\Paragraph; /** - * Textrun/paragraph element + * Textrun/paragraph element. */ class TextRun extends AbstractContainer { @@ -30,16 +30,16 @@ class TextRun extends AbstractContainer protected $container = 'TextRun'; /** - * Paragraph style + * Paragraph style. * - * @var string|\PhpOffice\PhpWord\Style\Paragraph + * @var \PhpOffice\PhpWord\Style\Paragraph|string */ protected $paragraphStyle; /** - * Create new instance + * Create new instance. * - * @param string|array|\PhpOffice\PhpWord\Style\Paragraph $paragraphStyle + * @param array|\PhpOffice\PhpWord\Style\Paragraph|string $paragraphStyle */ public function __construct($paragraphStyle = null) { @@ -47,9 +47,9 @@ class TextRun extends AbstractContainer } /** - * Get Paragraph style + * Get Paragraph style. * - * @return string|\PhpOffice\PhpWord\Style\Paragraph + * @return \PhpOffice\PhpWord\Style\Paragraph|string */ public function getParagraphStyle() { @@ -57,10 +57,11 @@ class TextRun extends AbstractContainer } /** - * Set Paragraph style + * Set Paragraph style. * - * @param string|array|\PhpOffice\PhpWord\Style\Paragraph $style - * @return string|\PhpOffice\PhpWord\Style\Paragraph + * @param array|\PhpOffice\PhpWord\Style\Paragraph|string $style + * + * @return \PhpOffice\PhpWord\Style\Paragraph|string */ public function setParagraphStyle($style = null) { diff --git a/PhpOffice/PhpWord/Element/Title.php b/PhpOffice/PhpWord/Element/Title.php old mode 100755 new mode 100644 index d01f7f3..947e941 --- a/PhpOffice/PhpWord/Element/Title.php +++ b/PhpOffice/PhpWord/Element/Title.php @@ -11,50 +11,51 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Element; -use PhpOffice\Common\Text as CommonText; +use InvalidArgumentException; +use PhpOffice\PhpWord\Shared\Text as SharedText; use PhpOffice\PhpWord\Style; /** - * Title element + * Title element. */ class Title extends AbstractElement { /** - * Title Text content + * Title Text content. * * @var string|TextRun */ private $text; /** - * Title depth + * Title depth. * * @var int */ private $depth = 1; /** - * Name of the heading style, e.g. 'Heading1' + * Name of the heading style, e.g. 'Heading1'. * * @var string */ private $style; /** - * Is part of collection + * Is part of collection. * * @var bool */ protected $collectionRelation = true; /** - * Create a new Title Element + * Create a new Title Element. * * @param string|TextRun $text * @param int $depth @@ -62,11 +63,11 @@ class Title extends AbstractElement public function __construct($text, $depth = 1) { if (is_string($text)) { - $this->text = CommonText::toUTF8($text); + $this->text = SharedText::toUTF8($text); } elseif ($text instanceof TextRun) { $this->text = $text; } else { - throw new \InvalidArgumentException('Invalid text, should be a string or a TextRun'); + throw new InvalidArgumentException('Invalid text, should be a string or a TextRun'); } $this->depth = $depth; @@ -77,7 +78,7 @@ class Title extends AbstractElement } /** - * Get Title Text content + * Get Title Text content. * * @return string */ @@ -87,7 +88,7 @@ class Title extends AbstractElement } /** - * Get depth + * Get depth. * * @return int */ @@ -97,7 +98,7 @@ class Title extends AbstractElement } /** - * Get Title style + * Get Title style. * * @return string */ diff --git a/PhpOffice/PhpWord/Element/TrackChange.php b/PhpOffice/PhpWord/Element/TrackChange.php old mode 100755 new mode 100644 index 91c221f..064e638 --- a/PhpOffice/PhpWord/Element/TrackChange.php +++ b/PhpOffice/PhpWord/Element/TrackChange.php @@ -11,14 +11,17 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Element; +use DateTime; + /** - * TrackChange element + * TrackChange element. + * * @see http://datypic.com/sc/ooxml/t-w_CT_TrackChange.html * @see http://datypic.com/sc/ooxml/t-w_CT_RunTrackChange.html */ @@ -33,44 +36,44 @@ class TrackChange extends AbstractContainer protected $container = 'TrackChange'; /** - * The type of change, (insert or delete), not applicable for PhpOffice\PhpWord\Element\Comment + * The type of change, (insert or delete), not applicable for PhpOffice\PhpWord\Element\Comment. * * @var string */ private $changeType; /** - * Author + * Author. * * @var string */ private $author; /** - * Date + * Date. * - * @var \DateTime + * @var DateTime */ private $date; /** - * Create a new TrackChange Element + * Create a new TrackChange Element. * * @param string $changeType * @param string $author - * @param null|int|bool|\DateTime $date + * @param null|bool|DateTime|int $date */ public function __construct($changeType = null, $author = null, $date = null) { $this->changeType = $changeType; $this->author = $author; if ($date !== null && $date !== false) { - $this->date = ($date instanceof \DateTime) ? $date : new \DateTime('@' . $date); + $this->date = ($date instanceof DateTime) ? $date : new DateTime('@' . $date); } } /** - * Get TrackChange Author + * Get TrackChange Author. * * @return string */ @@ -80,9 +83,9 @@ class TrackChange extends AbstractContainer } /** - * Get TrackChange Date + * Get TrackChange Date. * - * @return \DateTime + * @return DateTime */ public function getDate() { @@ -90,7 +93,7 @@ class TrackChange extends AbstractContainer } /** - * Get the Change type + * Get the Change type. * * @return string */ diff --git a/PhpOffice/PhpWord/Escaper/AbstractEscaper.php b/PhpOffice/PhpWord/Escaper/AbstractEscaper.php old mode 100755 new mode 100644 index 1575c06..571d5df --- a/PhpOffice/PhpWord/Escaper/AbstractEscaper.php +++ b/PhpOffice/PhpWord/Escaper/AbstractEscaper.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ diff --git a/PhpOffice/PhpWord/Escaper/EscaperInterface.php b/PhpOffice/PhpWord/Escaper/EscaperInterface.php old mode 100755 new mode 100644 index deb2cfb..ac68058 --- a/PhpOffice/PhpWord/Escaper/EscaperInterface.php +++ b/PhpOffice/PhpWord/Escaper/EscaperInterface.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ diff --git a/PhpOffice/PhpWord/Escaper/RegExp.php b/PhpOffice/PhpWord/Escaper/RegExp.php old mode 100755 new mode 100644 index f69aad8..84f6fae --- a/PhpOffice/PhpWord/Escaper/RegExp.php +++ b/PhpOffice/PhpWord/Escaper/RegExp.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ diff --git a/PhpOffice/PhpWord/Escaper/Rtf.php b/PhpOffice/PhpWord/Escaper/Rtf.php old mode 100755 new mode 100644 index 42eb22a..b627bcd --- a/PhpOffice/PhpWord/Escaper/Rtf.php +++ b/PhpOffice/PhpWord/Escaper/Rtf.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -46,6 +46,7 @@ class Rtf extends AbstractEscaper /** * @see http://www.randomchaos.com/documents/?source=php_and_unicode + * * @param string $input */ protected function escapeSingleValue($input) @@ -53,7 +54,7 @@ class Rtf extends AbstractEscaper $escapedValue = ''; $numberOfBytes = 1; - $bytes = array(); + $bytes = []; for ($i = 0; $i < strlen($input); ++$i) { $character = $input[$i]; $asciiCode = ord($character); @@ -87,7 +88,7 @@ class Rtf extends AbstractEscaper } $numberOfBytes = 1; - $bytes = array(); + $bytes = []; } } } diff --git a/PhpOffice/PhpWord/Escaper/Xml.php b/PhpOffice/PhpWord/Escaper/Xml.php old mode 100755 new mode 100644 index a769c5e..446ea77 --- a/PhpOffice/PhpWord/Escaper/Xml.php +++ b/PhpOffice/PhpWord/Escaper/Xml.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -26,7 +26,6 @@ class Xml extends AbstractEscaper { protected function escapeSingleValue($input) { - // todo: omit encoding parameter after migration onto PHP 5.4 - return htmlspecialchars($input, ENT_QUOTES, 'UTF-8'); + return (null !== $input) ? htmlspecialchars($input, ENT_QUOTES) : ''; } } diff --git a/PhpOffice/PhpWord/Exception/CopyFileException.php b/PhpOffice/PhpWord/Exception/CopyFileException.php old mode 100755 new mode 100644 index d1c3bd0..2ee8187 --- a/PhpOffice/PhpWord/Exception/CopyFileException.php +++ b/PhpOffice/PhpWord/Exception/CopyFileException.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -28,7 +28,7 @@ final class CopyFileException extends Exception * @param int $code The user defined exception code * @param \Exception $previous The previous exception used for the exception chaining */ - final public function __construct($source, $destination, $code = 0, \Exception $previous = null) + public function __construct($source, $destination, $code = 0, ?\Exception $previous = null) { parent::__construct( sprintf('Could not copy \'%s\' file to \'%s\'.', $source, $destination), diff --git a/PhpOffice/PhpWord/Exception/CreateTemporaryFileException.php b/PhpOffice/PhpWord/Exception/CreateTemporaryFileException.php old mode 100755 new mode 100644 index c8a0642..7be01d6 --- a/PhpOffice/PhpWord/Exception/CreateTemporaryFileException.php +++ b/PhpOffice/PhpWord/Exception/CreateTemporaryFileException.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -26,7 +26,7 @@ final class CreateTemporaryFileException extends Exception * @param int $code The user defined exception code * @param \Exception $previous The previous exception used for the exception chaining */ - final public function __construct($code = 0, \Exception $previous = null) + public function __construct($code = 0, ?\Exception $previous = null) { parent::__construct( 'Could not create a temporary file with unique name in the specified directory.', diff --git a/PhpOffice/PhpWord/Exception/Exception.php b/PhpOffice/PhpWord/Exception/Exception.php old mode 100755 new mode 100644 index d874625..7500dd3 --- a/PhpOffice/PhpWord/Exception/Exception.php +++ b/PhpOffice/PhpWord/Exception/Exception.php @@ -11,14 +11,14 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Exception; /** - * General exception + * General exception. */ class Exception extends \Exception { diff --git a/PhpOffice/PhpWord/Exception/InvalidImageException.php b/PhpOffice/PhpWord/Exception/InvalidImageException.php old mode 100755 new mode 100644 index 07c9668..3597444 --- a/PhpOffice/PhpWord/Exception/InvalidImageException.php +++ b/PhpOffice/PhpWord/Exception/InvalidImageException.php @@ -11,14 +11,14 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Exception; /** - * Exception used for when an image is not found + * Exception used for when an image is not found. */ class InvalidImageException extends Exception { diff --git a/PhpOffice/PhpWord/Exception/InvalidObjectException.php b/PhpOffice/PhpWord/Exception/InvalidObjectException.php old mode 100755 new mode 100644 index d8fef96..955b222 --- a/PhpOffice/PhpWord/Exception/InvalidObjectException.php +++ b/PhpOffice/PhpWord/Exception/InvalidObjectException.php @@ -11,14 +11,14 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Exception; /** - * Exception used for when an image is not found + * Exception used for when an image is not found. */ class InvalidObjectException extends Exception { diff --git a/PhpOffice/PhpWord/Exception/InvalidStyleException.php b/PhpOffice/PhpWord/Exception/InvalidStyleException.php old mode 100755 new mode 100644 index 58c1961..dd4e09a --- a/PhpOffice/PhpWord/Exception/InvalidStyleException.php +++ b/PhpOffice/PhpWord/Exception/InvalidStyleException.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -20,7 +20,7 @@ namespace PhpOffice\PhpWord\Exception; use InvalidArgumentException; /** - * Exception used for when a style value is invalid + * Exception used for when a style value is invalid. */ class InvalidStyleException extends InvalidArgumentException { diff --git a/PhpOffice/PhpWord/Exception/UnsupportedImageTypeException.php b/PhpOffice/PhpWord/Exception/UnsupportedImageTypeException.php old mode 100755 new mode 100644 index ee27065..8807fe0 --- a/PhpOffice/PhpWord/Exception/UnsupportedImageTypeException.php +++ b/PhpOffice/PhpWord/Exception/UnsupportedImageTypeException.php @@ -11,14 +11,14 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Exception; /** - * Exception used for when an image type is unsupported + * Exception used for when an image type is unsupported. */ class UnsupportedImageTypeException extends Exception { diff --git a/PhpOffice/PhpWord/IOFactory.php b/PhpOffice/PhpWord/IOFactory.php old mode 100755 new mode 100644 index 3929f48..de2656d --- a/PhpOffice/PhpWord/IOFactory.php +++ b/PhpOffice/PhpWord/IOFactory.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -20,22 +20,20 @@ namespace PhpOffice\PhpWord; use PhpOffice\PhpWord\Exception\Exception; use PhpOffice\PhpWord\Reader\ReaderInterface; use PhpOffice\PhpWord\Writer\WriterInterface; +use ReflectionClass; abstract class IOFactory { /** - * Create new writer + * Create new writer. * - * @param PhpWord $phpWord * @param string $name * - * @throws \PhpOffice\PhpWord\Exception\Exception - * * @return WriterInterface */ public static function createWriter(PhpWord $phpWord, $name = 'Word2007') { - if ($name !== 'WriterInterface' && !in_array($name, array('ODText', 'RTF', 'Word2007', 'HTML', 'PDF'), true)) { + if ($name !== 'WriterInterface' && !in_array($name, ['ODText', 'RTF', 'Word2007', 'HTML', 'PDF'], true)) { throw new Exception("\"{$name}\" is not a valid writer."); } @@ -45,12 +43,10 @@ abstract class IOFactory } /** - * Create new reader + * Create new reader. * * @param string $name * - * @throws Exception - * * @return ReaderInterface */ public static function createReader($name = 'Word2007') @@ -59,15 +55,13 @@ abstract class IOFactory } /** - * Create new object + * Create new object. * * @param string $type * @param string $name * @param \PhpOffice\PhpWord\PhpWord $phpWord * - * @throws \PhpOffice\PhpWord\Exception\Exception - * - * @return \PhpOffice\PhpWord\Writer\WriterInterface|\PhpOffice\PhpWord\Reader\ReaderInterface + * @return \PhpOffice\PhpWord\Reader\ReaderInterface|\PhpOffice\PhpWord\Writer\WriterInterface */ private static function createObject($type, $name, $phpWord = null) { @@ -75,14 +69,16 @@ abstract class IOFactory if (class_exists($class) && self::isConcreteClass($class)) { return new $class($phpWord); } + throw new Exception("\"{$name}\" is not a valid {$type}."); } /** - * Loads PhpWord from file + * Loads PhpWord from file. * * @param string $filename The name of the file * @param string $readerName + * * @return \PhpOffice\PhpWord\PhpWord $phpWord */ public static function load($filename, $readerName = 'Word2007') @@ -94,14 +90,15 @@ abstract class IOFactory } /** - * Check if it's a concrete class (not abstract nor interface) + * Check if it's a concrete class (not abstract nor interface). * * @param string $class + * * @return bool */ private static function isConcreteClass($class) { - $reflection = new \ReflectionClass($class); + $reflection = new ReflectionClass($class); return !$reflection->isAbstract() && !$reflection->isInterface(); } diff --git a/PhpOffice/PhpWord/LICENSE b/PhpOffice/PhpWord/LICENSE deleted file mode 100755 index 8a1acae..0000000 --- a/PhpOffice/PhpWord/LICENSE +++ /dev/null @@ -1,15 +0,0 @@ -PHPWord, a pure PHP library for reading and writing word processing documents. - -Copyright (c) 2010-2016 PHPWord. - -PHPWord is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License version 3 as published by -the Free Software Foundation. - -PHPWord is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License version 3 for more details. - -You should have received a copy of the GNU Lesser General Public License version 3 -along with PHPWord. If not, see . diff --git a/PhpOffice/PhpWord/Media.php b/PhpOffice/PhpWord/Media.php old mode 100755 new mode 100644 index cc1b290..22b0b06 --- a/PhpOffice/PhpWord/Media.php +++ b/PhpOffice/PhpWord/Media.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -21,19 +21,19 @@ use PhpOffice\PhpWord\Element\Image; use PhpOffice\PhpWord\Exception\Exception; /** - * Media collection + * Media collection. */ class Media { /** - * Media elements + * Media elements. * * @var array */ - private static $elements = array(); + private static $elements = []; /** - * Add new media element + * Add new media element. * * @since 0.10.0 * @since 0.9.2 @@ -43,31 +43,29 @@ class Media * @param string $source * @param \PhpOffice\PhpWord\Element\Image $image * - * @throws \PhpOffice\PhpWord\Exception\Exception - * * @return int */ - public static function addElement($container, $mediaType, $source, Image $image = null) + public static function addElement($container, $mediaType, $source, ?Image $image = null) { // Assign unique media Id and initiate media container if none exists $mediaId = md5($container . $source); if (!isset(self::$elements[$container])) { - self::$elements[$container] = array(); + self::$elements[$container] = []; } // Add media if not exists or point to existing media if (!isset(self::$elements[$container][$mediaId])) { $mediaCount = self::countElements($container); $mediaTypeCount = self::countElements($container, $mediaType); - $mediaTypeCount++; + ++$mediaTypeCount; $rId = ++$mediaCount; $target = null; - $mediaData = array('mediaIndex' => $mediaTypeCount); + $mediaData = ['mediaIndex' => $mediaTypeCount]; switch ($mediaType) { // Images case 'image': - if (is_null($image)) { + if (null === $image) { throw new Exception('Image object not assigned.'); } $isMemImage = $image->isMemImage(); @@ -82,14 +80,17 @@ class Media $target = "{$container}_image{$mediaTypeCount}.{$extension}"; $image->setTarget($target); $image->setMediaIndex($mediaTypeCount); + break; - // Objects + // Objects case 'object': $target = "{$container}_oleObject{$mediaTypeCount}.bin"; + break; - // Links + // Links case 'link': $target = $source; + break; } @@ -103,7 +104,7 @@ class Media } $mediaData = self::$elements[$container][$mediaId]; - if (!is_null($image)) { + if (null !== $image) { $image->setTarget($mediaData['target']); $image->setMediaIndex($mediaData['mediaIndex']); } @@ -112,11 +113,13 @@ class Media } /** - * Get media elements count + * Get media elements count. * * @param string $container section|headerx|footerx|footnote|endnote * @param string $mediaType image|object|link + * * @return int + * * @since 0.10.0 */ public static function countElements($container, $mediaType = null) @@ -125,12 +128,12 @@ class Media if (isset(self::$elements[$container])) { foreach (self::$elements[$container] as $mediaData) { - if (!is_null($mediaType)) { + if (null !== $mediaType) { if ($mediaType == $mediaData['type']) { - $mediaCount++; + ++$mediaCount; } } else { - $mediaCount++; + ++$mediaCount; } } } @@ -139,16 +142,18 @@ class Media } /** - * Get media elements + * Get media elements. * * @param string $container section|headerx|footerx|footnote|endnote * @param string $type image|object|link + * * @return array + * * @since 0.10.0 */ public static function getElements($container, $type = null) { - $elements = array(); + $elements = []; // If header/footer, search for headerx and footerx where x is number if ($container == 'header' || $container == 'footer') { @@ -169,16 +174,18 @@ class Media } /** - * Get elements by media type + * Get elements by media type. * * @param string $container section|footnote|endnote * @param string $type image|object|link + * * @return array + * * @since 0.11.0 Splitted from `getElements` to reduce complexity */ private static function getElementsByType($container, $type = null) { - $elements = array(); + $elements = []; foreach (self::$elements[$container] as $key => $data) { if ($type !== null) { @@ -194,172 +201,10 @@ class Media } /** - * Reset media elements + * Reset media elements. */ - public static function resetElements() + public static function resetElements(): void { - self::$elements = array(); - } - - /** - * Add new Section Media Element - * - * @deprecated 0.10.0 - * - * @param string $src - * @param string $type - * @param \PhpOffice\PhpWord\Element\Image $image - * - * @return int - * - * @codeCoverageIgnore - */ - public static function addSectionMediaElement($src, $type, Image $image = null) - { - return self::addElement('section', $type, $src, $image); - } - - /** - * Add new Section Link Element - * - * @deprecated 0.10.0 - * - * @param string $linkSrc - * - * @return int - * - * @codeCoverageIgnore - */ - public static function addSectionLinkElement($linkSrc) - { - return self::addElement('section', 'link', $linkSrc); - } - - /** - * Get Section Media Elements - * - * @deprecated 0.10.0 - * - * @param string $key - * - * @return array - * - * @codeCoverageIgnore - */ - public static function getSectionMediaElements($key = null) - { - return self::getElements('section', $key); - } - - /** - * Get Section Media Elements Count - * - * @deprecated 0.10.0 - * - * @param string $key - * - * @return int - * - * @codeCoverageIgnore - */ - public static function countSectionMediaElements($key = null) - { - return self::countElements('section', $key); - } - - /** - * Add new Header Media Element - * - * @deprecated 0.10.0 - * - * @param int $headerCount - * @param string $src - * @param \PhpOffice\PhpWord\Element\Image $image - * - * @return int - * - * @codeCoverageIgnore - */ - public static function addHeaderMediaElement($headerCount, $src, Image $image = null) - { - return self::addElement("header{$headerCount}", 'image', $src, $image); - } - - /** - * Get Header Media Elements Count - * - * @deprecated 0.10.0 - * - * @param string $key - * - * @return int - * - * @codeCoverageIgnore - */ - public static function countHeaderMediaElements($key) - { - return self::countElements($key); - } - - /** - * Get Header Media Elements - * - * @deprecated 0.10.0 - * - * @return array - * - * @codeCoverageIgnore - */ - public static function getHeaderMediaElements() - { - return self::getElements('header'); - } - - /** - * Add new Footer Media Element - * - * @deprecated 0.10.0 - * - * @param int $footerCount - * @param string $src - * @param \PhpOffice\PhpWord\Element\Image $image - * - * @return int - * - * @codeCoverageIgnore - */ - public static function addFooterMediaElement($footerCount, $src, Image $image = null) - { - return self::addElement("footer{$footerCount}", 'image', $src, $image); - } - - /** - * Get Footer Media Elements Count - * - * @deprecated 0.10.0 - * - * @param string $key - * - * @return int - * - * @codeCoverageIgnore - */ - public static function countFooterMediaElements($key) - { - return self::countElements($key); - } - - /** - * Get Footer Media Elements - * - * @deprecated 0.10.0 - * - * @return array - * - * @codeCoverageIgnore - */ - public static function getFooterMediaElements() - { - return self::getElements('footer'); + self::$elements = []; } } diff --git a/PhpOffice/PhpWord/Metadata/Compatibility.php b/PhpOffice/PhpWord/Metadata/Compatibility.php old mode 100755 new mode 100644 index bf0363a..b0bf140 --- a/PhpOffice/PhpWord/Metadata/Compatibility.php +++ b/PhpOffice/PhpWord/Metadata/Compatibility.php @@ -11,14 +11,14 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Metadata; /** - * Compatibility setting class + * Compatibility setting class. * * @since 0.12.0 * @see http://www.datypic.com/sc/ooxml/t-w_CT_Compat.html @@ -26,19 +26,20 @@ namespace PhpOffice\PhpWord\Metadata; class Compatibility { /** - * OOXML version + * OOXML version. * * 12 = 2007 * 14 = 2010 * 15 = 2013 * * @var int + * * @see http://msdn.microsoft.com/en-us/library/dd909048%28v=office.12%29.aspx */ private $ooxmlVersion = 12; /** - * Get OOXML version + * Get OOXML version. * * @return int */ @@ -48,9 +49,10 @@ class Compatibility } /** - * Set OOXML version + * Set OOXML version. * * @param int $value + * * @return self */ public function setOoxmlVersion($value) diff --git a/PhpOffice/PhpWord/Metadata/DocInfo.php b/PhpOffice/PhpWord/Metadata/DocInfo.php old mode 100755 new mode 100644 index 8a2f7d3..bdb2b7d --- a/PhpOffice/PhpWord/Metadata/DocInfo.php +++ b/PhpOffice/PhpWord/Metadata/DocInfo.php @@ -11,14 +11,16 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Metadata; +use DateTime; + /** - * Document information + * Document information. */ class DocInfo { @@ -31,91 +33,91 @@ class DocInfo const PROPERTY_TYPE_UNKNOWN = 'u'; /** - * Creator + * Creator. * * @var string */ private $creator; /** - * LastModifiedBy + * LastModifiedBy. * * @var string */ private $lastModifiedBy; /** - * Created + * Created. * * @var int */ private $created; /** - * Modified + * Modified. * * @var int */ private $modified; /** - * Title + * Title. * * @var string */ private $title; /** - * Description + * Description. * * @var string */ private $description; /** - * Subject + * Subject. * * @var string */ private $subject; /** - * Keywords + * Keywords. * * @var string */ private $keywords; /** - * Category + * Category. * * @var string */ private $category; /** - * Company + * Company. * * @var string */ private $company; /** - * Manager + * Manager. * * @var string */ private $manager; /** - * Custom Properties + * Custom Properties. * * @var array */ - private $customProperties = array(); + private $customProperties = []; /** - * Create new instance + * Create new instance. */ public function __construct() { @@ -133,7 +135,7 @@ class DocInfo } /** - * Get Creator + * Get Creator. * * @return string */ @@ -143,9 +145,10 @@ class DocInfo } /** - * Set Creator + * Set Creator. * * @param string $value + * * @return self */ public function setCreator($value = '') @@ -156,7 +159,7 @@ class DocInfo } /** - * Get Last Modified By + * Get Last Modified By. * * @return string */ @@ -166,9 +169,10 @@ class DocInfo } /** - * Set Last Modified By + * Set Last Modified By. * * @param string $value + * * @return self */ public function setLastModifiedBy($value = '') @@ -179,7 +183,7 @@ class DocInfo } /** - * Get Created + * Get Created. * * @return int */ @@ -189,9 +193,10 @@ class DocInfo } /** - * Set Created + * Set Created. * * @param int $value + * * @return self */ public function setCreated($value = null) @@ -202,7 +207,7 @@ class DocInfo } /** - * Get Modified + * Get Modified. * * @return int */ @@ -212,9 +217,10 @@ class DocInfo } /** - * Set Modified + * Set Modified. * * @param int $value + * * @return self */ public function setModified($value = null) @@ -225,7 +231,7 @@ class DocInfo } /** - * Get Title + * Get Title. * * @return string */ @@ -235,9 +241,10 @@ class DocInfo } /** - * Set Title + * Set Title. * * @param string $value + * * @return self */ public function setTitle($value = '') @@ -248,7 +255,7 @@ class DocInfo } /** - * Get Description + * Get Description. * * @return string */ @@ -258,9 +265,10 @@ class DocInfo } /** - * Set Description + * Set Description. * * @param string $value + * * @return self */ public function setDescription($value = '') @@ -271,7 +279,7 @@ class DocInfo } /** - * Get Subject + * Get Subject. * * @return string */ @@ -281,9 +289,10 @@ class DocInfo } /** - * Set Subject + * Set Subject. * * @param string $value + * * @return self */ public function setSubject($value = '') @@ -294,7 +303,7 @@ class DocInfo } /** - * Get Keywords + * Get Keywords. * * @return string */ @@ -304,9 +313,10 @@ class DocInfo } /** - * Set Keywords + * Set Keywords. * * @param string $value + * * @return self */ public function setKeywords($value = '') @@ -317,7 +327,7 @@ class DocInfo } /** - * Get Category + * Get Category. * * @return string */ @@ -327,9 +337,10 @@ class DocInfo } /** - * Set Category + * Set Category. * * @param string $value + * * @return self */ public function setCategory($value = '') @@ -340,7 +351,7 @@ class DocInfo } /** - * Get Company + * Get Company. * * @return string */ @@ -350,9 +361,10 @@ class DocInfo } /** - * Set Company + * Set Company. * * @param string $value + * * @return self */ public function setCompany($value = '') @@ -363,7 +375,7 @@ class DocInfo } /** - * Get Manager + * Get Manager. * * @return string */ @@ -373,9 +385,10 @@ class DocInfo } /** - * Set Manager + * Set Manager. * * @param string $value + * * @return self */ public function setManager($value = '') @@ -386,7 +399,7 @@ class DocInfo } /** - * Get a List of Custom Property Names + * Get a List of Custom Property Names. * * @return array of string */ @@ -396,9 +409,10 @@ class DocInfo } /** - * Check if a Custom Property is defined + * Check if a Custom Property is defined. * * @param string $propertyName + * * @return bool */ public function isCustomPropertySet($propertyName) @@ -407,9 +421,10 @@ class DocInfo } /** - * Get a Custom Property Value + * Get a Custom Property Value. * * @param string $propertyName + * * @return mixed */ public function getCustomPropertyValue($propertyName) @@ -422,9 +437,10 @@ class DocInfo } /** - * Get a Custom Property Type + * Get a Custom Property Type. * * @param string $propertyName + * * @return string */ public function getCustomPropertyType($propertyName) @@ -437,7 +453,7 @@ class DocInfo } /** - * Set a Custom Property + * Set a Custom Property. * * @param string $propertyName * @param mixed $propertyValue @@ -447,17 +463,18 @@ class DocInfo * 's': String * 'd': Date/Time * 'b': Boolean + * * @return self */ public function setCustomProperty($propertyName, $propertyValue = '', $propertyType = null) { - $propertyTypes = array( + $propertyTypes = [ self::PROPERTY_TYPE_INTEGER, self::PROPERTY_TYPE_FLOAT, self::PROPERTY_TYPE_STRING, self::PROPERTY_TYPE_DATE, self::PROPERTY_TYPE_BOOLEAN, - ); + ]; if (($propertyType === null) || (!in_array($propertyType, $propertyTypes))) { if ($propertyValue === null) { $propertyType = self::PROPERTY_TYPE_STRING; @@ -467,26 +484,27 @@ class DocInfo $propertyType = self::PROPERTY_TYPE_INTEGER; } elseif (is_bool($propertyValue)) { $propertyType = self::PROPERTY_TYPE_BOOLEAN; - } elseif ($propertyValue instanceof \DateTime) { + } elseif ($propertyValue instanceof DateTime) { $propertyType = self::PROPERTY_TYPE_DATE; } else { $propertyType = self::PROPERTY_TYPE_STRING; } } - $this->customProperties[$propertyName] = array( + $this->customProperties[$propertyName] = [ 'value' => $propertyValue, - 'type' => $propertyType, - ); + 'type' => $propertyType, + ]; return $this; } /** - * Convert document property based on type + * Convert document property based on type. * * @param string $propertyValue * @param string $propertyType + * * @return mixed */ public static function convertProperty($propertyValue, $propertyType) @@ -514,20 +532,21 @@ class DocInfo } /** - * Convert document property type + * Convert document property type. * * @param string $propertyType + * * @return string */ public static function convertPropertyType($propertyType) { - $typeGroups = array( - self::PROPERTY_TYPE_INTEGER => array('i1', 'i2', 'i4', 'i8', 'int', 'ui1', 'ui2', 'ui4', 'ui8', 'uint'), - self::PROPERTY_TYPE_FLOAT => array('r4', 'r8', 'decimal'), - self::PROPERTY_TYPE_STRING => array('empty', 'null', 'lpstr', 'lpwstr', 'bstr'), - self::PROPERTY_TYPE_DATE => array('date', 'filetime'), - self::PROPERTY_TYPE_BOOLEAN => array('bool'), - ); + $typeGroups = [ + self::PROPERTY_TYPE_INTEGER => ['i1', 'i2', 'i4', 'i8', 'int', 'ui1', 'ui2', 'ui4', 'ui8', 'uint'], + self::PROPERTY_TYPE_FLOAT => ['r4', 'r8', 'decimal'], + self::PROPERTY_TYPE_STRING => ['empty', 'null', 'lpstr', 'lpwstr', 'bstr'], + self::PROPERTY_TYPE_DATE => ['date', 'filetime'], + self::PROPERTY_TYPE_BOOLEAN => ['bool'], + ]; foreach ($typeGroups as $groupId => $groupMembers) { if (in_array($propertyType, $groupMembers)) { return $groupId; @@ -538,10 +557,11 @@ class DocInfo } /** - * Set default for null and empty value + * Set default for null and empty value. * * @param mixed $value * @param mixed $default + * * @return mixed */ private function setValue($value, $default) @@ -554,22 +574,23 @@ class DocInfo } /** - * Get conversion model depending on property type + * Get conversion model depending on property type. * * @param string $propertyType + * * @return string */ private static function getConversion($propertyType) { - $conversions = array( - 'empty' => array('empty'), - 'null' => array('null'), - 'int' => array('i1', 'i2', 'i4', 'i8', 'int'), - 'uint' => array('ui1', 'ui2', 'ui4', 'ui8', 'uint'), - 'float' => array('r4', 'r8', 'decimal'), - 'bool' => array('bool'), - 'date' => array('date', 'filetime'), - ); + $conversions = [ + 'empty' => ['empty'], + 'null' => ['null'], + 'int' => ['i1', 'i2', 'i4', 'i8', 'int'], + 'uint' => ['ui1', 'ui2', 'ui4', 'ui8', 'uint'], + 'float' => ['r4', 'r8', 'decimal'], + 'bool' => ['bool'], + 'date' => ['date', 'filetime'], + ]; foreach ($conversions as $conversion => $types) { if (in_array($propertyType, $types)) { return $conversion; diff --git a/PhpOffice/PhpWord/Metadata/Protection.php b/PhpOffice/PhpWord/Metadata/Protection.php old mode 100755 new mode 100644 index 15aa3ff..c9b3763 --- a/PhpOffice/PhpWord/Metadata/Protection.php +++ b/PhpOffice/PhpWord/Metadata/Protection.php @@ -11,17 +11,18 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Metadata; -use PhpOffice\Common\Microsoft\PasswordEncoder; +use InvalidArgumentException; +use PhpOffice\PhpWord\Shared\Microsoft\PasswordEncoder; use PhpOffice\PhpWord\SimpleType\DocProtect; /** - * Document protection class + * Document protection class. * * @since 0.12.0 * @see http://www.datypic.com/sc/ooxml/t-w_CT_DocProtect.html @@ -29,43 +30,44 @@ use PhpOffice\PhpWord\SimpleType\DocProtect; class Protection { /** - * Editing restriction none|readOnly|comments|trackedChanges|forms + * Editing restriction none|readOnly|comments|trackedChanges|forms. * * @var string + * * @see http://www.datypic.com/sc/ooxml/a-w_edit-1.html */ private $editing; /** - * password + * password. * * @var string */ private $password; /** - * Iterations to Run Hashing Algorithm + * Iterations to Run Hashing Algorithm. * * @var int */ private $spinCount = 100000; /** - * Cryptographic Hashing Algorithm (see constants defined in \PhpOffice\PhpWord\Shared\Microsoft\PasswordEncoder) + * Cryptographic Hashing Algorithm (see constants defined in \PhpOffice\PhpWord\Shared\Microsoft\PasswordEncoder). * * @var string */ private $algorithm = PasswordEncoder::ALGORITHM_SHA_1; /** - * Salt for Password Verifier + * Salt for Password Verifier. * * @var string */ private $salt; /** - * Create a new instance + * Create a new instance. * * @param string $editing */ @@ -77,7 +79,7 @@ class Protection } /** - * Get editing protection + * Get editing protection. * * @return string */ @@ -87,9 +89,10 @@ class Protection } /** - * Set editing protection + * Set editing protection. * * @param string $editing Any value of \PhpOffice\PhpWord\SimpleType\DocProtect + * * @return self */ public function setEditing($editing = null) @@ -101,7 +104,7 @@ class Protection } /** - * Get password + * Get password. * * @return string */ @@ -111,9 +114,10 @@ class Protection } /** - * Set password + * Set password. * * @param string $password + * * @return self */ public function setPassword($password) @@ -124,7 +128,7 @@ class Protection } /** - * Get count for hash iterations + * Get count for hash iterations. * * @return int */ @@ -134,9 +138,10 @@ class Protection } /** - * Set count for hash iterations + * Set count for hash iterations. * * @param int $spinCount + * * @return self */ public function setSpinCount($spinCount) @@ -147,7 +152,7 @@ class Protection } /** - * Get algorithm + * Get algorithm. * * @return string */ @@ -157,9 +162,10 @@ class Protection } /** - * Set algorithm + * Set algorithm. * * @param string $algorithm + * * @return self */ public function setAlgorithm($algorithm) @@ -170,7 +176,7 @@ class Protection } /** - * Get salt + * Get salt. * * @return string */ @@ -183,13 +189,13 @@ class Protection * Set salt. Salt HAS to be 16 characters, or an exception will be thrown. * * @param string $salt - * @throws \InvalidArgumentException + * * @return self */ public function setSalt($salt) { if ($salt !== null && strlen($salt) !== 16) { - throw new \InvalidArgumentException('salt has to be of exactly 16 bytes length'); + throw new InvalidArgumentException('salt has to be of exactly 16 bytes length'); } $this->salt = $salt; diff --git a/PhpOffice/PhpWord/Metadata/Settings.php b/PhpOffice/PhpWord/Metadata/Settings.php old mode 100755 new mode 100644 index b1552e0..e0e5ae0 --- a/PhpOffice/PhpWord/Metadata/Settings.php +++ b/PhpOffice/PhpWord/Metadata/Settings.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -23,7 +23,7 @@ use PhpOffice\PhpWord\SimpleType\Zoom; use PhpOffice\PhpWord\Style\Language; /** - * Setting class + * Setting class. * * @since 0.14.0 * @see http://www.datypic.com/sc/ooxml/t-w_CT_Settings.html @@ -31,72 +31,74 @@ use PhpOffice\PhpWord\Style\Language; class Settings { /** - * Magnification Setting + * Magnification Setting. * * @see http://www.datypic.com/sc/ooxml/e-w_zoom-1.html + * * @var mixed either integer, in which case it treated as a percent, or one of PhpOffice\PhpWord\SimpleType\Zoom */ private $zoom = 100; /** - * Mirror Page Margins + * Mirror Page Margins. * * @see http://www.datypic.com/sc/ooxml/e-w_mirrorMargins-1.html + * * @var bool */ private $mirrorMargins; /** - * Hide spelling errors + * Hide spelling errors. * * @var bool */ private $hideSpellingErrors = false; /** - * Hide grammatical errors + * Hide grammatical errors. * * @var bool */ private $hideGrammaticalErrors = false; /** - * Visibility of Annotation Types + * Visibility of Annotation Types. * * @var TrackChangesView */ private $revisionView; /** - * Track Revisions to Document + * Track Revisions to Document. * * @var bool */ private $trackRevisions = false; /** - * Do Not Use Move Syntax When Tracking Revisions + * Do Not Use Move Syntax When Tracking Revisions. * * @var bool */ private $doNotTrackMoves = false; /** - * Do Not Track Formatting Revisions When Tracking Revisions + * Do Not Track Formatting Revisions When Tracking Revisions. * * @var bool */ private $doNotTrackFormatting = false; /** - * Spelling and Grammatical Checking State + * Spelling and Grammatical Checking State. * * @var \PhpOffice\PhpWord\ComplexType\ProofState */ private $proofState; /** - * Document Editing Restrictions + * Document Editing Restrictions. * * @var \PhpOffice\PhpWord\Metadata\Protection */ @@ -110,49 +112,51 @@ class Settings private $evenAndOddHeaders = false; /** - * Theme Font Languages + * Theme Font Languages. * * @var Language */ private $themeFontLang; /** - * Automatically Recalculate Fields on Open + * Automatically Recalculate Fields on Open. * * @var bool */ private $updateFields = false; /** - * Radix Point for Field Code Evaluation + * Radix Point for Field Code Evaluation. * * @var string */ private $decimalSymbol = '.'; /** - * Automatically hyphenate document contents when displayed + * Automatically hyphenate document contents when displayed. * - * @var bool|null + * @var null|bool */ private $autoHyphenation; /** - * Maximum number of consecutively hyphenated lines + * Maximum number of consecutively hyphenated lines. * - * @var int|null + * @var null|int */ private $consecutiveHyphenLimit; /** - * The allowed amount of whitespace before hyphenation is applied - * @var float|null + * The allowed amount of whitespace before hyphenation is applied. + * + * @var null|float */ private $hyphenationZone; /** - * Do not hyphenate words in all capital letters - * @var bool|null + * Do not hyphenate words in all capital letters. + * + * @var null|bool */ private $doNotHyphenateCaps; @@ -171,7 +175,7 @@ class Settings /** * @param Protection $documentProtection */ - public function setDocumentProtection($documentProtection) + public function setDocumentProtection($documentProtection): void { $this->documentProtection = $documentProtection; } @@ -191,13 +195,13 @@ class Settings /** * @param ProofState $proofState */ - public function setProofState($proofState) + public function setProofState($proofState): void { $this->proofState = $proofState; } /** - * Are spelling errors hidden + * Are spelling errors hidden. * * @return bool */ @@ -207,17 +211,17 @@ class Settings } /** - * Hide spelling errors + * Hide spelling errors. * * @param bool $hideSpellingErrors */ - public function setHideSpellingErrors($hideSpellingErrors) + public function setHideSpellingErrors($hideSpellingErrors): void { $this->hideSpellingErrors = $hideSpellingErrors === null ? true : $hideSpellingErrors; } /** - * Are grammatical errors hidden + * Are grammatical errors hidden. * * @return bool */ @@ -227,11 +231,11 @@ class Settings } /** - * Hide grammatical errors + * Hide grammatical errors. * * @param bool $hideGrammaticalErrors */ - public function setHideGrammaticalErrors($hideGrammaticalErrors) + public function setHideGrammaticalErrors($hideGrammaticalErrors): void { $this->hideGrammaticalErrors = $hideGrammaticalErrors === null ? true : $hideGrammaticalErrors; } @@ -247,13 +251,13 @@ class Settings /** * @param bool $evenAndOddHeaders */ - public function setEvenAndOddHeaders($evenAndOddHeaders) + public function setEvenAndOddHeaders($evenAndOddHeaders): void { $this->evenAndOddHeaders = $evenAndOddHeaders === null ? true : $evenAndOddHeaders; } /** - * Get the Visibility of Annotation Types + * Get the Visibility of Annotation Types. * * @return \PhpOffice\PhpWord\ComplexType\TrackChangesView */ @@ -263,11 +267,11 @@ class Settings } /** - * Set the Visibility of Annotation Types + * Set the Visibility of Annotation Types. * * @param TrackChangesView $trackChangesView */ - public function setRevisionView(TrackChangesView $trackChangesView = null) + public function setRevisionView(?TrackChangesView $trackChangesView = null): void { $this->revisionView = $trackChangesView; } @@ -283,7 +287,7 @@ class Settings /** * @param bool $trackRevisions */ - public function setTrackRevisions($trackRevisions) + public function setTrackRevisions($trackRevisions): void { $this->trackRevisions = $trackRevisions === null ? true : $trackRevisions; } @@ -299,7 +303,7 @@ class Settings /** * @param bool $doNotTrackMoves */ - public function setDoNotTrackMoves($doNotTrackMoves) + public function setDoNotTrackMoves($doNotTrackMoves): void { $this->doNotTrackMoves = $doNotTrackMoves === null ? true : $doNotTrackMoves; } @@ -315,7 +319,7 @@ class Settings /** * @param bool $doNotTrackFormatting */ - public function setDoNotTrackFormatting($doNotTrackFormatting) + public function setDoNotTrackFormatting($doNotTrackFormatting): void { $this->doNotTrackFormatting = $doNotTrackFormatting === null ? true : $doNotTrackFormatting; } @@ -331,7 +335,7 @@ class Settings /** * @param mixed $zoom */ - public function setZoom($zoom) + public function setZoom($zoom): void { if (is_numeric($zoom)) { // zoom is a percentage @@ -353,13 +357,13 @@ class Settings /** * @param bool $mirrorMargins */ - public function setMirrorMargins($mirrorMargins) + public function setMirrorMargins($mirrorMargins): void { $this->mirrorMargins = $mirrorMargins; } /** - * Returns the Language + * Returns the Language. * * @return Language */ @@ -369,11 +373,11 @@ class Settings } /** - * sets the Language for this document + * sets the Language for this document. * * @param Language $themeFontLang */ - public function setThemeFontLang($themeFontLang) + public function setThemeFontLang($themeFontLang): void { $this->themeFontLang = $themeFontLang; } @@ -389,13 +393,13 @@ class Settings /** * @param bool $updateFields */ - public function setUpdateFields($updateFields) + public function setUpdateFields($updateFields): void { $this->updateFields = $updateFields === null ? false : $updateFields; } /** - * Returns the Radix Point for Field Code Evaluation + * Returns the Radix Point for Field Code Evaluation. * * @return string */ @@ -405,17 +409,17 @@ class Settings } /** - * sets the Radix Point for Field Code Evaluation + * sets the Radix Point for Field Code Evaluation. * * @param string $decimalSymbol */ - public function setDecimalSymbol($decimalSymbol) + public function setDecimalSymbol($decimalSymbol): void { $this->decimalSymbol = $decimalSymbol; } /** - * @return bool|null + * @return null|bool */ public function hasAutoHyphenation() { @@ -425,13 +429,13 @@ class Settings /** * @param bool $autoHyphenation */ - public function setAutoHyphenation($autoHyphenation) + public function setAutoHyphenation($autoHyphenation): void { $this->autoHyphenation = (bool) $autoHyphenation; } /** - * @return int|null + * @return null|int */ public function getConsecutiveHyphenLimit() { @@ -441,13 +445,13 @@ class Settings /** * @param int $consecutiveHyphenLimit */ - public function setConsecutiveHyphenLimit($consecutiveHyphenLimit) + public function setConsecutiveHyphenLimit($consecutiveHyphenLimit): void { $this->consecutiveHyphenLimit = (int) $consecutiveHyphenLimit; } /** - * @return float|null + * @return null|float */ public function getHyphenationZone() { @@ -457,7 +461,7 @@ class Settings /** * @param float $hyphenationZone Measurement unit is twip */ - public function setHyphenationZone($hyphenationZone) + public function setHyphenationZone($hyphenationZone): void { $this->hyphenationZone = $hyphenationZone; } @@ -473,7 +477,7 @@ class Settings /** * @param bool $doNotHyphenateCaps */ - public function setDoNotHyphenateCaps($doNotHyphenateCaps) + public function setDoNotHyphenateCaps($doNotHyphenateCaps): void { $this->doNotHyphenateCaps = (bool) $doNotHyphenateCaps; } diff --git a/PhpOffice/PhpWord/PhpWord.php b/PhpOffice/PhpWord/PhpWord.php old mode 100755 new mode 100644 index a78df2c..70ee652 --- a/PhpOffice/PhpWord/PhpWord.php +++ b/PhpOffice/PhpWord/PhpWord.php @@ -11,17 +11,18 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 -*/ + */ namespace PhpOffice\PhpWord; +use BadMethodCallException; use PhpOffice\PhpWord\Element\Section; use PhpOffice\PhpWord\Exception\Exception; /** - * PHPWord main class + * PHPWord main class. * * @method Collection\Titles getTitles() * @method Collection\Footnotes getFootnotes() @@ -34,7 +35,6 @@ use PhpOffice\PhpWord\Exception\Exception; * @method int addEndnote(Element\Endnote $endnote) * @method int addChart(Element\Chart $chart) * @method int addComment(Element\Comment $comment) - * * @method Style\Paragraph addParagraphStyle(string $styleName, mixed $styles) * @method Style\Font addFontStyle(string $styleName, mixed $fontStyle, mixed $paragraphStyle = null) * @method Style\Font addLinkStyle(string $styleName, mixed $styles) @@ -45,50 +45,30 @@ use PhpOffice\PhpWord\Exception\Exception; class PhpWord { /** - * Default font settings - * - * @deprecated 0.11.0 Use Settings constants - * - * @const string|int - */ - const DEFAULT_FONT_NAME = Settings::DEFAULT_FONT_NAME; - /** - * @deprecated 0.11.0 Use Settings constants - */ - const DEFAULT_FONT_SIZE = Settings::DEFAULT_FONT_SIZE; - /** - * @deprecated 0.11.0 Use Settings constants - */ - const DEFAULT_FONT_COLOR = Settings::DEFAULT_FONT_COLOR; - /** - * @deprecated 0.11.0 Use Settings constants - */ - const DEFAULT_FONT_CONTENT_TYPE = Settings::DEFAULT_FONT_CONTENT_TYPE; - - /** - * Collection of sections + * Collection of sections. * * @var \PhpOffice\PhpWord\Element\Section[] */ - private $sections = array(); + private $sections = []; /** - * Collections + * Collections. * * @var array */ - private $collections = array(); + private $collections = []; /** - * Metadata + * Metadata. * * @var array + * * @since 0.12.0 */ - private $metadata = array(); + private $metadata = []; /** - * Create new instance + * Create new instance. * * Collections are created dynamically */ @@ -99,14 +79,14 @@ class PhpWord Style::resetStyles(); // Collection - $collections = array('Bookmarks', 'Titles', 'Footnotes', 'Endnotes', 'Charts', 'Comments'); + $collections = ['Bookmarks', 'Titles', 'Footnotes', 'Endnotes', 'Charts', 'Comments']; foreach ($collections as $collection) { $class = 'PhpOffice\\PhpWord\\Collection\\' . $collection; $this->collections[$collection] = new $class(); } // Metadata - $metadata = array('DocInfo', 'Settings', 'Compatibility'); + $metadata = ['DocInfo', 'Settings', 'Compatibility']; foreach ($metadata as $meta) { $class = 'PhpOffice\\PhpWord\\Metadata\\' . $meta; $this->metadata[$meta] = new $class(); @@ -114,32 +94,30 @@ class PhpWord } /** - * Dynamic function call to reduce static dependency + * Dynamic function call to reduce static dependency. * * @since 0.12.0 * * @param mixed $function * @param mixed $args * - * @throws \BadMethodCallException - * * @return mixed */ public function __call($function, $args) { $function = strtolower($function); - $getCollection = array(); - $addCollection = array(); - $addStyle = array(); + $getCollection = []; + $addCollection = []; + $addStyle = []; - $collections = array('Bookmark', 'Title', 'Footnote', 'Endnote', 'Chart', 'Comment'); + $collections = ['Bookmark', 'Title', 'Footnote', 'Endnote', 'Chart', 'Comment']; foreach ($collections as $collection) { $getCollection[] = strtolower("get{$collection}s"); $addCollection[] = strtolower("add{$collection}"); } - $styles = array('Paragraph', 'Font', 'Table', 'Numbering', 'Link', 'Title'); + $styles = ['Paragraph', 'Font', 'Table', 'Numbering', 'Link', 'Title']; foreach ($styles as $style) { $addStyle[] = strtolower("add{$style}Style"); } @@ -158,20 +136,20 @@ class PhpWord /** @var \PhpOffice\PhpWord\Collection\AbstractCollection $collectionObject */ $collectionObject = $this->collections[$key]; - return $collectionObject->addItem(isset($args[0]) ? $args[0] : null); + return $collectionObject->addItem($args[0] ?? null); } // Run add style method if (in_array($function, $addStyle)) { - return forward_static_call_array(array('PhpOffice\\PhpWord\\Style', $function), $args); + return forward_static_call_array(['PhpOffice\\PhpWord\\Style', $function], $args); } // Exception - throw new \BadMethodCallException("Method $function is not defined."); + throw new BadMethodCallException("Method $function is not defined."); } /** - * Get document properties object + * Get document properties object. * * @return \PhpOffice\PhpWord\Metadata\DocInfo */ @@ -181,22 +159,10 @@ class PhpWord } /** - * Get protection - * - * @return \PhpOffice\PhpWord\Metadata\Protection - * @since 0.12.0 - * @deprecated Get the Document protection from PhpWord->getSettings()->getDocumentProtection(); - * @codeCoverageIgnore - */ - public function getProtection() - { - return $this->getSettings()->getDocumentProtection(); - } - - /** - * Get compatibility + * Get compatibility. * * @return \PhpOffice\PhpWord\Metadata\Compatibility + * * @since 0.12.0 */ public function getCompatibility() @@ -205,9 +171,10 @@ class PhpWord } /** - * Get compatibility + * Get compatibility. * * @return \PhpOffice\PhpWord\Metadata\Settings + * * @since 0.14.0 */ public function getSettings() @@ -216,7 +183,7 @@ class PhpWord } /** - * Get all sections + * Get all sections. * * @return \PhpOffice\PhpWord\Element\Section[] */ @@ -226,10 +193,11 @@ class PhpWord } /** - * Returns the section at the requested position + * Returns the section at the requested position. * * @param int $index - * @return \PhpOffice\PhpWord\Element\Section|null + * + * @return null|\PhpOffice\PhpWord\Element\Section */ public function getSection($index) { @@ -241,9 +209,10 @@ class PhpWord } /** - * Create new section + * Create new section. * * @param array $style + * * @return \PhpOffice\PhpWord\Element\Section */ public function addSection($style = null) @@ -256,18 +225,19 @@ class PhpWord } /** - * Sorts the sections using the callable passed + * Sorts the sections using the callable passed. * * @see http://php.net/manual/en/function.usort.php for usage + * * @param callable $sorter */ - public function sortSections($sorter) + public function sortSections($sorter): void { usort($this->sections, $sorter); } /** - * Get default font name + * Get default font name. * * @return string */ @@ -281,13 +251,13 @@ class PhpWord * * @param string $fontName */ - public function setDefaultFontName($fontName) + public function setDefaultFontName($fontName): void { Settings::setDefaultFontName($fontName); } /** - * Get default font size + * Get default font size. * * @return int */ @@ -301,15 +271,16 @@ class PhpWord * * @param int $fontSize */ - public function setDefaultFontSize($fontSize) + public function setDefaultFontSize($fontSize): void { Settings::setDefaultFontSize($fontSize); } /** - * Set default paragraph style definition to styles.xml + * Set default paragraph style definition to styles.xml. * * @param array $styles Paragraph style definition + * * @return \PhpOffice\PhpWord\Style\Paragraph */ public function setDefaultParagraphStyle($styles) @@ -318,45 +289,25 @@ class PhpWord } /** - * Load template by filename - * - * @deprecated 0.12.0 Use `new TemplateProcessor($documentTemplate)` instead. - * - * @param string $filename Fully qualified filename - * - * @throws \PhpOffice\PhpWord\Exception\Exception - * - * @return TemplateProcessor - * - * @codeCoverageIgnore - */ - public function loadTemplate($filename) - { - if (file_exists($filename)) { - return new TemplateProcessor($filename); - } - throw new Exception("Template file {$filename} not found."); - } - - /** - * Save to file or download + * Save to file or download. * * All exceptions should already been handled by the writers * * @param string $filename * @param string $format * @param bool $download + * * @return bool */ public function save($filename, $format = 'Word2007', $download = false) { - $mime = array( - 'Word2007' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - 'ODText' => 'application/vnd.oasis.opendocument.text', - 'RTF' => 'application/rtf', - 'HTML' => 'text/html', - 'PDF' => 'application/pdf', - ); + $mime = [ + 'Word2007' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'ODText' => 'application/vnd.oasis.opendocument.text', + 'RTF' => 'application/rtf', + 'HTML' => 'text/html', + 'PDF' => 'application/pdf', + ]; $writer = IOFactory::createWriter($this, $format); @@ -374,52 +325,4 @@ class PhpWord return true; } - - /** - * Create new section - * - * @deprecated 0.10.0 - * - * @param array $settings - * - * @return \PhpOffice\PhpWord\Element\Section - * - * @codeCoverageIgnore - */ - public function createSection($settings = null) - { - return $this->addSection($settings); - } - - /** - * Get document properties object - * - * @deprecated 0.12.0 - * - * @return \PhpOffice\PhpWord\Metadata\DocInfo - * - * @codeCoverageIgnore - */ - public function getDocumentProperties() - { - return $this->getDocInfo(); - } - - /** - * Set document properties object - * - * @deprecated 0.12.0 - * - * @param \PhpOffice\PhpWord\Metadata\DocInfo $documentProperties - * - * @return self - * - * @codeCoverageIgnore - */ - public function setDocumentProperties($documentProperties) - { - $this->metadata['Document'] = $documentProperties; - - return $this; - } } diff --git a/PhpOffice/PhpWord/Reader/AbstractReader.php b/PhpOffice/PhpWord/Reader/AbstractReader.php old mode 100755 new mode 100644 index 7db285f..ff768e3 --- a/PhpOffice/PhpWord/Reader/AbstractReader.php +++ b/PhpOffice/PhpWord/Reader/AbstractReader.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -20,7 +20,7 @@ namespace PhpOffice\PhpWord\Reader; use PhpOffice\PhpWord\Exception\Exception; /** - * Reader abstract class + * Reader abstract class. * * @since 0.8.0 * @@ -36,7 +36,7 @@ abstract class AbstractReader implements ReaderInterface protected $readDataOnly = true; /** - * File pointer + * File pointer. * * @var bool|resource */ @@ -54,9 +54,10 @@ abstract class AbstractReader implements ReaderInterface } /** - * Set read data only + * Set read data only. * * @param bool $value + * * @return self */ public function setReadDataOnly($value = true) @@ -67,12 +68,10 @@ abstract class AbstractReader implements ReaderInterface } /** - * Open file for reading + * Open file for reading. * * @param string $filename * - * @throws \PhpOffice\PhpWord\Exception\Exception - * * @return resource */ protected function openFile($filename) @@ -83,7 +82,7 @@ abstract class AbstractReader implements ReaderInterface } // Open file - $this->fileHandle = fopen($filename, 'r'); + $this->fileHandle = fopen($filename, 'rb'); if ($this->fileHandle === false) { throw new Exception("Could not open file $filename for reading."); } @@ -93,6 +92,7 @@ abstract class AbstractReader implements ReaderInterface * Can the current ReaderInterface read the file? * * @param string $filename + * * @return bool */ public function canRead($filename) @@ -109,16 +109,4 @@ abstract class AbstractReader implements ReaderInterface return true; } - - /** - * Read data only? - * - * @deprecated 0.10.0 - * - * @codeCoverageIgnore - */ - public function getReadDataOnly() - { - return $this->isReadDataOnly(); - } } diff --git a/PhpOffice/PhpWord/Reader/HTML.php b/PhpOffice/PhpWord/Reader/HTML.php old mode 100755 new mode 100644 index db9f208..30257d9 --- a/PhpOffice/PhpWord/Reader/HTML.php +++ b/PhpOffice/PhpWord/Reader/HTML.php @@ -11,29 +11,28 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Reader; +use Exception; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Shared\Html as HTMLParser; /** - * HTML Reader class + * HTML Reader class. * * @since 0.11.0 */ class HTML extends AbstractReader implements ReaderInterface { /** - * Loads PhpWord from file + * Loads PhpWord from file. * * @param string $docFile * - * @throws \Exception - * * @return \PhpOffice\PhpWord\PhpWord */ public function load($docFile) @@ -44,7 +43,7 @@ class HTML extends AbstractReader implements ReaderInterface $section = $phpWord->addSection(); HTMLParser::addHtml($section, file_get_contents($docFile), true); } else { - throw new \Exception("Cannot read {$docFile}."); + throw new Exception("Cannot read {$docFile}."); } return $phpWord; diff --git a/PhpOffice/PhpWord/Reader/MsDoc.php b/PhpOffice/PhpWord/Reader/MsDoc.php old mode 100755 new mode 100644 index 1a9e498..68c732f --- a/PhpOffice/PhpWord/Reader/MsDoc.php +++ b/PhpOffice/PhpWord/Reader/MsDoc.php @@ -11,75 +11,76 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Reader; -use PhpOffice\Common\Drawing; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Shared\Drawing; use PhpOffice\PhpWord\Shared\OLERead; use PhpOffice\PhpWord\Style; +use stdClass; /** - * Reader for Word97 + * Reader for Word97. * * @since 0.10.0 */ class MsDoc extends AbstractReader implements ReaderInterface { /** - * PhpWord object + * PhpWord object. * * @var PhpWord */ private $phpWord; /** - * WordDocument Stream - * - * @var + * WordDocument Stream. */ private $dataWorkDocument; + /** - * 1Table Stream - * - * @var + * 1Table Stream. */ private $data1Table; + /** - * Data Stream - * - * @var + * Data Stream. */ private $dataData; + /** - * Object Pool Stream - * - * @var + * Object Pool Stream. */ private $dataObjectPool; + /** - * @var \stdClass[] + * @var stdClass[] */ - private $arrayCharacters = array(); + private $arrayCharacters = []; + /** * @var array */ - private $arrayFib = array(); + private $arrayFib = []; + /** * @var string[] */ - private $arrayFonts = array(); + private $arrayFonts = []; + /** * @var string[] */ - private $arrayParagraphs = array(); + private $arrayParagraphs = []; + /** - * @var \stdClass[] + * @var stdClass[] */ - private $arraySections = array(); + private $arraySections = []; const VERSION_97 = '97'; const VERSION_2000 = '2000'; @@ -111,9 +112,10 @@ class MsDoc extends AbstractReader implements ReaderInterface const MSOBLIPCMYKJPEG = 0x12; /** - * Loads PhpWord from file + * Loads PhpWord from file. * * @param string $filename + * * @return PhpWord */ public function load($filename) @@ -129,10 +131,11 @@ class MsDoc extends AbstractReader implements ReaderInterface } /** - * Load an OLE Document + * Load an OLE Document. + * * @param string $filename */ - private function loadOLE($filename) + private function loadOLE($filename): void { // OLE reader $ole = new OLERead(); @@ -159,8 +162,8 @@ class MsDoc extends AbstractReader implements ReaderInterface private function getArrayCP($data, $posMem, $iNum) { - $arrayCP = array(); - for ($inc = 0; $inc < $iNum; $inc++) { + $arrayCP = []; + for ($inc = 0; $inc < $iNum; ++$inc) { $arrayCP[$inc] = self::getInt4d($data, $posMem); $posMem += 4; } @@ -171,6 +174,7 @@ class MsDoc extends AbstractReader implements ReaderInterface /** * @see http://msdn.microsoft.com/en-us/library/dd949344%28v=office.12%29.aspx * @see https://igor.io/2012/09/24/binary-parsing.html + * * @param string $data */ private function readFib($data) @@ -208,7 +212,7 @@ class MsDoc extends AbstractReader implements ReaderInterface // lKey $pos += 4; // envr - $pos += 1; + ++$pos; // $mem = self::getInt1d($data, $pos); // $fMac = ($mem >> 7) & 1; @@ -217,7 +221,7 @@ class MsDoc extends AbstractReader implements ReaderInterface // $reserved1 = ($mem >> 4) & 1; // $reserved2 = ($mem >> 3) & 1; // $fSpare0 = ($mem >> 0) & bindec('111'); - $pos += 1; + ++$pos; // reserved3 $pos += 2; @@ -317,21 +321,25 @@ class MsDoc extends AbstractReader implements ReaderInterface switch ($cbRgFcLcb) { case 0x005D: $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_97); + break; case 0x006C: $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_97); $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_2000); + break; case 0x0088: $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_97); $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_2000); $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_2002); + break; case 0x00A4: $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_97); $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_2000); $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_2002); $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_2003); + break; case 0x00B7: $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_97); @@ -339,6 +347,7 @@ class MsDoc extends AbstractReader implements ReaderInterface $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_2002); $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_2003); $pos = $this->readBlockFibRgFcLcb($data, $pos, self::VERSION_2007); + break; } //----- cswNew @@ -1100,7 +1109,7 @@ class MsDoc extends AbstractReader implements ReaderInterface return $pos; } - private function readFibContent() + private function readFibContent(): void { // Informations about Font $this->readRecordSttbfFfn(); @@ -1120,15 +1129,16 @@ class MsDoc extends AbstractReader implements ReaderInterface } /** - * Section and information about them + * Section and information about them. + * * @see : http://msdn.microsoft.com/en-us/library/dd924458%28v=office.12%29.aspx */ - private function readRecordPlcfSed() + private function readRecordPlcfSed(): void { $posMem = $this->arrayFib['fcPlcfSed']; // PlcfSed // PlcfSed : aCP - $aCP = array(); + $aCP = []; $aCP[0] = self::getInt4d($this->data1Table, $posMem); $posMem += 4; $aCP[1] = self::getInt4d($this->data1Table, $posMem); @@ -1138,7 +1148,7 @@ class MsDoc extends AbstractReader implements ReaderInterface //@see : http://msdn.microsoft.com/en-us/library/dd950194%28v=office.12%29.aspx $numSed = $this->getNumInLcb($this->arrayFib['lcbPlcfSed'], 12); - $aSed = array(); + $aSed = []; for ($iInc = 0; $iInc < $numSed; ++$iInc) { // Sed : http://msdn.microsoft.com/en-us/library/dd950982%28v=office.12%29.aspx // fn @@ -1165,10 +1175,11 @@ class MsDoc extends AbstractReader implements ReaderInterface } /** - * Specifies the fonts that are used in the document + * Specifies the fonts that are used in the document. + * * @see : http://msdn.microsoft.com/en-us/library/dd943880%28v=office.12%29.aspx */ - private function readRecordSttbfFfn() + private function readRecordSttbfFfn(): void { $posMem = $this->arrayFib['fcSttbfFfn']; @@ -1178,18 +1189,18 @@ class MsDoc extends AbstractReader implements ReaderInterface $posMem += 2; if ($cData < 0x7FF0 && $cbExtra == 0) { - for ($inc = 0; $inc < $cData; $inc++) { + for ($inc = 0; $inc < $cData; ++$inc) { // len - $posMem += 1; + ++$posMem; // ffid - $posMem += 1; + ++$posMem; // wWeight (400 : Normal - 700 bold) $posMem += 2; // chs - $posMem += 1; + ++$posMem; // ixchSzAlt $ixchSzAlt = self::getInt1d($this->data1Table, $posMem); - $posMem += 1; + ++$posMem; // panose $posMem += 10; // fs @@ -1215,19 +1226,20 @@ class MsDoc extends AbstractReader implements ReaderInterface $xszAlt .= chr($char); } while ($char != 0); } - $this->arrayFonts[] = array( + $this->arrayFonts[] = [ 'main' => $xszFfn, - 'alt' => $xszAlt, - ); + 'alt' => $xszAlt, + ]; } } } /** - * Paragraph and information about them + * Paragraph and information about them. + * * @see http://msdn.microsoft.com/en-us/library/dd908569%28v=office.12%29.aspx */ - private function readRecordPlcfBtePapx() + private function readRecordPlcfBtePapx(): void { $posMem = $this->arrayFib['fcPlcfBtePapx']; $num = $this->getNumInLcb($this->arrayFib['lcbPlcfBtePapx'], 4); @@ -1242,16 +1254,16 @@ class MsDoc extends AbstractReader implements ReaderInterface $string = ''; $numRun = self::getInt1d($this->dataWorkDocument, $offset + 511); - $arrayRGFC = array(); - for ($inc = 0; $inc <= $numRun; $inc++) { + $arrayRGFC = []; + for ($inc = 0; $inc <= $numRun; ++$inc) { $arrayRGFC[$inc] = self::getInt4d($this->dataWorkDocument, $offset); $offset += 4; } - $arrayRGB = array(); - for ($inc = 1; $inc <= $numRun; $inc++) { + $arrayRGB = []; + for ($inc = 1; $inc <= $numRun; ++$inc) { // @see http://msdn.microsoft.com/en-us/library/dd925804(v=office.12).aspx $arrayRGB[$inc] = self::getInt1d($this->dataWorkDocument, $offset); - $offset += 1; + ++$offset; // reserved $offset += 12; } @@ -1261,7 +1273,7 @@ class MsDoc extends AbstractReader implements ReaderInterface break; } $strLen = $arrayRGFC[$key + 1] - $arrayRGFC[$key] - 1; - for ($inc = 0; $inc < $strLen; $inc++) { + for ($inc = 0; $inc < $strLen; ++$inc) { $byte = self::getInt1d($this->dataWorkDocument, $arrayRGFC[$key] + $inc); if ($byte > 0) { $string .= chr($byte); @@ -1436,15 +1448,16 @@ class MsDoc extends AbstractReader implements ReaderInterface } /** - * Character formatting properties to text in a document + * Character formatting properties to text in a document. + * * @see http://msdn.microsoft.com/en-us/library/dd907108%28v=office.12%29.aspx */ - private function readRecordPlcfBteChpx() + private function readRecordPlcfBteChpx(): void { $posMem = $this->arrayFib['fcPlcfBteChpx']; $num = $this->getNumInLcb($this->arrayFib['lcbPlcfBteChpx'], 4); - $aPnBteChpx = array(); - for ($inc = 0; $inc <= $num; $inc++) { + $aPnBteChpx = []; + for ($inc = 0; $inc <= $num; ++$inc) { $aPnBteChpx[$inc] = self::getInt4d($this->data1Table, $posMem); $posMem += 4; } @@ -1457,21 +1470,21 @@ class MsDoc extends AbstractReader implements ReaderInterface // ChpxFkp // @see : http://msdn.microsoft.com/en-us/library/dd910989%28v=office.12%29.aspx $numRGFC = self::getInt1d($this->dataWorkDocument, $offset + 511); - $arrayRGFC = array(); - for ($inc = 0; $inc <= $numRGFC; $inc++) { + $arrayRGFC = []; + for ($inc = 0; $inc <= $numRGFC; ++$inc) { $arrayRGFC[$inc] = self::getInt4d($this->dataWorkDocument, $offset); $offset += 4; } - $arrayRGB = array(); - for ($inc = 1; $inc <= $numRGFC; $inc++) { + $arrayRGB = []; + for ($inc = 1; $inc <= $numRGFC; ++$inc) { $arrayRGB[$inc] = self::getInt1d($this->dataWorkDocument, $offset); - $offset += 1; + ++$offset; } $start = 0; foreach ($arrayRGB as $keyRGB => $rgb) { - $oStyle = new \stdClass(); + $oStyle = new stdClass(); $oStyle->pos_start = $start; $oStyle->pos_len = (int) ceil((($arrayRGFC[$keyRGB] - 1) - $arrayRGFC[$keyRGB - 1]) / 2); $start += $oStyle->pos_len; @@ -1482,7 +1495,7 @@ class MsDoc extends AbstractReader implements ReaderInterface $posRGB = $offsetBase + $rgb * 2; $cb = self::getInt1d($this->dataWorkDocument, $posRGB); - $posRGB += 1; + ++$posRGB; $oStyle->style = $this->readPrl($this->dataWorkDocument, $posRGB, $cb); $posRGB += $oStyle->style->length; @@ -1492,16 +1505,15 @@ class MsDoc extends AbstractReader implements ReaderInterface } /** - * @param $sprm - * @return \stdClass + * @return stdClass */ private function readSprm($sprm) { - $oSprm = new \stdClass(); + $oSprm = new stdClass(); $oSprm->isPmd = $sprm & 0x01FF; - $oSprm->f = ($sprm / 512) & 0x0001; - $oSprm->sgc = ($sprm / 1024) & 0x0007; - $oSprm->spra = ($sprm / 8192); + $oSprm->f = (int) ($sprm / 512) & 0x0001; + $oSprm->sgc = (int) ($sprm / 1024) & 0x0007; + $oSprm->spra = (int) ($sprm / 8192); return $oSprm; } @@ -1509,7 +1521,8 @@ class MsDoc extends AbstractReader implements ReaderInterface /** * @param string $data * @param int $pos - * @param \stdClass $oSprm + * @param stdClass $oSprm + * * @return array */ private function readSprmSpra($data, $pos, $oSprm) @@ -1524,64 +1537,75 @@ class MsDoc extends AbstractReader implements ReaderInterface switch (dechex($operand)) { case 0x00: $operand = false; + break; case 0x01: $operand = true; + break; case 0x80: $operand = self::SPRA_VALUE; + break; case 0x81: $operand = self::SPRA_VALUE_OPPOSITE; + break; } + break; case 0x1: $operand = self::getInt1d($data, $pos); $length = 1; + break; case 0x2: case 0x4: case 0x5: $operand = self::getInt2d($data, $pos); $length = 2; + break; case 0x3: if ($oSprm->isPmd != 0x70) { $operand = self::getInt4d($data, $pos); $length = 4; } + break; case 0x7: $operand = self::getInt3d($data, $pos); $length = 3; + break; default: // print_r('YO YO YO : '.PHP_EOL); } - return array( - 'length' => $length, + return [ + 'length' => $length, 'operand' => $operand, - ); + ]; } /** * @param $data int * @param $pos int * @param $cbNum int - * @return \stdClass + * + * @return stdClass + * * @see http://msdn.microsoft.com/en-us/library/dd772849%28v=office.12%29.aspx */ private function readPrl($data, $pos, $cbNum) { $posStart = $pos; - $oStylePrl = new \stdClass(); + $oStylePrl = new stdClass(); // Variables $sprmCPicLocation = null; $sprmCFData = null; - $sprmCFSpec = null; + //$sprmCFSpec = null; do { // Variables @@ -1601,199 +1625,250 @@ class MsDoc extends AbstractReader implements ReaderInterface // Paragraph property case 0x01: break; - // Character property + // Character property case 0x02: if (!isset($oStylePrl->styleFont)) { - $oStylePrl->styleFont = array(); + $oStylePrl->styleFont = []; } switch ($oSprm->isPmd) { // sprmCFRMarkIns case 0x01: break; - // sprmCFFldVanish + // sprmCFFldVanish case 0x02: break; - // sprmCPicLocation + // sprmCPicLocation case 0x03: $sprmCPicLocation = $operand; + break; - // sprmCFData + // sprmCFData case 0x06: $sprmCFData = dechex($operand) != 0x00; + break; - // sprmCFItalic + // sprmCFItalic case 0x36: // By default, text is not italicized. switch ($operand) { case false: case true: $oStylePrl->styleFont['italic'] = $operand; + break; case self::SPRA_VALUE: $oStylePrl->styleFont['italic'] = false; + break; case self::SPRA_VALUE_OPPOSITE: $oStylePrl->styleFont['italic'] = true; + break; } + break; - // sprmCIstd + // sprmCIstd case 0x30: //print_r('sprmCIstd : '.dechex($operand).PHP_EOL.PHP_EOL); break; - // sprmCFBold + // sprmCFBold case 0x35: // By default, text is not bold. switch ($operand) { case false: case true: $oStylePrl->styleFont['bold'] = $operand; + break; case self::SPRA_VALUE: $oStylePrl->styleFont['bold'] = false; + break; case self::SPRA_VALUE_OPPOSITE: $oStylePrl->styleFont['bold'] = true; + break; } + break; - // sprmCFStrike + // sprmCFStrike case 0x37: // By default, text is not struck through. switch ($operand) { case false: case true: $oStylePrl->styleFont['strikethrough'] = $operand; + break; case self::SPRA_VALUE: $oStylePrl->styleFont['strikethrough'] = false; + break; case self::SPRA_VALUE_OPPOSITE: $oStylePrl->styleFont['strikethrough'] = true; + break; } + break; - // sprmCKul + // sprmCKul case 0x3E: switch (dechex($operand)) { case 0x00: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_NONE; + break; case 0x01: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_SINGLE; + break; case 0x02: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_WORDS; + break; case 0x03: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DOUBLE; + break; case 0x04: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DOTTED; + break; case 0x06: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_HEAVY; + break; case 0x07: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DASH; + break; case 0x09: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DOTDASH; + break; case 0x0A: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DOTDOTDASH; + break; case 0x0B: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_WAVY; + break; case 0x14: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DOTTEDHEAVY; + break; case 0x17: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DASHHEAVY; + break; case 0x19: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DOTDASHHEAVY; + break; case 0x1A: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DOTDOTDASHHEAVY; + break; case 0x1B: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_WAVYHEAVY; + break; case 0x27: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DASHLONG; + break; case 0x2B: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_WAVYDOUBLE; + break; case 0x37: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_DASHLONGHEAVY; + break; default: $oStylePrl->styleFont['underline'] = Style\Font::UNDERLINE_NONE; + break; } + break; - // sprmCIco - //@see http://msdn.microsoft.com/en-us/library/dd773060%28v=office.12%29.aspx + // sprmCIco + //@see http://msdn.microsoft.com/en-us/library/dd773060%28v=office.12%29.aspx case 0x42: switch (dechex($operand)) { case 0x00: case 0x01: $oStylePrl->styleFont['color'] = '000000'; + break; case 0x02: $oStylePrl->styleFont['color'] = '0000FF'; + break; case 0x03: $oStylePrl->styleFont['color'] = '00FFFF'; + break; case 0x04: $oStylePrl->styleFont['color'] = '00FF00'; + break; case 0x05: $oStylePrl->styleFont['color'] = 'FF00FF'; + break; case 0x06: $oStylePrl->styleFont['color'] = 'FF0000'; + break; case 0x07: $oStylePrl->styleFont['color'] = 'FFFF00'; + break; case 0x08: $oStylePrl->styleFont['color'] = 'FFFFFF'; + break; case 0x09: $oStylePrl->styleFont['color'] = '000080'; + break; case 0x0A: $oStylePrl->styleFont['color'] = '008080'; + break; case 0x0B: $oStylePrl->styleFont['color'] = '008000'; + break; case 0x0C: $oStylePrl->styleFont['color'] = '800080'; + break; case 0x0D: $oStylePrl->styleFont['color'] = '800080'; + break; case 0x0E: $oStylePrl->styleFont['color'] = '808000'; + break; case 0x0F: $oStylePrl->styleFont['color'] = '808080'; + break; case 0x10: $oStylePrl->styleFont['color'] = 'C0C0C0'; } + break; - // sprmCHps + // sprmCHps case 0x43: $oStylePrl->styleFont['size'] = dechex($operand / 2); + break; - // sprmCIss + // sprmCIss case 0x48: if (!isset($oStylePrl->styleFont['superScript'])) { $oStylePrl->styleFont['superScript'] = false; @@ -1807,42 +1882,46 @@ class MsDoc extends AbstractReader implements ReaderInterface break; case 0x01: $oStylePrl->styleFont['superScript'] = true; + break; case 0x02: $oStylePrl->styleFont['subScript'] = true; + break; } + break; - // sprmCRgFtc0 + // sprmCRgFtc0 case 0x4F: $oStylePrl->styleFont['name'] = ''; if (isset($this->arrayFonts[$operand])) { $oStylePrl->styleFont['name'] = $this->arrayFonts[$operand]['main']; } + break; - // sprmCRgFtc1 + // sprmCRgFtc1 case 0x50: // if the language for the text is an East Asian language break; - // sprmCRgFtc2 + // sprmCRgFtc2 case 0x51: // if the character falls outside the Unicode character range break; - // sprmCFSpec + // sprmCFSpec case 0x55: - $sprmCFSpec = $operand; + //$sprmCFSpec = $operand; break; - // sprmCFtcBi + // sprmCFtcBi case 0x5E: break; - // sprmCFItalicBi + // sprmCFItalicBi case 0x5D: break; - // sprmCHpsBi + // sprmCHpsBi case 0x61: break; - // sprmCShd80 - //@see http://msdn.microsoft.com/en-us/library/dd923447%28v=office.12%29.aspx + // sprmCShd80 + //@see http://msdn.microsoft.com/en-us/library/dd923447%28v=office.12%29.aspx case 0x66: // $operand = self::getInt2d($data, $pos); $pos += 2; @@ -1851,78 +1930,86 @@ class MsDoc extends AbstractReader implements ReaderInterface // $icoBack = ($operand >> 6) && bindec('11111'); // $icoFore = ($operand >> 11) && bindec('11111'); break; - // sprmCCv - //@see : http://msdn.microsoft.com/en-us/library/dd952824%28v=office.12%29.aspx + // sprmCCv + //@see : http://msdn.microsoft.com/en-us/library/dd952824%28v=office.12%29.aspx case 0x70: $red = str_pad(dechex(self::getInt1d($this->dataWorkDocument, $pos)), 2, '0', STR_PAD_LEFT); - $pos += 1; + ++$pos; $green = str_pad(dechex(self::getInt1d($this->dataWorkDocument, $pos)), 2, '0', STR_PAD_LEFT); - $pos += 1; + ++$pos; $blue = str_pad(dechex(self::getInt1d($this->dataWorkDocument, $pos)), 2, '0', STR_PAD_LEFT); - $pos += 1; - $pos += 1; + ++$pos; + ++$pos; $oStylePrl->styleFont['color'] = $red . $green . $blue; $cbNum -= 4; + break; default: // print_r('@todo Character : 0x'.dechex($oSprm->isPmd)); // print_r(PHP_EOL); } + break; - // Picture property + // Picture property case 0x03: break; - // Section property + // Section property case 0x04: if (!isset($oStylePrl->styleSection)) { - $oStylePrl->styleSection = array(); + $oStylePrl->styleSection = []; } switch ($oSprm->isPmd) { // sprmSNfcPgn case 0x0E: // numbering format used for page numbers break; - // sprmSXaPage + // sprmSXaPage case 0x1F: $oStylePrl->styleSection['pageSizeW'] = $operand; + break; - // sprmSYaPage + // sprmSYaPage case 0x20: $oStylePrl->styleSection['pageSizeH'] = $operand; + break; - // sprmSDxaLeft + // sprmSDxaLeft case 0x21: $oStylePrl->styleSection['marginLeft'] = $operand; + break; - // sprmSDxaRight + // sprmSDxaRight case 0x22: $oStylePrl->styleSection['marginRight'] = $operand; + break; - // sprmSDyaTop + // sprmSDyaTop case 0x23: $oStylePrl->styleSection['marginTop'] = $operand; + break; - // sprmSDyaBottom + // sprmSDyaBottom case 0x24: $oStylePrl->styleSection['marginBottom'] = $operand; + break; - // sprmSFBiDi + // sprmSFBiDi case 0x28: // RTL layout break; - // sprmSDxtCharSpace + // sprmSDxtCharSpace case 0x30: // characpter pitch break; - // sprmSDyaLinePitch + // sprmSDyaLinePitch case 0x31: // line height break; - // sprmSClm + // sprmSClm case 0x32: // document grid mode break; - // sprmSTextFlow + // sprmSTextFlow case 0x33: // text flow break; @@ -1930,15 +2017,16 @@ class MsDoc extends AbstractReader implements ReaderInterface // print_r('@todo Section : 0x'.dechex($oSprm->isPmd)); // print_r(PHP_EOL); } + break; - // Table property + // Table property case 0x05: break; } } while ($cbNum > 0); - if (!is_null($sprmCPicLocation)) { - if (!is_null($sprmCFData) && $sprmCFData == 0x01) { + if (null !== $sprmCPicLocation) { + if (null !== $sprmCFData && $sprmCFData == 0x01) { // NilPICFAndBinData //@todo Read Hyperlink structure /*$lcb = self::getInt4d($this->dataData, $sprmCPicLocation); @@ -2070,9 +2158,9 @@ class MsDoc extends AbstractReader implements ReaderInterface $picmidDxaCropBottom = self::getInt2d($this->dataData, $sprmCPicLocation); $sprmCPicLocation += 2; // PICF : picmid : fReserved - $sprmCPicLocation += 1; + ++$sprmCPicLocation; // PICF : picmid : bpp - $sprmCPicLocation += 1; + ++$sprmCPicLocation; // PICF : picmid : brcTop80 $sprmCPicLocation += 4; // PICF : picmid : brcLeft80 @@ -2091,14 +2179,14 @@ class MsDoc extends AbstractReader implements ReaderInterface if ($mfpfMm == 0x0066) { // cchPicName $cchPicName = self::getInt1d($this->dataData, $sprmCPicLocation); - $sprmCPicLocation += 1; + ++$sprmCPicLocation; // stPicName - $stPicName = ''; - for ($inc = 0; $inc <= $cchPicName; $inc++) { - $chr = self::getInt1d($this->dataData, $sprmCPicLocation); - $sprmCPicLocation += 1; - $stPicName .= chr($chr); + //$stPicName = ''; + for ($inc = 0; $inc <= $cchPicName; ++$inc) { + //$chr = self::getInt1d($this->dataData, $sprmCPicLocation); + ++$sprmCPicLocation; + //$stPicName .= chr($chr); } } @@ -2119,9 +2207,9 @@ class MsDoc extends AbstractReader implements ReaderInterface //@see : http://msdn.microsoft.com/en-us/library/dd944923%28v=office.12%29.aspx case 0xF007: // btWin32 - $sprmCPicLocation += 1; + ++$sprmCPicLocation; // btMacOS - $sprmCPicLocation += 1; + ++$sprmCPicLocation; // rgbUid $sprmCPicLocation += 16; // tag @@ -2133,21 +2221,21 @@ class MsDoc extends AbstractReader implements ReaderInterface // foDelay $sprmCPicLocation += 4; // unused1 - $sprmCPicLocation += 1; + ++$sprmCPicLocation; // cbName $cbName = self::getInt1d($this->dataData, $sprmCPicLocation); - $sprmCPicLocation += 1; + ++$sprmCPicLocation; // unused2 - $sprmCPicLocation += 1; + ++$sprmCPicLocation; // unused3 - $sprmCPicLocation += 1; + ++$sprmCPicLocation; // nameData if ($cbName > 0) { - $nameData = ''; - for ($inc = 0; $inc <= ($cbName / 2); $inc++) { - $chr = self::getInt2d($this->dataData, $sprmCPicLocation); + //$nameData = ''; + for ($inc = 0; $inc <= ($cbName / 2); ++$inc) { + //$chr = self::getInt2d($this->dataData, $sprmCPicLocation); $sprmCPicLocation += 2; - $nameData .= chr($chr); + //$nameData .= chr($chr); } } // embeddedBlip @@ -2157,7 +2245,7 @@ class MsDoc extends AbstractReader implements ReaderInterface case self::OFFICEARTBLIPJPG: case self::OFFICEARTBLIPJPEG: if (!isset($oStylePrl->image)) { - $oStylePrl->image = array(); + $oStylePrl->image = []; } $sprmCPicLocation += 8; // embeddedBlip : rgbUid1 @@ -2167,7 +2255,7 @@ class MsDoc extends AbstractReader implements ReaderInterface $sprmCPicLocation += 16; } // embeddedBlip : tag - $sprmCPicLocation += 1; + ++$sprmCPicLocation; // embeddedBlip : BLIPFileData $oStylePrl->image['data'] = substr($this->dataData, $sprmCPicLocation, $embeddedBlipRH['recLen']); $oStylePrl->image['format'] = 'jpg'; @@ -2184,12 +2272,14 @@ class MsDoc extends AbstractReader implements ReaderInterface $oStylePrl->image['height'] = Drawing::twipsToPixels($iCropHeight * $picmidMy / 1000); $sprmCPicLocation += $embeddedBlipRH['recLen']; + break; case self::OFFICEARTBLIPPNG: break; default: // print_r(dechex($embeddedBlipRH['recType'])); } + break; } $fileBlockRH = $this->loadRecordHeader($this->dataData, $sprmCPicLocation); @@ -2203,9 +2293,11 @@ class MsDoc extends AbstractReader implements ReaderInterface } /** - * Read a record header + * Read a record header. + * * @param string $stream * @param int $pos + * * @return array */ private function loadRecordHeader($stream, $pos) @@ -2214,15 +2306,15 @@ class MsDoc extends AbstractReader implements ReaderInterface $recType = self::getInt2d($stream, $pos + 2); $recLen = self::getInt4d($stream, $pos + 4); - return array( - 'recVer' => ($rec >> 0) & bindec('1111'), + return [ + 'recVer' => ($rec >> 0) & bindec('1111'), 'recInstance' => ($rec >> 4) & bindec('111111111111'), - 'recType' => $recType, - 'recLen' => $recLen, - ); + 'recType' => $recType, + 'recLen' => $recLen, + ]; } - private function generatePhpWord() + private function generatePhpWord(): void { foreach ($this->arraySections as $itmSection) { $oSection = $this->phpWord->addSection(); @@ -2243,7 +2335,7 @@ class MsDoc extends AbstractReader implements ReaderInterface } // Style Character - $styleFont = array(); + $styleFont = []; if (isset($oCharacters->style)) { if (isset($oCharacters->style->styleFont)) { $styleFont = $oCharacters->style->styleFont; @@ -2283,7 +2375,7 @@ class MsDoc extends AbstractReader implements ReaderInterface if (isset($oCharacters->style->image)) { $fileImage = tempnam(sys_get_temp_dir(), 'PHPWord_MsDoc') . '.' . $oCharacters->style->image['format']; file_put_contents($fileImage, $oCharacters->style->image['data']); - $oSection->addImage($fileImage, array('width' => $oCharacters->style->image['width'], 'height' => $oCharacters->style->image['height'])); + $oSection->addImage($fileImage, ['width' => $oCharacters->style->image['width'], 'height' => $oCharacters->style->image['height']]); // print_r('>addImage<'.$fileImage.'>'.EOL); } } @@ -2296,10 +2388,11 @@ class MsDoc extends AbstractReader implements ReaderInterface } /** - * Read 8-bit unsigned integer + * Read 8-bit unsigned integer. * * @param string $data * @param int $pos + * * @return int */ public static function getInt1d($data, $pos) @@ -2308,10 +2401,11 @@ class MsDoc extends AbstractReader implements ReaderInterface } /** - * Read 16-bit unsigned integer + * Read 16-bit unsigned integer. * * @param string $data * @param int $pos + * * @return int */ public static function getInt2d($data, $pos) @@ -2320,10 +2414,11 @@ class MsDoc extends AbstractReader implements ReaderInterface } /** - * Read 24-bit signed integer + * Read 24-bit signed integer. * * @param string $data * @param int $pos + * * @return int */ public static function getInt3d($data, $pos) @@ -2332,10 +2427,11 @@ class MsDoc extends AbstractReader implements ReaderInterface } /** - * Read 32-bit signed integer + * Read 32-bit signed integer. * * @param string $data * @param int $pos + * * @return int */ public static function getInt4d($data, $pos) diff --git a/PhpOffice/PhpWord/Reader/ODText.php b/PhpOffice/PhpWord/Reader/ODText.php old mode 100755 new mode 100644 index 0b58dc5..aba280d --- a/PhpOffice/PhpWord/Reader/ODText.php +++ b/PhpOffice/PhpWord/Reader/ODText.php @@ -11,26 +11,27 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Reader; -use PhpOffice\Common\XMLReader; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Shared\XMLReader; /** - * Reader for ODText + * Reader for ODText. * * @since 0.10.0 */ class ODText extends AbstractReader implements ReaderInterface { /** - * Loads PhpWord from file + * Loads PhpWord from file. * * @param string $docFile + * * @return \PhpOffice\PhpWord\PhpWord */ public function load($docFile) @@ -38,10 +39,10 @@ class ODText extends AbstractReader implements ReaderInterface $phpWord = new PhpWord(); $relationships = $this->readRelationships($docFile); - $readerParts = array( + $readerParts = [ 'content.xml' => 'Content', - 'meta.xml' => 'Meta', - ); + 'meta.xml' => 'Meta', + ]; foreach ($readerParts as $xmlFile => $partName) { $this->readPart($phpWord, $relationships, $partName, $docFile, $xmlFile); @@ -53,13 +54,12 @@ class ODText extends AbstractReader implements ReaderInterface /** * Read document part. * - * @param \PhpOffice\PhpWord\PhpWord $phpWord * @param array $relationships * @param string $partName * @param string $docFile * @param string $xmlFile */ - private function readPart(PhpWord $phpWord, $relationships, $partName, $docFile, $xmlFile) + private function readPart(PhpWord $phpWord, $relationships, $partName, $docFile, $xmlFile): void { $partClass = "PhpOffice\\PhpWord\\Reader\\ODText\\{$partName}"; if (class_exists($partClass)) { @@ -71,14 +71,15 @@ class ODText extends AbstractReader implements ReaderInterface } /** - * Read all relationship files + * Read all relationship files. * * @param string $docFile + * * @return array */ private function readRelationships($docFile) { - $rels = array(); + $rels = []; $xmlFile = 'META-INF/manifest.xml'; $xmlReader = new XMLReader(); $xmlReader->getDomFromZip($docFile, $xmlFile); @@ -86,7 +87,7 @@ class ODText extends AbstractReader implements ReaderInterface foreach ($nodes as $node) { $type = $xmlReader->getAttribute('manifest:media-type', $node); $target = $xmlReader->getAttribute('manifest:full-path', $node); - $rels[] = array('type' => $type, 'target' => $target); + $rels[] = ['type' => $type, 'target' => $target]; } return $rels; diff --git a/PhpOffice/PhpWord/Reader/ODText/AbstractPart.php b/PhpOffice/PhpWord/Reader/ODText/AbstractPart.php old mode 100755 new mode 100644 index ff664e0..4c89192 --- a/PhpOffice/PhpWord/Reader/ODText/AbstractPart.php +++ b/PhpOffice/PhpWord/Reader/ODText/AbstractPart.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -20,9 +20,10 @@ namespace PhpOffice\PhpWord\Reader\ODText; use PhpOffice\PhpWord\Reader\Word2007\AbstractPart as Word2007AbstractPart; /** - * Abstract part reader + * Abstract part reader. * * @since 0.10.0 + * * @codeCoverageIgnore */ abstract class AbstractPart extends Word2007AbstractPart diff --git a/PhpOffice/PhpWord/Reader/ODText/Content.php b/PhpOffice/PhpWord/Reader/ODText/Content.php old mode 100755 new mode 100644 index 9dfd645..ccbc5ee --- a/PhpOffice/PhpWord/Reader/ODText/Content.php +++ b/PhpOffice/PhpWord/Reader/ODText/Content.php @@ -11,18 +11,19 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Reader\ODText; -use PhpOffice\Common\XMLReader; +use DateTime; use PhpOffice\PhpWord\Element\TrackChange; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Shared\XMLReader; /** - * Content reader + * Content reader. * * @since 0.10.0 */ @@ -30,15 +31,13 @@ class Content extends AbstractPart { /** * Read content.xml. - * - * @param \PhpOffice\PhpWord\PhpWord $phpWord */ - public function read(PhpWord $phpWord) + public function read(PhpWord $phpWord): void { $xmlReader = new XMLReader(); $xmlReader->getDomFromZip($this->docFile, $this->xmlFile); - $trackedChanges = array(); + $trackedChanges = []; $nodes = $xmlReader->getElements('office:body/office:text/*'); if ($nodes->length > 0) { @@ -49,6 +48,7 @@ class Content extends AbstractPart case 'text:h': // Heading $depth = $xmlReader->getAttribute('text:outline-level', $node); $section->addTitle($node->nodeValue, $depth); + break; case 'text:p': // Paragraph $children = $node->childNodes; @@ -59,15 +59,18 @@ class Content extends AbstractPart if (isset($trackedChanges[$changeId])) { $changed = $trackedChanges[$changeId]; } + break; case 'text:change-end': unset($changed); + break; case 'text:change': $changeId = $child->getAttribute('text:change-id'); if (isset($trackedChanges[$changeId])) { $changed = $trackedChanges[$changeId]; } + break; } } @@ -82,6 +85,7 @@ class Content extends AbstractPart } } } + break; case 'text:list': // List $listItems = $xmlReader->getElements('text:list-item/text:p', $node); @@ -89,6 +93,7 @@ class Content extends AbstractPart // $listStyleName = $xmlReader->getAttribute('text:style-name', $listItem); $section->addListItem($listItem->nodeValue, 0); } + break; case 'text:tracked-changes': $changedRegions = $xmlReader->getElements('text:changed-region', $node); @@ -99,11 +104,12 @@ class Content extends AbstractPart $dateNode = $xmlReader->getElements('office:change-info/dc:date', $changedRegion->firstChild); $date = $dateNode[0]->nodeValue; $date = preg_replace('/\.\d+$/', '', $date); - $date = \DateTime::createFromFormat('Y-m-d\TH:i:s', $date); + $date = DateTime::createFromFormat('Y-m-d\TH:i:s', $date); $changed = new TrackChange($type, $author, $date); $textNodes = $xmlReader->getElements('text:deletion/text:p', $changedRegion); - $trackedChanges[$changedRegion->getAttribute('text:id')] = array('changed' => $changed, 'textNodes'=> $textNodes); + $trackedChanges[$changedRegion->getAttribute('text:id')] = ['changed' => $changed, 'textNodes' => $textNodes]; } + break; } } diff --git a/PhpOffice/PhpWord/Reader/ODText/Meta.php b/PhpOffice/PhpWord/Reader/ODText/Meta.php old mode 100755 new mode 100644 index 8801a54..200ee13 --- a/PhpOffice/PhpWord/Reader/ODText/Meta.php +++ b/PhpOffice/PhpWord/Reader/ODText/Meta.php @@ -11,17 +11,17 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Reader\ODText; -use PhpOffice\Common\XMLReader; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Shared\XMLReader; /** - * Meta reader + * Meta reader. * * @since 0.11.0 */ @@ -30,10 +30,9 @@ class Meta extends AbstractPart /** * Read meta.xml. * - * @param \PhpOffice\PhpWord\PhpWord $phpWord * @todo Process property type */ - public function read(PhpWord $phpWord) + public function read(PhpWord $phpWord): void { $xmlReader = new XMLReader(); $xmlReader->getDomFromZip($this->docFile, $this->xmlFile); @@ -42,16 +41,16 @@ class Meta extends AbstractPart $metaNode = $xmlReader->getElement('office:meta'); // Standard properties - $properties = array( - 'title' => 'dc:title', - 'subject' => 'dc:subject', - 'description' => 'dc:description', - 'keywords' => 'meta:keyword', - 'creator' => 'meta:initial-creator', + $properties = [ + 'title' => 'dc:title', + 'subject' => 'dc:subject', + 'description' => 'dc:description', + 'keywords' => 'meta:keyword', + 'creator' => 'meta:initial-creator', 'lastModifiedBy' => 'dc:creator', // 'created' => 'meta:creation-date', // 'modified' => 'dc:date', - ); + ]; foreach ($properties as $property => $path) { $method = "set{$property}"; $propertyNode = $xmlReader->getElement($path, $metaNode); @@ -66,7 +65,7 @@ class Meta extends AbstractPart $property = $xmlReader->getAttribute('meta:name', $propertyNode); // Set category, company, and manager property - if (in_array($property, array('Category', 'Company', 'Manager'))) { + if (in_array($property, ['Category', 'Company', 'Manager'])) { $method = "set{$property}"; $docProps->$method($propertyNode->nodeValue); } else { diff --git a/PhpOffice/PhpWord/Reader/RTF.php b/PhpOffice/PhpWord/Reader/RTF.php old mode 100755 new mode 100644 index 620252f..2fdfb03 --- a/PhpOffice/PhpWord/Reader/RTF.php +++ b/PhpOffice/PhpWord/Reader/RTF.php @@ -11,29 +11,28 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Reader; +use Exception; use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\Reader\RTF\Document; /** - * RTF Reader class + * RTF Reader class. * * @since 0.11.0 */ class RTF extends AbstractReader implements ReaderInterface { /** - * Loads PhpWord from file + * Loads PhpWord from file. * * @param string $docFile * - * @throws \Exception - * * @return \PhpOffice\PhpWord\PhpWord */ public function load($docFile) @@ -45,7 +44,7 @@ class RTF extends AbstractReader implements ReaderInterface $doc->rtf = file_get_contents($docFile); $doc->read($phpWord); } else { - throw new \Exception("Cannot read {$docFile}."); + throw new Exception("Cannot read {$docFile}."); } return $phpWord; diff --git a/PhpOffice/PhpWord/Reader/RTF/Document.php b/PhpOffice/PhpWord/Reader/RTF/Document.php old mode 100755 new mode 100644 index b9509d7..21b76cf --- a/PhpOffice/PhpWord/Reader/RTF/Document.php +++ b/PhpOffice/PhpWord/Reader/RTF/Document.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -21,7 +21,7 @@ use PhpOffice\PhpWord\PhpWord; use PhpOffice\PhpWord\SimpleType\Jc; /** - * RTF document reader + * RTF document reader. * * References: * - How to Write an RTF Reader http://latex2rtf.sourceforge.net/rtfspec_45.html @@ -29,6 +29,7 @@ use PhpOffice\PhpWord\SimpleType\Jc; * - JavaScript RTF-parser by LazyGyu https://github.com/lazygyu/RTF-parser * * @since 0.11.0 + * * @SuppressWarnings(PHPMD.UnusedPrivateMethod) */ class Document @@ -39,109 +40,108 @@ class Document const SKIP = 'readSkip'; /** - * PhpWord object + * PhpWord object. * * @var \PhpOffice\PhpWord\PhpWord */ private $phpWord; /** - * Section object + * Section object. * * @var \PhpOffice\PhpWord\Element\Section */ private $section; /** - * Textrun object + * Textrun object. * * @var \PhpOffice\PhpWord\Element\TextRun */ private $textrun; /** - * RTF content + * RTF content. * * @var string */ public $rtf; /** - * Content length + * Content length. * * @var int */ private $length = 0; /** - * Character index + * Character index. * * @var int */ private $offset = 0; /** - * Current control word + * Current control word. * * @var string */ private $control = ''; /** - * Text content + * Text content. * * @var string */ private $text = ''; /** - * Parsing a control word flag + * Parsing a control word flag. * * @var bool */ private $isControl = false; /** - * First character flag: watch out for control symbols + * First character flag: watch out for control symbols. * * @var bool */ private $isFirst = false; /** - * Group groups + * Group groups. * * @var array */ - private $groups = array(); + private $groups = []; /** - * Parser flags; not used + * Parser flags; not used. * * @var array */ - private $flags = array(); + private $flags = []; /** - * Parse RTF content + * Parse RTF content. * * - Marks controlling characters `{`, `}`, and `\` * - Removes line endings * - Builds control words and control symbols * - Pushes every other character into the text queue * - * @param \PhpOffice\PhpWord\PhpWord $phpWord * @todo Use `fread` stream for scalability */ - public function read(PhpWord $phpWord) + public function read(PhpWord $phpWord): void { - $markers = array( + $markers = [ 123 => 'markOpening', // { 125 => 'markClosing', // } - 92 => 'markBackslash', // \ - 10 => 'markNewline', // LF - 13 => 'markNewline', // CR - ); + 92 => 'markBackslash', // \ + 10 => 'markNewline', // LF + 13 => 'markNewline', // CR + ]; $this->phpWord = $phpWord; $this->section = $phpWord->addSection(); @@ -176,7 +176,7 @@ class Document } } } - $this->offset++; + ++$this->offset; } $this->flushText(); } @@ -184,7 +184,7 @@ class Document /** * Mark opening braket `{` character. */ - private function markOpening() + private function markOpening(): void { $this->flush(true); array_push($this->groups, $this->flags); @@ -193,7 +193,7 @@ class Document /** * Mark closing braket `}` character. */ - private function markClosing() + private function markClosing(): void { $this->flush(true); $this->flags = array_pop($this->groups); @@ -202,7 +202,7 @@ class Document /** * Mark backslash `\` character. */ - private function markBackslash() + private function markBackslash(): void { if ($this->isFirst) { $this->setControl(false); @@ -217,7 +217,7 @@ class Document /** * Mark newline character: Flush control word because it's not possible to span multiline. */ - private function markNewline() + private function markNewline(): void { if ($this->isControl) { $this->flushControl(true); @@ -229,7 +229,7 @@ class Document * * @param bool $isControl */ - private function flush($isControl = false) + private function flush($isControl = false): void { if ($this->isControl) { $this->flushControl($isControl); @@ -243,10 +243,10 @@ class Document * * @param bool $isControl */ - private function flushControl($isControl = false) + private function flushControl($isControl = false): void { if (1 === preg_match('/^([A-Za-z]+)(-?[0-9]*) ?$/', $this->control, $match)) { - list(, $control, $parameter) = $match; + [, $control, $parameter] = $match; $this->parseControl($control, $parameter); } @@ -258,7 +258,7 @@ class Document /** * Flush text in queue. */ - private function flushText() + private function flushText(): void { if ($this->text != '') { if (isset($this->flags['property'])) { // Set property @@ -284,7 +284,7 @@ class Document * * @param bool $value */ - private function setControl($value) + private function setControl($value): void { $this->isControl = $value; $this->isFirst = $value; @@ -295,7 +295,7 @@ class Document * * @param string $char */ - private function pushText($char) + private function pushText($char): void { if ('<' == $char) { $this->text .= '<'; @@ -312,32 +312,32 @@ class Document * @param string $control * @param string $parameter */ - private function parseControl($control, $parameter) + private function parseControl($control, $parameter): void { - $controls = array( - 'par' => array(self::PARA, 'paragraph', true), - 'b' => array(self::STYL, 'font', 'bold', true), - 'i' => array(self::STYL, 'font', 'italic', true), - 'u' => array(self::STYL, 'font', 'underline', true), - 'strike' => array(self::STYL, 'font', 'strikethrough', true), - 'fs' => array(self::STYL, 'font', 'size', $parameter), - 'qc' => array(self::STYL, 'paragraph', 'alignment', Jc::CENTER), - 'sa' => array(self::STYL, 'paragraph', 'spaceAfter', $parameter), - 'fonttbl' => array(self::SKIP, 'fonttbl', null), - 'colortbl' => array(self::SKIP, 'colortbl', null), - 'info' => array(self::SKIP, 'info', null), - 'generator' => array(self::SKIP, 'generator', null), - 'title' => array(self::SKIP, 'title', null), - 'subject' => array(self::SKIP, 'subject', null), - 'category' => array(self::SKIP, 'category', null), - 'keywords' => array(self::SKIP, 'keywords', null), - 'comment' => array(self::SKIP, 'comment', null), - 'shppict' => array(self::SKIP, 'pic', null), - 'fldinst' => array(self::SKIP, 'link', null), - ); + $controls = [ + 'par' => [self::PARA, 'paragraph', true], + 'b' => [self::STYL, 'font', 'bold', true], + 'i' => [self::STYL, 'font', 'italic', true], + 'u' => [self::STYL, 'font', 'underline', true], + 'strike' => [self::STYL, 'font', 'strikethrough', true], + 'fs' => [self::STYL, 'font', 'size', $parameter], + 'qc' => [self::STYL, 'paragraph', 'alignment', Jc::CENTER], + 'sa' => [self::STYL, 'paragraph', 'spaceAfter', $parameter], + 'fonttbl' => [self::SKIP, 'fonttbl', null], + 'colortbl' => [self::SKIP, 'colortbl', null], + 'info' => [self::SKIP, 'info', null], + 'generator' => [self::SKIP, 'generator', null], + 'title' => [self::SKIP, 'title', null], + 'subject' => [self::SKIP, 'subject', null], + 'category' => [self::SKIP, 'category', null], + 'keywords' => [self::SKIP, 'keywords', null], + 'comment' => [self::SKIP, 'comment', null], + 'shppict' => [self::SKIP, 'pic', null], + 'fldinst' => [self::SKIP, 'link', null], + ]; if (isset($controls[$control])) { - list($function) = $controls[$control]; + [$function] = $controls[$control]; if (method_exists($this, $function)) { $directives = $controls[$control]; array_shift($directives); // remove the function variable; we won't need it @@ -351,9 +351,9 @@ class Document * * @param array $directives */ - private function readParagraph($directives) + private function readParagraph($directives): void { - list($property, $value) = $directives; + [$property, $value] = $directives; $this->textrun = $this->section->addTextRun(); $this->flags[$property] = $value; } @@ -363,9 +363,9 @@ class Document * * @param array $directives */ - private function readStyle($directives) + private function readStyle($directives): void { - list($style, $property, $value) = $directives; + [$style, $property, $value] = $directives; $this->flags['styles'][$style][$property] = $value; } @@ -374,9 +374,9 @@ class Document * * @param array $directives */ - private function readSkip($directives) + private function readSkip($directives): void { - list($property) = $directives; + [$property] = $directives; $this->flags['property'] = $property; $this->flags['skipped'] = true; } @@ -384,7 +384,7 @@ class Document /** * Read text. */ - private function readText() + private function readText(): void { $text = $this->textrun->addText($this->text); if (isset($this->flags['styles']['font'])) { diff --git a/PhpOffice/PhpWord/Reader/ReaderInterface.php b/PhpOffice/PhpWord/Reader/ReaderInterface.php old mode 100755 new mode 100644 index 4024cdb..83c6993 --- a/PhpOffice/PhpWord/Reader/ReaderInterface.php +++ b/PhpOffice/PhpWord/Reader/ReaderInterface.php @@ -11,14 +11,14 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Reader; /** - * Reader interface + * Reader interface. * * @since 0.8.0 */ @@ -28,12 +28,13 @@ interface ReaderInterface * Can the current ReaderInterface read the file? * * @param string $filename + * * @return bool */ public function canRead($filename); /** - * Loads PhpWord from file + * Loads PhpWord from file. * * @param string $filename */ diff --git a/PhpOffice/PhpWord/Reader/Word2007.php b/PhpOffice/PhpWord/Reader/Word2007.php old mode 100755 new mode 100644 index 52030ef..5f2a69c --- a/PhpOffice/PhpWord/Reader/Word2007.php +++ b/PhpOffice/PhpWord/Reader/Word2007.php @@ -11,29 +11,31 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Reader; -use PhpOffice\Common\XMLReader; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Shared\XMLReader; use PhpOffice\PhpWord\Shared\ZipArchive; /** - * Reader for Word2007 + * Reader for Word2007. * * @since 0.8.0 + * * @todo watermark, checkbox, toc * @todo Partly done: image, object */ class Word2007 extends AbstractReader implements ReaderInterface { /** - * Loads PhpWord from file + * Loads PhpWord from file. * * @param string $docFile + * * @return \PhpOffice\PhpWord\PhpWord */ public function load($docFile) @@ -41,23 +43,23 @@ class Word2007 extends AbstractReader implements ReaderInterface $phpWord = new PhpWord(); $relationships = $this->readRelationships($docFile); - $steps = array( - array('stepPart' => 'document', 'stepItems' => array( - 'styles' => 'Styles', + $steps = [ + ['stepPart' => 'document', 'stepItems' => [ + 'styles' => 'Styles', 'numbering' => 'Numbering', - )), - array('stepPart' => 'main', 'stepItems' => array( - 'officeDocument' => 'Document', - 'core-properties' => 'DocPropsCore', + ]], + ['stepPart' => 'main', 'stepItems' => [ + 'officeDocument' => 'Document', + 'core-properties' => 'DocPropsCore', 'extended-properties' => 'DocPropsApp', - 'custom-properties' => 'DocPropsCustom', - )), - array('stepPart' => 'document', 'stepItems' => array( - 'endnotes' => 'Endnotes', + 'custom-properties' => 'DocPropsCustom', + ]], + ['stepPart' => 'document', 'stepItems' => [ + 'endnotes' => 'Endnotes', 'footnotes' => 'Footnotes', - 'settings' => 'Settings', - )), - ); + 'settings' => 'Settings', + ]], + ]; foreach ($steps as $step) { $stepPart = $step['stepPart']; @@ -81,13 +83,12 @@ class Word2007 extends AbstractReader implements ReaderInterface /** * Read document part. * - * @param \PhpOffice\PhpWord\PhpWord $phpWord * @param array $relationships * @param string $partName * @param string $docFile * @param string $xmlFile */ - private function readPart(PhpWord $phpWord, $relationships, $partName, $docFile, $xmlFile) + private function readPart(PhpWord $phpWord, $relationships, $partName, $docFile, $xmlFile): void { $partClass = "PhpOffice\\PhpWord\\Reader\\Word2007\\{$partName}"; if (class_exists($partClass)) { @@ -99,14 +100,15 @@ class Word2007 extends AbstractReader implements ReaderInterface } /** - * Read all relationship files + * Read all relationship files. * * @param string $docFile + * * @return array */ private function readRelationships($docFile) { - $relationships = array(); + $relationships = []; // _rels/.rels $relationships['main'] = $this->getRels($docFile, '_rels/.rels'); @@ -115,7 +117,7 @@ class Word2007 extends AbstractReader implements ReaderInterface $wordRelsPath = 'word/_rels/'; $zip = new ZipArchive(); if ($zip->open($docFile) === true) { - for ($i = 0; $i < $zip->numFiles; $i++) { + for ($i = 0; $i < $zip->numFiles; ++$i) { $xmlFile = $zip->getNameIndex($i); if ((substr($xmlFile, 0, strlen($wordRelsPath))) == $wordRelsPath && (substr($xmlFile, -1)) != '/') { $docPart = str_replace('.xml.rels', '', str_replace($wordRelsPath, '', $xmlFile)); @@ -129,11 +131,12 @@ class Word2007 extends AbstractReader implements ReaderInterface } /** - * Get relationship array + * Get relationship array. * * @param string $docFile * @param string $xmlFile * @param string $targetPrefix + * * @return array */ private function getRels($docFile, $xmlFile, $targetPrefix = '') @@ -141,7 +144,7 @@ class Word2007 extends AbstractReader implements ReaderInterface $metaPrefix = 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/'; $officePrefix = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/'; - $rels = array(); + $rels = []; $xmlReader = new XMLReader(); $xmlReader->getDomFromZip($docFile, $xmlFile); @@ -163,7 +166,7 @@ class Word2007 extends AbstractReader implements ReaderInterface } // Push to return array - $rels[$rId] = array('type' => $type, 'target' => $target, 'docPart' => $docPart, 'targetMode' => $mode); + $rels[$rId] = ['type' => $type, 'target' => $target, 'docPart' => $docPart, 'targetMode' => $mode]; } ksort($rels); diff --git a/PhpOffice/PhpWord/Reader/Word2007/AbstractPart.php b/PhpOffice/PhpWord/Reader/Word2007/AbstractPart.php old mode 100755 new mode 100644 index 06dfe37..df3cfed --- a/PhpOffice/PhpWord/Reader/Word2007/AbstractPart.php +++ b/PhpOffice/PhpWord/Reader/Word2007/AbstractPart.php @@ -11,21 +11,23 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Reader\Word2007; -use PhpOffice\Common\XMLReader; +use DateTime; +use DOMElement; use PhpOffice\PhpWord\ComplexType\TblWidth as TblWidthComplexType; use PhpOffice\PhpWord\Element\AbstractContainer; use PhpOffice\PhpWord\Element\TextRun; use PhpOffice\PhpWord\Element\TrackChange; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Shared\XMLReader; /** - * Abstract part reader + * Abstract part reader. * * This class is inherited by ODText reader * @@ -34,7 +36,7 @@ use PhpOffice\PhpWord\PhpWord; abstract class AbstractPart { /** - * Conversion method + * Conversion method. * * @const int */ @@ -45,25 +47,25 @@ abstract class AbstractPart const READ_SIZE = 'attributeMultiplyByTwo'; // Read special attribute value for Font::$size /** - * Document file + * Document file. * * @var string */ protected $docFile; /** - * XML file + * XML file. * * @var string */ protected $xmlFile; /** - * Part relationships + * Part relationships. * * @var array */ - protected $rels = array(); + protected $rels = []; /** * Read part. @@ -71,7 +73,7 @@ abstract class AbstractPart abstract public function read(PhpWord $phpWord); /** - * Create new instance + * Create new instance. * * @param string $docFile * @param string $xmlFile @@ -87,7 +89,7 @@ abstract class AbstractPart * * @param array $value */ - public function setRels($value) + public function setRels($value): void { $this->rels = $value; } @@ -95,14 +97,12 @@ abstract class AbstractPart /** * Read w:p. * - * @param \PhpOffice\Common\XMLReader $xmlReader - * @param \DOMElement $domNode * @param \PhpOffice\PhpWord\Element\AbstractContainer $parent * @param string $docPart * * @todo Get font style for preserve text */ - protected function readParagraph(XMLReader $xmlReader, \DOMElement $domNode, $parent, $docPart = 'document') + protected function readParagraph(XMLReader $xmlReader, DOMElement $domNode, $parent, $docPart = 'document'): void { // Paragraph style $paragraphStyle = null; @@ -128,7 +128,7 @@ abstract class AbstractPart $ignoreText = false; } } - if (!is_null($instrText)) { + if (null !== $instrText) { $textContent .= '{' . $instrText . '}'; } else { if (false === $ignoreText) { @@ -177,19 +177,20 @@ abstract class AbstractPart } /** - * Returns the depth of the Heading, returns 0 for a Title + * Returns the depth of the Heading, returns 0 for a Title. * * @param array $paragraphStyle - * @return number|null + * + * @return null|number */ - private function getHeadingDepth(array $paragraphStyle = null) + private function getHeadingDepth(?array $paragraphStyle = null) { if (is_array($paragraphStyle) && isset($paragraphStyle['styleName'])) { if ('Title' === $paragraphStyle['styleName']) { return 0; } - $headingMatches = array(); + $headingMatches = []; preg_match('/Heading(\d)/', $paragraphStyle['styleName'], $headingMatches); if (!empty($headingMatches)) { return $headingMatches[1]; @@ -202,17 +203,15 @@ abstract class AbstractPart /** * Read w:r. * - * @param \PhpOffice\Common\XMLReader $xmlReader - * @param \DOMElement $domNode * @param \PhpOffice\PhpWord\Element\AbstractContainer $parent * @param string $docPart * @param mixed $paragraphStyle * * @todo Footnote paragraph style */ - protected function readRun(XMLReader $xmlReader, \DOMElement $domNode, $parent, $docPart, $paragraphStyle = null) + protected function readRun(XMLReader $xmlReader, DOMElement $domNode, $parent, $docPart, $paragraphStyle = null): void { - if (in_array($domNode->nodeName, array('w:ins', 'w:del', 'w:smartTag', 'w:hyperlink'))) { + if (in_array($domNode->nodeName, ['w:ins', 'w:del', 'w:smartTag', 'w:hyperlink'])) { $nodes = $xmlReader->getElements('*', $domNode); foreach ($nodes as $node) { $this->readRun($xmlReader, $node, $parent, $docPart, $paragraphStyle); @@ -227,16 +226,13 @@ abstract class AbstractPart } /** - * Parses nodes under w:r + * Parses nodes under w:r. * - * @param XMLReader $xmlReader - * @param \DOMElement $node - * @param AbstractContainer $parent * @param string $docPart * @param mixed $paragraphStyle * @param mixed $fontStyle */ - protected function readRunChild(XMLReader $xmlReader, \DOMElement $node, AbstractContainer $parent, $docPart, $paragraphStyle = null, $fontStyle = null) + protected function readRunChild(XMLReader $xmlReader, DOMElement $node, AbstractContainer $parent, $docPart, $paragraphStyle = null, $fontStyle = null): void { $runParent = $node->parentNode->parentNode; if ($node->nodeName == 'w:footnoteReference') { @@ -253,7 +249,7 @@ abstract class AbstractPart // Image $rId = $xmlReader->getAttribute('r:id', $node, 'v:shape/v:imagedata'); $target = $this->getMediaTarget($docPart, $rId); - if (!is_null($target)) { + if (null !== $target) { if ('External' == $this->getTargetMode($docPart, $rId)) { $imageSource = $target; } else { @@ -275,7 +271,7 @@ abstract class AbstractPart $embedId = $xmlReader->getAttribute('r:embed', $node, 'wp:anchor/a:graphic/a:graphicData/pic:pic/pic:blipFill/a:blip'); } $target = $this->getMediaTarget($docPart, $embedId); - if (!is_null($target)) { + if (null !== $target) { $imageSource = "zip://{$this->docFile}#{$target}"; $parent->addImage($imageSource, null, false, $name); } @@ -284,7 +280,7 @@ abstract class AbstractPart $rId = $xmlReader->getAttribute('r:id', $node, 'o:OLEObject'); // $rIdIcon = $xmlReader->getAttribute('r:id', $domNode, 'w:object/v:shape/v:imagedata'); $target = $this->getMediaTarget($docPart, $rId); - if (!is_null($target)) { + if (null !== $target) { $textContent = "<Object: {$target}>"; $parent->addText($textContent, $fontStyle, $paragraphStyle); } @@ -292,6 +288,19 @@ abstract class AbstractPart $parent->addTextBreak(); } elseif ($node->nodeName == 'w:tab') { $parent->addText("\t"); + } elseif ($node->nodeName == 'mc:AlternateContent') { + if ($node->hasChildNodes()) { + // Get fallback instead of mc:Choice to make sure it is compatible + $fallbackElements = $node->getElementsByTagName('Fallback'); + + if ($fallbackElements->length) { + $fallback = $fallbackElements->item(0); + // TextRun + $textContent = htmlspecialchars($fallback->nodeValue, ENT_QUOTES, 'UTF-8'); + + $parent->addText($textContent, $fontStyle, $paragraphStyle); + } + } } elseif ($node->nodeName == 'w:t' || $node->nodeName == 'w:delText') { // TextRun $textContent = htmlspecialchars($xmlReader->getValue('.', $node), ENT_QUOTES, 'UTF-8'); @@ -299,7 +308,7 @@ abstract class AbstractPart if ($runParent->nodeName == 'w:hyperlink') { $rId = $xmlReader->getAttribute('r:id', $runParent); $target = $this->getMediaTarget($docPart, $rId); - if (!is_null($target)) { + if (null !== $target) { $parent->addLink($target, $textContent, $fontStyle, $paragraphStyle); } else { $parent->addText($textContent, $fontStyle, $paragraphStyle); @@ -307,10 +316,10 @@ abstract class AbstractPart } else { /** @var AbstractElement $element */ $element = $parent->addText($textContent, $fontStyle, $paragraphStyle); - if (in_array($runParent->nodeName, array('w:ins', 'w:del'))) { + if (in_array($runParent->nodeName, ['w:ins', 'w:del'])) { $type = ($runParent->nodeName == 'w:del') ? TrackChange::DELETED : TrackChange::INSERTED; $author = $runParent->getAttribute('w:author'); - $date = \DateTime::createFromFormat('Y-m-d\TH:i:s\Z', $runParent->getAttribute('w:date')); + $date = DateTime::createFromFormat('Y-m-d\TH:i:s\Z', $runParent->getAttribute('w:date')); $element->setChangeInfo($type, $author, $date); } } @@ -320,12 +329,10 @@ abstract class AbstractPart /** * Read w:tbl. * - * @param \PhpOffice\Common\XMLReader $xmlReader - * @param \DOMElement $domNode * @param mixed $parent * @param string $docPart */ - protected function readTable(XMLReader $xmlReader, \DOMElement $domNode, $parent, $docPart = 'document') + protected function readTable(XMLReader $xmlReader, DOMElement $domNode, $parent, $docPart = 'document'): void { // Table style $tblStyle = null; @@ -343,11 +350,11 @@ abstract class AbstractPart $rowHeight = $xmlReader->getAttribute('w:val', $tblNode, 'w:trPr/w:trHeight'); $rowHRule = $xmlReader->getAttribute('w:hRule', $tblNode, 'w:trPr/w:trHeight'); $rowHRule = $rowHRule == 'exact'; - $rowStyle = array( - 'tblHeader' => $xmlReader->elementExists('w:trPr/w:tblHeader', $tblNode), - 'cantSplit' => $xmlReader->elementExists('w:trPr/w:cantSplit', $tblNode), + $rowStyle = [ + 'tblHeader' => $xmlReader->elementExists('w:trPr/w:tblHeader', $tblNode), + 'cantSplit' => $xmlReader->elementExists('w:trPr/w:cantSplit', $tblNode), 'exactHeight' => $rowHRule, - ); + ]; $row = $table->addRow($rowHeight, $rowStyle); $rowNodes = $xmlReader->getElements('*', $tblNode); @@ -358,7 +365,7 @@ abstract class AbstractPart $cellWidth = $xmlReader->getAttribute('w:w', $rowNode, 'w:tcPr/w:tcW'); $cellStyle = null; $cellStyleNode = $xmlReader->getElement('w:tcPr', $rowNode); - if (!is_null($cellStyleNode)) { + if (null !== $cellStyleNode) { $cellStyle = $this->readCellStyle($xmlReader, $cellStyleNode); } @@ -378,48 +385,44 @@ abstract class AbstractPart /** * Read w:pPr. * - * @param \PhpOffice\Common\XMLReader $xmlReader - * @param \DOMElement $domNode - * @return array|null + * @return null|array */ - protected function readParagraphStyle(XMLReader $xmlReader, \DOMElement $domNode) + protected function readParagraphStyle(XMLReader $xmlReader, DOMElement $domNode) { if (!$xmlReader->elementExists('w:pPr', $domNode)) { return null; } $styleNode = $xmlReader->getElement('w:pPr', $domNode); - $styleDefs = array( - 'styleName' => array(self::READ_VALUE, array('w:pStyle', 'w:name')), - 'alignment' => array(self::READ_VALUE, 'w:jc'), - 'basedOn' => array(self::READ_VALUE, 'w:basedOn'), - 'next' => array(self::READ_VALUE, 'w:next'), - 'indent' => array(self::READ_VALUE, 'w:ind', 'w:left'), - 'hanging' => array(self::READ_VALUE, 'w:ind', 'w:hanging'), - 'spaceAfter' => array(self::READ_VALUE, 'w:spacing', 'w:after'), - 'spaceBefore' => array(self::READ_VALUE, 'w:spacing', 'w:before'), - 'widowControl' => array(self::READ_FALSE, 'w:widowControl'), - 'keepNext' => array(self::READ_TRUE, 'w:keepNext'), - 'keepLines' => array(self::READ_TRUE, 'w:keepLines'), - 'pageBreakBefore' => array(self::READ_TRUE, 'w:pageBreakBefore'), - 'contextualSpacing' => array(self::READ_TRUE, 'w:contextualSpacing'), - 'bidi' => array(self::READ_TRUE, 'w:bidi'), - 'suppressAutoHyphens' => array(self::READ_TRUE, 'w:suppressAutoHyphens'), - ); + $styleDefs = [ + 'styleName' => [self::READ_VALUE, ['w:pStyle', 'w:name']], + 'alignment' => [self::READ_VALUE, 'w:jc'], + 'basedOn' => [self::READ_VALUE, 'w:basedOn'], + 'next' => [self::READ_VALUE, 'w:next'], + 'indent' => [self::READ_VALUE, 'w:ind', 'w:left'], + 'hanging' => [self::READ_VALUE, 'w:ind', 'w:hanging'], + 'spaceAfter' => [self::READ_VALUE, 'w:spacing', 'w:after'], + 'spaceBefore' => [self::READ_VALUE, 'w:spacing', 'w:before'], + 'widowControl' => [self::READ_FALSE, 'w:widowControl'], + 'keepNext' => [self::READ_TRUE, 'w:keepNext'], + 'keepLines' => [self::READ_TRUE, 'w:keepLines'], + 'pageBreakBefore' => [self::READ_TRUE, 'w:pageBreakBefore'], + 'contextualSpacing' => [self::READ_TRUE, 'w:contextualSpacing'], + 'bidi' => [self::READ_TRUE, 'w:bidi'], + 'suppressAutoHyphens' => [self::READ_TRUE, 'w:suppressAutoHyphens'], + ]; return $this->readStyleDefs($xmlReader, $styleNode, $styleDefs); } /** - * Read w:rPr + * Read w:rPr. * - * @param \PhpOffice\Common\XMLReader $xmlReader - * @param \DOMElement $domNode - * @return array|null + * @return null|array */ - protected function readFontStyle(XMLReader $xmlReader, \DOMElement $domNode) + protected function readFontStyle(XMLReader $xmlReader, DOMElement $domNode) { - if (is_null($domNode)) { + if (null === $domNode) { return null; } // Hyperlink has an extra w:r child @@ -431,64 +434,63 @@ abstract class AbstractPart } $styleNode = $xmlReader->getElement('w:rPr', $domNode); - $styleDefs = array( - 'styleName' => array(self::READ_VALUE, 'w:rStyle'), - 'name' => array(self::READ_VALUE, 'w:rFonts', array('w:ascii', 'w:hAnsi', 'w:eastAsia', 'w:cs')), - 'hint' => array(self::READ_VALUE, 'w:rFonts', 'w:hint'), - 'size' => array(self::READ_SIZE, array('w:sz', 'w:szCs')), - 'color' => array(self::READ_VALUE, 'w:color'), - 'underline' => array(self::READ_VALUE, 'w:u'), - 'bold' => array(self::READ_TRUE, 'w:b'), - 'italic' => array(self::READ_TRUE, 'w:i'), - 'strikethrough' => array(self::READ_TRUE, 'w:strike'), - 'doubleStrikethrough' => array(self::READ_TRUE, 'w:dstrike'), - 'smallCaps' => array(self::READ_TRUE, 'w:smallCaps'), - 'allCaps' => array(self::READ_TRUE, 'w:caps'), - 'superScript' => array(self::READ_EQUAL, 'w:vertAlign', 'w:val', 'superscript'), - 'subScript' => array(self::READ_EQUAL, 'w:vertAlign', 'w:val', 'subscript'), - 'fgColor' => array(self::READ_VALUE, 'w:highlight'), - 'rtl' => array(self::READ_TRUE, 'w:rtl'), - 'lang' => array(self::READ_VALUE, 'w:lang'), - 'position' => array(self::READ_VALUE, 'w:position'), - 'hidden' => array(self::READ_TRUE, 'w:vanish'), - ); + $styleDefs = [ + 'styleName' => [self::READ_VALUE, 'w:rStyle'], + 'name' => [self::READ_VALUE, 'w:rFonts', ['w:ascii', 'w:hAnsi', 'w:eastAsia', 'w:cs']], + 'hint' => [self::READ_VALUE, 'w:rFonts', 'w:hint'], + 'size' => [self::READ_SIZE, ['w:sz', 'w:szCs']], + 'color' => [self::READ_VALUE, 'w:color'], + 'underline' => [self::READ_VALUE, 'w:u'], + 'bold' => [self::READ_TRUE, 'w:b'], + 'italic' => [self::READ_TRUE, 'w:i'], + 'strikethrough' => [self::READ_TRUE, 'w:strike'], + 'doubleStrikethrough' => [self::READ_TRUE, 'w:dstrike'], + 'smallCaps' => [self::READ_TRUE, 'w:smallCaps'], + 'allCaps' => [self::READ_TRUE, 'w:caps'], + 'superScript' => [self::READ_EQUAL, 'w:vertAlign', 'w:val', 'superscript'], + 'subScript' => [self::READ_EQUAL, 'w:vertAlign', 'w:val', 'subscript'], + 'fgColor' => [self::READ_VALUE, 'w:highlight'], + 'rtl' => [self::READ_TRUE, 'w:rtl'], + 'lang' => [self::READ_VALUE, 'w:lang'], + 'position' => [self::READ_VALUE, 'w:position'], + 'hidden' => [self::READ_TRUE, 'w:vanish'], + ]; return $this->readStyleDefs($xmlReader, $styleNode, $styleDefs); } /** - * Read w:tblPr + * Read w:tblPr. + * + * @return null|array|string * - * @param \PhpOffice\Common\XMLReader $xmlReader - * @param \DOMElement $domNode - * @return string|array|null * @todo Capture w:tblStylePr w:type="firstRow" */ - protected function readTableStyle(XMLReader $xmlReader, \DOMElement $domNode) + protected function readTableStyle(XMLReader $xmlReader, DOMElement $domNode) { $style = null; - $margins = array('top', 'left', 'bottom', 'right'); - $borders = array_merge($margins, array('insideH', 'insideV')); + $margins = ['top', 'left', 'bottom', 'right']; + $borders = array_merge($margins, ['insideH', 'insideV']); if ($xmlReader->elementExists('w:tblPr', $domNode)) { if ($xmlReader->elementExists('w:tblPr/w:tblStyle', $domNode)) { $style = $xmlReader->getAttribute('w:val', $domNode, 'w:tblPr/w:tblStyle'); } else { $styleNode = $xmlReader->getElement('w:tblPr', $domNode); - $styleDefs = array(); + $styleDefs = []; foreach ($margins as $side) { $ucfSide = ucfirst($side); - $styleDefs["cellMargin$ucfSide"] = array(self::READ_VALUE, "w:tblCellMar/w:$side", 'w:w'); + $styleDefs["cellMargin$ucfSide"] = [self::READ_VALUE, "w:tblCellMar/w:$side", 'w:w']; } foreach ($borders as $side) { $ucfSide = ucfirst($side); - $styleDefs["border{$ucfSide}Size"] = array(self::READ_VALUE, "w:tblBorders/w:$side", 'w:sz'); - $styleDefs["border{$ucfSide}Color"] = array(self::READ_VALUE, "w:tblBorders/w:$side", 'w:color'); - $styleDefs["border{$ucfSide}Style"] = array(self::READ_VALUE, "w:tblBorders/w:$side", 'w:val'); + $styleDefs["border{$ucfSide}Size"] = [self::READ_VALUE, "w:tblBorders/w:$side", 'w:sz']; + $styleDefs["border{$ucfSide}Color"] = [self::READ_VALUE, "w:tblBorders/w:$side", 'w:color']; + $styleDefs["border{$ucfSide}Style"] = [self::READ_VALUE, "w:tblBorders/w:$side", 'w:val']; } - $styleDefs['layout'] = array(self::READ_VALUE, 'w:tblLayout', 'w:type'); - $styleDefs['bidiVisual'] = array(self::READ_TRUE, 'w:bidiVisual'); - $styleDefs['cellSpacing'] = array(self::READ_VALUE, 'w:tblCellSpacing', 'w:w'); + $styleDefs['layout'] = [self::READ_VALUE, 'w:tblLayout', 'w:type']; + $styleDefs['bidiVisual'] = [self::READ_TRUE, 'w:bidiVisual']; + $styleDefs['cellSpacing'] = [self::READ_VALUE, 'w:tblCellSpacing', 'w:w']; $style = $this->readStyleDefs($xmlReader, $styleNode, $styleDefs); $tablePositionNode = $xmlReader->getElement('w:tblpPr', $styleNode); @@ -507,77 +509,70 @@ abstract class AbstractPart } /** - * Read w:tblpPr + * Read w:tblpPr. * - * @param \PhpOffice\Common\XMLReader $xmlReader - * @param \DOMElement $domNode * @return array */ - private function readTablePosition(XMLReader $xmlReader, \DOMElement $domNode) + private function readTablePosition(XMLReader $xmlReader, DOMElement $domNode) { - $styleDefs = array( - 'leftFromText' => array(self::READ_VALUE, '.', 'w:leftFromText'), - 'rightFromText' => array(self::READ_VALUE, '.', 'w:rightFromText'), - 'topFromText' => array(self::READ_VALUE, '.', 'w:topFromText'), - 'bottomFromText' => array(self::READ_VALUE, '.', 'w:bottomFromText'), - 'vertAnchor' => array(self::READ_VALUE, '.', 'w:vertAnchor'), - 'horzAnchor' => array(self::READ_VALUE, '.', 'w:horzAnchor'), - 'tblpXSpec' => array(self::READ_VALUE, '.', 'w:tblpXSpec'), - 'tblpX' => array(self::READ_VALUE, '.', 'w:tblpX'), - 'tblpYSpec' => array(self::READ_VALUE, '.', 'w:tblpYSpec'), - 'tblpY' => array(self::READ_VALUE, '.', 'w:tblpY'), - ); + $styleDefs = [ + 'leftFromText' => [self::READ_VALUE, '.', 'w:leftFromText'], + 'rightFromText' => [self::READ_VALUE, '.', 'w:rightFromText'], + 'topFromText' => [self::READ_VALUE, '.', 'w:topFromText'], + 'bottomFromText' => [self::READ_VALUE, '.', 'w:bottomFromText'], + 'vertAnchor' => [self::READ_VALUE, '.', 'w:vertAnchor'], + 'horzAnchor' => [self::READ_VALUE, '.', 'w:horzAnchor'], + 'tblpXSpec' => [self::READ_VALUE, '.', 'w:tblpXSpec'], + 'tblpX' => [self::READ_VALUE, '.', 'w:tblpX'], + 'tblpYSpec' => [self::READ_VALUE, '.', 'w:tblpYSpec'], + 'tblpY' => [self::READ_VALUE, '.', 'w:tblpY'], + ]; return $this->readStyleDefs($xmlReader, $domNode, $styleDefs); } /** - * Read w:tblInd + * Read w:tblInd. * - * @param \PhpOffice\Common\XMLReader $xmlReader - * @param \DOMElement $domNode * @return TblWidthComplexType */ - private function readTableIndent(XMLReader $xmlReader, \DOMElement $domNode) + private function readTableIndent(XMLReader $xmlReader, DOMElement $domNode) { - $styleDefs = array( - 'value' => array(self::READ_VALUE, '.', 'w:w'), - 'type' => array(self::READ_VALUE, '.', 'w:type'), - ); + $styleDefs = [ + 'value' => [self::READ_VALUE, '.', 'w:w'], + 'type' => [self::READ_VALUE, '.', 'w:type'], + ]; $styleDefs = $this->readStyleDefs($xmlReader, $domNode, $styleDefs); return new TblWidthComplexType((int) $styleDefs['value'], $styleDefs['type']); } /** - * Read w:tcPr + * Read w:tcPr. * - * @param \PhpOffice\Common\XMLReader $xmlReader - * @param \DOMElement $domNode * @return array */ - private function readCellStyle(XMLReader $xmlReader, \DOMElement $domNode) + private function readCellStyle(XMLReader $xmlReader, DOMElement $domNode) { - $styleDefs = array( - 'valign' => array(self::READ_VALUE, 'w:vAlign'), - 'textDirection' => array(self::READ_VALUE, 'w:textDirection'), - 'gridSpan' => array(self::READ_VALUE, 'w:gridSpan'), - 'vMerge' => array(self::READ_VALUE, 'w:vMerge'), - 'bgColor' => array(self::READ_VALUE, 'w:shd', 'w:fill'), - ); + $styleDefs = [ + 'valign' => [self::READ_VALUE, 'w:vAlign'], + 'textDirection' => [self::READ_VALUE, 'w:textDirection'], + 'gridSpan' => [self::READ_VALUE, 'w:gridSpan'], + 'vMerge' => [self::READ_VALUE, 'w:vMerge', null, null, 'continue'], + 'bgColor' => [self::READ_VALUE, 'w:shd', 'w:fill'], + ]; return $this->readStyleDefs($xmlReader, $domNode, $styleDefs); } /** - * Returns the first child element found + * Returns the first child element found. * - * @param XMLReader $xmlReader - * @param \DOMElement $parentNode - * @param string|array $elements - * @return string|null + * @param null|array|string $elements + * + * @return null|string */ - private function findPossibleElement(XMLReader $xmlReader, \DOMElement $parentNode = null, $elements) + private function findPossibleElement(XMLReader $xmlReader, ?DOMElement $parentNode = null, $elements = null) { if (is_array($elements)) { //if element is an array, we take the first element that exists in the XML @@ -594,14 +589,13 @@ abstract class AbstractPart } /** - * Returns the first attribute found + * Returns the first attribute found. * - * @param XMLReader $xmlReader - * @param \DOMElement $node - * @param string|array $attributes - * @return string|null + * @param array|string $attributes + * + * @return null|string */ - private function findPossibleAttribute(XMLReader $xmlReader, \DOMElement $node, $attributes) + private function findPossibleAttribute(XMLReader $xmlReader, DOMElement $node, $attributes) { //if attribute is an array, we take the first attribute that exists in the XML if (is_array($attributes)) { @@ -618,20 +612,21 @@ abstract class AbstractPart } /** - * Read style definition + * Read style definition. * - * @param \PhpOffice\Common\XMLReader $xmlReader - * @param \DOMElement $parentNode + * @param DOMElement $parentNode * @param array $styleDefs + * * @ignoreScrutinizerPatch + * * @return array */ - protected function readStyleDefs(XMLReader $xmlReader, \DOMElement $parentNode = null, $styleDefs = array()) + protected function readStyleDefs(XMLReader $xmlReader, ?DOMElement $parentNode = null, $styleDefs = []) { - $styles = array(); + $styles = []; foreach ($styleDefs as $styleProp => $styleVal) { - list($method, $element, $attribute, $expected) = array_pad($styleVal, 4, null); + [$method, $element, $attribute, $expected, $default] = array_pad($styleVal, 5, null); $element = $this->findPossibleElement($xmlReader, $parentNode, $element); if ($element === null) { @@ -645,7 +640,7 @@ abstract class AbstractPart // Use w:val as default if no attribute assigned $attribute = ($attribute === null) ? 'w:val' : $attribute; - $attributeValue = $xmlReader->getAttribute($attribute, $node); + $attributeValue = $xmlReader->getAttribute($attribute, $node) ?? $default; $styleValue = $this->readStyleDef($method, $attributeValue, $expected); if ($styleValue !== null) { @@ -658,12 +653,15 @@ abstract class AbstractPart } /** - * Return style definition based on conversion method + * Return style definition based on conversion method. * * @param string $method + * * @ignoreScrutinizerPatch - * @param string|null $attributeValue + * + * @param null|string $attributeValue * @param mixed $expected + * * @return mixed */ private function readStyleDef($method, $attributeValue, $expected) @@ -684,10 +682,12 @@ abstract class AbstractPart } /** - * Parses the value of the on/off value, null is considered true as it means the w:val attribute was not present + * Parses the value of the on/off value, null is considered true as it means the w:val attribute was not present. * * @see http://www.datypic.com/sc/ooxml/t-w_ST_OnOff.html + * * @param string $value + * * @return bool */ private function isOn($value = null) @@ -696,17 +696,18 @@ abstract class AbstractPart } /** - * Returns the target of image, object, or link as stored in ::readMainRels + * Returns the target of image, object, or link as stored in ::readMainRels. * * @param string $docPart * @param string $rId - * @return string|null + * + * @return null|string */ private function getMediaTarget($docPart, $rId) { $target = null; - if (isset($this->rels[$docPart]) && isset($this->rels[$docPart][$rId])) { + if (isset($this->rels[$docPart], $this->rels[$docPart][$rId])) { $target = $this->rels[$docPart][$rId]['target']; } @@ -714,17 +715,18 @@ abstract class AbstractPart } /** - * Returns the target mode + * Returns the target mode. * * @param string $docPart * @param string $rId - * @return string|null + * + * @return null|string */ private function getTargetMode($docPart, $rId) { $mode = null; - if (isset($this->rels[$docPart]) && isset($this->rels[$docPart][$rId])) { + if (isset($this->rels[$docPart], $this->rels[$docPart][$rId])) { $mode = $this->rels[$docPart][$rId]['targetMode']; } diff --git a/PhpOffice/PhpWord/Reader/Word2007/DocPropsApp.php b/PhpOffice/PhpWord/Reader/Word2007/DocPropsApp.php old mode 100755 new mode 100644 index decc510..9d6f3cb --- a/PhpOffice/PhpWord/Reader/Word2007/DocPropsApp.php +++ b/PhpOffice/PhpWord/Reader/Word2007/DocPropsApp.php @@ -11,30 +11,30 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Reader\Word2007; /** - * Extended properties reader + * Extended properties reader. * * @since 0.10.0 */ class DocPropsApp extends DocPropsCore { /** - * Property mapping + * Property mapping. * * @var array */ - protected $mapping = array('Company' => 'setCompany', 'Manager' => 'setManager'); + protected $mapping = ['Company' => 'setCompany', 'Manager' => 'setManager']; /** - * Callback functions + * Callback functions. * * @var array */ - protected $callbacks = array(); + protected $callbacks = []; } diff --git a/PhpOffice/PhpWord/Reader/Word2007/DocPropsCore.php b/PhpOffice/PhpWord/Reader/Word2007/DocPropsCore.php old mode 100755 new mode 100644 index 36eeceb..d3eac27 --- a/PhpOffice/PhpWord/Reader/Word2007/DocPropsCore.php +++ b/PhpOffice/PhpWord/Reader/Word2007/DocPropsCore.php @@ -11,52 +11,50 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Reader\Word2007; -use PhpOffice\Common\XMLReader; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Shared\XMLReader; /** - * Core properties reader + * Core properties reader. * * @since 0.10.0 */ class DocPropsCore extends AbstractPart { /** - * Property mapping + * Property mapping. * * @var array */ - protected $mapping = array( - 'dc:creator' => 'setCreator', - 'dc:title' => 'setTitle', - 'dc:description' => 'setDescription', - 'dc:subject' => 'setSubject', - 'cp:keywords' => 'setKeywords', - 'cp:category' => 'setCategory', + protected $mapping = [ + 'dc:creator' => 'setCreator', + 'dc:title' => 'setTitle', + 'dc:description' => 'setDescription', + 'dc:subject' => 'setSubject', + 'cp:keywords' => 'setKeywords', + 'cp:category' => 'setCategory', 'cp:lastModifiedBy' => 'setLastModifiedBy', - 'dcterms:created' => 'setCreated', - 'dcterms:modified' => 'setModified', - ); + 'dcterms:created' => 'setCreated', + 'dcterms:modified' => 'setModified', + ]; /** - * Callback functions + * Callback functions. * * @var array */ - protected $callbacks = array('dcterms:created' => 'strtotime', 'dcterms:modified' => 'strtotime'); + protected $callbacks = ['dcterms:created' => 'strtotime', 'dcterms:modified' => 'strtotime']; /** * Read core/extended document properties. - * - * @param \PhpOffice\PhpWord\PhpWord $phpWord */ - public function read(PhpWord $phpWord) + public function read(PhpWord $phpWord): void { $xmlReader = new XMLReader(); $xmlReader->getDomFromZip($this->docFile, $this->xmlFile); diff --git a/PhpOffice/PhpWord/Reader/Word2007/DocPropsCustom.php b/PhpOffice/PhpWord/Reader/Word2007/DocPropsCustom.php old mode 100755 new mode 100644 index a6835aa..69cd551 --- a/PhpOffice/PhpWord/Reader/Word2007/DocPropsCustom.php +++ b/PhpOffice/PhpWord/Reader/Word2007/DocPropsCustom.php @@ -11,18 +11,18 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Reader\Word2007; -use PhpOffice\Common\XMLReader; use PhpOffice\PhpWord\Metadata\DocInfo; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Shared\XMLReader; /** - * Custom properties reader + * Custom properties reader. * * @since 0.11.0 */ @@ -30,10 +30,8 @@ class DocPropsCustom extends AbstractPart { /** * Read custom document properties. - * - * @param \PhpOffice\PhpWord\PhpWord $phpWord */ - public function read(PhpWord $phpWord) + public function read(PhpWord $phpWord): void { $xmlReader = new XMLReader(); $xmlReader->getDomFromZip($this->docFile, $this->xmlFile); diff --git a/PhpOffice/PhpWord/Reader/Word2007/Document.php b/PhpOffice/PhpWord/Reader/Word2007/Document.php old mode 100755 new mode 100644 index f0d1194..da42bdd --- a/PhpOffice/PhpWord/Reader/Word2007/Document.php +++ b/PhpOffice/PhpWord/Reader/Word2007/Document.php @@ -11,26 +11,28 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Reader\Word2007; -use PhpOffice\Common\XMLReader; +use DOMElement; use PhpOffice\PhpWord\Element\Section; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Shared\XMLReader; /** - * Document reader + * Document reader. * * @since 0.10.0 + * * @SuppressWarnings(PHPMD.UnusedPrivateMethod) For readWPNode */ class Document extends AbstractPart { /** - * PhpWord object + * PhpWord object. * * @var \PhpOffice\PhpWord\PhpWord */ @@ -38,15 +40,13 @@ class Document extends AbstractPart /** * Read document.xml. - * - * @param \PhpOffice\PhpWord\PhpWord $phpWord */ - public function read(PhpWord $phpWord) + public function read(PhpWord $phpWord): void { $this->phpWord = $phpWord; $xmlReader = new XMLReader(); $xmlReader->getDomFromZip($this->docFile, $this->xmlFile); - $readMethods = array('w:p' => 'readWPNode', 'w:tbl' => 'readTable', 'w:sectPr' => 'readWSectPrNode'); + $readMethods = ['w:p' => 'readWPNode', 'w:tbl' => 'readTable', 'w:sectPr' => 'readWSectPrNode']; $nodes = $xmlReader->getElements('w:body/*'); if ($nodes->length > 0) { @@ -64,16 +64,15 @@ class Document extends AbstractPart * Read header footer. * * @param array $settings - * @param \PhpOffice\PhpWord\Element\Section &$section */ - private function readHeaderFooter($settings, Section &$section) + private function readHeaderFooter($settings, Section &$section): void { - $readMethods = array('w:p' => 'readParagraph', 'w:tbl' => 'readTable'); + $readMethods = ['w:p' => 'readParagraph', 'w:tbl' => 'readTable']; if (is_array($settings) && isset($settings['hf'])) { foreach ($settings['hf'] as $rId => $hfSetting) { if (isset($this->rels['document'][$rId])) { - list($hfType, $xmlFile, $docPart) = array_values($this->rels['document'][$rId]); + [$hfType, $xmlFile, $docPart] = array_values($this->rels['document'][$rId]); $addMethod = "add{$hfType}"; $hfObject = $section->$addMethod($hfSetting['type']); @@ -95,31 +94,30 @@ class Document extends AbstractPart } /** - * Read w:sectPr + * Read w:sectPr. * - * @param \PhpOffice\Common\XMLReader $xmlReader - * @param \DOMElement $domNode * @ignoreScrutinizerPatch + * * @return array */ - private function readSectionStyle(XMLReader $xmlReader, \DOMElement $domNode) + private function readSectionStyle(XMLReader $xmlReader, DOMElement $domNode) { - $styleDefs = array( - 'breakType' => array(self::READ_VALUE, 'w:type'), - 'vAlign' => array(self::READ_VALUE, 'w:vAlign'), - 'pageSizeW' => array(self::READ_VALUE, 'w:pgSz', 'w:w'), - 'pageSizeH' => array(self::READ_VALUE, 'w:pgSz', 'w:h'), - 'orientation' => array(self::READ_VALUE, 'w:pgSz', 'w:orient'), - 'colsNum' => array(self::READ_VALUE, 'w:cols', 'w:num'), - 'colsSpace' => array(self::READ_VALUE, 'w:cols', 'w:space'), - 'marginTop' => array(self::READ_VALUE, 'w:pgMar', 'w:top'), - 'marginLeft' => array(self::READ_VALUE, 'w:pgMar', 'w:left'), - 'marginBottom' => array(self::READ_VALUE, 'w:pgMar', 'w:bottom'), - 'marginRight' => array(self::READ_VALUE, 'w:pgMar', 'w:right'), - 'headerHeight' => array(self::READ_VALUE, 'w:pgMar', 'w:header'), - 'footerHeight' => array(self::READ_VALUE, 'w:pgMar', 'w:footer'), - 'gutter' => array(self::READ_VALUE, 'w:pgMar', 'w:gutter'), - ); + $styleDefs = [ + 'breakType' => [self::READ_VALUE, 'w:type'], + 'vAlign' => [self::READ_VALUE, 'w:vAlign'], + 'pageSizeW' => [self::READ_VALUE, 'w:pgSz', 'w:w'], + 'pageSizeH' => [self::READ_VALUE, 'w:pgSz', 'w:h'], + 'orientation' => [self::READ_VALUE, 'w:pgSz', 'w:orient'], + 'colsNum' => [self::READ_VALUE, 'w:cols', 'w:num'], + 'colsSpace' => [self::READ_VALUE, 'w:cols', 'w:space'], + 'marginTop' => [self::READ_VALUE, 'w:pgMar', 'w:top'], + 'marginLeft' => [self::READ_VALUE, 'w:pgMar', 'w:left'], + 'marginBottom' => [self::READ_VALUE, 'w:pgMar', 'w:bottom'], + 'marginRight' => [self::READ_VALUE, 'w:pgMar', 'w:right'], + 'headerHeight' => [self::READ_VALUE, 'w:pgMar', 'w:header'], + 'footerHeight' => [self::READ_VALUE, 'w:pgMar', 'w:footer'], + 'gutter' => [self::READ_VALUE, 'w:pgMar', 'w:gutter'], + ]; $styles = $this->readStyleDefs($xmlReader, $domNode, $styleDefs); // Header and footer @@ -128,10 +126,10 @@ class Document extends AbstractPart foreach ($nodes as $node) { if ($node->nodeName == 'w:headerReference' || $node->nodeName == 'w:footerReference') { $id = $xmlReader->getAttribute('r:id', $node); - $styles['hf'][$id] = array( + $styles['hf'][$id] = [ 'method' => str_replace('w:', '', str_replace('Reference', '', $node->nodeName)), - 'type' => $xmlReader->getAttribute('w:type', $node), - ); + 'type' => $xmlReader->getAttribute('w:type', $node), + ]; } } @@ -141,13 +139,9 @@ class Document extends AbstractPart /** * Read w:p node. * - * @param \PhpOffice\Common\XMLReader $xmlReader - * @param \DOMElement $node - * @param \PhpOffice\PhpWord\Element\Section &$section - * * @todo */ - private function readWPNode(XMLReader $xmlReader, \DOMElement $node, Section &$section) + private function readWPNode(XMLReader $xmlReader, DOMElement $node, Section &$section): void { // Page break if ($xmlReader->getAttribute('w:type', $node, 'w:r/w:br') == 'page') { @@ -169,12 +163,8 @@ class Document extends AbstractPart /** * Read w:sectPr node. - * - * @param \PhpOffice\Common\XMLReader $xmlReader - * @param \DOMElement $node - * @param \PhpOffice\PhpWord\Element\Section &$section */ - private function readWSectPrNode(XMLReader $xmlReader, \DOMElement $node, Section &$section) + private function readWSectPrNode(XMLReader $xmlReader, DOMElement $node, Section &$section): void { $style = $this->readSectionStyle($xmlReader, $node); $section->setStyle($style); diff --git a/PhpOffice/PhpWord/Reader/Word2007/Endnotes.php b/PhpOffice/PhpWord/Reader/Word2007/Endnotes.php old mode 100755 new mode 100644 index aa8b65d..0c6b18d --- a/PhpOffice/PhpWord/Reader/Word2007/Endnotes.php +++ b/PhpOffice/PhpWord/Reader/Word2007/Endnotes.php @@ -11,28 +11,28 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Reader\Word2007; /** - * Endnotes reader + * Endnotes reader. * * @since 0.10.0 */ class Endnotes extends Footnotes { /** - * Collection name + * Collection name. * * @var string */ protected $collection = 'endnotes'; /** - * Element name + * Element name. * * @var string */ diff --git a/PhpOffice/PhpWord/Reader/Word2007/Footnotes.php b/PhpOffice/PhpWord/Reader/Word2007/Footnotes.php old mode 100755 new mode 100644 index 634f473..d40af19 --- a/PhpOffice/PhpWord/Reader/Word2007/Footnotes.php +++ b/PhpOffice/PhpWord/Reader/Word2007/Footnotes.php @@ -11,31 +11,31 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Reader\Word2007; -use PhpOffice\Common\XMLReader; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Shared\XMLReader; /** - * Footnotes reader + * Footnotes reader. * * @since 0.10.0 */ class Footnotes extends AbstractPart { /** - * Collection name footnotes|endnotes + * Collection name footnotes|endnotes. * * @var string */ protected $collection = 'footnotes'; /** - * Element name footnote|endnote + * Element name footnote|endnote. * * @var string */ @@ -43,10 +43,8 @@ class Footnotes extends AbstractPart /** * Read (footnotes|endnotes).xml. - * - * @param \PhpOffice\PhpWord\PhpWord $phpWord */ - public function read(PhpWord $phpWord) + public function read(PhpWord $phpWord): void { $xmlReader = new XMLReader(); $xmlReader->getDomFromZip($this->docFile, $this->xmlFile); @@ -58,7 +56,7 @@ class Footnotes extends AbstractPart // Avoid w:type "separator" and "continuationSeparator" // Only look for or without w:type attribute, or with w:type = normal - if ((is_null($type) || $type === 'normal')) { + if ((null === $type || $type === 'normal')) { $element = $this->getElement($phpWord, $id); if ($element !== null) { $pNodes = $xmlReader->getElements('w:p/*', $node); @@ -74,11 +72,11 @@ class Footnotes extends AbstractPart } /** - * Searches for the element with the given relationId + * Searches for the element with the given relationId. * - * @param PhpWord $phpWord * @param int $relationId - * @return \PhpOffice\PhpWord\Element\AbstractContainer|null + * + * @return null|\PhpOffice\PhpWord\Element\AbstractContainer */ private function getElement(PhpWord $phpWord, $relationId) { diff --git a/PhpOffice/PhpWord/Reader/Word2007/Numbering.php b/PhpOffice/PhpWord/Reader/Word2007/Numbering.php old mode 100755 new mode 100644 index 3f57cbf..4ae7a82 --- a/PhpOffice/PhpWord/Reader/Word2007/Numbering.php +++ b/PhpOffice/PhpWord/Reader/Word2007/Numbering.php @@ -11,17 +11,18 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Reader\Word2007; -use PhpOffice\Common\XMLReader; +use DOMElement; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Shared\XMLReader; /** - * Numbering reader + * Numbering reader. * * @since 0.10.0 */ @@ -29,13 +30,11 @@ class Numbering extends AbstractPart { /** * Read numbering.xml. - * - * @param \PhpOffice\PhpWord\PhpWord $phpWord */ - public function read(PhpWord $phpWord) + public function read(PhpWord $phpWord): void { - $abstracts = array(); - $numberings = array(); + $abstracts = []; + $numberings = []; $xmlReader = new XMLReader(); $xmlReader->getDomFromZip($this->docFile, $this->xmlFile); @@ -44,17 +43,19 @@ class Numbering extends AbstractPart if ($nodes->length > 0) { foreach ($nodes as $node) { $abstractId = $xmlReader->getAttribute('w:abstractNumId', $node); - $abstracts[$abstractId] = array('levels' => array()); + $abstracts[$abstractId] = ['levels' => []]; $abstract = &$abstracts[$abstractId]; $subnodes = $xmlReader->getElements('*', $node); foreach ($subnodes as $subnode) { switch ($subnode->nodeName) { case 'w:multiLevelType': $abstract['type'] = $xmlReader->getAttribute('w:val', $subnode); + break; case 'w:lvl': $levelId = $xmlReader->getAttribute('w:ilvl', $subnode); $abstract['levels'][$levelId] = $this->readLevel($xmlReader, $subnode, $levelId); + break; } } @@ -87,16 +88,15 @@ class Numbering extends AbstractPart } /** - * Read numbering level definition from w:abstractNum and w:num + * Read numbering level definition from w:abstractNum and w:num. * - * @param \PhpOffice\Common\XMLReader $xmlReader - * @param \DOMElement $subnode * @param int $levelId + * * @return array */ - private function readLevel(XMLReader $xmlReader, \DOMElement $subnode, $levelId) + private function readLevel(XMLReader $xmlReader, DOMElement $subnode, $levelId) { - $level = array(); + $level = []; $level['level'] = $levelId; $level['start'] = $xmlReader->getAttribute('w:val', $subnode, 'w:start'); @@ -112,7 +112,7 @@ class Numbering extends AbstractPart $level['hint'] = $xmlReader->getAttribute('w:hint', $subnode, 'w:rPr/w:rFonts'); foreach ($level as $key => $value) { - if (is_null($value)) { + if (null === $value) { unset($level[$key]); } } diff --git a/PhpOffice/PhpWord/Reader/Word2007/Settings.php b/PhpOffice/PhpWord/Reader/Word2007/Settings.php old mode 100755 new mode 100644 index 3084943..6357859 --- a/PhpOffice/PhpWord/Reader/Word2007/Settings.php +++ b/PhpOffice/PhpWord/Reader/Word2007/Settings.php @@ -11,25 +11,26 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Reader\Word2007; -use PhpOffice\Common\XMLReader; +use DOMElement; use PhpOffice\PhpWord\ComplexType\TrackChangesView; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Shared\XMLReader; use PhpOffice\PhpWord\Style\Language; /** - * Settings reader + * Settings reader. * * @since 0.14.0 */ class Settings extends AbstractPart { - private static $booleanProperties = array( + private static $booleanProperties = [ 'mirrorMargins', 'hideSpellingErrors', 'hideGrammaticalErrors', @@ -40,14 +41,12 @@ class Settings extends AbstractPart 'updateFields', 'autoHyphenation', 'doNotHyphenateCaps', - ); + ]; /** * Read settings.xml. - * - * @param \PhpOffice\PhpWord\PhpWord $phpWord */ - public function read(PhpWord $phpWord) + public function read(PhpWord $phpWord): void { $xmlReader = new XMLReader(); $xmlReader->getDomFromZip($this->docFile, $this->xmlFile); @@ -77,13 +76,9 @@ class Settings extends AbstractPart } /** - * Sets the document Language - * - * @param XMLReader $xmlReader - * @param PhpWord $phpWord - * @param \DOMElement $node + * Sets the document Language. */ - protected function setThemeFontLang(XMLReader $xmlReader, PhpWord $phpWord, \DOMElement $node) + protected function setThemeFontLang(XMLReader $xmlReader, PhpWord $phpWord, DOMElement $node): void { $val = $xmlReader->getAttribute('w:val', $node); $eastAsia = $xmlReader->getAttribute('w:eastAsia', $node); @@ -98,13 +93,9 @@ class Settings extends AbstractPart } /** - * Sets the document protection - * - * @param XMLReader $xmlReader - * @param PhpWord $phpWord - * @param \DOMElement $node + * Sets the document protection. */ - protected function setDocumentProtection(XMLReader $xmlReader, PhpWord $phpWord, \DOMElement $node) + protected function setDocumentProtection(XMLReader $xmlReader, PhpWord $phpWord, DOMElement $node): void { $documentProtection = $phpWord->getSettings()->getDocumentProtection(); @@ -115,13 +106,9 @@ class Settings extends AbstractPart } /** - * Sets the proof state - * - * @param XMLReader $xmlReader - * @param PhpWord $phpWord - * @param \DOMElement $node + * Sets the proof state. */ - protected function setProofState(XMLReader $xmlReader, PhpWord $phpWord, \DOMElement $node) + protected function setProofState(XMLReader $xmlReader, PhpWord $phpWord, DOMElement $node): void { $proofState = $phpWord->getSettings()->getProofState(); @@ -137,13 +124,9 @@ class Settings extends AbstractPart } /** - * Sets the proof state - * - * @param XMLReader $xmlReader - * @param PhpWord $phpWord - * @param \DOMElement $node + * Sets the proof state. */ - protected function setZoom(XMLReader $xmlReader, PhpWord $phpWord, \DOMElement $node) + protected function setZoom(XMLReader $xmlReader, PhpWord $phpWord, DOMElement $node): void { $percent = $xmlReader->getAttribute('w:percent', $node); $val = $xmlReader->getAttribute('w:val', $node); @@ -154,13 +137,9 @@ class Settings extends AbstractPart } /** - * Set the Revision view - * - * @param XMLReader $xmlReader - * @param PhpWord $phpWord - * @param \DOMElement $node + * Set the Revision view. */ - protected function setRevisionView(XMLReader $xmlReader, PhpWord $phpWord, \DOMElement $node) + protected function setRevisionView(XMLReader $xmlReader, PhpWord $phpWord, DOMElement $node): void { $revisionView = new TrackChangesView(); $revisionView->setMarkup(filter_var($xmlReader->getAttribute('w:markup', $node), FILTER_VALIDATE_BOOLEAN)); @@ -171,12 +150,7 @@ class Settings extends AbstractPart $phpWord->getSettings()->setRevisionView($revisionView); } - /** - * @param XMLReader $xmlReader - * @param PhpWord $phpWord - * @param \DOMElement $node - */ - protected function setConsecutiveHyphenLimit(XMLReader $xmlReader, PhpWord $phpWord, \DOMElement $node) + protected function setConsecutiveHyphenLimit(XMLReader $xmlReader, PhpWord $phpWord, DOMElement $node): void { $value = $xmlReader->getAttribute('w:val', $node); @@ -185,12 +159,7 @@ class Settings extends AbstractPart } } - /** - * @param XMLReader $xmlReader - * @param PhpWord $phpWord - * @param \DOMElement $node - */ - protected function setHyphenationZone(XMLReader $xmlReader, PhpWord $phpWord, \DOMElement $node) + protected function setHyphenationZone(XMLReader $xmlReader, PhpWord $phpWord, DOMElement $node): void { $value = $xmlReader->getAttribute('w:val', $node); diff --git a/PhpOffice/PhpWord/Reader/Word2007/Styles.php b/PhpOffice/PhpWord/Reader/Word2007/Styles.php old mode 100755 new mode 100644 index 97f29b4..4566398 --- a/PhpOffice/PhpWord/Reader/Word2007/Styles.php +++ b/PhpOffice/PhpWord/Reader/Word2007/Styles.php @@ -11,18 +11,18 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Reader\Word2007; -use PhpOffice\Common\XMLReader; use PhpOffice\PhpWord\PhpWord; +use PhpOffice\PhpWord\Shared\XMLReader; use PhpOffice\PhpWord\Style\Language; /** - * Styles reader + * Styles reader. * * @since 0.10.0 */ @@ -30,10 +30,8 @@ class Styles extends AbstractPart { /** * Read styles.xml. - * - * @param \PhpOffice\PhpWord\PhpWord $phpWord */ - public function read(PhpWord $phpWord) + public function read(PhpWord $phpWord): void { $xmlReader = new XMLReader(); $xmlReader->getDomFromZip($this->docFile, $this->xmlFile); @@ -65,10 +63,10 @@ class Styles extends AbstractPart foreach ($nodes as $node) { $type = $xmlReader->getAttribute('w:type', $node); $name = $xmlReader->getAttribute('w:val', $node, 'w:name'); - if (is_null($name)) { + if (null === $name) { $name = $xmlReader->getAttribute('w:styleId', $node); } - $headingMatches = array(); + $headingMatches = []; preg_match('/Heading\s*(\d)/i', $name, $headingMatches); // $default = ($xmlReader->getAttribute('w:default', $node) == 1); switch ($type) { @@ -86,18 +84,21 @@ class Styles extends AbstractPart $phpWord->addFontStyle($name, $fontStyle, $paragraphStyle); } } + break; case 'character': $fontStyle = $this->readFontStyle($xmlReader, $node); if (!empty($fontStyle)) { $phpWord->addFontStyle($name, $fontStyle); } + break; case 'table': $tStyle = $this->readTableStyle($xmlReader, $node); if (!empty($tStyle)) { $phpWord->addTableStyle($name, $tStyle); } + break; } } diff --git a/PhpOffice/PhpWord/Settings.php b/PhpOffice/PhpWord/Settings.php old mode 100755 new mode 100644 index 8de1a8d..7b1f456 --- a/PhpOffice/PhpWord/Settings.php +++ b/PhpOffice/PhpWord/Settings.php @@ -11,30 +11,30 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord; /** - * PHPWord settings class + * PHPWord settings class. * * @since 0.8.0 */ class Settings { /** - * Zip libraries + * Zip libraries. * * @const string */ const ZIPARCHIVE = 'ZipArchive'; const PCLZIP = 'PclZip'; - const OLD_LIB = 'PhpOffice\\PhpWord\\Shared\\ZipArchive'; // @deprecated 0.11 + const OLD_LIB = \PhpOffice\PhpWord\Shared\ZipArchive::class; // @deprecated 0.11 /** - * PDF rendering libraries + * PDF rendering libraries. * * @const string */ @@ -43,7 +43,7 @@ class Settings const PDF_RENDERER_MPDF = 'MPDF'; /** - * Measurement units multiplication factor + * Measurement units multiplication factor. * * Applied to: * - Section: margins, header/footer height, gutter, column spacing @@ -61,7 +61,7 @@ class Settings const UNIT_PICA = 'pica'; // = 1/6 inch = 12 points /** - * Default font settings + * Default font settings. * * OOXML defined font size values in halfpoints, i.e. twice of what PhpWord * use, and the conversion will be conducted during XML writing. @@ -70,55 +70,64 @@ class Settings const DEFAULT_FONT_SIZE = 10; const DEFAULT_FONT_COLOR = '000000'; const DEFAULT_FONT_CONTENT_TYPE = 'default'; // default|eastAsia|cs + const DEFAULT_PAPER = 'A4'; /** - * Compatibility option for XMLWriter + * Compatibility option for XMLWriter. * * @var bool */ private static $xmlWriterCompatibility = true; /** - * Name of the class used for Zip file management + * Name of the class used for Zip file management. * * @var string */ private static $zipClass = self::ZIPARCHIVE; /** - * Name of the external Library used for rendering PDF files + * Name of the external Library used for rendering PDF files. * * @var string */ - private static $pdfRendererName = null; + private static $pdfRendererName; /** - * Directory Path to the external Library used for rendering PDF files + * Directory Path to the external Library used for rendering PDF files. * * @var string */ - private static $pdfRendererPath = null; + private static $pdfRendererPath; /** - * Measurement unit + * Measurement unit. * - * @var int|float + * @var float|int */ private static $measurementUnit = self::UNIT_TWIP; /** - * Default font name + * Default font name. * * @var string */ private static $defaultFontName = self::DEFAULT_FONT_NAME; /** - * Default font size + * Default font size. + * * @var int */ private static $defaultFontSize = self::DEFAULT_FONT_SIZE; + /** + * Default paper. + * + * @var string + */ + private static $defaultPaper = self::DEFAULT_PAPER; + /** * The user defined temporary directory. * @@ -135,7 +144,7 @@ class Settings private static $outputEscapingEnabled = false; /** - * Return the compatibility option used by the XMLWriter + * Return the compatibility option used by the XMLWriter. * * @return bool Compatibility */ @@ -145,11 +154,12 @@ class Settings } /** - * Set the compatibility option used by the XMLWriter + * Set the compatibility option used by the XMLWriter. * * This sets the setIndent and setIndentString for better compatibility * * @param bool $compatibility + * * @return bool */ public static function setCompatibility($compatibility) @@ -161,7 +171,7 @@ class Settings } /** - * Get zip handler class + * Get zip handler class. * * @return string */ @@ -171,14 +181,15 @@ class Settings } /** - * Set zip handler class + * Set zip handler class. * * @param string $zipClass + * * @return bool */ public static function setZipClass($zipClass) { - if (in_array($zipClass, array(self::PCLZIP, self::ZIPARCHIVE, self::OLD_LIB))) { + if (in_array($zipClass, [self::PCLZIP, self::ZIPARCHIVE, self::OLD_LIB])) { self::$zipClass = $zipClass; return true; @@ -188,10 +199,11 @@ class Settings } /** - * Set details of the external library for rendering PDF files + * Set details of the external library for rendering PDF files. * * @param string $libraryName * @param string $libraryBaseDir + * * @return bool Success or failure */ public static function setPdfRenderer($libraryName, $libraryBaseDir) @@ -214,14 +226,15 @@ class Settings } /** - * Identify the external library to use for rendering PDF files + * Identify the external library to use for rendering PDF files. * * @param string $libraryName + * * @return bool */ public static function setPdfRendererName($libraryName) { - $pdfRenderers = array(self::PDF_RENDERER_DOMPDF, self::PDF_RENDERER_TCPDF, self::PDF_RENDERER_MPDF); + $pdfRenderers = [self::PDF_RENDERER_DOMPDF, self::PDF_RENDERER_TCPDF, self::PDF_RENDERER_MPDF]; if (!in_array($libraryName, $pdfRenderers)) { return false; } @@ -241,14 +254,15 @@ class Settings } /** - * Location of external library to use for rendering PDF files + * Location of external library to use for rendering PDF files. * * @param string $libraryBaseDir Directory path to the library's base folder + * * @return bool Success or failure */ public static function setPdfRendererPath($libraryBaseDir) { - if (false === file_exists($libraryBaseDir) || false === is_readable($libraryBaseDir)) { + if (!$libraryBaseDir || false === file_exists($libraryBaseDir) || false === is_readable($libraryBaseDir)) { return false; } self::$pdfRendererPath = $libraryBaseDir; @@ -257,7 +271,7 @@ class Settings } /** - * Get measurement unit + * Get measurement unit. * * @return string */ @@ -267,15 +281,16 @@ class Settings } /** - * Set measurement unit + * Set measurement unit. * * @param string $value + * * @return bool */ public static function setMeasurementUnit($value) { - $units = array(self::UNIT_TWIP, self::UNIT_CM, self::UNIT_MM, self::UNIT_INCH, - self::UNIT_POINT, self::UNIT_PICA, ); + $units = [self::UNIT_TWIP, self::UNIT_CM, self::UNIT_MM, self::UNIT_INCH, + self::UNIT_POINT, self::UNIT_PICA, ]; if (!in_array($value, $units)) { return false; } @@ -291,7 +306,7 @@ class Settings * * @param string $tempDir The user defined path to temporary directory */ - public static function setTempDir($tempDir) + public static function setTempDir($tempDir): void { self::$tempDir = $tempDir; } @@ -329,13 +344,13 @@ class Settings * * @param bool $outputEscapingEnabled */ - public static function setOutputEscapingEnabled($outputEscapingEnabled) + public static function setOutputEscapingEnabled($outputEscapingEnabled): void { self::$outputEscapingEnabled = $outputEscapingEnabled; } /** - * Get default font name + * Get default font name. * * @return string */ @@ -345,9 +360,10 @@ class Settings } /** - * Set default font name + * Set default font name. * * @param string $value + * * @return bool */ public static function setDefaultFontName($value) @@ -362,7 +378,7 @@ class Settings } /** - * Get default font size + * Get default font size. * * @return int */ @@ -372,9 +388,10 @@ class Settings } /** - * Set default font size + * Set default font size. * * @param int $value + * * @return bool */ public static function setDefaultFontSize($value) @@ -390,9 +407,10 @@ class Settings } /** - * Load setting from phpword.yml or phpword.yml.dist + * Load setting from phpword.yml or phpword.yml.dist. * * @param string $filename + * * @return array */ public static function loadConfig($filename = null) @@ -401,46 +419,65 @@ class Settings $configFile = null; $configPath = __DIR__ . '/../../'; if ($filename !== null) { - $files = array($filename); + $files = [$filename]; } else { - $files = array("{$configPath}phpword.ini", "{$configPath}phpword.ini.dist"); + $files = ["{$configPath}phpword.ini", "{$configPath}phpword.ini.dist"]; } foreach ($files as $file) { if (file_exists($file)) { $configFile = realpath($file); + break; } } // Parse config file - $config = array(); + $config = []; if ($configFile !== null) { $config = @parse_ini_file($configFile); if ($config === false) { - return $config; + return []; } } // Set config value + $appliedConfig = []; foreach ($config as $key => $value) { $method = "set{$key}"; if (method_exists(__CLASS__, $method)) { self::$method($value); + $appliedConfig[$key] = $value; } } - return $config; + return $appliedConfig; } /** - * Return the compatibility option used by the XMLWriter + * Get default paper. * - * @deprecated 0.10.0 - * - * @codeCoverageIgnore + * @return string */ - public static function getCompatibility() + public static function getDefaultPaper() { - return self::hasCompatibility(); + return self::$defaultPaper; + } + + /** + * Set default paper. + * + * @param string $value + * + * @return bool + */ + public static function setDefaultPaper($value) + { + if (is_string($value) && trim($value) !== '') { + self::$defaultPaper = $value; + + return true; + } + + return false; } } diff --git a/PhpOffice/PhpWord/Shared/AbstractEnum.php b/PhpOffice/PhpWord/Shared/AbstractEnum.php old mode 100755 new mode 100644 index f2375d8..e79f4b2 --- a/PhpOffice/PhpWord/Shared/AbstractEnum.php +++ b/PhpOffice/PhpWord/Shared/AbstractEnum.php @@ -11,24 +11,27 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Shared; +use InvalidArgumentException; +use ReflectionClass; + abstract class AbstractEnum { - private static $constCacheArray = null; + private static $constCacheArray; private static function getConstants() { if (self::$constCacheArray == null) { - self::$constCacheArray = array(); + self::$constCacheArray = []; } - $calledClass = get_called_class(); + $calledClass = static::class; if (!array_key_exists($calledClass, self::$constCacheArray)) { - $reflect = new \ReflectionClass($calledClass); + $reflect = new ReflectionClass($calledClass); self::$constCacheArray[$calledClass] = $reflect->getConstants(); } @@ -36,7 +39,7 @@ abstract class AbstractEnum } /** - * Returns all values for this enum + * Returns all values for this enum. * * @return array */ @@ -46,9 +49,10 @@ abstract class AbstractEnum } /** - * Returns true the value is valid for this enum + * Returns true the value is valid for this enum. * * @param string $value + * * @return bool true if value is valid */ public static function isValid($value) @@ -59,17 +63,17 @@ abstract class AbstractEnum } /** - * Validates that the value passed is a valid value + * Validates that the value passed is a valid value. * * @param string $value - * @throws \InvalidArgumentException if the value passed is not valid for this enum */ - public static function validate($value) + public static function validate($value): void { if (!self::isValid($value)) { - $calledClass = get_called_class(); + $calledClass = static::class; $values = array_values(self::getConstants()); - throw new \InvalidArgumentException("$value is not a valid value for $calledClass, possible values are " . implode(', ', $values)); + + throw new InvalidArgumentException("$value is not a valid value for $calledClass, possible values are " . implode(', ', $values)); } } } diff --git a/PhpOffice/PhpWord/Shared/Converter.php b/PhpOffice/PhpWord/Shared/Converter.php old mode 100755 new mode 100644 index 39e8054..53891f5 --- a/PhpOffice/PhpWord/Shared/Converter.php +++ b/PhpOffice/PhpWord/Shared/Converter.php @@ -11,14 +11,14 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Shared; /** - * Common converter functions + * Common converter functions. */ class Converter { @@ -31,9 +31,10 @@ class Converter const DEGREE_TO_ANGLE = 60000; /** - * Convert centimeter to twip + * Convert centimeter to twip. * * @param float $centimeter + * * @return float */ public static function cmToTwip($centimeter = 1) @@ -42,9 +43,10 @@ class Converter } /** - * Convert centimeter to inch + * Convert centimeter to inch. * * @param float $centimeter + * * @return float */ public static function cmToInch($centimeter = 1) @@ -53,9 +55,10 @@ class Converter } /** - * Convert centimeter to pixel + * Convert centimeter to pixel. * * @param float $centimeter + * * @return float */ public static function cmToPixel($centimeter = 1) @@ -64,9 +67,10 @@ class Converter } /** - * Convert centimeter to point + * Convert centimeter to point. * * @param float $centimeter + * * @return float */ public static function cmToPoint($centimeter = 1) @@ -75,9 +79,10 @@ class Converter } /** - * Convert centimeter to EMU + * Convert centimeter to EMU. * * @param float $centimeter + * * @return float */ public static function cmToEmu($centimeter = 1) @@ -86,9 +91,10 @@ class Converter } /** - * Convert inch to twip + * Convert inch to twip. * * @param float $inch + * * @return float */ public static function inchToTwip($inch = 1) @@ -97,9 +103,10 @@ class Converter } /** - * Convert inch to centimeter + * Convert inch to centimeter. * * @param float $inch + * * @return float */ public static function inchToCm($inch = 1) @@ -108,9 +115,10 @@ class Converter } /** - * Convert inch to pixel + * Convert inch to pixel. * * @param float $inch + * * @return float */ public static function inchToPixel($inch = 1) @@ -119,9 +127,10 @@ class Converter } /** - * Convert inch to point + * Convert inch to point. * * @param float $inch + * * @return float */ public static function inchToPoint($inch = 1) @@ -130,9 +139,10 @@ class Converter } /** - * Convert inch to EMU + * Convert inch to EMU. * * @param float $inch + * * @return int */ public static function inchToEmu($inch = 1) @@ -141,9 +151,10 @@ class Converter } /** - * Convert pixel to twip + * Convert pixel to twip. * * @param float $pixel + * * @return float */ public static function pixelToTwip($pixel = 1) @@ -152,9 +163,10 @@ class Converter } /** - * Convert pixel to centimeter + * Convert pixel to centimeter. * * @param float $pixel + * * @return float */ public static function pixelToCm($pixel = 1) @@ -163,9 +175,10 @@ class Converter } /** - * Convert pixel to point + * Convert pixel to point. * * @param float $pixel + * * @return float */ public static function pixelToPoint($pixel = 1) @@ -174,9 +187,10 @@ class Converter } /** - * Convert pixel to EMU + * Convert pixel to EMU. * * @param float $pixel + * * @return int */ public static function pixelToEmu($pixel = 1) @@ -185,9 +199,10 @@ class Converter } /** - * Convert point to twip unit + * Convert point to twip unit. * * @param float $point + * * @return float */ public static function pointToTwip($point = 1) @@ -196,9 +211,10 @@ class Converter } /** - * Convert point to pixel + * Convert point to pixel. * * @param float $point + * * @return float */ public static function pointToPixel($point = 1) @@ -207,9 +223,10 @@ class Converter } /** - * Convert point to EMU + * Convert point to EMU. * * @param float $point + * * @return float */ public static function pointToEmu($point = 1) @@ -218,9 +235,10 @@ class Converter } /** - * Convert point to cm + * Convert point to cm. * * @param float $point + * * @return float */ public static function pointToCm($point = 1) @@ -229,9 +247,10 @@ class Converter } /** - * Convert EMU to pixel + * Convert EMU to pixel. * * @param float $emu + * * @return float */ public static function emuToPixel($emu = 1) @@ -240,9 +259,10 @@ class Converter } /** - * Convert pica to point + * Convert pica to point. * * @param float $pica + * * @return float */ public static function picaToPoint($pica = 1) @@ -251,9 +271,10 @@ class Converter } /** - * Convert degree to angle + * Convert degree to angle. * * @param float $degree + * * @return int */ public static function degreeToAngle($degree = 1) @@ -262,9 +283,10 @@ class Converter } /** - * Convert angle to degrees + * Convert angle to degrees. * * @param float $angle + * * @return int */ public static function angleToDegree($angle = 1) @@ -273,9 +295,10 @@ class Converter } /** - * Convert colorname as string to RGB + * Convert colorname as string to RGB. * * @param string $value color name + * * @return string color as hex RGB string, or original value if unknown */ public static function stringToRgb($value) @@ -317,9 +340,10 @@ class Converter } /** - * Convert HTML hexadecimal to RGB + * Convert HTML hexadecimal to RGB. * * @param string $value HTML Color in hexadecimal + * * @return array Value in RGB */ public static function htmlToRgb($value) @@ -331,24 +355,25 @@ class Converter } if (strlen($value) == 6) { - list($red, $green, $blue) = array($value[0] . $value[1], $value[2] . $value[3], $value[4] . $value[5]); + [$red, $green, $blue] = [$value[0] . $value[1], $value[2] . $value[3], $value[4] . $value[5]]; } elseif (strlen($value) == 3) { - list($red, $green, $blue) = array($value[0] . $value[0], $value[1] . $value[1], $value[2] . $value[2]); + [$red, $green, $blue] = [$value[0] . $value[0], $value[1] . $value[1], $value[2] . $value[2]]; } else { return false; } - $red = hexdec($red); - $green = hexdec($green); - $blue = hexdec($blue); + $red = ctype_xdigit($red) ? hexdec($red) : 0; + $green = ctype_xdigit($green) ? hexdec($green) : 0; + $blue = ctype_xdigit($blue) ? hexdec($blue) : 0; - return array($red, $green, $blue); + return [$red, $green, $blue]; } /** - * Transforms a size in CSS format (eg. 10px, 10px, ...) to points + * Transforms a size in CSS format (eg. 10px, 10px, ...) to points. * * @param string $value + * * @return float */ public static function cssToPoint($value) @@ -356,7 +381,7 @@ class Converter if ($value == '0') { return 0; } - $matches = array(); + $matches = []; if (preg_match('/^[+-]?([0-9]+\.?[0-9]*)?(px|em|ex|%|in|cm|mm|pt|pc)$/i', $value, $matches)) { $size = $matches[1]; $unit = $matches[2]; @@ -381,9 +406,10 @@ class Converter } /** - * Transforms a size in CSS format (eg. 10px, 10px, ...) to twips + * Transforms a size in CSS format (eg. 10px, 10px, ...) to twips. * * @param string $value + * * @return float */ public static function cssToTwip($value) @@ -392,9 +418,10 @@ class Converter } /** - * Transforms a size in CSS format (eg. 10px, 10px, ...) to pixel + * Transforms a size in CSS format (eg. 10px, 10px, ...) to pixel. * * @param string $value + * * @return float */ public static function cssToPixel($value) @@ -403,9 +430,10 @@ class Converter } /** - * Transforms a size in CSS format (eg. 10px, 10px, ...) to cm + * Transforms a size in CSS format (eg. 10px, 10px, ...) to cm. * * @param string $value + * * @return float */ public static function cssToCm($value) @@ -414,9 +442,10 @@ class Converter } /** - * Transforms a size in CSS format (eg. 10px, 10px, ...) to emu + * Transforms a size in CSS format (eg. 10px, 10px, ...) to emu. * * @param string $value + * * @return float */ public static function cssToEmu($value) diff --git a/PhpOffice/PhpWord/Shared/Css.php b/PhpOffice/PhpWord/Shared/Css.php new file mode 100644 index 0000000..fea3e3d --- /dev/null +++ b/PhpOffice/PhpWord/Shared/Css.php @@ -0,0 +1,80 @@ +> + */ + private $styles = []; + + public function __construct(string $cssContent) + { + $this->cssContent = $cssContent; + } + + public function process(): void + { + $cssContent = str_replace(["\r", "\n"], '', $this->cssContent); + preg_match_all('/(.+?)\s?\{\s?(.+?)\s?\}/', $cssContent, $cssExtracted); + // Check the number of extracted + if (count($cssExtracted) != 3) { + return; + } + // Check if there are x selectors and x rules + if (count($cssExtracted[1]) != count($cssExtracted[2])) { + return; + } + + foreach ($cssExtracted[1] as $key => $selector) { + $rules = trim($cssExtracted[2][$key]); + $rules = explode(';', $rules); + foreach ($rules as $rule) { + if (empty($rule)) { + continue; + } + [$key, $value] = explode(':', trim($rule)); + $this->styles[$this->sanitize($selector)][$this->sanitize($key)] = $this->sanitize($value); + } + } + } + + public function getStyles(): array + { + return $this->styles; + } + + public function getStyle(string $selector): array + { + $selector = $this->sanitize($selector); + + return $this->styles[$selector] ?? []; + } + + private function sanitize(string $value): string + { + return addslashes(trim($value)); + } +} diff --git a/PhpOffice/PhpWord/Shared/Drawing.php b/PhpOffice/PhpWord/Shared/Drawing.php new file mode 100644 index 0000000..df21810 --- /dev/null +++ b/PhpOffice/PhpWord/Shared/Drawing.php @@ -0,0 +1,263 @@ +' . $html . ''; } // Load DOM - $orignalLibEntityLoader = libxml_disable_entity_loader(true); - $dom = new \DOMDocument(); + if (\PHP_VERSION_ID < 80000) { + $orignalLibEntityLoader = libxml_disable_entity_loader(true); + } + $dom = new DOMDocument(); $dom->preserveWhiteSpace = $preserveWhiteSpace; $dom->loadXML($html); - self::$xpath = new \DOMXPath($dom); + static::$xpath = new DOMXPath($dom); $node = $dom->getElementsByTagName('body'); - self::parseNode($node->item(0), $element); - libxml_disable_entity_loader($orignalLibEntityLoader); + static::parseNode($node->item(0), $element); + if (\PHP_VERSION_ID < 80000) { + libxml_disable_entity_loader($orignalLibEntityLoader); + } } /** - * parse Inline style of a node + * parse Inline style of a node. * - * @param \DOMNode $node Node to check on attributes and to compile a style array + * @param DOMNode $node Node to check on attributes and to compile a style array * @param array $styles is supplied, the inline style attributes are added to the already existing style + * * @return array */ - protected static function parseInlineStyle($node, $styles = array()) + protected static function parseInlineStyle($node, $styles = []) { if (XML_ELEMENT_NODE == $node->nodeType) { $attributes = $node->attributes; // get all the attributes(eg: id, class) foreach ($attributes as $attribute) { - switch ($attribute->name) { - case 'style': - $styles = self::parseStyle($attribute, $styles); - break; + $val = $attribute->value; + switch (strtolower($attribute->name)) { case 'align': - $styles['alignment'] = self::mapAlign($attribute->value); + $styles['alignment'] = self::mapAlign(trim($val)); + break; case 'lang': - $styles['lang'] = $attribute->value; + $styles['lang'] = $val; + + break; + case 'width': + // tables, cells + if (false !== strpos($val, '%')) { + // e.g. or
    + $styles['width'] = (int) $val * 50; + $styles['unit'] = \PhpOffice\PhpWord\SimpleType\TblWidth::PERCENT; + } else { + // e.g. , where "2" = 2px (always pixels) + $val = (int) $val . 'px'; + $styles['cellSpacing'] = Converter::cssToTwip($val); + + break; + case 'bgcolor': + // tables, rows, cells e.g. + $styles['bgColor'] = trim($val, '# '); + + break; + case 'valign': + // cells e.g. ' . PHP_EOL; $rowCells = $rows[$i]->getCells(); $rowCellCount = count($rowCells); - for ($j = 0; $j < $rowCellCount; $j++) { + for ($j = 0; $j < $rowCellCount; ++$j) { $cellStyle = $rowCells[$j]->getStyle(); $cellBgColor = $cellStyle->getBgColor(); + $cellBgColor === 'auto' && $cellBgColor = null; // auto cannot be parsed to hexadecimal number $cellFgColor = null; if ($cellBgColor) { $red = hexdec(substr($cellBgColor, 0, 2)); @@ -64,11 +65,11 @@ class Table extends AbstractElement $cellVMerge = $cellStyle->getVMerge(); // If this is the first cell of the vertical merge, find out how man rows it spans if ($cellVMerge === 'restart') { - for ($k = $i + 1; $k < $rowCount; $k++) { + for ($k = $i + 1; $k < $rowCount; ++$k) { $kRowCells = $rows[$k]->getCells(); if (isset($kRowCells[$j])) { if ($kRowCells[$j]->getStyle()->getVMerge() === 'continue') { - $cellRowSpan++; + ++$cellRowSpan; } else { break; } @@ -82,14 +83,14 @@ class Table extends AbstractElement $cellTag = $tblHeader ? 'th' : 'td'; $cellColSpanAttr = (is_numeric($cellColSpan) && ($cellColSpan > 1) ? " colspan=\"{$cellColSpan}\"" : ''); $cellRowSpanAttr = ($cellRowSpan > 1 ? " rowspan=\"{$cellRowSpan}\"" : ''); - $cellBgColorAttr = (is_null($cellBgColor) ? '' : " bgcolor=\"#{$cellBgColor}\""); - $cellFgColorAttr = (is_null($cellFgColor) ? '' : " color=\"#{$cellFgColor}\""); + $cellBgColorAttr = (null === $cellBgColor ? '' : " bgcolor=\"#{$cellBgColor}\""); + $cellFgColorAttr = (null === $cellFgColor ? '' : " color=\"#{$cellFgColor}\""); $content .= "<{$cellTag}{$cellColSpanAttr}{$cellRowSpanAttr}{$cellBgColorAttr}{$cellFgColorAttr}>" . PHP_EOL; $writer = new Container($this->parentWriter, $rowCells[$j]); $content .= $writer->write(); if ($cellRowSpan > 1) { // There shouldn't be any content in the subsequent merged cells, but lets check anyway - for ($k = $i + 1; $k < $rowCount; $k++) { + for ($k = $i + 1; $k < $rowCount; ++$k) { $kRowCells = $rows[$k]->getCells(); if (isset($kRowCells[$j])) { if ($kRowCells[$j]->getStyle()->getVMerge() === 'continue') { @@ -115,9 +116,10 @@ class Table extends AbstractElement } /** - * Translates Table style in CSS equivalent + * Translates Table style in CSS equivalent. + * + * @param null|\PhpOffice\PhpWord\Style\Table|string $tableStyle * - * @param string|\PhpOffice\PhpWord\Style\Table|null $tableStyle * @return string */ private function getTableStyle($tableStyle = null) diff --git a/PhpOffice/PhpWord/Writer/HTML/Element/Text.php b/PhpOffice/PhpWord/Writer/HTML/Element/Text.php old mode 100755 new mode 100644 index 04d76a8..a360f09 --- a/PhpOffice/PhpWord/Writer/HTML/Element/Text.php +++ b/PhpOffice/PhpWord/Writer/HTML/Element/Text.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -25,42 +25,42 @@ use PhpOffice\PhpWord\Writer\HTML\Style\Font as FontStyleWriter; use PhpOffice\PhpWord\Writer\HTML\Style\Paragraph as ParagraphStyleWriter; /** - * Text element HTML writer + * Text element HTML writer. * * @since 0.10.0 */ class Text extends AbstractElement { /** - * Text written after opening + * Text written after opening. * * @var string */ private $openingText = ''; /** - * Text written before closing + * Text written before closing. * * @var string */ private $closingText = ''; /** - * Opening tags + * Opening tags. * * @var string */ private $openingTags = ''; /** - * Closing tag + * Closing tag. * * @var string */ private $closingTags = ''; /** - * Write text + * Write text. * * @return string */ @@ -91,7 +91,7 @@ class Text extends AbstractElement * * @param string $value */ - public function setOpeningText($value) + public function setOpeningText($value): void { $this->openingText = $value; } @@ -101,13 +101,13 @@ class Text extends AbstractElement * * @param string $value */ - public function setClosingText($value) + public function setClosingText($value): void { $this->closingText = $value; } /** - * Write opening + * Write opening. * * @return string */ @@ -129,7 +129,7 @@ class Text extends AbstractElement } /** - * Write ending + * Write ending. * * @return string */ @@ -154,7 +154,7 @@ class Text extends AbstractElement } /** - * writes the track change opening tag + * writes the track change opening tag. * * @return string the HTML, an empty string if no track change information */ @@ -172,7 +172,7 @@ class Text extends AbstractElement $content .= ' array('author'=> $changed->getAuthor(), 'id' => $this->element->getElementId())); + $changedProp = ['changed' => ['author' => $changed->getAuthor(), 'id' => $this->element->getElementId()]]; if ($changed->getDate() != null) { $changedProp['changed']['date'] = $changed->getDate()->format('Y-m-d\TH:i:s\Z'); } @@ -189,7 +189,7 @@ class Text extends AbstractElement } /** - * writes the track change closing tag + * writes the track change closing tag. * * @return string the HTML, an empty string if no track change information */ @@ -211,7 +211,7 @@ class Text extends AbstractElement } /** - * Write paragraph style + * Write paragraph style. * * @return string */ @@ -243,7 +243,7 @@ class Text extends AbstractElement /** * Get font style. */ - private function getFontStyle() + private function getFontStyle(): void { /** @var \PhpOffice\PhpWord\Element\Text $element Type hint */ $element = $this->element; diff --git a/PhpOffice/PhpWord/Writer/HTML/Element/TextBreak.php b/PhpOffice/PhpWord/Writer/HTML/Element/TextBreak.php old mode 100755 new mode 100644 index 6ff092d..af73cb4 --- a/PhpOffice/PhpWord/Writer/HTML/Element/TextBreak.php +++ b/PhpOffice/PhpWord/Writer/HTML/Element/TextBreak.php @@ -11,21 +11,21 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Writer\HTML\Element; /** - * TextBreak element HTML writer + * TextBreak element HTML writer. * * @since 0.10.0 */ class TextBreak extends AbstractElement { /** - * Write text break + * Write text break. * * @return string */ diff --git a/PhpOffice/PhpWord/Writer/HTML/Element/TextRun.php b/PhpOffice/PhpWord/Writer/HTML/Element/TextRun.php old mode 100755 new mode 100644 index b2deaf2..abae7d3 --- a/PhpOffice/PhpWord/Writer/HTML/Element/TextRun.php +++ b/PhpOffice/PhpWord/Writer/HTML/Element/TextRun.php @@ -11,21 +11,21 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Writer\HTML\Element; /** - * TextRun element HTML writer + * TextRun element HTML writer. * * @since 0.10.0 */ class TextRun extends Text { /** - * Write text run + * Write text run. * * @return string */ diff --git a/PhpOffice/PhpWord/Writer/HTML/Element/Title.php b/PhpOffice/PhpWord/Writer/HTML/Element/Title.php old mode 100755 new mode 100644 index 04ed61f..3749505 --- a/PhpOffice/PhpWord/Writer/HTML/Element/Title.php +++ b/PhpOffice/PhpWord/Writer/HTML/Element/Title.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -20,14 +20,14 @@ namespace PhpOffice\PhpWord\Writer\HTML\Element; use PhpOffice\PhpWord\Settings; /** - * TextRun element HTML writer + * TextRun element HTML writer. * * @since 0.10.0 */ class Title extends AbstractElement { /** - * Write heading + * Write heading. * * @return string */ diff --git a/PhpOffice/PhpWord/Writer/HTML/Part/AbstractPart.php b/PhpOffice/PhpWord/Writer/HTML/Part/AbstractPart.php old mode 100755 new mode 100644 index 2d86f39..8612e28 --- a/PhpOffice/PhpWord/Writer/HTML/Part/AbstractPart.php +++ b/PhpOffice/PhpWord/Writer/HTML/Part/AbstractPart.php @@ -11,15 +11,15 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Writer\HTML\Part; +use Laminas\Escaper\Escaper; use PhpOffice\PhpWord\Exception\Exception; use PhpOffice\PhpWord\Writer\AbstractWriter; -use Zend\Escaper\Escaper; /** * @since 0.11.0 @@ -32,7 +32,7 @@ abstract class AbstractPart private $parentWriter; /** - * @var \Zend\Escaper\Escaper + * @var \Laminas\Escaper\Escaper */ protected $escaper; @@ -49,14 +49,12 @@ abstract class AbstractPart /** * @param \PhpOffice\PhpWord\Writer\AbstractWriter $writer */ - public function setParentWriter(AbstractWriter $writer = null) + public function setParentWriter(?AbstractWriter $writer = null): void { $this->parentWriter = $writer; } /** - * @throws \PhpOffice\PhpWord\Exception\Exception - * * @return \PhpOffice\PhpWord\Writer\AbstractWriter */ public function getParentWriter() @@ -64,6 +62,7 @@ abstract class AbstractPart if ($this->parentWriter !== null) { return $this->parentWriter; } + throw new Exception('No parent WriterInterface assigned.'); } } diff --git a/PhpOffice/PhpWord/Writer/HTML/Part/Body.php b/PhpOffice/PhpWord/Writer/HTML/Part/Body.php old mode 100755 new mode 100644 index a029f96..19aae8a --- a/PhpOffice/PhpWord/Writer/HTML/Part/Body.php +++ b/PhpOffice/PhpWord/Writer/HTML/Part/Body.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -21,14 +21,14 @@ use PhpOffice\PhpWord\Writer\HTML\Element\Container; use PhpOffice\PhpWord\Writer\HTML\Element\TextRun as TextRunWriter; /** - * RTF body part writer + * RTF body part writer. * * @since 0.11.0 */ class Body extends AbstractPart { /** - * Write part + * Write part. * * @return string */ @@ -52,7 +52,7 @@ class Body extends AbstractPart } /** - * Write footnote/endnote contents as textruns + * Write footnote/endnote contents as textruns. * * @return string */ @@ -68,7 +68,7 @@ class Body extends AbstractPart if (!empty($notes)) { $content .= '
    ' . PHP_EOL; foreach ($notes as $noteId => $noteMark) { - list($noteType, $noteTypeId) = explode('-', $noteMark); + [$noteType, $noteTypeId] = explode('-', $noteMark); $method = 'get' . ($noteType == 'endnote' ? 'Endnotes' : 'Footnotes'); $collection = $phpWord->$method()->getItems(); diff --git a/PhpOffice/PhpWord/Writer/HTML/Part/Head.php b/PhpOffice/PhpWord/Writer/HTML/Part/Head.php old mode 100755 new mode 100644 index 1107bec..a2541aa --- a/PhpOffice/PhpWord/Writer/HTML/Part/Head.php +++ b/PhpOffice/PhpWord/Writer/HTML/Part/Head.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -26,30 +26,30 @@ use PhpOffice\PhpWord\Writer\HTML\Style\Generic as GenericStyleWriter; use PhpOffice\PhpWord\Writer\HTML\Style\Paragraph as ParagraphStyleWriter; /** - * RTF head part writer + * RTF head part writer. * * @since 0.11.0 */ class Head extends AbstractPart { /** - * Write part + * Write part. * * @return string */ public function write() { $docProps = $this->getParentWriter()->getPhpWord()->getDocInfo(); - $propertiesMapping = array( - 'creator' => 'author', - 'title' => '', + $propertiesMapping = [ + 'creator' => 'author', + 'title' => '', 'description' => '', - 'subject' => '', - 'keywords' => '', - 'category' => '', - 'company' => '', - 'manager' => '', - ); + 'subject' => '', + 'keywords' => '', + 'category' => '', + 'company' => '', + 'manager' => '', + ]; $title = $docProps->getTitle(); $title = ($title != '') ? $title : 'PHPWord'; @@ -74,7 +74,7 @@ class Head extends AbstractPart } /** - * Get styles + * Get styles. * * @return string */ @@ -83,30 +83,30 @@ class Head extends AbstractPart $css = '
    + if (preg_match('#(?:top|bottom|middle|baseline)#i', $val, $matches)) { + $styles['valign'] = self::mapAlignVertical($matches[0]); + } + break; } } + + $attributeIdentifier = $attributes->getNamedItem('id'); + if ($attributeIdentifier && self::$css) { + $styles = self::parseStyleDeclarations(self::$css->getStyle('#' . $attributeIdentifier->value), $styles); + } + + $attributeClass = $attributes->getNamedItem('class'); + if ($attributeClass) { + if (self::$css) { + $styles = self::parseStyleDeclarations(self::$css->getStyle('.' . $attributeClass->value), $styles); + } + $styles['className'] = $attributeClass->value; + } + + $attributeStyle = $attributes->getNamedItem('style'); + if ($attributeStyle) { + $styles = self::parseStyle($attributeStyle, $styles); + } } return $styles; @@ -116,69 +179,78 @@ class Html /** * Parse a node and add a corresponding element to the parent element. * - * @param \DOMNode $node node to parse + * @param DOMNode $node node to parse * @param \PhpOffice\PhpWord\Element\AbstractContainer $element object to add an element corresponding with the node * @param array $styles Array with all styles * @param array $data Array to transport data to a next level in the DOM tree, for example level of listitems */ - protected static function parseNode($node, $element, $styles = array(), $data = array()) + protected static function parseNode($node, $element, $styles = [], $data = []): void { + if ($node->nodeName == 'style') { + self::$css = new Css($node->textContent); + self::$css->process(); + + return; + } + // Populate styles array - $styleTypes = array('font', 'paragraph', 'list', 'table', 'row', 'cell'); + $styleTypes = ['font', 'paragraph', 'list', 'table', 'row', 'cell']; foreach ($styleTypes as $styleType) { if (!isset($styles[$styleType])) { - $styles[$styleType] = array(); + $styles[$styleType] = []; } } // Node mapping table - $nodes = array( - // $method $node $element $styles $data $argument1 $argument2 - 'p' => array('Paragraph', $node, $element, $styles, null, null, null), - 'h1' => array('Heading', null, $element, $styles, null, 'Heading1', null), - 'h2' => array('Heading', null, $element, $styles, null, 'Heading2', null), - 'h3' => array('Heading', null, $element, $styles, null, 'Heading3', null), - 'h4' => array('Heading', null, $element, $styles, null, 'Heading4', null), - 'h5' => array('Heading', null, $element, $styles, null, 'Heading5', null), - 'h6' => array('Heading', null, $element, $styles, null, 'Heading6', null), - '#text' => array('Text', $node, $element, $styles, null, null, null), - 'strong' => array('Property', null, null, $styles, null, 'bold', true), - 'b' => array('Property', null, null, $styles, null, 'bold', true), - 'em' => array('Property', null, null, $styles, null, 'italic', true), - 'i' => array('Property', null, null, $styles, null, 'italic', true), - 'u' => array('Property', null, null, $styles, null, 'underline', 'single'), - 'sup' => array('Property', null, null, $styles, null, 'superScript', true), - 'sub' => array('Property', null, null, $styles, null, 'subScript', true), - 'span' => array('Span', $node, null, $styles, null, null, null), - 'font' => array('Span', $node, null, $styles, null, null, null), - 'table' => array('Table', $node, $element, $styles, null, null, null), - 'tr' => array('Row', $node, $element, $styles, null, null, null), - 'td' => array('Cell', $node, $element, $styles, null, null, null), - 'th' => array('Cell', $node, $element, $styles, null, null, null), - 'ul' => array('List', $node, $element, $styles, $data, null, null), - 'ol' => array('List', $node, $element, $styles, $data, null, null), - 'li' => array('ListItem', $node, $element, $styles, $data, null, null), - 'img' => array('Image', $node, $element, $styles, null, null, null), - 'br' => array('LineBreak', null, $element, $styles, null, null, null), - 'a' => array('Link', $node, $element, $styles, null, null, null), - ); + $nodes = [ + // $method $node $element $styles $data $argument1 $argument2 + 'p' => ['Paragraph', $node, $element, $styles, null, null, null], + 'h1' => ['Heading', null, $element, $styles, null, 'Heading1', null], + 'h2' => ['Heading', null, $element, $styles, null, 'Heading2', null], + 'h3' => ['Heading', null, $element, $styles, null, 'Heading3', null], + 'h4' => ['Heading', null, $element, $styles, null, 'Heading4', null], + 'h5' => ['Heading', null, $element, $styles, null, 'Heading5', null], + 'h6' => ['Heading', null, $element, $styles, null, 'Heading6', null], + '#text' => ['Text', $node, $element, $styles, null, null, null], + 'strong' => ['Property', null, null, $styles, null, 'bold', true], + 'b' => ['Property', null, null, $styles, null, 'bold', true], + 'em' => ['Property', null, null, $styles, null, 'italic', true], + 'i' => ['Property', null, null, $styles, null, 'italic', true], + 'u' => ['Property', null, null, $styles, null, 'underline', 'single'], + 'sup' => ['Property', null, null, $styles, null, 'superScript', true], + 'sub' => ['Property', null, null, $styles, null, 'subScript', true], + 'span' => ['Span', $node, null, $styles, null, null, null], + 'font' => ['Span', $node, null, $styles, null, null, null], + 'table' => ['Table', $node, $element, $styles, null, null, null], + 'tr' => ['Row', $node, $element, $styles, null, null, null], + 'td' => ['Cell', $node, $element, $styles, null, null, null], + 'th' => ['Cell', $node, $element, $styles, null, null, null], + 'ul' => ['List', $node, $element, $styles, $data, null, null], + 'ol' => ['List', $node, $element, $styles, $data, null, null], + 'li' => ['ListItem', $node, $element, $styles, $data, null, null], + 'img' => ['Image', $node, $element, $styles, null, null, null], + 'br' => ['LineBreak', null, $element, $styles, null, null, null], + 'a' => ['Link', $node, $element, $styles, null, null, null], + 'input' => ['Input', $node, $element, $styles, null, null, null], + 'hr' => ['HorizRule', $node, $element, $styles, null, null, null], + ]; $newElement = null; - $keys = array('node', 'element', 'styles', 'data', 'argument1', 'argument2'); + $keys = ['node', 'element', 'styles', 'data', 'argument1', 'argument2']; if (isset($nodes[$node->nodeName])) { // Execute method based on node mapping table and return $newElement or null // Arguments are passed by reference - $arguments = array(); - $args = array(); - list($method, $args[0], $args[1], $args[2], $args[3], $args[4], $args[5]) = $nodes[$node->nodeName]; - for ($i = 0; $i <= 5; $i++) { + $arguments = []; + $args = []; + [$method, $args[0], $args[1], $args[2], $args[3], $args[4], $args[5]] = $nodes[$node->nodeName]; + for ($i = 0; $i <= 5; ++$i) { if ($args[$i] !== null) { $arguments[$keys[$i]] = &$args[$i]; } } $method = "parse{$method}"; - $newElement = call_user_func_array(array('PhpOffice\PhpWord\Shared\Html', $method), $arguments); + $newElement = call_user_func_array(['PhpOffice\PhpWord\Shared\Html', $method], array_values($arguments)); // Retrieve back variables from arguments foreach ($keys as $key) { @@ -198,12 +270,12 @@ class Html /** * Parse child nodes. * - * @param \DOMNode $node + * @param DOMNode $node * @param \PhpOffice\PhpWord\Element\AbstractContainer $element * @param array $styles * @param array $data */ - protected static function parseChildNodes($node, $element, $styles, $data) + protected static function parseChildNodes($node, $element, $styles, $data): void { if ('li' != $node->nodeName) { $cNodes = $node->childNodes; @@ -218,27 +290,56 @@ class Html } /** - * Parse paragraph node + * Parse paragraph node. * - * @param \DOMNode $node + * @param DOMNode $node * @param \PhpOffice\PhpWord\Element\AbstractContainer $element * @param array &$styles - * @return \PhpOffice\PhpWord\Element\TextRun + * + * @return \PhpOffice\PhpWord\Element\PageBreak|\PhpOffice\PhpWord\Element\TextRun */ protected static function parseParagraph($node, $element, &$styles) { $styles['paragraph'] = self::recursiveParseStylesInHierarchy($node, $styles['paragraph']); - $newElement = $element->addTextRun($styles['paragraph']); + if (isset($styles['paragraph']['isPageBreak']) && $styles['paragraph']['isPageBreak']) { + return $element->addPageBreak(); + } - return $newElement; + return $element->addTextRun($styles['paragraph']); } /** - * Parse heading node + * Parse input node. + * + * @param DOMNode $node + * @param \PhpOffice\PhpWord\Element\AbstractContainer $element + * @param array &$styles + */ + protected static function parseInput($node, $element, &$styles): void + { + $attributes = $node->attributes; + if (null === $attributes->getNamedItem('type')) { + return; + } + + $inputType = $attributes->getNamedItem('type')->value; + switch ($inputType) { + case 'checkbox': + $checked = ($checked = $attributes->getNamedItem('checked')) && $checked->value === 'true' ? true : false; + $textrun = $element->addTextRun($styles['paragraph']); + $textrun->addFormField('checkbox')->setValue($checked); + + break; + } + } + + /** + * Parse heading node. * * @param \PhpOffice\PhpWord\Element\AbstractContainer $element * @param array &$styles * @param string $argument1 Name of heading style + * * @return \PhpOffice\PhpWord\Element\TextRun * * @todo Think of a clever way of defining header styles, now it is only based on the assumption, that @@ -253,13 +354,13 @@ class Html } /** - * Parse text node + * Parse text node. * - * @param \DOMNode $node + * @param DOMNode $node * @param \PhpOffice\PhpWord\Element\AbstractContainer $element * @param array &$styles */ - protected static function parseText($node, $element, &$styles) + protected static function parseText($node, $element, &$styles): void { $styles['font'] = self::recursiveParseStylesInHierarchy($node, $styles['font']); @@ -268,40 +369,41 @@ class Html $styles['paragraph']['alignment'] = $styles['font']['alignment']; } - if (is_callable(array($element, 'addText'))) { + if (is_callable([$element, 'addText'])) { $element->addText($node->nodeValue, $styles['font'], $styles['paragraph']); } } /** - * Parse property node + * Parse property node. * * @param array &$styles * @param string $argument1 Style name * @param string $argument2 Style value */ - protected static function parseProperty(&$styles, $argument1, $argument2) + protected static function parseProperty(&$styles, $argument1, $argument2): void { $styles['font'][$argument1] = $argument2; } /** - * Parse span node + * Parse span node. * - * @param \DOMNode $node + * @param DOMNode $node * @param array &$styles */ - protected static function parseSpan($node, &$styles) + protected static function parseSpan($node, &$styles): void { self::parseInlineStyle($node, $styles['font']); } /** - * Parse table node + * Parse table node. * - * @param \DOMNode $node + * @param DOMNode $node * @param \PhpOffice\PhpWord\Element\AbstractContainer $element * @param array &$styles + * * @return Table $element * * @todo As soon as TableItem, RowItem and CellItem support relative width and height @@ -312,27 +414,27 @@ class Html $newElement = $element->addTable($elementStyles); - // $attributes = $node->attributes; - // if ($attributes->getNamedItem('width') !== null) { - // $newElement->setWidth($attributes->getNamedItem('width')->value); - // } + // Add style name from CSS Class + if (isset($elementStyles['className'])) { + $newElement->getStyle()->setStyleName($elementStyles['className']); + } - // if ($attributes->getNamedItem('height') !== null) { - // $newElement->setHeight($attributes->getNamedItem('height')->value); - // } - // if ($attributes->getNamedItem('width') !== null) { - // $newElement=$element->addCell($width=$attributes->getNamedItem('width')->value); - // } + $attributes = $node->attributes; + if ($attributes->getNamedItem('border') !== null) { + $border = (int) $attributes->getNamedItem('border')->value; + $newElement->getStyle()->setBorderSize(Converter::pixelToTwip($border)); + } return $newElement; } /** - * Parse a table row + * Parse a table row. * - * @param \DOMNode $node + * @param DOMNode $node * @param \PhpOffice\PhpWord\Element\Table $element * @param array &$styles + * * @return Row $element */ protected static function parseRow($node, $element, &$styles) @@ -346,11 +448,12 @@ class Html } /** - * Parse table cell + * Parse table cell. * - * @param \DOMNode $node + * @param DOMNode $node * @param \PhpOffice\PhpWord\Element\Table $element * @param array &$styles + * * @return \PhpOffice\PhpWord\Element\Cell|\PhpOffice\PhpWord\Element\TextRun $element */ protected static function parseCell($node, $element, &$styles) @@ -361,24 +464,27 @@ class Html if (!empty($colspan)) { $cellStyles['gridSpan'] = $colspan - 0; } - $cell = $element->addCell(null, $cellStyles); + + // set cell width to control column widths + $width = $cellStyles['width'] ?? null; + unset($cellStyles['width']); // would not apply + $cell = $element->addCell($width, $cellStyles); if (self::shouldAddTextRun($node)) { - return $cell->addTextRun(self::parseInlineStyle($node, $styles['paragraph'])); + return $cell->addTextRun(self::filterOutNonInheritedStyles(self::parseInlineStyle($node, $styles['paragraph']))); } return $cell; } /** - * Checks if $node contains an HTML element that cannot be added to TextRun + * Checks if $node contains an HTML element that cannot be added to TextRun. * - * @param \DOMNode $node * @return bool Returns true if the node contains an HTML element that cannot be added to TextRun */ - protected static function shouldAddTextRun(\DOMNode $node) + protected static function shouldAddTextRun(DOMNode $node) { - $containsBlockElement = self::$xpath->query('.//table|./p|./ul|./ol', $node)->length > 0; + $containsBlockElement = self::$xpath->query('.//table|./p|./ul|./ol|./h1|./h2|./h3|./h4|./h5|./h6', $node)->length > 0; if ($containsBlockElement) { return false; } @@ -389,25 +495,56 @@ class Html /** * Recursively parses styles on parent nodes * TODO if too slow, add caching of parent nodes, !! everything is static here so watch out for concurrency !! - * - * @param \DOMNode $node - * @param array &$styles */ - protected static function recursiveParseStylesInHierarchy(\DOMNode $node, array $style) + protected static function recursiveParseStylesInHierarchy(DOMNode $node, array $style) { - $parentStyle = self::parseInlineStyle($node, array()); - $style = array_merge($parentStyle, $style); + $parentStyle = []; if ($node->parentNode != null && XML_ELEMENT_NODE == $node->parentNode->nodeType) { - $style = self::recursiveParseStylesInHierarchy($node->parentNode, $style); + $parentStyle = self::recursiveParseStylesInHierarchy($node->parentNode, []); } + if ($node->nodeName === '#text') { + $parentStyle = array_merge($parentStyle, $style); + } else { + $parentStyle = self::filterOutNonInheritedStyles($parentStyle); + } + $style = self::parseInlineStyle($node, $parentStyle); return $style; } /** - * Parse list node + * Removes non-inherited styles from array. + */ + protected static function filterOutNonInheritedStyles(array $styles) + { + $nonInheritedStyles = [ + 'borderSize', + 'borderTopSize', + 'borderRightSize', + 'borderBottomSize', + 'borderLeftSize', + 'borderColor', + 'borderTopColor', + 'borderRightColor', + 'borderBottomColor', + 'borderLeftColor', + 'borderStyle', + 'spaceAfter', + 'spaceBefore', + 'underline', + 'strikethrough', + 'hidden', + ]; + + $styles = array_diff_key($styles, array_flip($nonInheritedStyles)); + + return $styles; + } + + /** + * Parse list node. * - * @param \DOMNode $node + * @param DOMNode $node * @param \PhpOffice\PhpWord\Element\AbstractContainer $element * @param array &$styles * @param array &$data @@ -416,11 +553,38 @@ class Html { $isOrderedList = $node->nodeName === 'ol'; if (isset($data['listdepth'])) { - $data['listdepth']++; + ++$data['listdepth']; } else { $data['listdepth'] = 0; $styles['list'] = 'listStyle_' . self::$listIndex++; - $element->getPhpWord()->addNumberingStyle($styles['list'], self::getListStyle($isOrderedList)); + $style = $element->getPhpWord()->addNumberingStyle($styles['list'], self::getListStyle($isOrderedList)); + + // extract attributes start & type e.g.
      + $start = 0; + $type = ''; + foreach ($node->attributes as $attribute) { + switch ($attribute->name) { + case 'start': + $start = (int) $attribute->value; + + break; + case 'type': + $type = $attribute->value; + + break; + } + } + + $levels = $style->getLevels(); + /** @var \PhpOffice\PhpWord\Style\NumberingLevel */ + $level = $levels[0]; + if ($start > 0) { + $level->setStart($start); + } + $type = $type ? self::mapListType($type) : null; + if ($type) { + $level->setFormat($type); + } } if ($node->parentNode->nodeName === 'li') { return $element->getParent(); @@ -429,47 +593,48 @@ class Html /** * @param bool $isOrderedList + * * @return array */ protected static function getListStyle($isOrderedList) { if ($isOrderedList) { - return array( - 'type' => 'multilevel', - 'levels' => array( - array('format' => NumberFormat::DECIMAL, 'text' => '%1.', 'alignment' => 'left', 'tabPos' => 720, 'left' => 720, 'hanging' => 360), - array('format' => NumberFormat::LOWER_LETTER, 'text' => '%2.', 'alignment' => 'left', 'tabPos' => 1440, 'left' => 1440, 'hanging' => 360), - array('format' => NumberFormat::LOWER_ROMAN, 'text' => '%3.', 'alignment' => 'right', 'tabPos' => 2160, 'left' => 2160, 'hanging' => 180), - array('format' => NumberFormat::DECIMAL, 'text' => '%4.', 'alignment' => 'left', 'tabPos' => 2880, 'left' => 2880, 'hanging' => 360), - array('format' => NumberFormat::LOWER_LETTER, 'text' => '%5.', 'alignment' => 'left', 'tabPos' => 3600, 'left' => 3600, 'hanging' => 360), - array('format' => NumberFormat::LOWER_ROMAN, 'text' => '%6.', 'alignment' => 'right', 'tabPos' => 4320, 'left' => 4320, 'hanging' => 180), - array('format' => NumberFormat::DECIMAL, 'text' => '%7.', 'alignment' => 'left', 'tabPos' => 5040, 'left' => 5040, 'hanging' => 360), - array('format' => NumberFormat::LOWER_LETTER, 'text' => '%8.', 'alignment' => 'left', 'tabPos' => 5760, 'left' => 5760, 'hanging' => 360), - array('format' => NumberFormat::LOWER_ROMAN, 'text' => '%9.', 'alignment' => 'right', 'tabPos' => 6480, 'left' => 6480, 'hanging' => 180), - ), - ); + return [ + 'type' => 'multilevel', + 'levels' => [ + ['format' => NumberFormat::DECIMAL, 'text' => '%1.', 'alignment' => 'left', 'tabPos' => 720, 'left' => 720, 'hanging' => 360], + ['format' => NumberFormat::LOWER_LETTER, 'text' => '%2.', 'alignment' => 'left', 'tabPos' => 1440, 'left' => 1440, 'hanging' => 360], + ['format' => NumberFormat::LOWER_ROMAN, 'text' => '%3.', 'alignment' => 'right', 'tabPos' => 2160, 'left' => 2160, 'hanging' => 180], + ['format' => NumberFormat::DECIMAL, 'text' => '%4.', 'alignment' => 'left', 'tabPos' => 2880, 'left' => 2880, 'hanging' => 360], + ['format' => NumberFormat::LOWER_LETTER, 'text' => '%5.', 'alignment' => 'left', 'tabPos' => 3600, 'left' => 3600, 'hanging' => 360], + ['format' => NumberFormat::LOWER_ROMAN, 'text' => '%6.', 'alignment' => 'right', 'tabPos' => 4320, 'left' => 4320, 'hanging' => 180], + ['format' => NumberFormat::DECIMAL, 'text' => '%7.', 'alignment' => 'left', 'tabPos' => 5040, 'left' => 5040, 'hanging' => 360], + ['format' => NumberFormat::LOWER_LETTER, 'text' => '%8.', 'alignment' => 'left', 'tabPos' => 5760, 'left' => 5760, 'hanging' => 360], + ['format' => NumberFormat::LOWER_ROMAN, 'text' => '%9.', 'alignment' => 'right', 'tabPos' => 6480, 'left' => 6480, 'hanging' => 180], + ], + ]; } - return array( - 'type' => 'hybridMultilevel', - 'levels' => array( - array('format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 720, 'left' => 720, 'hanging' => 360, 'font' => 'Symbol', 'hint' => 'default'), - array('format' => NumberFormat::BULLET, 'text' => 'o', 'alignment' => 'left', 'tabPos' => 1440, 'left' => 1440, 'hanging' => 360, 'font' => 'Courier New', 'hint' => 'default'), - array('format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 2160, 'left' => 2160, 'hanging' => 360, 'font' => 'Wingdings', 'hint' => 'default'), - array('format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 2880, 'left' => 2880, 'hanging' => 360, 'font' => 'Symbol', 'hint' => 'default'), - array('format' => NumberFormat::BULLET, 'text' => 'o', 'alignment' => 'left', 'tabPos' => 3600, 'left' => 3600, 'hanging' => 360, 'font' => 'Courier New', 'hint' => 'default'), - array('format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 4320, 'left' => 4320, 'hanging' => 360, 'font' => 'Wingdings', 'hint' => 'default'), - array('format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 5040, 'left' => 5040, 'hanging' => 360, 'font' => 'Symbol', 'hint' => 'default'), - array('format' => NumberFormat::BULLET, 'text' => 'o', 'alignment' => 'left', 'tabPos' => 5760, 'left' => 5760, 'hanging' => 360, 'font' => 'Courier New', 'hint' => 'default'), - array('format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 6480, 'left' => 6480, 'hanging' => 360, 'font' => 'Wingdings', 'hint' => 'default'), - ), - ); + return [ + 'type' => 'hybridMultilevel', + 'levels' => [ + ['format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 720, 'left' => 720, 'hanging' => 360, 'font' => 'Symbol', 'hint' => 'default'], + ['format' => NumberFormat::BULLET, 'text' => 'o', 'alignment' => 'left', 'tabPos' => 1440, 'left' => 1440, 'hanging' => 360, 'font' => 'Courier New', 'hint' => 'default'], + ['format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 2160, 'left' => 2160, 'hanging' => 360, 'font' => 'Wingdings', 'hint' => 'default'], + ['format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 2880, 'left' => 2880, 'hanging' => 360, 'font' => 'Symbol', 'hint' => 'default'], + ['format' => NumberFormat::BULLET, 'text' => 'o', 'alignment' => 'left', 'tabPos' => 3600, 'left' => 3600, 'hanging' => 360, 'font' => 'Courier New', 'hint' => 'default'], + ['format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 4320, 'left' => 4320, 'hanging' => 360, 'font' => 'Wingdings', 'hint' => 'default'], + ['format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 5040, 'left' => 5040, 'hanging' => 360, 'font' => 'Symbol', 'hint' => 'default'], + ['format' => NumberFormat::BULLET, 'text' => 'o', 'alignment' => 'left', 'tabPos' => 5760, 'left' => 5760, 'hanging' => 360, 'font' => 'Courier New', 'hint' => 'default'], + ['format' => NumberFormat::BULLET, 'text' => '', 'alignment' => 'left', 'tabPos' => 6480, 'left' => 6480, 'hanging' => 360, 'font' => 'Wingdings', 'hint' => 'default'], + ], + ]; } /** - * Parse list item node + * Parse list item node. * - * @param \DOMNode $node + * @param DOMNode $node * @param \PhpOffice\PhpWord\Element\AbstractContainer $element * @param array &$styles * @param array $data @@ -477,7 +642,7 @@ class Html * @todo This function is almost the same like `parseChildNodes`. Merged? * @todo As soon as ListItem inherits from AbstractContainer or TextRun delete parsing part of childNodes */ - protected static function parseListItem($node, $element, &$styles, $data) + protected static function parseListItem($node, $element, &$styles, $data): void { $cNodes = $node->childNodes; if (!empty($cNodes)) { @@ -489,59 +654,82 @@ class Html } /** - * Parse style + * Parse style. * - * @param \DOMAttr $attribute + * @param DOMAttr $attribute * @param array $styles + * * @return array */ protected static function parseStyle($attribute, $styles) { $properties = explode(';', trim($attribute->value, " \t\n\r\0\x0B;")); + $selectors = []; foreach ($properties as $property) { - list($cKey, $cValue) = array_pad(explode(':', $property, 2), 2, null); - $cValue = trim($cValue); - switch (trim($cKey)) { + [$cKey, $cValue] = array_pad(explode(':', $property, 2), 2, null); + $selectors[strtolower(trim($cKey))] = trim($cValue ?? ''); + } + + return self::parseStyleDeclarations($selectors, $styles); + } + + protected static function parseStyleDeclarations(array $selectors, array $styles) + { + foreach ($selectors as $property => $value) { + switch ($property) { case 'text-decoration': - switch ($cValue) { + switch ($value) { case 'underline': $styles['underline'] = 'single'; + break; case 'line-through': $styles['strikethrough'] = true; + break; } + break; case 'text-align': - $styles['alignment'] = self::mapAlign($cValue); + $styles['alignment'] = self::mapAlign($value); + break; case 'display': - $styles['hidden'] = $cValue === 'none' || $cValue === 'hidden'; + $styles['hidden'] = $value === 'none' || $value === 'hidden'; + break; case 'direction': - $styles['rtl'] = $cValue === 'rtl'; + $styles['rtl'] = $value === 'rtl'; + break; case 'font-size': - $styles['size'] = Converter::cssToPoint($cValue); + $styles['size'] = Converter::cssToPoint($value); + break; case 'font-family': - $cValue = array_map('trim', explode(',', $cValue)); - $styles['name'] = ucwords($cValue[0]); + $value = array_map('trim', explode(',', $value)); + $styles['name'] = ucwords($value[0]); + break; case 'color': - $styles['color'] = trim($cValue, '#'); + $styles['color'] = trim($value, '#'); + break; case 'background-color': - $styles['bgColor'] = trim($cValue, '#'); + $styles['bgColor'] = trim($value, '#'); + break; case 'line-height': - $matches = array(); - if (preg_match('/([0-9]+\.?[0-9]*[a-z]+)/', $cValue, $matches)) { + $matches = []; + if ($value === 'normal') { + $spacingLineRule = \PhpOffice\PhpWord\SimpleType\LineSpacingRule::AUTO; + $spacing = 0; + } elseif (preg_match('/([0-9]+\.?[0-9]*[a-z]+)/', $value, $matches)) { //matches number with a unit, e.g. 12px, 15pt, 20mm, ... $spacingLineRule = \PhpOffice\PhpWord\SimpleType\LineSpacingRule::EXACT; $spacing = Converter::cssToTwip($matches[1]); - } elseif (preg_match('/([0-9]+)%/', $cValue, $matches)) { + } elseif (preg_match('/([0-9]+)%/', $value, $matches)) { //matches percentages $spacingLineRule = \PhpOffice\PhpWord\SimpleType\LineSpacingRule::AUTO; //we are subtracting 1 line height because the Spacing writer is adding one line @@ -550,64 +738,119 @@ class Html //any other, wich is a multiplier. E.g. 1.2 $spacingLineRule = \PhpOffice\PhpWord\SimpleType\LineSpacingRule::AUTO; //we are subtracting 1 line height because the Spacing writer is adding one line - $spacing = ($cValue * Paragraph::LINE_HEIGHT) - Paragraph::LINE_HEIGHT; + $spacing = ($value * Paragraph::LINE_HEIGHT) - Paragraph::LINE_HEIGHT; } $styles['spacingLineRule'] = $spacingLineRule; $styles['line-spacing'] = $spacing; + break; case 'letter-spacing': - $styles['letter-spacing'] = Converter::cssToTwip($cValue); + $styles['letter-spacing'] = Converter::cssToTwip($value); + break; case 'text-indent': - $styles['indentation']['firstLine'] = Converter::cssToTwip($cValue); + $styles['indentation']['firstLine'] = Converter::cssToTwip($value); + break; case 'font-weight': $tValue = false; - if (preg_match('#bold#', $cValue)) { + if (preg_match('#bold#', $value)) { $tValue = true; // also match bolder } $styles['bold'] = $tValue; + break; case 'font-style': $tValue = false; - if (preg_match('#(?:italic|oblique)#', $cValue)) { + if (preg_match('#(?:italic|oblique)#', $value)) { $tValue = true; } $styles['italic'] = $tValue; + + break; + case 'margin': + $value = Converter::cssToTwip($value); + $styles['spaceBefore'] = $value; + $styles['spaceAfter'] = $value; + break; case 'margin-top': - $styles['spaceBefore'] = Converter::cssToPoint($cValue); + // BC change: up to ver. 0.17.0 incorrectly converted to points - Converter::cssToPoint($value) + $styles['spaceBefore'] = Converter::cssToTwip($value); + break; case 'margin-bottom': - $styles['spaceAfter'] = Converter::cssToPoint($cValue); + // BC change: up to ver. 0.17.0 incorrectly converted to points - Converter::cssToPoint($value) + $styles['spaceAfter'] = Converter::cssToTwip($value); + break; case 'border-color': - self::mapBorderColor($styles, $cValue); + self::mapBorderColor($styles, $value); + break; case 'border-width': - $styles['borderSize'] = Converter::cssToPoint($cValue); + $styles['borderSize'] = Converter::cssToPoint($value); + break; case 'border-style': - $styles['borderStyle'] = self::mapBorderStyle($cValue); + $styles['borderStyle'] = self::mapBorderStyle($value); + break; case 'width': - if (preg_match('/([0-9]+[a-z]+)/', $cValue, $matches)) { + if (preg_match('/([0-9]+[a-z]+)/', $value, $matches)) { $styles['width'] = Converter::cssToTwip($matches[1]); $styles['unit'] = \PhpOffice\PhpWord\SimpleType\TblWidth::TWIP; - } elseif (preg_match('/([0-9]+)%/', $cValue, $matches)) { + } elseif (preg_match('/([0-9]+)%/', $value, $matches)) { $styles['width'] = $matches[1] * 50; $styles['unit'] = \PhpOffice\PhpWord\SimpleType\TblWidth::PERCENT; - } elseif (preg_match('/([0-9]+)/', $cValue, $matches)) { + } elseif (preg_match('/([0-9]+)/', $value, $matches)) { $styles['width'] = $matches[1]; $styles['unit'] = \PhpOffice\PhpWord\SimpleType\TblWidth::AUTO; } + break; case 'border': - if (preg_match('/([0-9]+[^0-9]*)\s+(\#[a-fA-F0-9]+)\s+([a-z]+)/', $cValue, $matches)) { - $styles['borderSize'] = Converter::cssToPoint($matches[1]); - $styles['borderColor'] = trim($matches[2], '#'); - $styles['borderStyle'] = self::mapBorderStyle($matches[3]); + case 'border-top': + case 'border-bottom': + case 'border-right': + case 'border-left': + // must have exact order [width color style], e.g. "1px #0011CC solid" or "2pt green solid" + // Word does not accept shortened hex colors e.g. #CCC, only full e.g. #CCCCCC + if (preg_match('/([0-9]+[^0-9]*)\s+(\#[a-fA-F0-9]+|[a-zA-Z]+)\s+([a-z]+)/', $value, $matches)) { + if (false !== strpos($property, '-')) { + $tmp = explode('-', $property); + $which = $tmp[1]; + $which = ucfirst($which); // e.g. bottom -> Bottom + } else { + $which = ''; + } + // Note - border width normalization: + // Width of border in Word is calculated differently than HTML borders, usually showing up too bold. + // Smallest 1px (or 1pt) appears in Word like 2-3px/pt in HTML once converted to twips. + // Therefore we need to normalize converted twip value to cca 1/2 of value. + // This may be adjusted, if better ratio or formula found. + // BC change: up to ver. 0.17.0 was $size converted to points - Converter::cssToPoint($size) + $size = Converter::cssToTwip($matches[1]); + $size = (int) ($size / 2); + // valid variants may be e.g. borderSize, borderTopSize, borderLeftColor, etc .. + $styles["border{$which}Size"] = $size; // twips + $styles["border{$which}Color"] = trim($matches[2], '#'); + $styles["border{$which}Style"] = self::mapBorderStyle($matches[3]); } + + break; + case 'vertical-align': + // https://developer.mozilla.org/en-US/docs/Web/CSS/vertical-align + if (preg_match('#(?:top|bottom|middle|sub|baseline)#i', $value, $matches)) { + $styles['valign'] = self::mapAlignVertical($matches[0]); + } + + break; + case 'page-break-after': + if ($value == 'always') { + $styles['isPageBreak'] = true; + } + break; } } @@ -616,57 +859,62 @@ class Html } /** - * Parse image node + * Parse image node. * - * @param \DOMNode $node + * @param DOMNode $node * @param \PhpOffice\PhpWord\Element\AbstractContainer $element * * @return \PhpOffice\PhpWord\Element\Image - **/ + */ protected static function parseImage($node, $element) { - $style = array(); + $style = []; $src = null; foreach ($node->attributes as $attribute) { switch ($attribute->name) { case 'src': $src = $attribute->value; + break; case 'width': $width = $attribute->value; $style['width'] = $width; $style['unit'] = \PhpOffice\PhpWord\Style\Image::UNIT_PX; + break; case 'height': $height = $attribute->value; $style['height'] = $height; $style['unit'] = \PhpOffice\PhpWord\Style\Image::UNIT_PX; + break; case 'style': $styleattr = explode(';', $attribute->value); foreach ($styleattr as $attr) { if (strpos($attr, ':')) { - list($k, $v) = explode(':', $attr); + [$k, $v] = explode(':', $attr); switch ($k) { case 'float': if (trim($v) == 'right') { $style['hPos'] = \PhpOffice\PhpWord\Style\Image::POS_RIGHT; - $style['hPosRelTo'] = \PhpOffice\PhpWord\Style\Image::POS_RELTO_PAGE; + $style['hPosRelTo'] = \PhpOffice\PhpWord\Style\Image::POS_RELTO_MARGIN; // inner section area $style['pos'] = \PhpOffice\PhpWord\Style\Image::POS_RELATIVE; $style['wrap'] = \PhpOffice\PhpWord\Style\Image::WRAP_TIGHT; $style['overlap'] = true; } if (trim($v) == 'left') { $style['hPos'] = \PhpOffice\PhpWord\Style\Image::POS_LEFT; - $style['hPosRelTo'] = \PhpOffice\PhpWord\Style\Image::POS_RELTO_PAGE; + $style['hPosRelTo'] = \PhpOffice\PhpWord\Style\Image::POS_RELTO_MARGIN; // inner section area $style['pos'] = \PhpOffice\PhpWord\Style\Image::POS_RELATIVE; $style['wrap'] = \PhpOffice\PhpWord\Style\Image::WRAP_TIGHT; $style['overlap'] = true; } + break; } } } + break; } } @@ -674,7 +922,7 @@ class Html if (strpos($src, 'data:image') !== false) { $tmpDir = Settings::getTempDir() . '/'; - $match = array(); + $match = []; preg_match('/data:image\/(\w+);base64,(.+)/', $src, $match); $src = $imgFile = $tmpDir . uniqid() . '.' . $match[1]; @@ -689,16 +937,16 @@ class Html $src = urldecode($src); if (!is_file($src) - && !is_null(self::$options) - && isset(self::$options['IMG_SRC_SEARCH']) - && isset(self::$options['IMG_SRC_REPLACE'])) { + && null !== self::$options + && isset(self::$options['IMG_SRC_SEARCH'], self::$options['IMG_SRC_REPLACE']) + ) { $src = str_replace(self::$options['IMG_SRC_SEARCH'], self::$options['IMG_SRC_REPLACE'], $src); } if (!is_file($src)) { if ($imgBlob = @file_get_contents($src)) { $tmpDir = Settings::getTempDir() . '/'; - $match = array(); + $match = []; preg_match('/.+\.(\w+)$/', $src, $match); $src = $tmpDir . uniqid() . '.' . $match[1]; @@ -714,16 +962,17 @@ class Html if (is_file($src)) { $newElement = $element->addImage($src, $style); } else { - throw new \Exception("Could not load image $originSrc"); + throw new Exception("Could not load image $originSrc"); } return $newElement; } /** - * Transforms a CSS border style into a word border style + * Transforms a CSS border style into a word border style. * * @param string $cssBorderStyle + * * @return null|string */ protected static function mapBorderStyle($cssBorderStyle) @@ -739,25 +988,26 @@ class Html } } - protected static function mapBorderColor(&$styles, $cssBorderColor) + protected static function mapBorderColor(&$styles, $cssBorderColor): void { $numColors = substr_count($cssBorderColor, '#'); if ($numColors === 1) { $styles['borderColor'] = trim($cssBorderColor, '#'); } elseif ($numColors > 1) { $colors = explode(' ', $cssBorderColor); - $borders = array('borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor'); - for ($i = 0; $i < min(4, $numColors, count($colors)); $i++) { + $borders = ['borderTopColor', 'borderRightColor', 'borderBottomColor', 'borderLeftColor']; + for ($i = 0; $i < min(4, $numColors, count($colors)); ++$i) { $styles[$borders[$i]] = trim($colors[$i], '#'); } } } /** - * Transforms a HTML/CSS alignment into a \PhpOffice\PhpWord\SimpleType\Jc + * Transforms a HTML/CSS alignment into a \PhpOffice\PhpWord\SimpleType\Jc. * * @param string $cssAlignment - * @return string|null + * + * @return null|string */ protected static function mapAlign($cssAlignment) { @@ -774,19 +1024,72 @@ class Html } /** - * Parse line break + * Transforms a HTML/CSS vertical alignment. + * + * @param string $alignment + * + * @return null|string + */ + protected static function mapAlignVertical($alignment) + { + $alignment = strtolower($alignment); + switch ($alignment) { + case 'top': + case 'baseline': + case 'bottom': + return $alignment; + case 'middle': + return 'center'; + case 'sub': + return 'bottom'; + case 'text-top': + case 'baseline': + return 'top'; + default: + // @discuss - which one should apply: + // - Word uses default vert. alignment: top + // - all browsers use default vert. alignment: middle + // Returning empty string means attribute wont be set so use Word default (top). + return ''; + } + } + + /** + * Map list style for ordered list. + * + * @param string $cssListType + */ + protected static function mapListType($cssListType) + { + switch ($cssListType) { + case 'a': + return NumberFormat::LOWER_LETTER; // a, b, c, .. + case 'A': + return NumberFormat::UPPER_LETTER; // A, B, C, .. + case 'i': + return NumberFormat::LOWER_ROMAN; // i, ii, iii, iv, .. + case 'I': + return NumberFormat::UPPER_ROMAN; // I, II, III, IV, .. + case '1': + default: + return NumberFormat::DECIMAL; // 1, 2, 3, .. + } + } + + /** + * Parse line break. * * @param \PhpOffice\PhpWord\Element\AbstractContainer $element */ - protected static function parseLineBreak($element) + protected static function parseLineBreak($element): void { $element->addTextBreak(); } /** - * Parse link node + * Parse link node. * - * @param \DOMNode $node + * @param DOMNode $node * @param \PhpOffice\PhpWord\Element\AbstractContainer $element * @param array $styles */ @@ -797,6 +1100,7 @@ class Html switch ($attribute->name) { case 'href': $target = $attribute->value; + break; } } @@ -808,4 +1112,38 @@ class Html return $element->addLink($target, $node->textContent, $styles['font'], $styles['paragraph']); } + + /** + * Render horizontal rule + * Note: Word rule is not the same as HTML's
      since it does not support width and thus neither alignment. + * + * @param DOMNode $node + * @param \PhpOffice\PhpWord\Element\AbstractContainer $element + */ + protected static function parseHorizRule($node, $element): void + { + $styles = self::parseInlineStyle($node); + + //
      is implemented as an empty paragraph - extending 100% inside the section + // Some properties may be controlled, e.g.
      + + $fontStyle = $styles + ['size' => 3]; + + $paragraphStyle = $styles + [ + 'lineHeight' => 0.25, // multiply default line height - e.g. 1, 1.5 etc + 'spacing' => 0, // twip + 'spaceBefore' => 120, // twip, 240/2 (default line height) + 'spaceAfter' => 120, // twip + 'borderBottomSize' => empty($styles['line-height']) ? 1 : $styles['line-height'], + 'borderBottomColor' => empty($styles['color']) ? '000000' : $styles['color'], + 'borderBottomStyle' => 'single', // same as "solid" + ]; + + $element->addText('', $fontStyle, $paragraphStyle); + + // Notes:
      cannot be: + // - table - throws error "cannot be inside textruns", e.g. lists + // - line - that is a shape, has different behaviour + // - repeated text, e.g. underline "_", because of unpredictable line wrapping + } } diff --git a/PhpOffice/PhpWord/Shared/Microsoft/PasswordEncoder.php b/PhpOffice/PhpWord/Shared/Microsoft/PasswordEncoder.php new file mode 100644 index 0000000..5ff42e4 --- /dev/null +++ b/PhpOffice/PhpWord/Shared/Microsoft/PasswordEncoder.php @@ -0,0 +1,243 @@ + [1, 'md2'], + self::ALGORITHM_MD4 => [2, 'md4'], + self::ALGORITHM_MD5 => [3, 'md5'], + self::ALGORITHM_SHA_1 => [4, 'sha1'], + self::ALGORITHM_MAC => [5, ''], // 'mac' -> not possible with hash() + self::ALGORITHM_RIPEMD => [6, 'ripemd'], + self::ALGORITHM_RIPEMD_160 => [7, 'ripemd160'], + self::ALGORITHM_HMAC => [9, ''], //'hmac' -> not possible with hash() + self::ALGORITHM_SHA_256 => [12, 'sha256'], + self::ALGORITHM_SHA_384 => [13, 'sha384'], + self::ALGORITHM_SHA_512 => [14, 'sha512'], + ]; + + private static $initialCodeArray = [ + 0xE1F0, + 0x1D0F, + 0xCC9C, + 0x84C0, + 0x110C, + 0x0E10, + 0xF1CE, + 0x313E, + 0x1872, + 0xE139, + 0xD40F, + 0x84F9, + 0x280C, + 0xA96A, + 0x4EC3, + ]; + + private static $encryptionMatrix = [ + [0xAEFC, 0x4DD9, 0x9BB2, 0x2745, 0x4E8A, 0x9D14, 0x2A09], + [0x7B61, 0xF6C2, 0xFDA5, 0xEB6B, 0xC6F7, 0x9DCF, 0x2BBF], + [0x4563, 0x8AC6, 0x05AD, 0x0B5A, 0x16B4, 0x2D68, 0x5AD0], + [0x0375, 0x06EA, 0x0DD4, 0x1BA8, 0x3750, 0x6EA0, 0xDD40], + [0xD849, 0xA0B3, 0x5147, 0xA28E, 0x553D, 0xAA7A, 0x44D5], + [0x6F45, 0xDE8A, 0xAD35, 0x4A4B, 0x9496, 0x390D, 0x721A], + [0xEB23, 0xC667, 0x9CEF, 0x29FF, 0x53FE, 0xA7FC, 0x5FD9], + [0x47D3, 0x8FA6, 0x0F6D, 0x1EDA, 0x3DB4, 0x7B68, 0xF6D0], + [0xB861, 0x60E3, 0xC1C6, 0x93AD, 0x377B, 0x6EF6, 0xDDEC], + [0x45A0, 0x8B40, 0x06A1, 0x0D42, 0x1A84, 0x3508, 0x6A10], + [0xAA51, 0x4483, 0x8906, 0x022D, 0x045A, 0x08B4, 0x1168], + [0x76B4, 0xED68, 0xCAF1, 0x85C3, 0x1BA7, 0x374E, 0x6E9C], + [0x3730, 0x6E60, 0xDCC0, 0xA9A1, 0x4363, 0x86C6, 0x1DAD], + [0x3331, 0x6662, 0xCCC4, 0x89A9, 0x0373, 0x06E6, 0x0DCC], + [0x1021, 0x2042, 0x4084, 0x8108, 0x1231, 0x2462, 0x48C4], + ]; + + private static $passwordMaxLength = 15; + + /** + * Create a hashed password that MS Word will be able to work with. + * + * @see https://blogs.msdn.microsoft.com/vsod/2010/04/05/how-to-set-the-editing-restrictions-in-word-using-open-xml-sdk-2-0/ + * + * @param string $password + * @param string $algorithmName + * @param string $salt + * @param int $spinCount + * + * @return string + */ + public static function hashPassword($password, $algorithmName = self::ALGORITHM_SHA_1, $salt = null, $spinCount = 10000) + { + $origEncoding = mb_internal_encoding(); + mb_internal_encoding('UTF-8'); + + $password = mb_substr($password, 0, min(self::$passwordMaxLength, mb_strlen($password))); + + // Get the single-byte values by iterating through the Unicode characters of the truncated password. + // For each character, if the low byte is not equal to 0, take it. Otherwise, take the high byte. + $passUtf8 = mb_convert_encoding($password, 'UCS-2LE', 'UTF-8'); + $byteChars = []; + + for ($i = 0; $i < mb_strlen($password); ++$i) { + $byteChars[$i] = ord(substr($passUtf8, $i * 2, 1)); + + if ($byteChars[$i] == 0) { + $byteChars[$i] = ord(substr($passUtf8, $i * 2 + 1, 1)); + } + } + + // build low-order word and hig-order word and combine them + $combinedKey = self::buildCombinedKey($byteChars); + // build reversed hexadecimal string + $hex = str_pad(strtoupper(dechex($combinedKey & 0xFFFFFFFF)), 8, '0', \STR_PAD_LEFT); + $reversedHex = $hex[6] . $hex[7] . $hex[4] . $hex[5] . $hex[2] . $hex[3] . $hex[0] . $hex[1]; + + $generatedKey = mb_convert_encoding($reversedHex, 'UCS-2LE', 'UTF-8'); + + // Implementation Notes List: + // Word requires that the initial hash of the password with the salt not be considered in the count. + // The initial hash of salt + key is not included in the iteration count. + $algorithm = self::getAlgorithm($algorithmName); + $generatedKey = hash($algorithm, $salt . $generatedKey, true); + + for ($i = 0; $i < $spinCount; ++$i) { + $generatedKey = hash($algorithm, $generatedKey . pack('CCCC', $i, $i >> 8, $i >> 16, $i >> 24), true); + } + $generatedKey = base64_encode($generatedKey); + + mb_internal_encoding($origEncoding); + + return $generatedKey; + } + + /** + * Get algorithm from self::$algorithmMapping. + * + * @param string $algorithmName + * + * @return string + */ + private static function getAlgorithm($algorithmName) + { + $algorithm = self::$algorithmMapping[$algorithmName][1]; + if ($algorithm == '') { + $algorithm = 'sha1'; + } + + return $algorithm; + } + + /** + * Returns the algorithm ID. + * + * @param string $algorithmName + * + * @return int + */ + public static function getAlgorithmId($algorithmName) + { + return self::$algorithmMapping[$algorithmName][0]; + } + + /** + * Build combined key from low-order word and high-order word. + * + * @param array $byteChars byte array representation of password + * + * @return int + */ + private static function buildCombinedKey($byteChars) + { + $byteCharsLength = count($byteChars); + // Compute the high-order word + // Initialize from the initial code array (see above), depending on the passwords length. + $highOrderWord = self::$initialCodeArray[$byteCharsLength - 1]; + + // For each character in the password: + // For every bit in the character, starting with the least significant and progressing to (but excluding) + // the most significant, if the bit is set, XOR the key’s high-order word with the corresponding word from + // the Encryption Matrix + for ($i = 0; $i < $byteCharsLength; ++$i) { + $tmp = self::$passwordMaxLength - $byteCharsLength + $i; + $matrixRow = self::$encryptionMatrix[$tmp]; + for ($intBit = 0; $intBit < 7; ++$intBit) { + if (($byteChars[$i] & (0x0001 << $intBit)) != 0) { + $highOrderWord = ($highOrderWord ^ $matrixRow[$intBit]); + } + } + } + + // Compute low-order word + // Initialize with 0 + $lowOrderWord = 0; + // For each character in the password, going backwards + for ($i = $byteCharsLength - 1; $i >= 0; --$i) { + // low-order word = (((low-order word SHR 14) AND 0x0001) OR (low-order word SHL 1) AND 0x7FFF)) XOR character + $lowOrderWord = (((($lowOrderWord >> 14) & 0x0001) | (($lowOrderWord << 1) & 0x7FFF)) ^ $byteChars[$i]); + } + // Lastly, low-order word = (((low-order word SHR 14) AND 0x0001) OR (low-order word SHL 1) AND 0x7FFF)) XOR strPassword length XOR 0xCE4B. + $lowOrderWord = (((($lowOrderWord >> 14) & 0x0001) | (($lowOrderWord << 1) & 0x7FFF)) ^ $byteCharsLength ^ 0xCE4B); + + // Combine the Low and High Order Word + return self::int32(($highOrderWord << 16) + $lowOrderWord); + } + + /** + * Simulate behaviour of (signed) int32. + * + * @codeCoverageIgnore + * + * @param int $value + * + * @return int + */ + private static function int32($value) + { + $value = ($value & 0xFFFFFFFF); + + if ($value & 0x80000000) { + $value = -((~$value & 0xFFFFFFFF) + 1); + } + + return $value; + } +} diff --git a/PhpOffice/PhpWord/Shared/OLERead.php b/PhpOffice/PhpWord/Shared/OLERead.php old mode 100755 new mode 100644 index 2e6a899..316e781 --- a/PhpOffice/PhpWord/Shared/OLERead.php +++ b/PhpOffice/PhpWord/Shared/OLERead.php @@ -14,6 +14,7 @@ * @copyright 2010-2018 PHPWord contributors * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ + namespace PhpOffice\PhpWord\Shared; use PhpOffice\PhpWord\Exception\Exception; @@ -29,40 +30,37 @@ class OLERead const IDENTIFIER_OLE = IDENTIFIER_OLE; // Size of a sector = 512 bytes - const BIG_BLOCK_SIZE = 0x200; + const BIG_BLOCK_SIZE = 0x200; // Size of a short sector = 64 bytes - const SMALL_BLOCK_SIZE = 0x40; + const SMALL_BLOCK_SIZE = 0x40; // Size of a directory entry always = 128 bytes - const PROPERTY_STORAGE_BLOCK_SIZE = 0x80; + const PROPERTY_STORAGE_BLOCK_SIZE = 0x80; // Minimum size of a standard stream = 4096 bytes, streams smaller than this are stored as short streams - const SMALL_BLOCK_THRESHOLD = 0x1000; + const SMALL_BLOCK_THRESHOLD = 0x1000; // header offsets - const NUM_BIG_BLOCK_DEPOT_BLOCKS_POS = 0x2c; - const ROOT_START_BLOCK_POS = 0x30; - const SMALL_BLOCK_DEPOT_BLOCK_POS = 0x3c; - const EXTENSION_BLOCK_POS = 0x44; - const NUM_EXTENSION_BLOCK_POS = 0x48; - const BIG_BLOCK_DEPOT_BLOCKS_POS = 0x4c; + const NUM_BIG_BLOCK_DEPOT_BLOCKS_POS = 0x2c; + const ROOT_START_BLOCK_POS = 0x30; + const SMALL_BLOCK_DEPOT_BLOCK_POS = 0x3c; + const EXTENSION_BLOCK_POS = 0x44; + const NUM_EXTENSION_BLOCK_POS = 0x48; + const BIG_BLOCK_DEPOT_BLOCKS_POS = 0x4c; // property storage offsets (directory offsets) - const SIZE_OF_NAME_POS = 0x40; - const TYPE_POS = 0x42; - const START_BLOCK_POS = 0x74; - const SIZE_POS = 0x78; - - - - public $wrkdocument = null; - public $wrk1Table = null; - public $wrkData = null; - public $wrkObjectPool = null; - public $summaryInformation = null; - public $docSummaryInfos = null; + const SIZE_OF_NAME_POS = 0x40; + const TYPE_POS = 0x42; + const START_BLOCK_POS = 0x74; + const SIZE_POS = 0x78; + public $wrkdocument = null; + public $wrk1Table = null; + public $wrkData = null; + public $wrkObjectPool = null; + public $summaryInformation = null; + public $docSummaryInfos = null; /** * Read the file @@ -75,7 +73,7 @@ class OLERead { // Check if file exists and is readable if (!is_readable($sFileName)) { - throw new Exception("Could not open " . $sFileName . " for reading! File does not exist, or it is not readable."); + throw new Exception('Could not open ' . $sFileName . ' for reading! File does not exist, or it is not readable.'); } // Get the file identifier @@ -112,7 +110,7 @@ class OLERead // @codeCoverageIgnoreStart if ($this->numExtensionBlocks != 0) { - $bbdBlocks = (self::BIG_BLOCK_SIZE - self::BIG_BLOCK_DEPOT_BLOCKS_POS)/4; + $bbdBlocks = (self::BIG_BLOCK_SIZE - self::BIG_BLOCK_DEPOT_BLOCKS_POS) / 4; } // @codeCoverageIgnoreEnd @@ -144,8 +142,8 @@ class OLERead for ($i = 0; $i < $this->numBigBlockDepotBlocks; ++$i) { $pos = ($bigBlockDepotBlocks[$i] + 1) * self::BIG_BLOCK_SIZE; - $this->bigBlockChain .= substr($this->data, $pos, 4*$bbs); - $pos += 4*$bbs; + $this->bigBlockChain .= substr($this->data, $pos, 4 * $bbs); + $pos += 4 * $bbs; } $pos = 0; @@ -154,10 +152,10 @@ class OLERead while ($sbdBlock != -2) { $pos = ($sbdBlock + 1) * self::BIG_BLOCK_SIZE; - $this->smallBlockChain .= substr($this->data, $pos, 4*$bbs); - $pos += 4*$bbs; + $this->smallBlockChain .= substr($this->data, $pos, 4 * $bbs); + $pos += 4 * $bbs; - $sbdBlock = self::getInt4d($this->bigBlockChain, $sbdBlock*4); + $sbdBlock = self::getInt4d($this->bigBlockChain, $sbdBlock * 4); } // read the directory stream @@ -170,6 +168,7 @@ class OLERead /** * Extract binary stream data * + * @param mixed $stream * @return string */ public function getStream($stream) @@ -189,7 +188,7 @@ class OLERead $pos = $block * self::SMALL_BLOCK_SIZE; $streamData .= substr($rootdata, $pos, self::SMALL_BLOCK_SIZE); - $block = self::getInt4d($this->smallBlockChain, $block*4); + $block = self::getInt4d($this->smallBlockChain, $block * 4); } return $streamData; @@ -201,7 +200,7 @@ class OLERead } if ($numBlocks == 0) { - return '';// @codeCoverageIgnore + return ''; // @codeCoverageIgnore } $block = $this->props[$stream]['startBlock']; @@ -209,7 +208,7 @@ class OLERead while ($block != -2) { $pos = ($block + 1) * self::BIG_BLOCK_SIZE; $streamData .= substr($this->data, $pos, self::BIG_BLOCK_SIZE); - $block = self::getInt4d($this->bigBlockChain, $block*4); + $block = self::getInt4d($this->bigBlockChain, $block * 4); } return $streamData; @@ -229,8 +228,9 @@ class OLERead while ($block != -2) { $pos = ($block + 1) * self::BIG_BLOCK_SIZE; $data .= substr($this->data, $pos, self::BIG_BLOCK_SIZE); - $block = self::getInt4d($this->bigBlockChain, $block*4); + $block = self::getInt4d($this->bigBlockChain, $block * 4); } + return $data; } @@ -248,7 +248,7 @@ class OLERead $data = substr($this->entry, $offset, self::PROPERTY_STORAGE_BLOCK_SIZE); // size in bytes of name - $nameSize = ord($data[self::SIZE_OF_NAME_POS]) | (ord($data[self::SIZE_OF_NAME_POS+1]) << 8); + $nameSize = ord($data[self::SIZE_OF_NAME_POS]) | (ord($data[self::SIZE_OF_NAME_POS + 1]) << 8); // type of entry $type = ord($data[self::TYPE_POS]); @@ -259,14 +259,13 @@ class OLERead $size = self::getInt4d($data, self::SIZE_POS); - $name = str_replace("\x00", "", substr($data, 0, $nameSize)); + $name = str_replace("\x00", '', substr($data, 0, $nameSize)); - - $this->props[] = array ( - 'name' => $name, - 'type' => $type, + $this->props[] = array( + 'name' => $name, + 'type' => $type, 'startBlock' => $startBlock, - 'size' => $size); + 'size' => $size, ); // tmp helper to simplify checks $upName = strtoupper($name); @@ -318,6 +317,7 @@ class OLERead } else { $ord24 = ($or24 & 127) << 24; } + return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $ord24; } } diff --git a/PhpOffice/PhpWord/Shared/PCLZip/pclzip.lib.php b/PhpOffice/PhpWord/Shared/PCLZip/pclzip.lib.php old mode 100755 new mode 100644 index 3fbc932..6292749 --- a/PhpOffice/PhpWord/Shared/PCLZip/pclzip.lib.php +++ b/PhpOffice/PhpWord/Shared/PCLZip/pclzip.lib.php @@ -3258,17 +3258,6 @@ class PclZip $v_extract = true; } } - // ----- Look for extract by ereg rule - // ereg() is deprecated with PHP 5.3 - /* - elseif ( (isset($p_options[PCLZIP_OPT_BY_EREG])) - && ($p_options[PCLZIP_OPT_BY_EREG] != "")) { - - if (ereg($p_options[PCLZIP_OPT_BY_EREG], $v_header['stored_filename'])) { - $v_extract = true; - } - } - */ // ----- Look for extract by preg rule } elseif ((isset($p_options[PCLZIP_OPT_BY_PREG])) && ($p_options[PCLZIP_OPT_BY_PREG] != "")) { @@ -4545,18 +4534,6 @@ class PclZip } } - // ----- Look for extract by ereg rule - // ereg() is deprecated with PHP 5.3 - /* - elseif ( (isset($p_options[PCLZIP_OPT_BY_EREG])) - && ($p_options[PCLZIP_OPT_BY_EREG] != "")) { - - if (ereg($p_options[PCLZIP_OPT_BY_EREG], $v_header_list[$v_nb_extracted]['stored_filename'])) { - $v_found = true; - } - } - */ - // ----- Look for extract by preg rule } elseif ((isset($p_options[PCLZIP_OPT_BY_PREG])) && ($p_options[PCLZIP_OPT_BY_PREG] != "")) { diff --git a/PhpOffice/PhpWord/Shared/Text.php b/PhpOffice/PhpWord/Shared/Text.php new file mode 100644 index 0000000..667d67a --- /dev/null +++ b/PhpOffice/PhpWord/Shared/Text.php @@ -0,0 +1,252 @@ +) + * element or in the shared string element. + * + * @param string $value Value to escape + * + * @return string + */ + public static function controlCharacterPHP2OOXML($value = '') + { + if (empty(self::$controlCharacters)) { + self::buildControlCharacters(); + } + + return str_replace(array_values(self::$controlCharacters), array_keys(self::$controlCharacters), $value); + } + + /** + * Return a number formatted for being integrated in xml files. + * + * @param float $number + * @param int $decimals + * + * @return string + */ + public static function numberFormat($number, $decimals) + { + return number_format($number, $decimals, '.', ''); + } + + /** + * @param int $dec + * + * @see http://stackoverflow.com/a/7153133/2235790 + * + * @author velcrow + * + * @return string + */ + public static function chr($dec) + { + if ($dec <= 0x7F) { + return chr($dec); + } + if ($dec <= 0x7FF) { + return chr(($dec >> 6) + 192) . chr(($dec & 63) + 128); + } + if ($dec <= 0xFFFF) { + return chr(($dec >> 12) + 224) . chr((($dec >> 6) & 63) + 128) . chr(($dec & 63) + 128); + } + if ($dec <= 0x1FFFFF) { + return chr(($dec >> 18) + 240) . chr((($dec >> 12) & 63) + 128) . chr((($dec >> 6) & 63) + 128) . chr(($dec & 63) + 128); + } + + return ''; + } + + /** + * Convert from OpenXML escaped control character to PHP control character. + * + * @param string $value Value to unescape + * + * @return string + */ + public static function controlCharacterOOXML2PHP($value = '') + { + if (empty(self::$controlCharacters)) { + self::buildControlCharacters(); + } + + return str_replace(array_keys(self::$controlCharacters), array_values(self::$controlCharacters), $value); + } + + /** + * Check if a string contains UTF-8 data. + * + * @param string $value + * + * @return bool + */ + public static function isUTF8($value = '') + { + return is_string($value) && ($value === '' || preg_match('/^./su', $value) == 1); + } + + /** + * Return UTF8 encoded value. + * + * @param string $value + * + * @return string + */ + public static function toUTF8($value = '') + { + if (null !== $value && !self::isUTF8($value)) { + $value = utf8_encode($value); + } + + return $value; + } + + /** + * Returns unicode from UTF8 text. + * + * The function is splitted to reduce cyclomatic complexity + * + * @param string $text UTF8 text + * + * @return string Unicode text + * + * @since 0.11.0 + */ + public static function toUnicode($text) + { + return self::unicodeToEntities(self::utf8ToUnicode($text)); + } + + /** + * Returns unicode array from UTF8 text. + * + * @param string $text UTF8 text + * + * @return array + * + * @since 0.11.0 + * @see http://www.randomchaos.com/documents/?source=php_and_unicode + */ + public static function utf8ToUnicode($text) + { + $unicode = []; + $values = []; + $lookingFor = 1; + + // Gets unicode for each character + for ($i = 0; $i < strlen($text); ++$i) { + $thisValue = ord($text[$i]); + if ($thisValue < 128) { + $unicode[] = $thisValue; + } else { + if (count($values) == 0) { + $lookingFor = $thisValue < 224 ? 2 : 3; + } + $values[] = $thisValue; + if (count($values) == $lookingFor) { + if ($lookingFor == 3) { + $number = (($values[0] % 16) * 4096) + (($values[1] % 64) * 64) + ($values[2] % 64); + } else { + $number = (($values[0] % 32) * 64) + ($values[1] % 64); + } + $unicode[] = $number; + $values = []; + $lookingFor = 1; + } + } + } + + return $unicode; + } + + /** + * Returns entites from unicode array. + * + * @param array $unicode + * + * @return string + * + * @since 0.11.0 + * @see http://www.randomchaos.com/documents/?source=php_and_unicode + */ + private static function unicodeToEntities($unicode) + { + $entities = ''; + + foreach ($unicode as $value) { + if ($value != 65279) { + $entities .= $value > 127 ? '\uc0{\u' . $value . '}' : chr($value); + } + } + + return $entities; + } + + /** + * Return name without underscore for < 0.10.0 variable name compatibility. + * + * @param string $value + * + * @return string + */ + public static function removeUnderscorePrefix($value) + { + if (null !== $value) { + if (substr($value, 0, 1) == '_') { + $value = substr($value, 1); + } + } + + return $value; + } +} diff --git a/PhpOffice/PhpWord/Shared/XMLReader.php b/PhpOffice/PhpWord/Shared/XMLReader.php new file mode 100644 index 0000000..73762db --- /dev/null +++ b/PhpOffice/PhpWord/Shared/XMLReader.php @@ -0,0 +1,231 @@ +open($zipFile); + $content = $zip->getFromName($xmlFile); + $zip->close(); + + if ($content === false) { + return false; + } + + return $this->getDomFromString($content); + } + + /** + * Get DOMDocument from content string. + * + * @param string $content + * + * @return DOMDocument + */ + public function getDomFromString($content) + { + if (\PHP_VERSION_ID < 80000) { + $originalLibXMLEntityValue = libxml_disable_entity_loader(true); + } + $this->dom = new DOMDocument(); + $this->dom->loadXML($content); + if (\PHP_VERSION_ID < 80000) { + libxml_disable_entity_loader($originalLibXMLEntityValue); + } + + return $this->dom; + } + + /** + * Get elements. + * + * @param string $path + * @param DOMElement $contextNode + * + * @return DOMNodeList + */ + public function getElements($path, ?DOMElement $contextNode = null) + { + if ($this->dom === null) { + return []; + } + if ($this->xpath === null) { + $this->xpath = new DOMXpath($this->dom); + } + + if (null === $contextNode) { + return $this->xpath->query($path); + } + + return $this->xpath->query($path, $contextNode); + } + + /** + * Registers the namespace with the DOMXPath object. + * + * @param string $prefix The prefix + * @param string $namespaceURI The URI of the namespace + * + * @return bool true on success or false on failure + */ + public function registerNamespace($prefix, $namespaceURI) + { + if ($this->dom === null) { + throw new InvalidArgumentException('Dom needs to be loaded before registering a namespace'); + } + if ($this->xpath === null) { + $this->xpath = new DOMXpath($this->dom); + } + + return $this->xpath->registerNamespace($prefix, $namespaceURI); + } + + /** + * Get element. + * + * @param string $path + * @param DOMElement $contextNode + * + * @return null|DOMElement + */ + public function getElement($path, ?DOMElement $contextNode = null) + { + $elements = $this->getElements($path, $contextNode); + if ($elements->length > 0) { + return $elements->item(0); + } + + return null; + } + + /** + * Get element attribute. + * + * @param string $attribute + * @param DOMElement $contextNode + * @param string $path + * + * @return null|string + */ + public function getAttribute($attribute, ?DOMElement $contextNode = null, $path = null) + { + $return = null; + if ($path !== null) { + $elements = $this->getElements($path, $contextNode); + if ($elements->length > 0) { + /** @var DOMElement $node Type hint */ + $node = $elements->item(0); + $return = $node->getAttribute($attribute); + } + } else { + if ($contextNode !== null) { + $return = $contextNode->getAttribute($attribute); + } + } + + return ($return == '') ? null : $return; + } + + /** + * Get element value. + * + * @param string $path + * @param DOMElement $contextNode + * + * @return null|string + */ + public function getValue($path, ?DOMElement $contextNode = null) + { + $elements = $this->getElements($path, $contextNode); + if ($elements->length > 0) { + return $elements->item(0)->nodeValue; + } + + return null; + } + + /** + * Count elements. + * + * @param string $path + * @param DOMElement $contextNode + * + * @return int + */ + public function countElements($path, ?DOMElement $contextNode = null) + { + $elements = $this->getElements($path, $contextNode); + + return $elements->length; + } + + /** + * Element exists. + * + * @param string $path + * @param DOMElement $contextNode + * + * @return bool + */ + public function elementExists($path, ?DOMElement $contextNode = null) + { + return $this->getElements($path, $contextNode)->length > 0; + } +} diff --git a/PhpOffice/PhpWord/Shared/XMLWriter.php b/PhpOffice/PhpWord/Shared/XMLWriter.php new file mode 100644 index 0000000..9f51c0e --- /dev/null +++ b/PhpOffice/PhpWord/Shared/XMLWriter.php @@ -0,0 +1,187 @@ +openMemory(); + } else { + if (!$pTemporaryStorageDir || !is_dir($pTemporaryStorageDir)) { + $pTemporaryStorageDir = sys_get_temp_dir(); + } + // Create temporary filename + $this->tempFileName = @tempnam($pTemporaryStorageDir, 'xml'); + + // Open storage + $this->openUri($this->tempFileName); + } + + if ($compatibility) { + $this->setIndent(false); + $this->setIndentString(''); + } else { + $this->setIndent(true); + $this->setIndentString(' '); + } + } + + /** + * Destructor. + */ + public function __destruct() + { + // Unlink temporary files + if (empty($this->tempFileName)) { + return; + } + if (PHP_OS != 'WINNT' && @unlink($this->tempFileName) === false) { + throw new Exception('The file ' . $this->tempFileName . ' could not be deleted.'); + } + } + + /** + * Get written data. + * + * @return string + */ + public function getData() + { + if ($this->tempFileName == '') { + return $this->outputMemory(true); + } + + $this->flush(); + + return file_get_contents($this->tempFileName); + } + + /** + * Write simple element and attribute(s) block. + * + * There are two options: + * 1. If the `$attributes` is an array, then it's an associative array of attributes + * 2. If not, then it's a simple attribute-value pair + * + * @param string $element + * @param array|string $attributes + * @param string $value + */ + public function writeElementBlock($element, $attributes, $value = null): void + { + $this->startElement($element); + if (!is_array($attributes)) { + $attributes = [$attributes => $value]; + } + foreach ($attributes as $attribute => $value) { + $this->writeAttribute($attribute, $value); + } + $this->endElement(); + } + + /** + * Write element if ... + * + * @param bool $condition + * @param string $element + * @param string $attribute + * @param mixed $value + */ + public function writeElementIf($condition, $element, $attribute = null, $value = null): void + { + if ($condition == true) { + if (null === $attribute) { + $this->writeElement($element, $value); + } else { + $this->startElement($element); + $this->writeAttribute($attribute, $value); + $this->endElement(); + } + } + } + + /** + * Write attribute if ... + * + * @param bool $condition + * @param string $attribute + * @param mixed $value + */ + public function writeAttributeIf($condition, $attribute, $value): void + { + if ($condition == true) { + $this->writeAttribute($attribute, $value); + } + } + + /** + * @param string $name + * @param mixed $value + * + * @return bool + */ + #[ReturnTypeWillChange] + public function writeAttribute($name, $value) + { + if (is_float($value)) { + $value = json_encode($value); + } + + return parent::writeAttribute($name, $value ?? ''); + } +} diff --git a/PhpOffice/PhpWord/Shared/ZipArchive.php b/PhpOffice/PhpWord/Shared/ZipArchive.php old mode 100755 new mode 100644 index a5ed939..74922b2 --- a/PhpOffice/PhpWord/Shared/ZipArchive.php +++ b/PhpOffice/PhpWord/Shared/ZipArchive.php @@ -11,17 +11,18 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Shared; +use PclZip; use PhpOffice\PhpWord\Exception\Exception; use PhpOffice\PhpWord\Settings; /** - * ZipArchive wrapper + * ZipArchive wrapper. * * Wraps zip archive functionality of PHP ZipArchive and PCLZip. PHP ZipArchive * properties and methods are bypassed and used as the model for the PCLZip @@ -41,42 +42,42 @@ class ZipArchive const OVERWRITE = 8; // Emulate \ZipArchive::OVERWRITE /** - * Number of files (emulate ZipArchive::$numFiles) + * Number of files (emulate ZipArchive::$numFiles). * * @var int */ public $numFiles = 0; /** - * Archive filename (emulate ZipArchive::$filename) + * Archive filename (emulate ZipArchive::$filename). * * @var string */ public $filename; /** - * Temporary storage directory + * Temporary storage directory. * * @var string */ private $tempDir; /** - * Internal zip archive object + * Internal zip archive object. * - * @var \ZipArchive|\PclZip + * @var PclZip|\ZipArchive */ private $zip; /** - * Use PCLZip (default behaviour) + * Use PCLZip (default behaviour). * * @var bool */ private $usePclzip = true; /** - * Create new instance + * Create new instance. */ public function __construct() { @@ -90,12 +91,13 @@ class ZipArchive } /** - * Catch function calls: pass to ZipArchive or PCLZip + * Catch function calls: pass to ZipArchive or PCLZip. * * `call_user_func_array` can only used for public function, hence the `public` in all `pcl...` methods * * @param mixed $function * @param mixed $args + * * @return mixed */ public function __call($function, $args) @@ -112,17 +114,18 @@ class ZipArchive // Run function $result = false; if (method_exists($zipObject, $zipFunction)) { - $result = @call_user_func_array(array($zipObject, $zipFunction), $args); + $result = @call_user_func_array([$zipObject, $zipFunction], $args); } return $result; } /** - * Open a new zip archive + * Open a new zip archive. * * @param string $filename The file name of the ZIP archive to open * @param int $flags The mode to use to open the archive + * * @return bool */ public function open($filename, $flags = null) @@ -133,13 +136,20 @@ class ZipArchive if (!$this->usePclzip) { $zip = new \ZipArchive(); - $result = $zip->open($this->filename, $flags ?? 0); + + // PHP 8.1 compat - passing null as second arg to \ZipArchive::open() is deprecated + // passing 0 achieves the same behaviour + if ($flags === null) { + $flags = 0; + } + + $result = $zip->open($this->filename, $flags); // Scrutizer will report the property numFiles does not exist // See https://github.com/scrutinizer-ci/php-analyzer/issues/190 $this->numFiles = $zip->numFiles; } else { - $zip = new \PclZip($this->filename); + $zip = new PclZip($this->filename); $zipContent = $zip->listContent(); $this->numFiles = is_array($zipContent) ? count($zipContent) : 0; } @@ -149,9 +159,7 @@ class ZipArchive } /** - * Close the active archive - * - * @throws \PhpOffice\PhpWord\Exception\Exception + * Close the active archive. * * @return bool * @@ -169,11 +177,13 @@ class ZipArchive } /** - * Extract the archive contents (emulate \ZipArchive) + * Extract the archive contents (emulate \ZipArchive). * * @param string $destination - * @param string|array $entries + * @param array|string $entries + * * @return bool + * * @since 0.10.0 */ public function extractTo($destination, $entries = null) @@ -190,9 +200,10 @@ class ZipArchive } /** - * Extract file from archive by given file name (emulate \ZipArchive) + * Extract file from archive by given file name (emulate \ZipArchive). * * @param string $filename Filename for the file in zip archive + * * @return string $contents File string contents */ public function getFromName($filename) @@ -211,15 +222,16 @@ class ZipArchive } /** - * Add a new file to the zip archive (emulate \ZipArchive) + * Add a new file to the zip archive (emulate \ZipArchive). * * @param string $filename Directory/Name of the file to add to the zip archive * @param string $localname Directory/Name of the file added to the zip + * * @return bool */ public function pclzipAddFile($filename, $localname = null) { - /** @var \PclZip $zip Type hint */ + /** @var PclZip $zip Type hint */ $zip = $this->zip; // Bugfix GH-261 https://github.com/PHPOffice/PHPWord/pull/261 @@ -262,15 +274,16 @@ class ZipArchive } /** - * Add a new file to the zip archive from a string of raw data (emulate \ZipArchive) + * Add a new file to the zip archive from a string of raw data (emulate \ZipArchive). * * @param string $localname Directory/Name of the file to add to the zip archive * @param string $contents String of data to add to the zip archive + * * @return bool */ public function pclzipAddFromString($localname, $contents) { - /** @var \PclZip $zip Type hint */ + /** @var PclZip $zip Type hint */ $zip = $this->zip; $filenameParts = pathinfo($localname); @@ -293,20 +306,22 @@ class ZipArchive } /** - * Extract the archive contents (emulate \ZipArchive) + * Extract the archive contents (emulate \ZipArchive). * * @param string $destination - * @param string|array $entries + * @param array|string $entries + * * @return bool + * * @since 0.10.0 */ public function pclzipExtractTo($destination, $entries = null) { - /** @var \PclZip $zip Type hint */ + /** @var PclZip $zip Type hint */ $zip = $this->zip; // Extract all files - if (is_null($entries)) { + if (null === $entries) { $result = $zip->extract(PCLZIP_OPT_PATH, $destination); return $result > 0; @@ -314,7 +329,7 @@ class ZipArchive // Extract by entries if (!is_array($entries)) { - $entries = array($entries); + $entries = [$entries]; } foreach ($entries as $entry) { $entryIndex = $this->locateName($entry); @@ -328,14 +343,15 @@ class ZipArchive } /** - * Extract file from archive by given file name (emulate \ZipArchive) + * Extract file from archive by given file name (emulate \ZipArchive). * * @param string $filename Filename for the file in zip archive + * * @return string $contents File string contents */ public function pclzipGetFromName($filename) { - /** @var \PclZip $zip Type hint */ + /** @var PclZip $zip Type hint */ $zip = $this->zip; $listIndex = $this->pclzipLocateName($filename); $contents = false; @@ -355,15 +371,17 @@ class ZipArchive } /** - * Returns the name of an entry using its index (emulate \ZipArchive) + * Returns the name of an entry using its index (emulate \ZipArchive). * * @param int $index - * @return string|bool + * + * @return bool|string + * * @since 0.10.0 */ public function pclzipGetNameIndex($index) { - /** @var \PclZip $zip Type hint */ + /** @var PclZip $zip Type hint */ $zip = $this->zip; $list = $zip->listContent(); if (isset($list[$index])) { @@ -374,14 +392,15 @@ class ZipArchive } /** - * Returns the index of the entry in the archive (emulate \ZipArchive) + * Returns the index of the entry in the archive (emulate \ZipArchive). * * @param string $filename Filename for the file in zip archive + * * @return int */ public function pclzipLocateName($filename) { - /** @var \PclZip $zip Type hint */ + /** @var PclZip $zip Type hint */ $zip = $this->zip; $list = $zip->listContent(); $listCount = count($list); @@ -390,6 +409,7 @@ class ZipArchive if (strtolower($list[$i]['filename']) == strtolower($filename) || strtolower($list[$i]['stored_filename']) == strtolower($filename)) { $listIndex = $i; + break; } } diff --git a/PhpOffice/PhpWord/SimpleType/Border.php b/PhpOffice/PhpWord/SimpleType/Border.php new file mode 100644 index 0000000..6cb42f0 --- /dev/null +++ b/PhpOffice/PhpWord/SimpleType/Border.php @@ -0,0 +1,57 @@ +aliases[$key])) { $key = $this->aliases[$key]; } + + if ($key === 'align') { + $key = 'alignment'; + } + $method = 'set' . Text::removeUnderscorePrefix($key); if (method_exists($this, $method)) { $this->$method($value); @@ -171,12 +184,13 @@ abstract class AbstractStyle } /** - * Set style by using associative array + * Set style by using associative array. * * @param array $values + * * @return self */ - public function setStyleByArray($values = array()) + public function setStyleByArray($values = []) { foreach ($values as $key => $value) { $this->setStyleValue($key, $value); @@ -186,10 +200,11 @@ abstract class AbstractStyle } /** - * Set default for null and empty value + * Set default for null and empty value. * * @param string $value (was: mixed) * @param string $default (was: mixed) + * * @return string (was: mixed) */ protected function setNonEmptyVal($value, $default) @@ -202,10 +217,11 @@ abstract class AbstractStyle } /** - * Set bool value + * Set bool value. * * @param bool $value * @param bool $default + * * @return bool */ protected function setBoolVal($value, $default) @@ -218,11 +234,12 @@ abstract class AbstractStyle } /** - * Set numeric value + * Set numeric value. * * @param mixed $value - * @param int|float|null $default - * @return int|float|null + * @param null|float|int $default + * + * @return null|float|int */ protected function setNumericVal($value, $default = null) { @@ -234,11 +251,12 @@ abstract class AbstractStyle } /** - * Set integer value: Convert string that contains only numeric into integer + * Set integer value: Convert string that contains only numeric into integer. * - * @param int|null $value - * @param int|null $default - * @return int|null + * @param null|int $value + * @param null|int $default + * + * @return null|int */ protected function setIntVal($value, $default = null) { @@ -255,11 +273,12 @@ abstract class AbstractStyle } /** - * Set float value: Convert string that contains only numeric into float + * Set float value: Convert string that contains only numeric into float. * * @param mixed $value - * @param float|null $default - * @return float|null + * @param null|float $default + * + * @return null|float */ protected function setFloatVal($value, $default = null) { @@ -274,19 +293,18 @@ abstract class AbstractStyle } /** - * Set enum value + * Set enum value. * * @param mixed $value * @param array $enum * @param mixed $default * - * @throws \InvalidArgumentException * @return mixed */ - protected function setEnumVal($value = null, $enum = array(), $default = null) + protected function setEnumVal($value = null, $enum = [], $default = null) { if ($value != null && trim($value) != '' && !empty($enum) && !in_array($value, $enum)) { - throw new \InvalidArgumentException("Invalid style value: {$value} Options:" . implode(',', $enum)); + throw new InvalidArgumentException("Invalid style value: {$value} Options:" . implode(',', $enum)); } elseif ($value === null || trim($value) == '') { $value = $default; } @@ -295,16 +313,17 @@ abstract class AbstractStyle } /** - * Set object value + * Set object value. * * @param mixed $value * @param string $styleName * @param mixed &$style + * * @return mixed */ protected function setObjectVal($value, $styleName, &$style) { - $styleClass = substr(get_class($this), 0, strrpos(get_class($this), '\\')) . '\\' . $styleName; + $styleClass = substr(static::class, 0, strrpos(static::class, '\\')) . '\\' . $styleName; if (is_array($value)) { /** @var \PhpOffice\PhpWord\Style\AbstractStyle $style Type hint */ if (!$style instanceof $styleClass) { @@ -319,11 +338,12 @@ abstract class AbstractStyle } /** - * Set $property value and set $pairProperty = false when $value = true + * Set $property value and set $pairProperty = false when $value = true. * * @param bool &$property * @param bool &$pairProperty * @param bool $value + * * @return self */ protected function setPairedVal(&$property, &$pairProperty, $value) @@ -335,20 +355,4 @@ abstract class AbstractStyle return $this; } - - /** - * Set style using associative array - * - * @deprecated 0.11.0 - * - * @param array $style - * - * @return self - * - * @codeCoverageIgnore - */ - public function setArrayStyle(array $style = array()) - { - return $this->setStyleByArray($style); - } } diff --git a/PhpOffice/PhpWord/Style/Border.php b/PhpOffice/PhpWord/Style/Border.php old mode 100755 new mode 100644 index d032d07..2ee69be --- a/PhpOffice/PhpWord/Style/Border.php +++ b/PhpOffice/PhpWord/Style/Border.php @@ -11,120 +11,121 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Style; /** - * Border style + * Border style. */ class Border extends AbstractStyle { /** - * Border Top Size + * Border Top Size. * - * @var int|float + * @var float|int */ protected $borderTopSize; /** - * Border Top Color + * Border Top Color. * * @var string */ protected $borderTopColor; /** - * Border Top Style + * Border Top Style. * * @var string */ protected $borderTopStyle; /** - * Border Left Size + * Border Left Size. * - * @var int|float + * @var float|int */ protected $borderLeftSize; /** - * Border Left Color + * Border Left Color. * * @var string */ protected $borderLeftColor; /** - * Border Left Style + * Border Left Style. * * @var string */ protected $borderLeftStyle; /** - * Border Right Size + * Border Right Size. * - * @var int|float + * @var float|int */ protected $borderRightSize; /** - * Border Right Color + * Border Right Color. * * @var string */ protected $borderRightColor; /** - * Border Right Style + * Border Right Style. * * @var string */ protected $borderRightStyle; /** - * Border Bottom Size + * Border Bottom Size. * - * @var int|float + * @var float|int */ protected $borderBottomSize; /** - * Border Bottom Color + * Border Bottom Color. * * @var string */ protected $borderBottomColor; /** - * Border Bottom Style + * Border Bottom Style. * * @var string */ protected $borderBottomStyle; /** - * Get border size + * Get border size. * * @return int[] */ public function getBorderSize() { - return array( + return [ $this->getBorderTopSize(), $this->getBorderLeftSize(), $this->getBorderRightSize(), $this->getBorderBottomSize(), - ); + ]; } /** - * Set border size + * Set border size. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setBorderSize($value = null) @@ -138,24 +139,25 @@ class Border extends AbstractStyle } /** - * Get border color + * Get border color. * * @return string[] */ public function getBorderColor() { - return array( + return [ $this->getBorderTopColor(), $this->getBorderLeftColor(), $this->getBorderRightColor(), $this->getBorderBottomColor(), - ); + ]; } /** - * Set border color + * Set border color. * * @param string $value + * * @return self */ public function setBorderColor($value = null) @@ -169,24 +171,25 @@ class Border extends AbstractStyle } /** - * Get border style + * Get border style. * * @return string[] */ public function getBorderStyle() { - return array( + return [ $this->getBorderTopStyle(), $this->getBorderLeftStyle(), $this->getBorderRightStyle(), $this->getBorderBottomStyle(), - ); + ]; } /** - * Set border style + * Set border style. * * @param string $value + * * @return self */ public function setBorderStyle($value = null) @@ -200,9 +203,9 @@ class Border extends AbstractStyle } /** - * Get border top size + * Get border top size. * - * @return int|float + * @return float|int */ public function getBorderTopSize() { @@ -210,9 +213,10 @@ class Border extends AbstractStyle } /** - * Set border top size + * Set border top size. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setBorderTopSize($value = null) @@ -223,7 +227,7 @@ class Border extends AbstractStyle } /** - * Get border top color + * Get border top color. * * @return string */ @@ -233,9 +237,10 @@ class Border extends AbstractStyle } /** - * Set border top color + * Set border top color. * * @param string $value + * * @return self */ public function setBorderTopColor($value = null) @@ -246,7 +251,7 @@ class Border extends AbstractStyle } /** - * Get border top style + * Get border top style. * * @return string */ @@ -256,9 +261,10 @@ class Border extends AbstractStyle } /** - * Set border top Style + * Set border top Style. * * @param string $value + * * @return self */ public function setBorderTopStyle($value = null) @@ -269,9 +275,9 @@ class Border extends AbstractStyle } /** - * Get border left size + * Get border left size. * - * @return int|float + * @return float|int */ public function getBorderLeftSize() { @@ -279,9 +285,10 @@ class Border extends AbstractStyle } /** - * Set border left size + * Set border left size. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setBorderLeftSize($value = null) @@ -292,7 +299,7 @@ class Border extends AbstractStyle } /** - * Get border left color + * Get border left color. * * @return string */ @@ -302,9 +309,10 @@ class Border extends AbstractStyle } /** - * Set border left color + * Set border left color. * * @param string $value + * * @return self */ public function setBorderLeftColor($value = null) @@ -315,7 +323,7 @@ class Border extends AbstractStyle } /** - * Get border left style + * Get border left style. * * @return string */ @@ -325,9 +333,10 @@ class Border extends AbstractStyle } /** - * Set border left style + * Set border left style. * * @param string $value + * * @return self */ public function setBorderLeftStyle($value = null) @@ -338,9 +347,9 @@ class Border extends AbstractStyle } /** - * Get border right size + * Get border right size. * - * @return int|float + * @return float|int */ public function getBorderRightSize() { @@ -348,9 +357,10 @@ class Border extends AbstractStyle } /** - * Set border right size + * Set border right size. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setBorderRightSize($value = null) @@ -361,7 +371,7 @@ class Border extends AbstractStyle } /** - * Get border right color + * Get border right color. * * @return string */ @@ -371,9 +381,10 @@ class Border extends AbstractStyle } /** - * Set border right color + * Set border right color. * * @param string $value + * * @return self */ public function setBorderRightColor($value = null) @@ -384,7 +395,7 @@ class Border extends AbstractStyle } /** - * Get border right style + * Get border right style. * * @return string */ @@ -394,9 +405,10 @@ class Border extends AbstractStyle } /** - * Set border right style + * Set border right style. * * @param string $value + * * @return self */ public function setBorderRightStyle($value = null) @@ -407,9 +419,9 @@ class Border extends AbstractStyle } /** - * Get border bottom size + * Get border bottom size. * - * @return int|float + * @return float|int */ public function getBorderBottomSize() { @@ -417,9 +429,10 @@ class Border extends AbstractStyle } /** - * Set border bottom size + * Set border bottom size. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setBorderBottomSize($value = null) @@ -430,7 +443,7 @@ class Border extends AbstractStyle } /** - * Get border bottom color + * Get border bottom color. * * @return string */ @@ -440,9 +453,10 @@ class Border extends AbstractStyle } /** - * Set border bottom color + * Set border bottom color. * * @param string $value + * * @return self */ public function setBorderBottomColor($value = null) @@ -453,7 +467,7 @@ class Border extends AbstractStyle } /** - * Get border bottom style + * Get border bottom style. * * @return string */ @@ -463,9 +477,10 @@ class Border extends AbstractStyle } /** - * Set border bottom style + * Set border bottom style. * * @param string $value + * * @return self */ public function setBorderBottomStyle($value = null) @@ -476,7 +491,7 @@ class Border extends AbstractStyle } /** - * Check if any of the border is not null + * Check if any of the border is not null. * * @return bool */ diff --git a/PhpOffice/PhpWord/Style/Cell.php b/PhpOffice/PhpWord/Style/Cell.php old mode 100755 new mode 100644 index 1276b5b..b2bd9ae --- a/PhpOffice/PhpWord/Style/Cell.php +++ b/PhpOffice/PhpWord/Style/Cell.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -21,58 +21,38 @@ use PhpOffice\PhpWord\SimpleType\TblWidth; use PhpOffice\PhpWord\SimpleType\VerticalJc; /** - * Table cell style + * Table cell style. */ class Cell extends Border { - /** - * Vertical alignment constants - * - * @const string - * @deprecated Use \PhpOffice\PhpWord\SimpleType\VerticalJc::TOP instead - */ - const VALIGN_TOP = 'top'; - /** - * @deprecated Use \PhpOffice\PhpWord\SimpleType\VerticalJc::CENTER instead - */ - const VALIGN_CENTER = 'center'; - /** - * @deprecated Use \PhpOffice\PhpWord\SimpleType\VerticalJc::BOTTOM instead - */ - const VALIGN_BOTTOM = 'bottom'; - /** - * @deprecated Use \PhpOffice\PhpWord\SimpleType\VerticalJc::BOTH instead - */ - const VALIGN_BOTH = 'both'; - //Text direction constants /** - * Left to Right, Top to Bottom + * Left to Right, Top to Bottom. */ const TEXT_DIR_LRTB = 'lrTb'; /** - * Top to Bottom, Right to Left + * Top to Bottom, Right to Left. */ const TEXT_DIR_TBRL = 'tbRl'; /** - * Bottom to Top, Left to Right + * Bottom to Top, Left to Right. */ const TEXT_DIR_BTLR = 'btLr'; /** - * Left to Right, Top to Bottom Rotated + * Left to Right, Top to Bottom Rotated. */ const TEXT_DIR_LRTBV = 'lrTbV'; /** - * Top to Bottom, Right to Left Rotated + * Top to Bottom, Right to Left Rotated. */ const TEXT_DIR_TBRLV = 'tbRlV'; /** - * Top to Bottom, Left to Right Rotated + * Top to Bottom, Left to Right Rotated. */ const TEXT_DIR_TBLRV = 'tbLrV'; /** - * Vertical merge (rowspan) constants + * Vertical merge (rowspan) constants. * * @const string */ @@ -80,35 +60,35 @@ class Cell extends Border const VMERGE_CONTINUE = 'continue'; /** - * Default border color + * Default border color. * * @const string */ const DEFAULT_BORDER_COLOR = '000000'; /** - * Vertical align (top, center, both, bottom) + * Vertical align (top, center, both, bottom). * * @var string */ private $vAlign; /** - * Text Direction + * Text Direction. * * @var string */ private $textDirection; /** - * colspan + * colspan. * * @var int */ private $gridSpan; /** - * rowspan (restart, continue) + * rowspan (restart, continue). * * - restart: Start/restart merged region * - continue: Continue merged region @@ -118,21 +98,21 @@ class Cell extends Border private $vMerge; /** - * Shading + * Shading. * * @var \PhpOffice\PhpWord\Style\Shading */ private $shading; /** - * Width + * Width. * * @var int */ private $width; /** - * Width unit + * Width unit. * * @var string */ @@ -149,9 +129,10 @@ class Cell extends Border } /** - * Set vertical align + * Set vertical align. * * @param string $value + * * @return self */ public function setVAlign($value = null) @@ -173,21 +154,22 @@ class Cell extends Border } /** - * Set text direction + * Set text direction. * * @param string $value + * * @return self */ public function setTextDirection($value = null) { - $enum = array(self::TEXT_DIR_BTLR, self::TEXT_DIR_TBRL); + $enum = [self::TEXT_DIR_BTLR, self::TEXT_DIR_TBRL]; $this->textDirection = $this->setEnumVal($value, $enum, $this->textDirection); return $this; } /** - * Get background + * Get background. * * @return string */ @@ -201,14 +183,15 @@ class Cell extends Border } /** - * Set background + * Set background. * * @param string $value + * * @return self */ public function setBgColor($value = null) { - return $this->setShading(array('fill' => $value)); + return $this->setShading(['fill' => $value]); } /** @@ -222,9 +205,10 @@ class Cell extends Border } /** - * Set grid span (colspan) + * Set grid span (colspan). * * @param int $value + * * @return self */ public function setGridSpan($value = null) @@ -245,21 +229,22 @@ class Cell extends Border } /** - * Set vertical merge (rowspan) + * Set vertical merge (rowspan). * * @param string $value + * * @return self */ public function setVMerge($value = null) { - $enum = array(self::VMERGE_RESTART, self::VMERGE_CONTINUE); + $enum = [self::VMERGE_RESTART, self::VMERGE_CONTINUE]; $this->vMerge = $this->setEnumVal($value, $enum, $this->vMerge); return $this; } /** - * Get shading + * Get shading. * * @return \PhpOffice\PhpWord\Style\Shading */ @@ -269,9 +254,10 @@ class Cell extends Border } /** - * Set shading + * Set shading. * * @param mixed $value + * * @return self */ public function setShading($value = null) @@ -282,7 +268,7 @@ class Cell extends Border } /** - * Get cell width + * Get cell width. * * @return int */ @@ -292,20 +278,21 @@ class Cell extends Border } /** - * Set cell width + * Set cell width. * * @param int $value + * * @return self */ public function setWidth($value) { - $this->setIntVal($value); + $this->width = $this->setIntVal($value); return $this; } /** - * Get width unit + * Get width unit. * * @return string */ @@ -315,26 +302,14 @@ class Cell extends Border } /** - * Set width unit + * Set width unit. * * @param string $value */ public function setUnit($value) { - $this->unit = $this->setEnumVal($value, array(TblWidth::AUTO, TblWidth::PERCENT, TblWidth::TWIP), TblWidth::TWIP); + $this->unit = $this->setEnumVal($value, [TblWidth::AUTO, TblWidth::PERCENT, TblWidth::TWIP], TblWidth::TWIP); return $this; } - - /** - * Get default border color - * - * @deprecated 0.10.0 - * - * @codeCoverageIgnore - */ - public function getDefaultBorderColor() - { - return self::DEFAULT_BORDER_COLOR; - } } diff --git a/PhpOffice/PhpWord/Style/Chart.php b/PhpOffice/PhpWord/Style/Chart.php old mode 100755 new mode 100644 index 06b4829..9dbc8db --- a/PhpOffice/PhpWord/Style/Chart.php +++ b/PhpOffice/PhpWord/Style/Chart.php @@ -11,81 +11,89 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Style; /** - * Chart style + * Chart style. * * @since 0.12.0 */ class Chart extends AbstractStyle { /** - * Width (in EMU) + * Width (in EMU). * * @var int */ private $width = 1000000; /** - * Height (in EMU) + * Height (in EMU). * * @var int */ private $height = 1000000; /** - * Is 3D; applies to pie, bar, line, area + * Is 3D; applies to pie, bar, line, area. * * @var bool */ private $is3d = false; /** - * A list of colors to use in the chart + * A list of colors to use in the chart. * * @var array */ - private $colors = array(); + private $colors = []; /** - * Chart title + * Chart title. * * @var string */ - private $title = null; + private $title; /** - * Chart legend visibility + * Chart legend visibility. * * @var bool */ private $showLegend = false; /** - * A list of display options for data labels + * Chart legend Position. + * Possible values are 'r', 't', 'b', 'l', 'tr'. + * + * @var string + */ + private $legendPosition = 'r'; + + /** + * A list of display options for data labels. * * @var array */ - private $dataLabelOptions = array( - 'showVal' => true, // value - 'showCatName' => true, // category name - 'showLegendKey' => false, //show the cart legend - 'showSerName' => false, // series name - 'showPercent' => false, - 'showLeaderLines' => false, - 'showBubbleSize' => false, - ); + private $dataLabelOptions = [ + 'showVal' => true, // value + 'showCatName' => true, // category name + 'showLegendKey' => false, //show the cart legend + 'showSerName' => false, // series name + 'showPercent' => false, + 'showLeaderLines' => false, + 'showBubbleSize' => false, + ]; /** * A string that tells the writer where to write chart labels or to skip * "nextTo" - sets labels next to the axis (bar graphs on the left) (default) * "low" - labels on the left side of the graph - * "high" - labels on the right side of the graph + * "high" - labels on the right side of the graph. * * @var string */ @@ -95,7 +103,7 @@ class Chart extends AbstractStyle * A string that tells the writer where to write chart labels or to skip * "nextTo" - sets labels next to the axis (bar graphs on the bottom) (default) * "low" - labels are below the graph - * "high" - labels above the graph + * "high" - labels above the graph. * * @var string */ @@ -113,45 +121,45 @@ class Chart extends AbstractStyle /** * The position for major tick marks - * Possible values are 'in', 'out', 'cross', 'none' + * Possible values are 'in', 'out', 'cross', 'none'. * * @var string */ private $majorTickMarkPos = 'none'; /** - * Show labels for axis + * Show labels for axis. * * @var bool */ private $showAxisLabels = false; /** - * Show Gridlines for Y-Axis + * Show Gridlines for Y-Axis. * * @var bool */ private $gridY = false; /** - * Show Gridlines for X-Axis + * Show Gridlines for X-Axis. * * @var bool */ private $gridX = false; /** - * Create a new instance + * Create a new instance. * * @param array $style */ - public function __construct($style = array()) + public function __construct($style = []) { $this->setStyleByArray($style); } /** - * Get width + * Get width. * * @return int */ @@ -161,9 +169,10 @@ class Chart extends AbstractStyle } /** - * Set width + * Set width. * * @param int $value + * * @return self */ public function setWidth($value = null) @@ -174,7 +183,7 @@ class Chart extends AbstractStyle } /** - * Get height + * Get height. * * @return int */ @@ -184,9 +193,10 @@ class Chart extends AbstractStyle } /** - * Set height + * Set height. * * @param int $value + * * @return self */ public function setHeight($value = null) @@ -197,7 +207,7 @@ class Chart extends AbstractStyle } /** - * Is 3D + * Is 3D. * * @return bool */ @@ -207,9 +217,10 @@ class Chart extends AbstractStyle } /** - * Set 3D + * Set 3D. * * @param bool $value + * * @return self */ public function set3d($value = true) @@ -233,8 +244,10 @@ class Chart extends AbstractStyle * Set the colors to use in a chart. * * @param array $value a list of colors to use in the chart + * + * @return self */ - public function setColors($value = array()) + public function setColors($value = []) { $this->colors = $value; @@ -242,7 +255,7 @@ class Chart extends AbstractStyle } /** - * Get the chart title + * Get the chart title. * * @return string */ @@ -252,9 +265,11 @@ class Chart extends AbstractStyle } /** - * Set the chart title + * Set the chart title. * * @param string $value + * + * @return self */ public function setTitle($value = null) { @@ -264,7 +279,7 @@ class Chart extends AbstractStyle } /** - * Get chart legend visibility + * Get chart legend visibility. * * @return bool */ @@ -274,9 +289,11 @@ class Chart extends AbstractStyle } /** - * Set chart legend visibility + * Set chart legend visibility. * * @param bool $value + * + * @return self */ public function setShowLegend($value = false) { @@ -285,6 +302,38 @@ class Chart extends AbstractStyle return $this; } + /** + * Get chart legend position. + * + * @return string + */ + public function getLegendPosition() + { + return $this->legendPosition; + } + + /** + * Set chart legend position. choices: + * "r" - right of chart + * "b" - bottom of chart + * "t" - top of chart + * "l" - left of chart + * "tr" - top right of chart. + * + * default: right + * + * @param string $legendPosition + * + * @return self + */ + public function setLegendPosition($legendPosition = 'r') + { + $enum = ['r', 'b', 't', 'l', 'tr']; + $this->legendPosition = $this->setEnumVal($legendPosition, $enum, $this->legendPosition); + + return $this; + } + /* * Show labels for axis * @@ -296,9 +345,10 @@ class Chart extends AbstractStyle } /** - * Set show Gridlines for Y-Axis + * Set show Gridlines for Y-Axis. * * @param bool $value + * * @return self */ public function setShowAxisLabels($value = true) @@ -309,7 +359,7 @@ class Chart extends AbstractStyle } /** - * get the list of options for data labels + * get the list of options for data labels. * * @return array */ @@ -324,11 +374,14 @@ class Chart extends AbstractStyle * * @param array $values [description] */ - public function setDataLabelOptions($values = array()) + public function setDataLabelOptions($values = []): void { foreach (array_keys($this->dataLabelOptions) as $option) { if (isset($values[$option])) { - $this->dataLabelOptions[$option] = $this->setBoolVal($values[$option], $this->dataLabelOptions[$option]); + $this->dataLabelOptions[$option] = $this->setBoolVal( + $values[$option], + $this->dataLabelOptions[$option] + ); } } } @@ -344,9 +397,10 @@ class Chart extends AbstractStyle } /** - * Set show Gridlines for Y-Axis + * Set show Gridlines for Y-Axis. * * @param bool $value + * * @return self */ public function setShowGridY($value = true) @@ -357,7 +411,7 @@ class Chart extends AbstractStyle } /** - * Get the categoryLabelPosition setting + * Get the categoryLabelPosition setting. * * @return string */ @@ -371,21 +425,22 @@ class Chart extends AbstractStyle * "none" - skips writing labels * "nextTo" - sets labels next to the (bar graphs on the left) * "low" - labels on the left side of the graph - * "high" - labels on the right side of the graph + * "high" - labels on the right side of the graph. * * @param mixed $labelPosition + * * @return self */ public function setCategoryLabelPosition($labelPosition) { - $enum = array('nextTo', 'low', 'high'); + $enum = ['nextTo', 'low', 'high']; $this->categoryLabelPosition = $this->setEnumVal($labelPosition, $enum, $this->categoryLabelPosition); return $this; } /** - * Get the valueAxisLabelPosition setting + * Get the valueAxisLabelPosition setting. * * @return string */ @@ -399,21 +454,22 @@ class Chart extends AbstractStyle * "none" - skips writing labels * "nextTo" - sets labels next to the value * "low" - sets labels are below the graph - * "high" - sets labels above the graph + * "high" - sets labels above the graph. * * @param string * @param mixed $labelPosition */ public function setValueLabelPosition($labelPosition) { - $enum = array('nextTo', 'low', 'high'); + $enum = ['nextTo', 'low', 'high']; $this->valueLabelPosition = $this->setEnumVal($labelPosition, $enum, $this->valueLabelPosition); return $this; } /** - * Get the categoryAxisTitle + * Get the categoryAxisTitle. + * * @return string */ public function getCategoryAxisTitle() @@ -422,7 +478,8 @@ class Chart extends AbstractStyle } /** - * Set the title that appears on the category side of the chart + * Set the title that appears on the category side of the chart. + * * @param string $axisTitle */ public function setCategoryAxisTitle($axisTitle) @@ -433,7 +490,8 @@ class Chart extends AbstractStyle } /** - * Get the valueAxisTitle + * Get the valueAxisTitle. + * * @return string */ public function getValueAxisTitle() @@ -442,7 +500,8 @@ class Chart extends AbstractStyle } /** - * Set the title that appears on the value side of the chart + * Set the title that appears on the value side of the chart. + * * @param string $axisTitle */ public function setValueAxisTitle($axisTitle) @@ -458,17 +517,18 @@ class Chart extends AbstractStyle } /** - * Set the position for major tick marks + * Set the position for major tick marks. + * * @param string $position */ - public function setMajorTickPosition($position) + public function setMajorTickPosition($position): void { - $enum = array('in', 'out', 'cross', 'none'); + $enum = ['in', 'out', 'cross', 'none']; $this->majorTickMarkPos = $this->setEnumVal($position, $enum, $this->majorTickMarkPos); } /** - * Show Gridlines for X-Axis + * Show Gridlines for X-Axis. * * @return bool */ @@ -478,9 +538,10 @@ class Chart extends AbstractStyle } /** - * Set show Gridlines for X-Axis + * Set show Gridlines for X-Axis. * * @param bool $value + * * @return self */ public function setShowGridX($value = true) diff --git a/PhpOffice/PhpWord/Style/Extrusion.php b/PhpOffice/PhpWord/Style/Extrusion.php old mode 100755 new mode 100644 index 4c860bc..dd00231 --- a/PhpOffice/PhpWord/Style/Extrusion.php +++ b/PhpOffice/PhpWord/Style/Extrusion.php @@ -11,14 +11,14 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Style; /** - * 3D extrusion style + * 3D extrusion style. * * @see http://www.schemacentral.com/sc/ooxml/t-o_CT_Extrusion.html * @since 0.12.0 @@ -26,7 +26,7 @@ namespace PhpOffice\PhpWord\Style; class Extrusion extends AbstractStyle { /** - * Type constants + * Type constants. * * @const string */ @@ -34,31 +34,31 @@ class Extrusion extends AbstractStyle const EXTRUSION_PERSPECTIVE = 'perspective'; /** - * Type: parallel|perspective + * Type: parallel|perspective. * * @var string */ private $type; /** - * Color + * Color. * * @var string */ private $color; /** - * Create a new instance + * Create a new instance. * * @param array $style */ - public function __construct($style = array()) + public function __construct($style = []) { $this->setStyleByArray($style); } /** - * Get type + * Get type. * * @return string */ @@ -68,21 +68,22 @@ class Extrusion extends AbstractStyle } /** - * Set pattern + * Set pattern. * * @param string $value + * * @return self */ public function setType($value = null) { - $enum = array(self::EXTRUSION_PARALLEL, self::EXTRUSION_PERSPECTIVE); + $enum = [self::EXTRUSION_PARALLEL, self::EXTRUSION_PERSPECTIVE]; $this->type = $this->setEnumVal($value, $enum, null); return $this; } /** - * Get color + * Get color. * * @return string */ @@ -92,9 +93,10 @@ class Extrusion extends AbstractStyle } /** - * Set color + * Set color. * * @param string $value + * * @return self */ public function setColor($value = null) diff --git a/PhpOffice/PhpWord/Style/Fill.php b/PhpOffice/PhpWord/Style/Fill.php old mode 100755 new mode 100644 index 360bcf3..af0149d --- a/PhpOffice/PhpWord/Style/Fill.php +++ b/PhpOffice/PhpWord/Style/Fill.php @@ -11,14 +11,14 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Style; /** - * Fill style + * Fill style. * * There are still lot of interesting things for this style that can be added, including gradient. See @see . * @@ -28,24 +28,24 @@ namespace PhpOffice\PhpWord\Style; class Fill extends AbstractStyle { /** - * Color + * Color. * * @var string */ private $color; /** - * Create a new instance + * Create a new instance. * * @param array $style */ - public function __construct($style = array()) + public function __construct($style = []) { $this->setStyleByArray($style); } /** - * Get color + * Get color. * * @return string */ @@ -55,9 +55,10 @@ class Fill extends AbstractStyle } /** - * Set color + * Set color. * * @param string $value + * * @return self */ public function setColor($value = null) diff --git a/PhpOffice/PhpWord/Style/Font.php b/PhpOffice/PhpWord/Style/Font.php old mode 100755 new mode 100644 index 09e6f1a..d0cc250 --- a/PhpOffice/PhpWord/Style/Font.php +++ b/PhpOffice/PhpWord/Style/Font.php @@ -11,19 +11,19 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Style; /** - * Font style + * Font style. */ class Font extends AbstractStyle { /** - * Underline types + * Underline types. * * @const string */ @@ -33,14 +33,6 @@ class Font extends AbstractStyle const UNDERLINE_DASHLONG = 'dashLong'; const UNDERLINE_DASHLONGHEAVY = 'dashLongHeavy'; const UNDERLINE_DOUBLE = 'dbl'; - /** - * @deprecated use UNDERLINE_DOTHASH instead, TODO remove in version 1.0 - */ - const UNDERLINE_DOTHASH = 'dotDash'; // Incorrect spelling, for backwards compatibility - /** - * @deprecated use UNDERLINE_DOTDASHHEAVY instead, TODO remove in version 1.0 - */ - const UNDERLINE_DOTHASHHEAVY = 'dotDashHeavy'; // Incorrect spelling, for backwards compatibility const UNDERLINE_DOTDASH = 'dotDash'; const UNDERLINE_DOTDASHHEAVY = 'dotDashHeavy'; const UNDERLINE_DOTDOTDASH = 'dotDotDash'; @@ -55,7 +47,7 @@ class Font extends AbstractStyle const UNDERLINE_WORDS = 'words'; /** - * Foreground colors + * Foreground colors. * * @const string */ @@ -76,169 +68,174 @@ class Font extends AbstractStyle const FGCOLOR_BLACK = 'black'; /** - * Aliases + * Aliases. * * @var array */ - protected $aliases = array('line-height' => 'lineHeight', 'letter-spacing' => 'spacing'); + protected $aliases = ['line-height' => 'lineHeight', 'letter-spacing' => 'spacing']; /** - * Font style type + * Font style type. * * @var string */ private $type; /** - * Font name + * Font name. * * @var string */ private $name; /** - * Font Content Type + * Font Content Type. * * @var string */ private $hint; /** - * Font size + * Font size. * - * @var int|float + * @var float|int */ private $size; /** - * Font color + * Font color. * * @var string */ private $color; /** - * Bold + * Bold. * * @var bool */ private $bold; /** - * Italic + * Italic. * * @var bool */ private $italic; /** - * Undeline + * Undeline. * * @var string */ private $underline = self::UNDERLINE_NONE; /** - * Superscript + * Superscript. * * @var bool */ private $superScript = false; /** - * Subscript + * Subscript. * * @var bool */ private $subScript = false; /** - * Strikethrough + * Strikethrough. * * @var bool */ private $strikethrough; /** - * Double strikethrough + * Double strikethrough. * * @var bool */ private $doubleStrikethrough; /** - * Small caps + * Small caps. * * @var bool + * * @see http://www.schemacentral.com/sc/ooxml/e-w_smallCaps-1.html */ private $smallCaps; /** - * All caps + * All caps. * * @var bool + * * @see http://www.schemacentral.com/sc/ooxml/e-w_caps-1.html */ private $allCaps; /** - * Foreground/highlight + * Foreground/highlight. * * @var string */ private $fgColor; /** - * Expanded/compressed text: 0-600 (percent) + * Expanded/compressed text: 0-600 (percent). * * @var int + * * @since 0.12.0 * @see http://www.schemacentral.com/sc/ooxml/e-w_w-1.html */ private $scale; /** - * Character spacing adjustment: twip + * Character spacing adjustment: twip. + * + * @var float|int * - * @var int|float * @since 0.12.0 * @see http://www.schemacentral.com/sc/ooxml/e-w_spacing-2.html */ private $spacing; /** - * Font kerning: halfpoint + * Font kerning: halfpoint. + * + * @var float|int * - * @var int|float * @since 0.12.0 * @see http://www.schemacentral.com/sc/ooxml/e-w_kern-1.html */ private $kerning; /** - * Paragraph style + * Paragraph style. * * @var \PhpOffice\PhpWord\Style\Paragraph */ private $paragraph; /** - * Shading + * Shading. * * @var \PhpOffice\PhpWord\Style\Shading */ private $shading; /** - * Right to left languages + * Right to left languages. * * @var bool */ private $rtl; /** - * noProof (disables AutoCorrect) + * noProof (disables AutoCorrect). * * @var bool * http://www.datypic.com/sc/ooxml/e-w_noProof-1.html @@ -246,33 +243,35 @@ class Font extends AbstractStyle private $noProof; /** - * Languages + * Languages. * * @var \PhpOffice\PhpWord\Style\Language */ private $lang; /** - * Hidden text + * Hidden text. * * @var bool + * * @see http://www.datypic.com/sc/ooxml/e-w_vanish-1.html */ private $hidden; /** - * Vertically Raised or Lowered Text + * Vertically Raised or Lowered Text. * * @var int Signed Half-Point Measurement + * * @see http://www.datypic.com/sc/ooxml/e-w_position-1.html */ private $position; /** - * Create new font style + * Create new font style. * * @param string $type Type of font - * @param array|string|\PhpOffice\PhpWord\Style\AbstractStyle $paragraph Paragraph styles definition + * @param array|\PhpOffice\PhpWord\Style\AbstractStyle|string $paragraph Paragraph styles definition */ public function __construct($type = 'text', $paragraph = null) { @@ -281,51 +280,52 @@ class Font extends AbstractStyle } /** - * Get style values + * Get style values. * * @return array + * * @since 0.12.0 */ public function getStyleValues() { - $styles = array( - 'name' => $this->getStyleName(), - 'basic' => array( - 'name' => $this->getName(), - 'size' => $this->getSize(), - 'color' => $this->getColor(), - 'hint' => $this->getHint(), - ), - 'style' => array( - 'bold' => $this->isBold(), - 'italic' => $this->isItalic(), + $styles = [ + 'name' => $this->getStyleName(), + 'basic' => [ + 'name' => $this->getName(), + 'size' => $this->getSize(), + 'color' => $this->getColor(), + 'hint' => $this->getHint(), + ], + 'style' => [ + 'bold' => $this->isBold(), + 'italic' => $this->isItalic(), 'underline' => $this->getUnderline(), - 'strike' => $this->isStrikethrough(), - 'dStrike' => $this->isDoubleStrikethrough(), - 'super' => $this->isSuperScript(), - 'sub' => $this->isSubScript(), + 'strike' => $this->isStrikethrough(), + 'dStrike' => $this->isDoubleStrikethrough(), + 'super' => $this->isSuperScript(), + 'sub' => $this->isSubScript(), 'smallCaps' => $this->isSmallCaps(), - 'allCaps' => $this->isAllCaps(), - 'fgColor' => $this->getFgColor(), - 'hidden' => $this->isHidden(), - ), - 'spacing' => array( - 'scale' => $this->getScale(), - 'spacing' => $this->getSpacing(), - 'kerning' => $this->getKerning(), - 'position' => $this->getPosition(), - ), - 'paragraph' => $this->getParagraph(), - 'rtl' => $this->isRTL(), - 'shading' => $this->getShading(), - 'lang' => $this->getLang(), - ); + 'allCaps' => $this->isAllCaps(), + 'fgColor' => $this->getFgColor(), + 'hidden' => $this->isHidden(), + ], + 'spacing' => [ + 'scale' => $this->getScale(), + 'spacing' => $this->getSpacing(), + 'kerning' => $this->getKerning(), + 'position' => $this->getPosition(), + ], + 'paragraph' => $this->getParagraph(), + 'rtl' => $this->isRTL(), + 'shading' => $this->getShading(), + 'lang' => $this->getLang(), + ]; return $styles; } /** - * Get style type + * Get style type. * * @return string */ @@ -335,7 +335,7 @@ class Font extends AbstractStyle } /** - * Get font name + * Get font name. * * @return string */ @@ -345,9 +345,10 @@ class Font extends AbstractStyle } /** - * Set font name + * Set font name. * * @param string $value + * * @return self */ public function setName($value = null) @@ -358,7 +359,7 @@ class Font extends AbstractStyle } /** - * Get Font Content Type + * Get Font Content Type. * * @return string */ @@ -368,9 +369,10 @@ class Font extends AbstractStyle } /** - * Set Font Content Type + * Set Font Content Type. * * @param string $value + * * @return self */ public function setHint($value = null) @@ -381,9 +383,9 @@ class Font extends AbstractStyle } /** - * Get font size + * Get font size. * - * @return int|float + * @return float|int */ public function getSize() { @@ -391,9 +393,10 @@ class Font extends AbstractStyle } /** - * Set font size + * Set font size. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setSize($value = null) @@ -404,7 +407,7 @@ class Font extends AbstractStyle } /** - * Get font color + * Get font color. * * @return string */ @@ -414,9 +417,10 @@ class Font extends AbstractStyle } /** - * Set font color + * Set font color. * * @param string $value + * * @return self */ public function setColor($value = null) @@ -427,7 +431,7 @@ class Font extends AbstractStyle } /** - * Get bold + * Get bold. * * @return bool */ @@ -437,9 +441,10 @@ class Font extends AbstractStyle } /** - * Set bold + * Set bold. * * @param bool $value + * * @return self */ public function setBold($value = true) @@ -450,7 +455,7 @@ class Font extends AbstractStyle } /** - * Get italic + * Get italic. * * @return bool */ @@ -460,9 +465,10 @@ class Font extends AbstractStyle } /** - * Set italic + * Set italic. * * @param bool $value + * * @return self */ public function setItalic($value = true) @@ -473,7 +479,7 @@ class Font extends AbstractStyle } /** - * Get underline + * Get underline. * * @return string */ @@ -483,9 +489,10 @@ class Font extends AbstractStyle } /** - * Set underline + * Set underline. * * @param string $value + * * @return self */ public function setUnderline($value = self::UNDERLINE_NONE) @@ -496,7 +503,7 @@ class Font extends AbstractStyle } /** - * Get superscript + * Get superscript. * * @return bool */ @@ -506,9 +513,10 @@ class Font extends AbstractStyle } /** - * Set superscript + * Set superscript. * * @param bool $value + * * @return self */ public function setSuperScript($value = true) @@ -517,7 +525,7 @@ class Font extends AbstractStyle } /** - * Get subscript + * Get subscript. * * @return bool */ @@ -527,9 +535,10 @@ class Font extends AbstractStyle } /** - * Set subscript + * Set subscript. * * @param bool $value + * * @return self */ public function setSubScript($value = true) @@ -538,7 +547,7 @@ class Font extends AbstractStyle } /** - * Get strikethrough + * Get strikethrough. * * @return bool */ @@ -548,9 +557,10 @@ class Font extends AbstractStyle } /** - * Set strikethrough + * Set strikethrough. * * @param bool $value + * * @return self */ public function setStrikethrough($value = true) @@ -559,7 +569,7 @@ class Font extends AbstractStyle } /** - * Get double strikethrough + * Get double strikethrough. * * @return bool */ @@ -569,9 +579,10 @@ class Font extends AbstractStyle } /** - * Set double strikethrough + * Set double strikethrough. * * @param bool $value + * * @return self */ public function setDoubleStrikethrough($value = true) @@ -580,7 +591,7 @@ class Font extends AbstractStyle } /** - * Get small caps + * Get small caps. * * @return bool */ @@ -590,9 +601,10 @@ class Font extends AbstractStyle } /** - * Set small caps + * Set small caps. * * @param bool $value + * * @return self */ public function setSmallCaps($value = true) @@ -601,7 +613,7 @@ class Font extends AbstractStyle } /** - * Get all caps + * Get all caps. * * @return bool */ @@ -611,9 +623,10 @@ class Font extends AbstractStyle } /** - * Set all caps + * Set all caps. * * @param bool $value + * * @return self */ public function setAllCaps($value = true) @@ -622,7 +635,7 @@ class Font extends AbstractStyle } /** - * Get foreground/highlight color + * Get foreground/highlight color. * * @return string */ @@ -632,9 +645,10 @@ class Font extends AbstractStyle } /** - * Set foreground/highlight color + * Set foreground/highlight color. * * @param string $value + * * @return self */ public function setFgColor($value = null) @@ -645,7 +659,7 @@ class Font extends AbstractStyle } /** - * Get background + * Get background. * * @return string */ @@ -655,18 +669,19 @@ class Font extends AbstractStyle } /** - * Set background + * Set background. * * @param string $value + * * @return \PhpOffice\PhpWord\Style\Table */ public function setBgColor($value = null) { - $this->setShading(array('fill' => $value)); + $this->setShading(['fill' => $value]); } /** - * Get scale + * Get scale. * * @return int */ @@ -676,9 +691,10 @@ class Font extends AbstractStyle } /** - * Set scale + * Set scale. * * @param int $value + * * @return self */ public function setScale($value = null) @@ -689,9 +705,9 @@ class Font extends AbstractStyle } /** - * Get font spacing + * Get font spacing. * - * @return int|float + * @return float|int */ public function getSpacing() { @@ -699,9 +715,10 @@ class Font extends AbstractStyle } /** - * Set font spacing + * Set font spacing. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setSpacing($value = null) @@ -712,9 +729,9 @@ class Font extends AbstractStyle } /** - * Get font kerning + * Get font kerning. * - * @return int|float + * @return float|int */ public function getKerning() { @@ -722,9 +739,10 @@ class Font extends AbstractStyle } /** - * Set font kerning + * Set font kerning. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setKerning($value = null) @@ -735,7 +753,7 @@ class Font extends AbstractStyle } /** - * Get noProof (disables autocorrect) + * Get noProof (disables autocorrect). * * @return bool */ @@ -745,9 +763,10 @@ class Font extends AbstractStyle } /** - * Set noProof (disables autocorrect) + * Set noProof (disables autocorrect). * * @param bool $value + * * @return $this */ public function setNoProof($value = false) @@ -758,9 +777,9 @@ class Font extends AbstractStyle } /** - * Get line height + * Get line height. * - * @return int|float + * @return float|int */ public function getLineHeight() { @@ -768,20 +787,21 @@ class Font extends AbstractStyle } /** - * Set lineheight + * Set lineheight. + * + * @param float|int|string $value * - * @param int|float|string $value * @return self */ public function setLineHeight($value) { - $this->setParagraph(array('lineHeight' => $value)); + $this->setParagraph(['lineHeight' => $value]); return $this; } /** - * Get paragraph style + * Get paragraph style. * * @return \PhpOffice\PhpWord\Style\Paragraph */ @@ -791,9 +811,10 @@ class Font extends AbstractStyle } /** - * Set Paragraph + * Set Paragraph. * * @param mixed $value + * * @return self */ public function setParagraph($value = null) @@ -804,7 +825,7 @@ class Font extends AbstractStyle } /** - * Get rtl + * Get rtl. * * @return bool */ @@ -814,9 +835,10 @@ class Font extends AbstractStyle } /** - * Set rtl + * Set rtl. * * @param bool $value + * * @return self */ public function setRTL($value = true) @@ -827,7 +849,7 @@ class Font extends AbstractStyle } /** - * Get shading + * Get shading. * * @return \PhpOffice\PhpWord\Style\Shading */ @@ -837,9 +859,10 @@ class Font extends AbstractStyle } /** - * Set shading + * Set shading. * * @param mixed $value + * * @return self */ public function setShading($value = null) @@ -850,7 +873,7 @@ class Font extends AbstractStyle } /** - * Get language + * Get language. * * @return \PhpOffice\PhpWord\Style\Language */ @@ -860,9 +883,10 @@ class Font extends AbstractStyle } /** - * Set language + * Set language. * * @param mixed $value + * * @return self */ public function setLang($value = null) @@ -876,79 +900,7 @@ class Font extends AbstractStyle } /** - * Get bold - * - * @deprecated 0.10.0 - * - * @codeCoverageIgnore - */ - public function getBold() - { - return $this->isBold(); - } - - /** - * Get italic - * - * @deprecated 0.10.0 - * - * @codeCoverageIgnore - */ - public function getItalic() - { - return $this->isItalic(); - } - - /** - * Get superscript - * - * @deprecated 0.10.0 - * - * @codeCoverageIgnore - */ - public function getSuperScript() - { - return $this->isSuperScript(); - } - - /** - * Get subscript - * - * @deprecated 0.10.0 - * - * @codeCoverageIgnore - */ - public function getSubScript() - { - return $this->isSubScript(); - } - - /** - * Get strikethrough - * - * @deprecated 0.10.0 - * - * @codeCoverageIgnore - */ - public function getStrikethrough() - { - return $this->isStrikethrough(); - } - - /** - * Get paragraph style - * - * @deprecated 0.11.0 - * - * @codeCoverageIgnore - */ - public function getParagraphStyle() - { - return $this->getParagraph(); - } - - /** - * Get hidden text + * Get hidden text. * * @return bool */ @@ -958,9 +910,10 @@ class Font extends AbstractStyle } /** - * Set hidden text + * Set hidden text. * * @param bool $value + * * @return self */ public function setHidden($value = true) @@ -971,7 +924,7 @@ class Font extends AbstractStyle } /** - * Get position + * Get position. * * @return int */ @@ -981,9 +934,10 @@ class Font extends AbstractStyle } /** - * Set position + * Set position. * * @param int $value + * * @return self */ public function setPosition($value = null) diff --git a/PhpOffice/PhpWord/Style/Frame.php b/PhpOffice/PhpWord/Style/Frame.php old mode 100755 new mode 100644 index e87b7a8..45fc583 --- a/PhpOffice/PhpWord/Style/Frame.php +++ b/PhpOffice/PhpWord/Style/Frame.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -20,17 +20,18 @@ namespace PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\SimpleType\Jc; /** - * Frame defines the size and position of an object + * Frame defines the size and position of an object. * * Width, height, left/hpos, top/vpos, hrel, vrel, wrap, zindex * * @since 0.12.0 + * * @todo Make existing style (image, textbox, etc) use this style */ class Frame extends AbstractStyle { /** - * Length unit + * Length unit. * * @const string */ @@ -46,7 +47,7 @@ class Frame extends AbstractStyle const POS_RELATIVE = 'relative'; /** - * Horizontal/vertical value + * Horizontal/vertical value. * * @const string */ @@ -59,7 +60,7 @@ class Frame extends AbstractStyle const POS_OUTSIDE = 'outside'; /** - * Position relative to + * Position relative to. * * @const string */ @@ -77,7 +78,7 @@ class Frame extends AbstractStyle const POS_RELTO_OMARGIN = 'outer-margin-area'; /** - * Wrap type + * Wrap type. * * @const string */ @@ -95,124 +96,125 @@ class Frame extends AbstractStyle private $alignment = ''; /** - * Unit + * Unit. * * @var string */ private $unit = 'pt'; /** - * Width + * Width. * - * @var int|float + * @var float|int */ private $width; /** - * Height + * Height. * - * @var int|float + * @var float|int */ private $height; /** - * Leftmost (horizontal) position + * Leftmost (horizontal) position. * - * @var int|float + * @var float|int */ private $left = 0; /** - * Topmost (vertical) position + * Topmost (vertical) position. * - * @var int|float + * @var float|int */ private $top = 0; /** - * Position type: absolute|relative + * Position type: absolute|relative. * * @var string */ private $pos; /** - * Horizontal position + * Horizontal position. * * @var string */ private $hPos; /** - * Horizontal position relative to + * Horizontal position relative to. * * @var string */ private $hPosRelTo; /** - * Vertical position + * Vertical position. * * @var string */ private $vPos; /** - * Vertical position relative to + * Vertical position relative to. * * @var string */ private $vPosRelTo; /** - * Wrap type + * Wrap type. * * @var string */ private $wrap; /** - * Top wrap distance + * Top wrap distance. * * @var float */ private $wrapDistanceTop; /** - * Bottom wrap distance + * Bottom wrap distance. * * @var float */ private $wrapDistanceBottom; /** - * Left wrap distance + * Left wrap distance. * * @var float */ private $wrapDistanceLeft; /** - * Right wrap distance + * Right wrap distance. * * @var float */ private $wrapDistanceRight; /** - * Vertically raised or lowered text + * Vertically raised or lowered text. * * @var int + * * @see http://www.datypic.com/sc/ooxml/e-w_position-1.html */ private $position; /** - * Create a new instance + * Create a new instance. * * @param array $style */ - public function __construct($style = array()) + public function __construct($style = []) { $this->setStyleByArray($style); } @@ -244,33 +246,7 @@ class Frame extends AbstractStyle } /** - * @deprecated 0.13.0 Use the `getAlignment` method instead. - * - * @return string - * - * @codeCoverageIgnore - */ - public function getAlign() - { - return $this->getAlignment(); - } - - /** - * @deprecated 0.13.0 Use the `setAlignment` method instead. - * - * @param string $value - * - * @return self - * - * @codeCoverageIgnore - */ - public function setAlign($value = null) - { - return $this->setAlignment($value); - } - - /** - * Get unit + * Get unit. * * @return string */ @@ -280,9 +256,10 @@ class Frame extends AbstractStyle } /** - * Set unit + * Set unit. * * @param string $value + * * @return self */ public function setUnit($value) @@ -293,9 +270,9 @@ class Frame extends AbstractStyle } /** - * Get width + * Get width. * - * @return int|float + * @return float|int */ public function getWidth() { @@ -303,9 +280,10 @@ class Frame extends AbstractStyle } /** - * Set width + * Set width. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setWidth($value = null) @@ -316,9 +294,9 @@ class Frame extends AbstractStyle } /** - * Get height + * Get height. * - * @return int|float + * @return float|int */ public function getHeight() { @@ -326,9 +304,10 @@ class Frame extends AbstractStyle } /** - * Set height + * Set height. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setHeight($value = null) @@ -339,9 +318,9 @@ class Frame extends AbstractStyle } /** - * Get left + * Get left. * - * @return int|float + * @return float|int */ public function getLeft() { @@ -349,9 +328,10 @@ class Frame extends AbstractStyle } /** - * Set left + * Set left. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setLeft($value = 0) @@ -362,9 +342,9 @@ class Frame extends AbstractStyle } /** - * Get topmost position + * Get topmost position. * - * @return int|float + * @return float|int */ public function getTop() { @@ -372,9 +352,10 @@ class Frame extends AbstractStyle } /** - * Set topmost position + * Set topmost position. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setTop($value = 0) @@ -385,7 +366,7 @@ class Frame extends AbstractStyle } /** - * Get position type + * Get position type. * * @return string */ @@ -395,24 +376,25 @@ class Frame extends AbstractStyle } /** - * Set position type + * Set position type. * * @param string $value + * * @return self */ public function setPos($value) { - $enum = array( + $enum = [ self::POS_ABSOLUTE, self::POS_RELATIVE, - ); + ]; $this->pos = $this->setEnumVal($value, $enum, $this->pos); return $this; } /** - * Get horizontal position + * Get horizontal position. * * @return string */ @@ -422,30 +404,31 @@ class Frame extends AbstractStyle } /** - * Set horizontal position + * Set horizontal position. * * @since 0.12.0 "absolute" option is available. * * @param string $value + * * @return self */ public function setHPos($value) { - $enum = array( + $enum = [ self::POS_ABSOLUTE, self::POS_LEFT, self::POS_CENTER, self::POS_RIGHT, self::POS_INSIDE, self::POS_OUTSIDE, - ); + ]; $this->hPos = $this->setEnumVal($value, $enum, $this->hPos); return $this; } /** - * Get vertical position + * Get vertical position. * * @return string */ @@ -455,30 +438,31 @@ class Frame extends AbstractStyle } /** - * Set vertical position + * Set vertical position. * * @since 0.12.0 "absolute" option is available. * * @param string $value + * * @return self */ public function setVPos($value) { - $enum = array( + $enum = [ self::POS_ABSOLUTE, self::POS_TOP, self::POS_CENTER, self::POS_BOTTOM, self::POS_INSIDE, self::POS_OUTSIDE, - ); + ]; $this->vPos = $this->setEnumVal($value, $enum, $this->vPos); return $this; } /** - * Get horizontal position relative to + * Get horizontal position relative to. * * @return string */ @@ -488,14 +472,15 @@ class Frame extends AbstractStyle } /** - * Set horizontal position relative to + * Set horizontal position relative to. * * @param string $value + * * @return self */ public function setHPosRelTo($value) { - $enum = array( + $enum = [ self::POS_RELTO_MARGIN, self::POS_RELTO_PAGE, self::POS_RELTO_COLUMN, @@ -504,14 +489,14 @@ class Frame extends AbstractStyle self::POS_RELTO_RMARGIN, self::POS_RELTO_IMARGIN, self::POS_RELTO_OMARGIN, - ); + ]; $this->hPosRelTo = $this->setEnumVal($value, $enum, $this->hPosRelTo); return $this; } /** - * Get vertical position relative to + * Get vertical position relative to. * * @return string */ @@ -521,14 +506,15 @@ class Frame extends AbstractStyle } /** - * Set vertical position relative to + * Set vertical position relative to. * * @param string $value + * * @return self */ public function setVPosRelTo($value) { - $enum = array( + $enum = [ self::POS_RELTO_MARGIN, self::POS_RELTO_PAGE, self::POS_RELTO_TEXT, @@ -537,14 +523,14 @@ class Frame extends AbstractStyle self::POS_RELTO_BMARGIN, self::POS_RELTO_IMARGIN, self::POS_RELTO_OMARGIN, - ); + ]; $this->vPosRelTo = $this->setEnumVal($value, $enum, $this->vPosRelTo); return $this; } /** - * Get wrap type + * Get wrap type. * * @return string */ @@ -554,14 +540,15 @@ class Frame extends AbstractStyle } /** - * Set wrap type + * Set wrap type. * * @param string $value + * * @return self */ public function setWrap($value) { - $enum = array( + $enum = [ self::WRAP_INLINE, self::WRAP_SQUARE, self::WRAP_TIGHT, @@ -569,14 +556,14 @@ class Frame extends AbstractStyle self::WRAP_TOPBOTTOM, self::WRAP_BEHIND, self::WRAP_INFRONT, - ); + ]; $this->wrap = $this->setEnumVal($value, $enum, $this->wrap); return $this; } /** - * Get top distance from text wrap + * Get top distance from text wrap. * * @return float */ @@ -586,9 +573,10 @@ class Frame extends AbstractStyle } /** - * Set top distance from text wrap + * Set top distance from text wrap. * * @param int $value + * * @return self */ public function setWrapDistanceTop($value = null) @@ -599,7 +587,7 @@ class Frame extends AbstractStyle } /** - * Get bottom distance from text wrap + * Get bottom distance from text wrap. * * @return float */ @@ -609,9 +597,10 @@ class Frame extends AbstractStyle } /** - * Set bottom distance from text wrap + * Set bottom distance from text wrap. * * @param float $value + * * @return self */ public function setWrapDistanceBottom($value = null) @@ -622,7 +611,7 @@ class Frame extends AbstractStyle } /** - * Get left distance from text wrap + * Get left distance from text wrap. * * @return float */ @@ -632,9 +621,10 @@ class Frame extends AbstractStyle } /** - * Set left distance from text wrap + * Set left distance from text wrap. * * @param float $value + * * @return self */ public function setWrapDistanceLeft($value = null) @@ -645,7 +635,7 @@ class Frame extends AbstractStyle } /** - * Get right distance from text wrap + * Get right distance from text wrap. * * @return float */ @@ -655,9 +645,10 @@ class Frame extends AbstractStyle } /** - * Set right distance from text wrap + * Set right distance from text wrap. * * @param float $value + * * @return self */ public function setWrapDistanceRight($value = null) @@ -668,7 +659,7 @@ class Frame extends AbstractStyle } /** - * Get position + * Get position. * * @return int */ @@ -678,9 +669,10 @@ class Frame extends AbstractStyle } /** - * Set position + * Set position. * * @param int $value + * * @return self */ public function setPosition($value = null) diff --git a/PhpOffice/PhpWord/Style/Image.php b/PhpOffice/PhpWord/Style/Image.php old mode 100755 new mode 100644 index 70aafe1..dbb8572 --- a/PhpOffice/PhpWord/Style/Image.php +++ b/PhpOffice/PhpWord/Style/Image.php @@ -11,19 +11,19 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Style; /** - * Image and memory image style + * Image and memory image style. */ class Image extends Frame { /** - * Backward compatibility constants + * Backward compatibility constants. * * @const string */ @@ -56,7 +56,7 @@ class Image extends Frame const POSITION_RELATIVE = self::POS_RELATIVE; /** - * Create new instance + * Create new instance. */ public function __construct() { @@ -73,9 +73,9 @@ class Image extends Frame } /** - * Get margin top + * Get margin top. * - * @return int|float + * @return float|int */ public function getMarginTop() { @@ -83,10 +83,12 @@ class Image extends Frame } /** - * Set margin top + * Set margin top. * * @ignoreScrutinizerPatch - * @param int|float $value + * + * @param float|int $value + * * @return self */ public function setMarginTop($value = 0) @@ -97,9 +99,9 @@ class Image extends Frame } /** - * Get margin left + * Get margin left. * - * @return int|float + * @return float|int */ public function getMarginLeft() { @@ -107,10 +109,12 @@ class Image extends Frame } /** - * Set margin left + * Set margin left. * * @ignoreScrutinizerPatch - * @param int|float $value + * + * @param float|int $value + * * @return self */ public function setMarginLeft($value = 0) @@ -121,7 +125,7 @@ class Image extends Frame } /** - * Get wrapping style + * Get wrapping style. * * @return string */ @@ -131,12 +135,10 @@ class Image extends Frame } /** - * Set wrapping style + * Set wrapping style. * * @param string $wrappingStyle * - * @throws \InvalidArgumentException - * * @return self */ public function setWrappingStyle($wrappingStyle) @@ -147,7 +149,7 @@ class Image extends Frame } /** - * Get positioning type + * Get positioning type. * * @return string */ @@ -157,12 +159,10 @@ class Image extends Frame } /** - * Set positioning type + * Set positioning type. * * @param string $positioning * - * @throws \InvalidArgumentException - * * @return self */ public function setPositioning($positioning) @@ -173,7 +173,7 @@ class Image extends Frame } /** - * Get horizontal alignment + * Get horizontal alignment. * * @return string */ @@ -183,12 +183,10 @@ class Image extends Frame } /** - * Set horizontal alignment + * Set horizontal alignment. * * @param string $alignment * - * @throws \InvalidArgumentException - * * @return self */ public function setPosHorizontal($alignment) @@ -199,7 +197,7 @@ class Image extends Frame } /** - * Get vertical alignment + * Get vertical alignment. * * @return string */ @@ -209,12 +207,10 @@ class Image extends Frame } /** - * Set vertical alignment + * Set vertical alignment. * * @param string $alignment * - * @throws \InvalidArgumentException - * * @return self */ public function setPosVertical($alignment) @@ -225,7 +221,7 @@ class Image extends Frame } /** - * Get horizontal relation + * Get horizontal relation. * * @return string */ @@ -235,12 +231,10 @@ class Image extends Frame } /** - * Set horizontal relation + * Set horizontal relation. * * @param string $relto * - * @throws \InvalidArgumentException - * * @return self */ public function setPosHorizontalRel($relto) @@ -251,7 +245,7 @@ class Image extends Frame } /** - * Get vertical relation + * Get vertical relation. * * @return string */ @@ -261,12 +255,10 @@ class Image extends Frame } /** - * Set vertical relation + * Set vertical relation. * * @param string $relto * - * @throws \InvalidArgumentException - * * @return self */ public function setPosVerticalRel($relto) diff --git a/PhpOffice/PhpWord/Style/Indentation.php b/PhpOffice/PhpWord/Style/Indentation.php old mode 100755 new mode 100644 index e422395..87277b4 --- a/PhpOffice/PhpWord/Style/Indentation.php +++ b/PhpOffice/PhpWord/Style/Indentation.php @@ -11,14 +11,14 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Style; /** - * Paragraph indentation style + * Paragraph indentation style. * * @see http://www.schemacentral.com/sc/ooxml/t-w_CT_Ind.html * @since 0.10.0 @@ -26,47 +26,47 @@ namespace PhpOffice\PhpWord\Style; class Indentation extends AbstractStyle { /** - * Left indentation (twip) + * Left indentation (twip). * - * @var int|float + * @var float|int */ private $left = 0; /** - * Right indentation (twip) + * Right indentation (twip). * - * @var int|float + * @var float|int */ private $right = 0; /** - * Additional first line indentation (twip) + * Additional first line indentation (twip). * - * @var int|float + * @var float|int */ private $firstLine; /** - * Indentation removed from first line (twip) + * Indentation removed from first line (twip). * - * @var int|float + * @var float|int */ private $hanging; /** - * Create a new instance + * Create a new instance. * * @param array $style */ - public function __construct($style = array()) + public function __construct($style = []) { $this->setStyleByArray($style); } /** - * Get left + * Get left. * - * @return int|float + * @return float|int */ public function getLeft() { @@ -74,9 +74,10 @@ class Indentation extends AbstractStyle } /** - * Set left + * Set left. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setLeft($value = null) @@ -87,9 +88,9 @@ class Indentation extends AbstractStyle } /** - * Get right + * Get right. * - * @return int|float + * @return float|int */ public function getRight() { @@ -97,9 +98,10 @@ class Indentation extends AbstractStyle } /** - * Set right + * Set right. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setRight($value = null) @@ -110,9 +112,9 @@ class Indentation extends AbstractStyle } /** - * Get first line + * Get first line. * - * @return int|float + * @return float|int */ public function getFirstLine() { @@ -120,9 +122,10 @@ class Indentation extends AbstractStyle } /** - * Set first line + * Set first line. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setFirstLine($value = null) @@ -133,9 +136,9 @@ class Indentation extends AbstractStyle } /** - * Get hanging + * Get hanging. * - * @return int|float + * @return float|int */ public function getHanging() { @@ -143,9 +146,10 @@ class Indentation extends AbstractStyle } /** - * Set hanging + * Set hanging. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setHanging($value = null) diff --git a/PhpOffice/PhpWord/Style/Language.php b/PhpOffice/PhpWord/Style/Language.php old mode 100755 new mode 100644 index 18ef889..e47cceb --- a/PhpOffice/PhpWord/Style/Language.php +++ b/PhpOffice/PhpWord/Style/Language.php @@ -11,15 +11,17 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Style; +use InvalidArgumentException; + /** * Language - * A couple of predefined values are defined here, see the websites below for more values + * A couple of predefined values are defined here, see the websites below for more values. * * @see http://www.datypic.com/sc/ooxml/t-w_CT_Language.html * @see https://technet.microsoft.com/en-us/library/cc287874(v=office.12).aspx @@ -68,6 +70,9 @@ final class Language extends AbstractStyle const NL_NL = 'nl-NL'; const NL_NL_ID = 1043; + const SV_SE = 'sv-SE'; + const SV_SE_ID = 1053; + const UK_UA = 'uk-UA'; const UK_UA_ID = 1058; @@ -75,40 +80,41 @@ final class Language extends AbstractStyle const RU_RU_ID = 1049; /** - * Language ID, used for RTF document generation + * Language ID, used for RTF document generation. * * @var int + * * @see https://technet.microsoft.com/en-us/library/cc179219.aspx */ private $langId; /** - * Latin Language + * Latin Language. * * @var string */ private $latin; /** - * East Asian Language + * East Asian Language. * * @var string */ private $eastAsia; /** - * Complex Script Language + * Complex Script Language. * * @var string */ private $bidirectional; /** - * Constructor + * Constructor. * - * @param string|null $latin - * @param string|null $eastAsia - * @param string|null $bidirectional + * @param null|string $latin + * @param null|string $eastAsia + * @param null|string $bidirectional */ public function __construct($latin = null, $eastAsia = null, $bidirectional = null) { @@ -124,10 +130,11 @@ final class Language extends AbstractStyle } /** - * Set the Latin Language + * Set the Latin Language. * * @param string $latin * The value for the latin language + * * @return self */ public function setLatin($latin) @@ -138,9 +145,9 @@ final class Language extends AbstractStyle } /** - * Get the Latin Language + * Get the Latin Language. * - * @return string|null + * @return null|string */ public function getLatin() { @@ -148,11 +155,13 @@ final class Language extends AbstractStyle } /** - * Set the Language ID + * Set the Language ID. * * @param int $langId * The value for the language ID + * * @return self + * * @see https://technet.microsoft.com/en-us/library/cc287874(v=office.12).aspx */ public function setLangId($langId) @@ -163,7 +172,7 @@ final class Language extends AbstractStyle } /** - * Get the Language ID + * Get the Language ID. * * @return int */ @@ -173,10 +182,11 @@ final class Language extends AbstractStyle } /** - * Set the East Asian Language + * Set the East Asian Language. * * @param string $eastAsia * The value for the east asian language + * * @return self */ public function setEastAsia($eastAsia) @@ -187,9 +197,9 @@ final class Language extends AbstractStyle } /** - * Get the East Asian Language + * Get the East Asian Language. * - * @return string|null + * @return null|string */ public function getEastAsia() { @@ -197,10 +207,11 @@ final class Language extends AbstractStyle } /** - * Set the Complex Script Language + * Set the Complex Script Language. * * @param string $bidirectional * The value for the complex script language + * * @return self */ public function setBidirectional($bidirectional) @@ -211,9 +222,9 @@ final class Language extends AbstractStyle } /** - * Get the Complex Script Language + * Get the Complex Script Language. * - * @return string|null + * @return null|string */ public function getBidirectional() { @@ -221,19 +232,24 @@ final class Language extends AbstractStyle } /** - * Validates that the language passed is in the format xx-xx + * Validates that the language passed is in the format xx-xx. * * @param string $locale + * * @return string */ private function validateLocale($locale) { - if (strlen($locale) === 2) { + if ($locale !== null) { + $locale = str_replace('_', '-', $locale); + } + + if ($locale !== null && strlen($locale) === 2) { return strtolower($locale) . '-' . strtoupper($locale); } if ($locale !== null && $locale !== 'zxx' && strstr($locale, '-') === false) { - throw new \InvalidArgumentException($locale . ' is not a valid language code'); + throw new InvalidArgumentException($locale . ' is not a valid language code'); } return $locale; diff --git a/PhpOffice/PhpWord/Style/Line.php b/PhpOffice/PhpWord/Style/Line.php old mode 100755 new mode 100644 index a9952ee..2aeefa5 --- a/PhpOffice/PhpWord/Style/Line.php +++ b/PhpOffice/PhpWord/Style/Line.php @@ -11,26 +11,26 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Style; /** - * Line style + * Line style. */ class Line extends Image { /** - * Connector types + * Connector types. * * @const string */ const CONNECTOR_TYPE_STRAIGHT = 'straight'; /** - * Arrow styles + * Arrow styles. * * @const string */ @@ -41,7 +41,7 @@ class Line extends Image const ARROW_STYLE_OVAL = 'oval'; /** - * Dash styles + * Dash styles. * * @const string */ @@ -54,56 +54,56 @@ class Line extends Image const DASH_STYLE_LONG_DASH_DOT_DOT = 'longdashdotdot'; /** - * flip Line + * flip Line. * * @var bool */ private $flip = false; /** - * connectorType + * connectorType. * * @var string */ private $connectorType = self::CONNECTOR_TYPE_STRAIGHT; /** - * Line Weight + * Line Weight. * * @var int */ private $weight; /** - * Line color + * Line color. * * @var string */ private $color; /** - * Dash style + * Dash style. * * @var string */ private $dash; /** - * Begin arrow + * Begin arrow. * * @var string */ private $beginArrow; /** - * End arrow + * End arrow. * * @var string */ private $endArrow; /** - * Get flip + * Get flip. * * @return bool */ @@ -113,9 +113,10 @@ class Line extends Image } /** - * Set flip + * Set flip. * * @param bool $value + * * @return self */ public function setFlip($value = false) @@ -126,7 +127,7 @@ class Line extends Image } /** - * Get connectorType + * Get connectorType. * * @return string */ @@ -136,23 +137,24 @@ class Line extends Image } /** - * Set connectorType + * Set connectorType. * * @param string $value + * * @return self */ public function setConnectorType($value = null) { - $enum = array( + $enum = [ self::CONNECTOR_TYPE_STRAIGHT, - ); + ]; $this->connectorType = $this->setEnumVal($value, $enum, $this->connectorType); return $this; } /** - * Get weight + * Get weight. * * @return int */ @@ -162,9 +164,10 @@ class Line extends Image } /** - * Set weight + * Set weight. * * @param int $value Weight in points + * * @return self */ public function setWeight($value = null) @@ -175,7 +178,7 @@ class Line extends Image } /** - * Get color + * Get color. * * @return string */ @@ -185,9 +188,10 @@ class Line extends Image } /** - * Set color + * Set color. * * @param string $value + * * @return self */ public function setColor($value = null) @@ -198,7 +202,7 @@ class Line extends Image } /** - * Get beginArrow + * Get beginArrow. * * @return string */ @@ -208,24 +212,25 @@ class Line extends Image } /** - * Set beginArrow + * Set beginArrow. * * @param string $value + * * @return self */ public function setBeginArrow($value = null) { - $enum = array( + $enum = [ self::ARROW_STYLE_BLOCK, self::ARROW_STYLE_CLASSIC, self::ARROW_STYLE_DIAMOND, self::ARROW_STYLE_OPEN, self::ARROW_STYLE_OVAL, - ); + ]; $this->beginArrow = $this->setEnumVal($value, $enum, $this->beginArrow); return $this; } /** - * Get endArrow + * Get endArrow. * * @return string */ @@ -235,24 +240,25 @@ class Line extends Image } /** - * Set endArrow + * Set endArrow. * * @param string $value + * * @return self */ public function setEndArrow($value = null) { - $enum = array( + $enum = [ self::ARROW_STYLE_BLOCK, self::ARROW_STYLE_CLASSIC, self::ARROW_STYLE_DIAMOND, self::ARROW_STYLE_OPEN, self::ARROW_STYLE_OVAL, - ); + ]; $this->endArrow = $this->setEnumVal($value, $enum, $this->endArrow); return $this; } /** - * Get Dash + * Get Dash. * * @return string */ @@ -262,18 +268,19 @@ class Line extends Image } /** - * Set Dash + * Set Dash. * * @param string $value + * * @return self */ public function setDash($value = null) { - $enum = array( + $enum = [ self::DASH_STYLE_DASH, self::DASH_STYLE_DASH_DOT, self::DASH_STYLE_LONG_DASH, self::DASH_STYLE_LONG_DASH_DOT, self::DASH_STYLE_LONG_DASH_DOT_DOT, self::DASH_STYLE_ROUND_DOT, self::DASH_STYLE_SQUARE_DOT, - ); + ]; $this->dash = $this->setEnumVal($value, $enum, $this->dash); return $this; diff --git a/PhpOffice/PhpWord/Style/LineNumbering.php b/PhpOffice/PhpWord/Style/LineNumbering.php old mode 100755 new mode 100644 index 451252d..61a98dc --- a/PhpOffice/PhpWord/Style/LineNumbering.php +++ b/PhpOffice/PhpWord/Style/LineNumbering.php @@ -11,14 +11,14 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Style; /** - * Line numbering style + * Line numbering style. * * @see http://www.schemacentral.com/sc/ooxml/t-w_CT_LineNumber.html * @since 0.10.0 @@ -31,46 +31,47 @@ class LineNumbering extends AbstractStyle const LINE_NUMBERING_NEW_SECTION = 'newSection'; /** - * Line numbering starting value + * Line numbering starting value. * * @var int */ private $start = 1; /** - * Line number increments + * Line number increments. * * @var int */ private $increment = 1; /** - * Distance between text and line numbering in twip + * Distance between text and line numbering in twip. * - * @var int|float + * @var float|int */ private $distance; /** - * Line numbering restart setting continuous|newPage|newSection + * Line numbering restart setting continuous|newPage|newSection. * * @var string + * * @see http://www.schemacentral.com/sc/ooxml/a-w_restart-1.html */ private $restart; /** - * Create a new instance + * Create a new instance. * * @param array $style */ - public function __construct($style = array()) + public function __construct($style = []) { $this->setStyleByArray($style); } /** - * Get start + * Get start. * * @return int */ @@ -80,9 +81,10 @@ class LineNumbering extends AbstractStyle } /** - * Set start + * Set start. * * @param int $value + * * @return self */ public function setStart($value = null) @@ -93,7 +95,7 @@ class LineNumbering extends AbstractStyle } /** - * Get increment + * Get increment. * * @return int */ @@ -103,9 +105,10 @@ class LineNumbering extends AbstractStyle } /** - * Set increment + * Set increment. * * @param int $value + * * @return self */ public function setIncrement($value = null) @@ -116,9 +119,9 @@ class LineNumbering extends AbstractStyle } /** - * Get distance + * Get distance. * - * @return int|float + * @return float|int */ public function getDistance() { @@ -126,9 +129,10 @@ class LineNumbering extends AbstractStyle } /** - * Set distance + * Set distance. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setDistance($value = null) @@ -139,7 +143,7 @@ class LineNumbering extends AbstractStyle } /** - * Get restart + * Get restart. * * @return string */ @@ -149,14 +153,15 @@ class LineNumbering extends AbstractStyle } /** - * Set distance + * Set distance. * * @param string $value + * * @return self */ public function setRestart($value = null) { - $enum = array(self::LINE_NUMBERING_CONTINUOUS, self::LINE_NUMBERING_NEW_PAGE, self::LINE_NUMBERING_NEW_SECTION); + $enum = [self::LINE_NUMBERING_CONTINUOUS, self::LINE_NUMBERING_NEW_PAGE, self::LINE_NUMBERING_NEW_SECTION]; $this->restart = $this->setEnumVal($value, $enum, $this->restart); return $this; diff --git a/PhpOffice/PhpWord/Style/ListItem.php b/PhpOffice/PhpWord/Style/ListItem.php old mode 100755 new mode 100644 index 4293940..28d3fb1 --- a/PhpOffice/PhpWord/Style/ListItem.php +++ b/PhpOffice/PhpWord/Style/ListItem.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -20,7 +20,7 @@ namespace PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Style; /** - * List item style + * List item style. * * Before version 0.10.0, numbering style is defined statically with $listType. * After version 0.10.0, numbering style is defined by using Numbering and @@ -36,30 +36,32 @@ class ListItem extends AbstractStyle const TYPE_ALPHANUM = 9; /** - * Legacy list type + * Legacy list type. * * @var int */ private $listType; /** - * Numbering style name + * Numbering style name. * * @var string + * * @since 0.10.0 */ private $numStyle; /** - * Numbering definition instance ID + * Numbering definition instance ID. * * @var int + * * @since 0.10.0 */ private $numId; /** - * Create new instance + * Create new instance. * * @param string $numStyle */ @@ -73,7 +75,7 @@ class ListItem extends AbstractStyle } /** - * Get List Type + * Get List Type. * * @return int */ @@ -83,18 +85,19 @@ class ListItem extends AbstractStyle } /** - * Set legacy list type for version < 0.10.0 + * Set legacy list type for version < 0.10.0. * * @param int $value + * * @return self */ public function setListType($value = self::TYPE_BULLET_FILLED) { - $enum = array( + $enum = [ self::TYPE_SQUARE_FILLED, self::TYPE_BULLET_FILLED, self::TYPE_BULLET_EMPTY, self::TYPE_NUMBER, self::TYPE_NUMBER_NESTED, self::TYPE_ALPHANUM, - ); + ]; $this->listType = $this->setEnumVal($value, $enum, $this->listType); $this->getListTypeStyle(); @@ -102,7 +105,7 @@ class ListItem extends AbstractStyle } /** - * Get numbering style name + * Get numbering style name. * * @return string */ @@ -112,9 +115,10 @@ class ListItem extends AbstractStyle } /** - * Set numbering style name + * Set numbering style name. * * @param string $value + * * @return self */ public function setNumStyle($value) @@ -130,7 +134,7 @@ class ListItem extends AbstractStyle } /** - * Get numbering Id + * Get numbering Id. * * @return int */ @@ -140,19 +144,21 @@ class ListItem extends AbstractStyle } /** - * Set numbering Id. Same numId means same list + * Set numbering Id. Same numId means same list. + * * @param mixed $numInt */ - public function setNumId($numInt) + public function setNumId($numInt): void { $this->numId = $numInt; $this->getListTypeStyle(); } /** - * Get legacy numbering definition + * Get legacy numbering definition. * * @return array + * * @since 0.10.0 */ private function getListTypeStyle() @@ -171,13 +177,13 @@ class ListItem extends AbstractStyle } // Property mapping for numbering level information - $properties = array('start', 'format', 'text', 'alignment', 'tabPos', 'left', 'hanging', 'font', 'hint'); + $properties = ['start', 'format', 'text', 'alignment', 'tabPos', 'left', 'hanging', 'font', 'hint']; // Legacy level information - $listTypeStyles = array( - self::TYPE_SQUARE_FILLED => array( - 'type' => 'hybridMultilevel', - 'levels' => array( + $listTypeStyles = [ + self::TYPE_SQUARE_FILLED => [ + 'type' => 'hybridMultilevel', + 'levels' => [ 0 => '1, bullet, , left, 720, 720, 360, Wingdings, default', 1 => '1, bullet, o, left, 1440, 1440, 360, Courier New, default', 2 => '1, bullet, , left, 2160, 2160, 360, Wingdings, default', @@ -187,11 +193,11 @@ class ListItem extends AbstractStyle 6 => '1, bullet, , left, 5040, 5040, 360, Symbol, default', 7 => '1, bullet, o, left, 5760, 5760, 360, Courier New, default', 8 => '1, bullet, , left, 6480, 6480, 360, Wingdings, default', - ), - ), - self::TYPE_BULLET_FILLED => array( - 'type' => 'hybridMultilevel', - 'levels' => array( + ], + ], + self::TYPE_BULLET_FILLED => [ + 'type' => 'hybridMultilevel', + 'levels' => [ 0 => '1, bullet, , left, 720, 720, 360, Symbol, default', 1 => '1, bullet, o, left, 1440, 1440, 360, Courier New, default', 2 => '1, bullet, , left, 2160, 2160, 360, Wingdings, default', @@ -201,11 +207,11 @@ class ListItem extends AbstractStyle 6 => '1, bullet, , left, 5040, 5040, 360, Symbol, default', 7 => '1, bullet, o, left, 5760, 5760, 360, Courier New, default', 8 => '1, bullet, , left, 6480, 6480, 360, Wingdings, default', - ), - ), - self::TYPE_BULLET_EMPTY => array( - 'type' => 'hybridMultilevel', - 'levels' => array( + ], + ], + self::TYPE_BULLET_EMPTY => [ + 'type' => 'hybridMultilevel', + 'levels' => [ 0 => '1, bullet, o, left, 720, 720, 360, Courier New, default', 1 => '1, bullet, o, left, 1440, 1440, 360, Courier New, default', 2 => '1, bullet, , left, 2160, 2160, 360, Wingdings, default', @@ -215,11 +221,11 @@ class ListItem extends AbstractStyle 6 => '1, bullet, , left, 5040, 5040, 360, Symbol, default', 7 => '1, bullet, o, left, 5760, 5760, 360, Courier New, default', 8 => '1, bullet, , left, 6480, 6480, 360, Wingdings, default', - ), - ), - self::TYPE_NUMBER => array( - 'type' => 'hybridMultilevel', - 'levels' => array( + ], + ], + self::TYPE_NUMBER => [ + 'type' => 'hybridMultilevel', + 'levels' => [ 0 => '1, decimal, %1., left, 720, 720, 360, , default', 1 => '1, bullet, o, left, 1440, 1440, 360, Courier New, default', 2 => '1, bullet, , left, 2160, 2160, 360, Wingdings, default', @@ -229,11 +235,11 @@ class ListItem extends AbstractStyle 6 => '1, bullet, , left, 5040, 5040, 360, Symbol, default', 7 => '1, bullet, o, left, 5760, 5760, 360, Courier New, default', 8 => '1, bullet, , left, 6480, 6480, 360, Wingdings, default', - ), - ), - self::TYPE_NUMBER_NESTED => array( - 'type' => 'multilevel', - 'levels' => array( + ], + ], + self::TYPE_NUMBER_NESTED => [ + 'type' => 'multilevel', + 'levels' => [ 0 => '1, decimal, %1., left, 360, 360, 360, , ', 1 => '1, decimal, %1.%2., left, 792, 792, 432, , ', 2 => '1, decimal, %1.%2.%3., left, 1224, 1224, 504, , ', @@ -243,11 +249,11 @@ class ListItem extends AbstractStyle 6 => '1, decimal, %1.%2.%3.%4.%5.%6.%7., left, 3600, 3240, 1080, , ', 7 => '1, decimal, %1.%2.%3.%4.%5.%6.%7.%8., left, 3960, 3744, 1224, , ', 8 => '1, decimal, %1.%2.%3.%4.%5.%6.%7.%8.%9., left, 4680, 4320, 1440, , ', - ), - ), - self::TYPE_ALPHANUM => array( - 'type' => 'multilevel', - 'levels' => array( + ], + ], + self::TYPE_ALPHANUM => [ + 'type' => 'multilevel', + 'levels' => [ 0 => '1, decimal, %1., left, 720, 720, 360, , ', 1 => '1, lowerLetter, %2., left, 1440, 1440, 360, , ', 2 => '1, lowerRoman, %3., right, 2160, 2160, 180, , ', @@ -257,18 +263,18 @@ class ListItem extends AbstractStyle 6 => '1, decimal, %7., left, 5040, 5040, 360, , ', 7 => '1, lowerLetter, %8., left, 5760, 5760, 360, , ', 8 => '1, lowerRoman, %9., right, 6480, 6480, 180, , ', - ), - ), - ); + ], + ], + ]; // Populate style and register to global Style register $style = $listTypeStyles[$this->listType]; $numProperties = count($properties); foreach ($style['levels'] as $key => $value) { - $level = array(); + $level = []; $levelProperties = explode(', ', $value); $level['level'] = $key; - for ($i = 0; $i < $numProperties; $i++) { + for ($i = 0; $i < $numProperties; ++$i) { $property = $properties[$i]; $level[$property] = $levelProperties[$i]; } diff --git a/PhpOffice/PhpWord/Style/Numbering.php b/PhpOffice/PhpWord/Style/Numbering.php old mode 100755 new mode 100644 index f7855cf..0efb088 --- a/PhpOffice/PhpWord/Style/Numbering.php +++ b/PhpOffice/PhpWord/Style/Numbering.php @@ -11,14 +11,14 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Style; /** - * Numbering style + * Numbering style. * * @see http://www.schemacentral.com/sc/ooxml/e-w_numbering.html * @see http://www.schemacentral.com/sc/ooxml/e-w_abstractNum-1.html @@ -28,30 +28,32 @@ namespace PhpOffice\PhpWord\Style; class Numbering extends AbstractStyle { /** - * Numbering definition instance ID + * Numbering definition instance ID. * * @var int + * * @see http://www.schemacentral.com/sc/ooxml/e-w_num-1.html */ private $numId; /** - * Multilevel type singleLevel|multilevel|hybridMultilevel + * Multilevel type singleLevel|multilevel|hybridMultilevel. * * @var string + * * @see http://www.schemacentral.com/sc/ooxml/a-w_val-67.html */ private $type; /** - * Numbering levels + * Numbering levels. * * @var NumberingLevel[] */ - private $levels = array(); + private $levels = []; /** - * Get Id + * Get Id. * * @return int */ @@ -61,9 +63,10 @@ class Numbering extends AbstractStyle } /** - * Set Id + * Set Id. * * @param int $value + * * @return self */ public function setNumId($value) @@ -74,7 +77,7 @@ class Numbering extends AbstractStyle } /** - * Get multilevel type + * Get multilevel type. * * @return string */ @@ -84,21 +87,22 @@ class Numbering extends AbstractStyle } /** - * Set multilevel type + * Set multilevel type. * * @param string $value + * * @return self */ public function setType($value) { - $enum = array('singleLevel', 'multilevel', 'hybridMultilevel'); + $enum = ['singleLevel', 'multilevel', 'hybridMultilevel']; $this->type = $this->setEnumVal($value, $enum, $this->type); return $this; } /** - * Get levels + * Get levels. * * @return NumberingLevel[] */ @@ -108,9 +112,10 @@ class Numbering extends AbstractStyle } /** - * Set multilevel type + * Set multilevel type. * * @param array $values + * * @return self */ public function setLevels($values) diff --git a/PhpOffice/PhpWord/Style/NumberingLevel.php b/PhpOffice/PhpWord/Style/NumberingLevel.php old mode 100755 new mode 100644 index e9b32f0..39c0d83 --- a/PhpOffice/PhpWord/Style/NumberingLevel.php +++ b/PhpOffice/PhpWord/Style/NumberingLevel.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -21,7 +21,7 @@ use PhpOffice\PhpWord\SimpleType\Jc; use PhpOffice\PhpWord\SimpleType\NumberFormat; /** - * Numbering level definition + * Numbering level definition. * * @see http://www.schemacentral.com/sc/ooxml/e-w_lvl-1.html * @since 0.10.0 @@ -29,105 +29,112 @@ use PhpOffice\PhpWord\SimpleType\NumberFormat; class NumberingLevel extends AbstractStyle { /** - * Level number, 0 to 8 (total 9 levels) + * Level number, 0 to 8 (total 9 levels). * * @var int */ private $level = 0; /** - * Starting value w:start + * Starting value w:start. * * @var int + * * @see http://www.schemacentral.com/sc/ooxml/e-w_start-1.html */ private $start = 1; /** - * Numbering format w:numFmt, one of PhpOffice\PhpWord\SimpleType\NumberFormat + * Numbering format w:numFmt, one of PhpOffice\PhpWord\SimpleType\NumberFormat. * * @var string + * * @see http://www.schemacentral.com/sc/ooxml/t-w_ST_NumberFormat.html */ private $format; /** - * Restart numbering level symbol w:lvlRestart + * Restart numbering level symbol w:lvlRestart. * * @var int + * * @see http://www.schemacentral.com/sc/ooxml/e-w_lvlRestart-1.html */ private $restart; /** - * Related paragraph style + * Related paragraph style. * * @var string + * * @see http://www.schemacentral.com/sc/ooxml/e-w_pStyle-2.html */ private $pStyle; /** - * Content between numbering symbol and paragraph text w:suff + * Content between numbering symbol and paragraph text w:suff. * * @var string tab|space|nothing + * * @see http://www.schemacentral.com/sc/ooxml/e-w_suff-1.html */ private $suffix = 'tab'; /** - * Numbering level text e.g. %1 for nonbullet or bullet character + * Numbering level text e.g. %1 for nonbullet or bullet character. * * @var string + * * @see http://www.schemacentral.com/sc/ooxml/e-w_lvlText-1.html */ private $text; /** - * Justification, w:lvlJc + * Justification, w:lvlJc. * * @var string, one of PhpOffice\PhpWord\SimpleType\Jc */ private $alignment = ''; /** - * Left + * Left. * * @var int */ private $left; /** - * Hanging + * Hanging. * * @var int */ private $hanging; /** - * Tab position + * Tab position. * * @var int */ private $tabPos; /** - * Font family + * Font family. * * @var string */ private $font; /** - * Hint default|eastAsia|cs + * Hint default|eastAsia|cs. * * @var string + * * @see http://www.schemacentral.com/sc/ooxml/a-w_hint-1.html */ private $hint; /** - * Get level + * Get level. * * @return int */ @@ -137,9 +144,10 @@ class NumberingLevel extends AbstractStyle } /** - * Set level + * Set level. * * @param int $value + * * @return self */ public function setLevel($value) @@ -150,7 +158,7 @@ class NumberingLevel extends AbstractStyle } /** - * Get start + * Get start. * * @return int */ @@ -160,9 +168,10 @@ class NumberingLevel extends AbstractStyle } /** - * Set start + * Set start. * * @param int $value + * * @return self */ public function setStart($value) @@ -173,7 +182,7 @@ class NumberingLevel extends AbstractStyle } /** - * Get format + * Get format. * * @return string */ @@ -183,9 +192,10 @@ class NumberingLevel extends AbstractStyle } /** - * Set format + * Set format. * * @param string $value + * * @return self */ public function setFormat($value) @@ -196,7 +206,7 @@ class NumberingLevel extends AbstractStyle } /** - * Get restart + * Get restart. * * @return int */ @@ -206,9 +216,10 @@ class NumberingLevel extends AbstractStyle } /** - * Set restart + * Set restart. * * @param int $value + * * @return self */ public function setRestart($value) @@ -219,7 +230,7 @@ class NumberingLevel extends AbstractStyle } /** - * Get related paragraph style + * Get related paragraph style. * * @return string */ @@ -229,9 +240,10 @@ class NumberingLevel extends AbstractStyle } /** - * Set related paragraph style + * Set related paragraph style. * * @param string $value + * * @return self */ public function setPStyle($value) @@ -242,7 +254,7 @@ class NumberingLevel extends AbstractStyle } /** - * Get suffix + * Get suffix. * * @return string */ @@ -252,21 +264,22 @@ class NumberingLevel extends AbstractStyle } /** - * Set suffix + * Set suffix. * * @param string $value + * * @return self */ public function setSuffix($value) { - $enum = array('tab', 'space', 'nothing'); + $enum = ['tab', 'space', 'nothing']; $this->suffix = $this->setEnumVal($value, $enum, $this->suffix); return $this; } /** - * Get text + * Get text. * * @return string */ @@ -276,9 +289,10 @@ class NumberingLevel extends AbstractStyle } /** - * Set text + * Set text. * * @param string $value + * * @return self */ public function setText($value) @@ -315,33 +329,7 @@ class NumberingLevel extends AbstractStyle } /** - * @deprecated 0.13.0 Use the `getAlignment` method instead. - * - * @return string - * - * @codeCoverageIgnore - */ - public function getAlign() - { - return $this->getAlignment(); - } - - /** - * @deprecated 0.13.0 Use the `setAlignment` method instead. - * - * @param string $value - * - * @return self - * - * @codeCoverageIgnore - */ - public function setAlign($value) - { - return $this->setAlignment($value); - } - - /** - * Get left + * Get left. * * @return int */ @@ -351,9 +339,10 @@ class NumberingLevel extends AbstractStyle } /** - * Set left + * Set left. * * @param int $value + * * @return self */ public function setLeft($value) @@ -364,7 +353,7 @@ class NumberingLevel extends AbstractStyle } /** - * Get hanging + * Get hanging. * * @return int */ @@ -374,9 +363,10 @@ class NumberingLevel extends AbstractStyle } /** - * Set hanging + * Set hanging. * * @param int $value + * * @return self */ public function setHanging($value) @@ -387,7 +377,7 @@ class NumberingLevel extends AbstractStyle } /** - * Get tab + * Get tab. * * @return int */ @@ -397,9 +387,10 @@ class NumberingLevel extends AbstractStyle } /** - * Set tab + * Set tab. * * @param int $value + * * @return self */ public function setTabPos($value) @@ -410,7 +401,7 @@ class NumberingLevel extends AbstractStyle } /** - * Get font + * Get font. * * @return string */ @@ -420,9 +411,10 @@ class NumberingLevel extends AbstractStyle } /** - * Set font + * Set font. * * @param string $value + * * @return self */ public function setFont($value) @@ -433,7 +425,7 @@ class NumberingLevel extends AbstractStyle } /** - * Get hint + * Get hint. * * @return string */ @@ -443,14 +435,15 @@ class NumberingLevel extends AbstractStyle } /** - * Set hint + * Set hint. * * @param string $value + * * @return self */ public function setHint($value = null) { - $enum = array('default', 'eastAsia', 'cs'); + $enum = ['default', 'eastAsia', 'cs']; $this->hint = $this->setEnumVal($value, $enum, $this->hint); return $this; diff --git a/PhpOffice/PhpWord/Style/Outline.php b/PhpOffice/PhpWord/Style/Outline.php old mode 100755 new mode 100644 index a04ad97..6d83f03 --- a/PhpOffice/PhpWord/Style/Outline.php +++ b/PhpOffice/PhpWord/Style/Outline.php @@ -11,14 +11,14 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Style; /** - * Outline defines the line/border of the object + * Outline defines the line/border of the object. * * @see http://www.schemacentral.com/sc/ooxml/t-v_CT_Stroke.html * @see http://www.w3.org/TR/1998/NOTE-VML-19980513#_Toc416858395 @@ -27,9 +27,10 @@ namespace PhpOffice\PhpWord\Style; class Outline extends AbstractStyle { /** - * Line style constants + * Line style constants. * * @see http://www.schemacentral.com/sc/ooxml/t-v_ST_StrokeLineStyle.html + * * @const string */ const LINE_SINGLE = 'single'; @@ -39,9 +40,10 @@ class Outline extends AbstractStyle const LINE_THICK_BETWEEN_THIN = 'thickBetweenThin'; /** - * Line style constants + * Line style constants. * * @see http://www.schemacentral.com/sc/ooxml/t-v_ST_StrokeEndCap.html + * * @const string */ const ENDCAP_FLAT = 'flat'; @@ -49,9 +51,10 @@ class Outline extends AbstractStyle const ENDCAP_ROUND = 'round'; /** - * Arrowhead type constants + * Arrowhead type constants. * * @see http://www.schemacentral.com/sc/ooxml/t-v_ST_StrokeArrowType.html + * * @const string */ const ARROW_NONE = 'none'; @@ -62,74 +65,75 @@ class Outline extends AbstractStyle const ARROW_OPEN = 'open'; /** - * Unit; No set method for now + * Unit; No set method for now. * * @var string */ private $unit = 'pt'; /** - * Outline weight + * Outline weight. * - * @var int|float + * @var float|int */ private $weight; /** - * Outline color + * Outline color. * * @var string */ private $color; /** - * Dash type + * Dash type. * * @var string */ private $dash; /** - * Line style + * Line style. * * @var string */ private $line; /** - * End cap + * End cap. * * @var string + * * @see http://www.schemacentral.com/sc/ooxml/t-v_ST_StrokeEndCap.html */ private $endCap; /** - * Start arrow type + * Start arrow type. * * @var string */ private $startArrow; /** - * End arrow type + * End arrow type. * * @var string */ private $endArrow; /** - * Create a new instance + * Create a new instance. * * @param array $style */ - public function __construct($style = array()) + public function __construct($style = []) { $this->setStyleByArray($style); } /** - * Get unit + * Get unit. * * @return string */ @@ -139,9 +143,9 @@ class Outline extends AbstractStyle } /** - * Get weight + * Get weight. * - * @return int|float + * @return float|int */ public function getWeight() { @@ -149,9 +153,10 @@ class Outline extends AbstractStyle } /** - * Set weight + * Set weight. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setWeight($value = null) @@ -162,7 +167,7 @@ class Outline extends AbstractStyle } /** - * Get color + * Get color. * * @return string */ @@ -172,9 +177,10 @@ class Outline extends AbstractStyle } /** - * Set color + * Set color. * * @param string $value + * * @return self */ public function setColor($value = null) @@ -185,7 +191,7 @@ class Outline extends AbstractStyle } /** - * Get dash type + * Get dash type. * * @return string */ @@ -195,9 +201,10 @@ class Outline extends AbstractStyle } /** - * Set dash type + * Set dash type. * * @param string $value + * * @return self */ public function setDash($value = null) @@ -208,7 +215,7 @@ class Outline extends AbstractStyle } /** - * Get line style + * Get line style. * * @return string */ @@ -218,22 +225,23 @@ class Outline extends AbstractStyle } /** - * Set line style + * Set line style. * * @param string $value + * * @return self */ public function setLine($value = null) { - $enum = array(self::LINE_SINGLE, self::LINE_THIN_THIN, self::LINE_THIN_THICK, - self::LINE_THICK_THIN, self::LINE_THICK_BETWEEN_THIN, ); + $enum = [self::LINE_SINGLE, self::LINE_THIN_THIN, self::LINE_THIN_THICK, + self::LINE_THICK_THIN, self::LINE_THICK_BETWEEN_THIN, ]; $this->line = $this->setEnumVal($value, $enum, null); return $this; } /** - * Get endCap style + * Get endCap style. * * @return string */ @@ -243,21 +251,22 @@ class Outline extends AbstractStyle } /** - * Set endCap style + * Set endCap style. * * @param string $value + * * @return self */ public function setEndCap($value = null) { - $enum = array(self::ENDCAP_FLAT, self::ENDCAP_SQUARE, self::ENDCAP_ROUND); + $enum = [self::ENDCAP_FLAT, self::ENDCAP_SQUARE, self::ENDCAP_ROUND]; $this->endCap = $this->setEnumVal($value, $enum, null); return $this; } /** - * Get startArrow + * Get startArrow. * * @return string */ @@ -267,22 +276,23 @@ class Outline extends AbstractStyle } /** - * Set pattern + * Set pattern. * * @param string $value + * * @return self */ public function setStartArrow($value = null) { - $enum = array(self::ARROW_NONE, self::ARROW_BLOCK, self::ARROW_CLASSIC, - self::ARROW_OVAL, self::ARROW_DIAMOND, self::ARROW_OPEN, ); + $enum = [self::ARROW_NONE, self::ARROW_BLOCK, self::ARROW_CLASSIC, + self::ARROW_OVAL, self::ARROW_DIAMOND, self::ARROW_OPEN, ]; $this->startArrow = $this->setEnumVal($value, $enum, null); return $this; } /** - * Get endArrow + * Get endArrow. * * @return string */ @@ -292,15 +302,16 @@ class Outline extends AbstractStyle } /** - * Set pattern + * Set pattern. * * @param string $value + * * @return self */ public function setEndArrow($value = null) { - $enum = array(self::ARROW_NONE, self::ARROW_BLOCK, self::ARROW_CLASSIC, - self::ARROW_OVAL, self::ARROW_DIAMOND, self::ARROW_OPEN, ); + $enum = [self::ARROW_NONE, self::ARROW_BLOCK, self::ARROW_CLASSIC, + self::ARROW_OVAL, self::ARROW_DIAMOND, self::ARROW_OPEN, ]; $this->endArrow = $this->setEnumVal($value, $enum, null); return $this; diff --git a/PhpOffice/PhpWord/Style/Paper.php b/PhpOffice/PhpWord/Style/Paper.php old mode 100755 new mode 100644 index 3c93ed8..3a340bd --- a/PhpOffice/PhpWord/Style/Paper.php +++ b/PhpOffice/PhpWord/Style/Paper.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -20,7 +20,7 @@ namespace PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\Shared\Converter; /** - * Paper size from ISO/IEC 29500-1:2012 pg. 1656-1657 + * Paper size from ISO/IEC 29500-1:2012 pg. 1656-1657. * * 1 = Letter paper (8.5 in. by 11 in.) * 2 = Letter small paper (8.5 in. by 11 in.) @@ -94,43 +94,43 @@ use PhpOffice\PhpWord\Shared\Converter; class Paper extends AbstractStyle { /** - * Paper sizes + * Paper sizes. * * @var array */ - private $sizes = array( - 'A3' => array(297, 420, 'mm'), - 'A4' => array(210, 297, 'mm'), - 'A5' => array(148, 210, 'mm'), - 'B5' => array(176, 250, 'mm'), - 'Folio' => array(8.5, 13, 'in'), - 'Legal' => array(8.5, 14, 'in'), - 'Letter' => array(8.5, 11, 'in'), - ); + private $sizes = [ + 'A3' => [297, 420, 'mm'], + 'A4' => [210, 297, 'mm'], + 'A5' => [148, 210, 'mm'], + 'B5' => [176, 250, 'mm'], + 'Folio' => [8.5, 13, 'in'], + 'Legal' => [8.5, 14, 'in'], + 'Letter' => [8.5, 11, 'in'], + ]; /** - * Paper size + * Paper size. * * @var string */ private $size = 'A4'; /** - * Width + * Width. * * @var float (twip) */ private $width; /** - * Height + * Height. * * @var float (twip) */ private $height; /** - * Create a new instance + * Create a new instance. * * @param string $size */ @@ -140,7 +140,7 @@ class Paper extends AbstractStyle } /** - * Get size + * Get size. * * @return string */ @@ -150,16 +150,17 @@ class Paper extends AbstractStyle } /** - * Set size + * Set size. * * @param string $size + * * @return self */ public function setSize($size) { $this->size = $this->setEnumVal($size, array_keys($this->sizes), $this->size); - list($width, $height, $unit) = $this->sizes[$this->size]; + [$width, $height, $unit] = $this->sizes[$this->size]; if ($unit == 'mm') { $this->width = Converter::cmToTwip($width / 10); @@ -173,7 +174,7 @@ class Paper extends AbstractStyle } /** - * Get width + * Get width. * * @return float */ @@ -183,7 +184,7 @@ class Paper extends AbstractStyle } /** - * Get height + * Get height. * * @return float */ diff --git a/PhpOffice/PhpWord/Style/Paragraph.php b/PhpOffice/PhpWord/Style/Paragraph.php old mode 100755 new mode 100644 index 72f0f80..e220165 --- a/PhpOffice/PhpWord/Style/Paragraph.php +++ b/PhpOffice/PhpWord/Style/Paragraph.php @@ -11,19 +11,19 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Style; -use PhpOffice\Common\Text; use PhpOffice\PhpWord\Exception\InvalidStyleException; +use PhpOffice\PhpWord\Shared\Text; use PhpOffice\PhpWord\SimpleType\Jc; use PhpOffice\PhpWord\SimpleType\TextAlignment; /** - * Paragraph style + * Paragraph style. * * OOXML: * - General: alignment, outline level @@ -57,21 +57,21 @@ class Paragraph extends Border const LINE_HEIGHT = 240; /** - * Aliases + * Aliases. * * @var array */ - protected $aliases = array('line-height' => 'lineHeight', 'line-spacing' => 'spacing'); + protected $aliases = ['line-height' => 'lineHeight', 'line-spacing' => 'spacing']; /** - * Parent style + * Parent style. * * @var string */ private $basedOn = 'Normal'; /** - * Style for next paragraph + * Style for next paragraph. * * @var string */ @@ -83,115 +83,116 @@ class Paragraph extends Border private $alignment = ''; /** - * Indentation + * Indentation. * - * @var \PhpOffice\PhpWord\Style\Indentation + * @var null|\PhpOffice\PhpWord\Style\Indentation */ private $indentation; /** - * Spacing + * Spacing. * * @var \PhpOffice\PhpWord\Style\Spacing */ private $spacing; /** - * Text line height + * Text line height. * * @var int */ private $lineHeight; /** - * Allow first/last line to display on a separate page + * Allow first/last line to display on a separate page. * * @var bool */ private $widowControl = true; /** - * Keep paragraph with next paragraph + * Keep paragraph with next paragraph. * * @var bool */ private $keepNext = false; /** - * Keep all lines on one page + * Keep all lines on one page. * * @var bool */ private $keepLines = false; /** - * Start paragraph on next page + * Start paragraph on next page. * * @var bool */ private $pageBreakBefore = false; /** - * Numbering style name + * Numbering style name. * * @var string */ private $numStyle; /** - * Numbering level + * Numbering level. * * @var int */ private $numLevel = 0; /** - * Set of Custom Tab Stops + * Set of Custom Tab Stops. * * @var \PhpOffice\PhpWord\Style\Tab[] */ - private $tabs = array(); + private $tabs = []; /** - * Shading + * Shading. * * @var \PhpOffice\PhpWord\Style\Shading */ private $shading; /** - * Ignore Spacing Above and Below When Using Identical Styles + * Ignore Spacing Above and Below When Using Identical Styles. * * @var bool */ private $contextualSpacing = false; /** - * Right to Left Paragraph Layout + * Right to Left Paragraph Layout. * * @var bool */ private $bidi = false; /** - * Vertical Character Alignment on Line + * Vertical Character Alignment on Line. * * @var string */ private $textAlignment; /** - * Suppress hyphenation for paragraph + * Suppress hyphenation for paragraph. * * @var bool */ private $suppressAutoHyphens = false; /** - * Set Style value + * Set Style value. * * @param string $key * @param mixed $value + * * @return self */ public function setStyleValue($key, $value) @@ -205,41 +206,42 @@ class Paragraph extends Border } /** - * Get style values + * Get style values. * * An experiment to retrieve all style values in one function. This will * reduce function call and increase cohesion between functions. Should be * implemented in all styles. * * @ignoreScrutinizerPatch + * * @return array */ public function getStyleValues() { - $styles = array( - 'name' => $this->getStyleName(), - 'basedOn' => $this->getBasedOn(), - 'next' => $this->getNext(), - 'alignment' => $this->getAlignment(), - 'indentation' => $this->getIndentation(), - 'spacing' => $this->getSpace(), - 'pagination' => array( - 'widowControl' => $this->hasWidowControl(), - 'keepNext' => $this->isKeepNext(), - 'keepLines' => $this->isKeepLines(), - 'pageBreak' => $this->hasPageBreakBefore(), - ), - 'numbering' => array( - 'style' => $this->getNumStyle(), - 'level' => $this->getNumLevel(), - ), - 'tabs' => $this->getTabs(), - 'shading' => $this->getShading(), - 'contextualSpacing' => $this->hasContextualSpacing(), - 'bidi' => $this->isBidi(), - 'textAlignment' => $this->getTextAlignment(), + $styles = [ + 'name' => $this->getStyleName(), + 'basedOn' => $this->getBasedOn(), + 'next' => $this->getNext(), + 'alignment' => $this->getAlignment(), + 'indentation' => $this->getIndentation(), + 'spacing' => $this->getSpace(), + 'pagination' => [ + 'widowControl' => $this->hasWidowControl(), + 'keepNext' => $this->isKeepNext(), + 'keepLines' => $this->isKeepLines(), + 'pageBreak' => $this->hasPageBreakBefore(), + ], + 'numbering' => [ + 'style' => $this->getNumStyle(), + 'level' => $this->getNumLevel(), + ], + 'tabs' => $this->getTabs(), + 'shading' => $this->getShading(), + 'contextualSpacing' => $this->hasContextualSpacing(), + 'bidi' => $this->isBidi(), + 'textAlignment' => $this->getTextAlignment(), 'suppressAutoHyphens' => $this->hasSuppressAutoHyphens(), - ); + ]; return $styles; } @@ -271,33 +273,7 @@ class Paragraph extends Border } /** - * @deprecated 0.13.0 Use the `getAlignment` method instead. - * - * @return string - * - * @codeCoverageIgnore - */ - public function getAlign() - { - return $this->getAlignment(); - } - - /** - * @deprecated 0.13.0 Use the `setAlignment` method instead. - * - * @param string $value - * - * @return self - * - * @codeCoverageIgnore - */ - public function setAlign($value = null) - { - return $this->setAlignment($value); - } - - /** - * Get parent style ID + * Get parent style ID. * * @return string */ @@ -307,9 +283,10 @@ class Paragraph extends Border } /** - * Set parent style ID + * Set parent style ID. * * @param string $value + * * @return self */ public function setBasedOn($value = 'Normal') @@ -320,7 +297,7 @@ class Paragraph extends Border } /** - * Get style for next paragraph + * Get style for next paragraph. * * @return string */ @@ -330,9 +307,10 @@ class Paragraph extends Border } /** - * Set style for next paragraph + * Set style for next paragraph. * * @param string $value + * * @return self */ public function setNext($value = null) @@ -343,7 +321,7 @@ class Paragraph extends Border } /** - * Get shading + * Get shading. * * @return \PhpOffice\PhpWord\Style\Indentation */ @@ -353,9 +331,10 @@ class Paragraph extends Border } /** - * Set shading + * Set shading. * * @param mixed $value + * * @return self */ public function setIndentation($value = null) @@ -366,7 +345,7 @@ class Paragraph extends Border } /** - * Get indentation + * Get indentation. * * @return int */ @@ -376,18 +355,19 @@ class Paragraph extends Border } /** - * Set indentation + * Set indentation. * * @param int $value + * * @return self */ public function setIndent($value = null) { - return $this->setIndentation(array('left' => $value)); + return $this->setIndentation(['left' => $value]); } /** - * Get hanging + * Get hanging. * * @return int */ @@ -397,20 +377,22 @@ class Paragraph extends Border } /** - * Set hanging + * Set hanging. * * @param int $value + * * @return self */ public function setHanging($value = null) { - return $this->setIndentation(array('hanging' => $value)); + return $this->setIndentation(['hanging' => $value]); } /** - * Get spacing + * Get spacing. * * @return \PhpOffice\PhpWord\Style\Spacing + * * @todo Rename to getSpacing in 1.0 */ public function getSpace() @@ -419,10 +401,12 @@ class Paragraph extends Border } /** - * Set spacing + * Set spacing. * * @param mixed $value + * * @return self + * * @todo Rename to setSpacing in 1.0 */ public function setSpace($value = null) @@ -433,7 +417,7 @@ class Paragraph extends Border } /** - * Get space before paragraph + * Get space before paragraph. * * @return int */ @@ -443,18 +427,19 @@ class Paragraph extends Border } /** - * Set space before paragraph + * Set space before paragraph. * * @param int $value + * * @return self */ public function setSpaceBefore($value = null) { - return $this->setSpace(array('before' => $value)); + return $this->setSpace(['before' => $value]); } /** - * Get space after paragraph + * Get space after paragraph. * * @return int */ @@ -464,20 +449,21 @@ class Paragraph extends Border } /** - * Set space after paragraph + * Set space after paragraph. * * @param int $value + * * @return self */ public function setSpaceAfter($value = null) { - return $this->setSpace(array('after' => $value)); + return $this->setSpace(['after' => $value]); } /** - * Get spacing between lines + * Get spacing between lines. * - * @return int|float + * @return float|int */ public function getSpacing() { @@ -485,18 +471,19 @@ class Paragraph extends Border } /** - * Set spacing between lines + * Set spacing between lines. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setSpacing($value = null) { - return $this->setSpace(array('line' => $value)); + return $this->setSpace(['line' => $value]); } /** - * Get spacing line rule + * Get spacing line rule. * * @return string */ @@ -506,20 +493,21 @@ class Paragraph extends Border } /** - * Set the spacing line rule + * Set the spacing line rule. * * @param string $value Possible values are defined in LineSpacingRule + * * @return \PhpOffice\PhpWord\Style\Paragraph */ public function setSpacingLineRule($value) { - return $this->setSpace(array('lineRule' => $value)); + return $this->setSpace(['lineRule' => $value]); } /** - * Get line height + * Get line height. * - * @return int|float + * @return float|int */ public function getLineHeight() { @@ -527,11 +515,10 @@ class Paragraph extends Border } /** - * Set the line height + * Set the line height. * - * @param int|float|string $lineHeight + * @param float|int|string $lineHeight * - * @throws \PhpOffice\PhpWord\Exception\InvalidStyleException * @return self */ public function setLineHeight($lineHeight) @@ -552,7 +539,7 @@ class Paragraph extends Border } /** - * Get allow first/last line to display on a separate page setting + * Get allow first/last line to display on a separate page setting. * * @return bool */ @@ -562,9 +549,10 @@ class Paragraph extends Border } /** - * Set keep paragraph with next paragraph setting + * Set keep paragraph with next paragraph setting. * * @param bool $value + * * @return self */ public function setWidowControl($value = true) @@ -575,7 +563,7 @@ class Paragraph extends Border } /** - * Get keep paragraph with next paragraph setting + * Get keep paragraph with next paragraph setting. * * @return bool */ @@ -585,9 +573,10 @@ class Paragraph extends Border } /** - * Set keep paragraph with next paragraph setting + * Set keep paragraph with next paragraph setting. * * @param bool $value + * * @return self */ public function setKeepNext($value = true) @@ -598,7 +587,7 @@ class Paragraph extends Border } /** - * Get keep all lines on one page setting + * Get keep all lines on one page setting. * * @return bool */ @@ -608,9 +597,10 @@ class Paragraph extends Border } /** - * Set keep all lines on one page setting + * Set keep all lines on one page setting. * * @param bool $value + * * @return self */ public function setKeepLines($value = true) @@ -621,7 +611,7 @@ class Paragraph extends Border } /** - * Get start paragraph on next page setting + * Get start paragraph on next page setting. * * @return bool */ @@ -631,9 +621,10 @@ class Paragraph extends Border } /** - * Set start paragraph on next page setting + * Set start paragraph on next page setting. * * @param bool $value + * * @return self */ public function setPageBreakBefore($value = true) @@ -644,7 +635,7 @@ class Paragraph extends Border } /** - * Get numbering style name + * Get numbering style name. * * @return string */ @@ -654,9 +645,10 @@ class Paragraph extends Border } /** - * Set numbering style name + * Set numbering style name. * * @param string $value + * * @return self */ public function setNumStyle($value) @@ -667,7 +659,7 @@ class Paragraph extends Border } /** - * Get numbering level + * Get numbering level. * * @return int */ @@ -677,9 +669,10 @@ class Paragraph extends Border } /** - * Set numbering level + * Set numbering level. * * @param int $value + * * @return self */ public function setNumLevel($value = 0) @@ -690,7 +683,7 @@ class Paragraph extends Border } /** - * Get tabs + * Get tabs. * * @return \PhpOffice\PhpWord\Style\Tab[] */ @@ -700,9 +693,10 @@ class Paragraph extends Border } /** - * Set tabs + * Set tabs. * * @param array $value + * * @return self */ public function setTabs($value = null) @@ -715,55 +709,7 @@ class Paragraph extends Border } /** - * Get allow first/last line to display on a separate page setting - * - * @deprecated 0.10.0 - * - * @codeCoverageIgnore - */ - public function getWidowControl() - { - return $this->hasWidowControl(); - } - - /** - * Get keep paragraph with next paragraph setting - * - * @deprecated 0.10.0 - * - * @codeCoverageIgnore - */ - public function getKeepNext() - { - return $this->isKeepNext(); - } - - /** - * Get keep all lines on one page setting - * - * @deprecated 0.10.0 - * - * @codeCoverageIgnore - */ - public function getKeepLines() - { - return $this->isKeepLines(); - } - - /** - * Get start paragraph on next page setting - * - * @deprecated 0.10.0 - * - * @codeCoverageIgnore - */ - public function getPageBreakBefore() - { - return $this->hasPageBreakBefore(); - } - - /** - * Get shading + * Get shading. * * @return \PhpOffice\PhpWord\Style\Shading */ @@ -773,9 +719,10 @@ class Paragraph extends Border } /** - * Set shading + * Set shading. * * @param mixed $value + * * @return self */ public function setShading($value = null) @@ -786,7 +733,7 @@ class Paragraph extends Border } /** - * Get contextualSpacing + * Get contextualSpacing. * * @return bool */ @@ -796,9 +743,10 @@ class Paragraph extends Border } /** - * Set contextualSpacing + * Set contextualSpacing. * * @param bool $contextualSpacing + * * @return self */ public function setContextualSpacing($contextualSpacing) @@ -809,7 +757,7 @@ class Paragraph extends Border } /** - * Get bidirectional + * Get bidirectional. * * @return bool */ @@ -819,10 +767,11 @@ class Paragraph extends Border } /** - * Set bidi + * Set bidi. * * @param bool $bidi * Set to true to write from right to left + * * @return self */ public function setBidi($bidi) @@ -833,7 +782,7 @@ class Paragraph extends Border } /** - * Get textAlignment + * Get textAlignment. * * @return string */ @@ -843,9 +792,10 @@ class Paragraph extends Border } /** - * Set textAlignment + * Set textAlignment. * * @param string $textAlignment + * * @return self */ public function setTextAlignment($textAlignment) @@ -867,7 +817,7 @@ class Paragraph extends Border /** * @param bool $suppressAutoHyphens */ - public function setSuppressAutoHyphens($suppressAutoHyphens) + public function setSuppressAutoHyphens($suppressAutoHyphens): void { $this->suppressAutoHyphens = (bool) $suppressAutoHyphens; } diff --git a/PhpOffice/PhpWord/Style/Row.php b/PhpOffice/PhpWord/Style/Row.php old mode 100755 new mode 100644 index ad801af..765c54f --- a/PhpOffice/PhpWord/Style/Row.php +++ b/PhpOffice/PhpWord/Style/Row.php @@ -11,49 +11,49 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Style; /** - * Table row style + * Table row style. * * @since 0.8.0 */ class Row extends AbstractStyle { /** - * Repeat table row on every new page + * Repeat table row on every new page. * * @var bool */ private $tblHeader = false; /** - * Table row cannot break across pages + * Table row cannot break across pages. * * @var bool */ private $cantSplit = false; /** - * Table row exact height + * Table row exact height. * * @var bool */ private $exactHeight = false; /** - * Create a new row style + * Create a new row style. */ public function __construct() { } /** - * Is tblHeader + * Is tblHeader. * * @return bool */ @@ -63,9 +63,10 @@ class Row extends AbstractStyle } /** - * Is tblHeader + * Is tblHeader. * * @param bool $value + * * @return self */ public function setTblHeader($value = true) @@ -76,7 +77,7 @@ class Row extends AbstractStyle } /** - * Is cantSplit + * Is cantSplit. * * @return bool */ @@ -86,9 +87,10 @@ class Row extends AbstractStyle } /** - * Is cantSplit + * Is cantSplit. * * @param bool $value + * * @return self */ public function setCantSplit($value = true) @@ -99,7 +101,7 @@ class Row extends AbstractStyle } /** - * Is exactHeight + * Is exactHeight. * * @return bool */ @@ -109,9 +111,10 @@ class Row extends AbstractStyle } /** - * Set exactHeight + * Set exactHeight. * * @param bool $value + * * @return self */ public function setExactHeight($value = true) @@ -120,40 +123,4 @@ class Row extends AbstractStyle return $this; } - - /** - * Get tblHeader - * - * @deprecated 0.10.0 - * - * @codeCoverageIgnore - */ - public function getTblHeader() - { - return $this->isTblHeader(); - } - - /** - * Get cantSplit - * - * @deprecated 0.10.0 - * - * @codeCoverageIgnore - */ - public function getCantSplit() - { - return $this->isCantSplit(); - } - - /** - * Get exactHeight - * - * @deprecated 0.10.0 - * - * @codeCoverageIgnore - */ - public function getExactHeight() - { - return $this->isExactHeight(); - } } diff --git a/PhpOffice/PhpWord/Style/Section.php b/PhpOffice/PhpWord/Style/Section.php old mode 100755 new mode 100644 index ff9b0be..29e06bf --- a/PhpOffice/PhpWord/Style/Section.php +++ b/PhpOffice/PhpWord/Style/Section.php @@ -11,21 +11,22 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Style; +use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\SimpleType\VerticalJc; /** - * Section settings + * Section settings. */ class Section extends Border { /** - * Page orientation + * Page orientation. * * @const string */ @@ -33,7 +34,7 @@ class Section extends Border const ORIENTATION_LANDSCAPE = 'landscape'; /** - * Page default constants + * Page default constants. * * @const int|float */ @@ -47,107 +48,109 @@ class Section extends Border const DEFAULT_COLUMN_SPACING = 720; // In twips. /** - * Page Orientation + * Page Orientation. * * @var string + * * @see http://www.schemacentral.com/sc/ooxml/a-w_orient-1.html */ private $orientation = self::ORIENTATION_PORTRAIT; /** - * Paper size + * Paper size. * * @var \PhpOffice\PhpWord\Style\Paper */ private $paper; /** - * Page Size Width + * Page Size Width. * - * @var int|float + * @var float|int */ private $pageSizeW = self::DEFAULT_WIDTH; /** - * Page Size Height + * Page Size Height. * - * @var int|float + * @var float|int */ private $pageSizeH = self::DEFAULT_HEIGHT; /** - * Top margin spacing + * Top margin spacing. * - * @var int|float + * @var float|int */ private $marginTop = self::DEFAULT_MARGIN; /** - * Left margin spacing + * Left margin spacing. * - * @var int|float + * @var float|int */ private $marginLeft = self::DEFAULT_MARGIN; /** - * Right margin spacing + * Right margin spacing. * - * @var int|float + * @var float|int */ private $marginRight = self::DEFAULT_MARGIN; /** - * Bottom margin spacing + * Bottom margin spacing. * - * @var int|float + * @var float|int */ private $marginBottom = self::DEFAULT_MARGIN; /** - * Page gutter spacing + * Page gutter spacing. + * + * @var float|int * - * @var int|float * @see http://www.schemacentral.com/sc/ooxml/e-w_pgMar-1.html */ private $gutter = self::DEFAULT_GUTTER; /** - * Header height + * Header height. * - * @var int|float + * @var float|int */ private $headerHeight = self::DEFAULT_HEADER_HEIGHT; /** - * Footer height + * Footer height. * - * @var int|float + * @var float|int */ private $footerHeight = self::DEFAULT_FOOTER_HEIGHT; /** - * Page Numbering Start + * Page Numbering Start. * * @var int */ private $pageNumberingStart; /** - * Section columns count + * Section columns count. * * @var int */ private $colsNum = self::DEFAULT_COLUMN_COUNT; /** - * Section spacing between columns + * Section spacing between columns. * - * @var int|float + * @var float|int */ private $colsSpace = self::DEFAULT_COLUMN_SPACING; /** - * Section break type + * Section break type. * * Options: * - nextPage: Next page section break @@ -161,23 +164,24 @@ class Section extends Border private $breakType; /** - * Line numbering + * Line numbering. * * @var \PhpOffice\PhpWord\Style\LineNumbering + * * @see http://www.schemacentral.com/sc/ooxml/e-w_lnNumType-1.html */ private $lineNumbering; /** * Vertical Text Alignment on Page - * One of \PhpOffice\PhpWord\SimpleType\VerticalJc + * One of \PhpOffice\PhpWord\SimpleType\VerticalJc. * * @var string */ private $vAlign; /** - * Create new instance + * Create new instance. */ public function __construct() { @@ -185,7 +189,7 @@ class Section extends Border } /** - * Get paper size + * Get paper size. * * @return string */ @@ -195,13 +199,17 @@ class Section extends Border } /** - * Set paper size + * Set paper size. * * @param string $value + * * @return self */ - public function setPaperSize($value = 'A4') + public function setPaperSize($value = '') { + if (!$value) { + $value = Settings::getDefaultPaper(); + } if ($this->paper === null) { $this->paper = new Paper(); } @@ -213,10 +221,11 @@ class Section extends Border } /** - * Set Setting Value + * Set Setting Value. * * @param string $key * @param string $value + * * @return self */ public function setSettingValue($key, $value) @@ -225,20 +234,21 @@ class Section extends Border } /** - * Set orientation + * Set orientation. * * @param string $value + * * @return self */ public function setOrientation($value = null) { - $enum = array(self::ORIENTATION_PORTRAIT, self::ORIENTATION_LANDSCAPE); + $enum = [self::ORIENTATION_PORTRAIT, self::ORIENTATION_LANDSCAPE]; $this->orientation = $this->setEnumVal($value, $enum, $this->orientation); - /** @var int|float $longSide Type hint */ + /** @var float|int $longSide Type hint */ $longSide = $this->pageSizeW >= $this->pageSizeH ? $this->pageSizeW : $this->pageSizeH; - /** @var int|float $shortSide Type hint */ + /** @var float|int $shortSide Type hint */ $shortSide = $this->pageSizeW < $this->pageSizeH ? $this->pageSizeW : $this->pageSizeH; if ($this->orientation == self::ORIENTATION_PORTRAIT) { @@ -253,7 +263,7 @@ class Section extends Border } /** - * Get Page Orientation + * Get Page Orientation. * * @return string */ @@ -263,7 +273,7 @@ class Section extends Border } /** - * Set Portrait Orientation + * Set Portrait Orientation. * * @return self */ @@ -273,7 +283,7 @@ class Section extends Border } /** - * Set Landscape Orientation + * Set Landscape Orientation. * * @return self */ @@ -283,9 +293,9 @@ class Section extends Border } /** - * Get Page Size Width + * Get Page Size Width. * - * @return int|float|null + * @return null|float|int * * @since 0.12.0 */ @@ -295,7 +305,7 @@ class Section extends Border } /** - * @param int|float|null $value + * @param null|float|int $value * * @return \PhpOffice\PhpWord\Style\Section * @@ -309,9 +319,9 @@ class Section extends Border } /** - * Get Page Size Height + * Get Page Size Height. * - * @return int|float|null + * @return null|float|int * * @since 0.12.0 */ @@ -321,7 +331,7 @@ class Section extends Border } /** - * @param int|float|null $value + * @param null|float|int $value * * @return \PhpOffice\PhpWord\Style\Section * @@ -335,9 +345,9 @@ class Section extends Border } /** - * Get Margin Top + * Get Margin Top. * - * @return int|float + * @return float|int */ public function getMarginTop() { @@ -345,9 +355,10 @@ class Section extends Border } /** - * Set Margin Top + * Set Margin Top. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setMarginTop($value = null) @@ -358,9 +369,9 @@ class Section extends Border } /** - * Get Margin Left + * Get Margin Left. * - * @return int|float + * @return float|int */ public function getMarginLeft() { @@ -368,9 +379,10 @@ class Section extends Border } /** - * Set Margin Left + * Set Margin Left. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setMarginLeft($value = null) @@ -381,9 +393,9 @@ class Section extends Border } /** - * Get Margin Right + * Get Margin Right. * - * @return int|float + * @return float|int */ public function getMarginRight() { @@ -391,9 +403,10 @@ class Section extends Border } /** - * Set Margin Right + * Set Margin Right. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setMarginRight($value = null) @@ -404,9 +417,9 @@ class Section extends Border } /** - * Get Margin Bottom + * Get Margin Bottom. * - * @return int|float + * @return float|int */ public function getMarginBottom() { @@ -414,9 +427,10 @@ class Section extends Border } /** - * Set Margin Bottom + * Set Margin Bottom. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setMarginBottom($value = null) @@ -427,9 +441,9 @@ class Section extends Border } /** - * Get gutter + * Get gutter. * - * @return int|float + * @return float|int */ public function getGutter() { @@ -437,9 +451,10 @@ class Section extends Border } /** - * Set gutter + * Set gutter. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setGutter($value = null) @@ -450,9 +465,9 @@ class Section extends Border } /** - * Get Header Height + * Get Header Height. * - * @return int|float + * @return float|int */ public function getHeaderHeight() { @@ -460,9 +475,10 @@ class Section extends Border } /** - * Set Header Height + * Set Header Height. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setHeaderHeight($value = null) @@ -473,9 +489,9 @@ class Section extends Border } /** - * Get Footer Height + * Get Footer Height. * - * @return int|float + * @return float|int */ public function getFooterHeight() { @@ -483,9 +499,10 @@ class Section extends Border } /** - * Set Footer Height + * Set Footer Height. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setFooterHeight($value = null) @@ -496,7 +513,7 @@ class Section extends Border } /** - * Get page numbering start + * Get page numbering start. * * @return null|int */ @@ -506,9 +523,10 @@ class Section extends Border } /** - * Set page numbering start + * Set page numbering start. * * @param null|int $pageNumberingStart + * * @return self */ public function setPageNumberingStart($pageNumberingStart = null) @@ -519,7 +537,7 @@ class Section extends Border } /** - * Get Section Columns Count + * Get Section Columns Count. * * @return int */ @@ -529,9 +547,10 @@ class Section extends Border } /** - * Set Section Columns Count + * Set Section Columns Count. * * @param int $value + * * @return self */ public function setColsNum($value = null) @@ -542,9 +561,9 @@ class Section extends Border } /** - * Get Section Space Between Columns + * Get Section Space Between Columns. * - * @return int|float + * @return float|int */ public function getColsSpace() { @@ -552,9 +571,10 @@ class Section extends Border } /** - * Set Section Space Between Columns + * Set Section Space Between Columns. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setColsSpace($value = null) @@ -565,7 +585,7 @@ class Section extends Border } /** - * Get Break Type + * Get Break Type. * * @return string */ @@ -575,9 +595,10 @@ class Section extends Border } /** - * Set Break Type + * Set Break Type. * * @param string $value + * * @return self */ public function setBreakType($value = null) @@ -588,7 +609,7 @@ class Section extends Border } /** - * Get line numbering + * Get line numbering. * * @return \PhpOffice\PhpWord\Style\LineNumbering */ @@ -598,9 +619,10 @@ class Section extends Border } /** - * Set line numbering + * Set line numbering. * * @param mixed $value + * * @return self */ public function setLineNumbering($value = null) @@ -611,7 +633,7 @@ class Section extends Border } /** - * Get vertical alignment + * Get vertical alignment. * * @return string */ @@ -621,9 +643,10 @@ class Section extends Border } /** - * Set vertical alignment + * Set vertical alignment. * * @param string $value + * * @return self */ public function setVAlign($value = null) diff --git a/PhpOffice/PhpWord/Style/Shading.php b/PhpOffice/PhpWord/Style/Shading.php old mode 100755 new mode 100644 index 154df26..81d69e3 --- a/PhpOffice/PhpWord/Style/Shading.php +++ b/PhpOffice/PhpWord/Style/Shading.php @@ -11,14 +11,14 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Style; /** - * Shading style + * Shading style. * * @see http://www.schemacentral.com/sc/ooxml/t-w_CT_Shd.html * @since 0.10.0 @@ -26,9 +26,10 @@ namespace PhpOffice\PhpWord\Style; class Shading extends AbstractStyle { /** - * Pattern constants (partly) + * Pattern constants (partly). * * @const string + * * @see http://www.schemacentral.com/sc/ooxml/t-w_ST_Shd.html */ const PATTERN_CLEAR = 'clear'; // No pattern @@ -40,39 +41,40 @@ class Shading extends AbstractStyle const PATTERN_DCROSS = 'diagCross'; // Diagonal cross pattern /** - * Shading pattern + * Shading pattern. * * @var string + * * @see http://www.schemacentral.com/sc/ooxml/t-w_ST_Shd.html */ private $pattern = self::PATTERN_CLEAR; /** - * Shading pattern color + * Shading pattern color. * * @var string */ private $color; /** - * Shading background color + * Shading background color. * * @var string */ private $fill; /** - * Create a new instance + * Create a new instance. * * @param array $style */ - public function __construct($style = array()) + public function __construct($style = []) { $this->setStyleByArray($style); } /** - * Get pattern + * Get pattern. * * @return string */ @@ -82,24 +84,25 @@ class Shading extends AbstractStyle } /** - * Set pattern + * Set pattern. * * @param string $value + * * @return self */ public function setPattern($value = null) { - $enum = array( + $enum = [ self::PATTERN_CLEAR, self::PATTERN_SOLID, self::PATTERN_HSTRIPE, self::PATTERN_VSTRIPE, self::PATTERN_DSTRIPE, self::PATTERN_HCROSS, self::PATTERN_DCROSS, - ); + ]; $this->pattern = $this->setEnumVal($value, $enum, $this->pattern); return $this; } /** - * Get color + * Get color. * * @return string */ @@ -109,9 +112,10 @@ class Shading extends AbstractStyle } /** - * Set pattern + * Set pattern. * * @param string $value + * * @return self */ public function setColor($value = null) @@ -122,7 +126,7 @@ class Shading extends AbstractStyle } /** - * Get fill + * Get fill. * * @return string */ @@ -132,9 +136,10 @@ class Shading extends AbstractStyle } /** - * Set fill + * Set fill. * * @param string $value + * * @return self */ public function setFill($value = null) diff --git a/PhpOffice/PhpWord/Style/Shadow.php b/PhpOffice/PhpWord/Style/Shadow.php old mode 100755 new mode 100644 index 1379a32..3f6252d --- a/PhpOffice/PhpWord/Style/Shadow.php +++ b/PhpOffice/PhpWord/Style/Shadow.php @@ -11,14 +11,14 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Style; /** - * Shadow style + * Shadow style. * * @see http://www.schemacentral.com/sc/ooxml/t-v_CT_Shadow.html * @since 0.12.0 @@ -26,31 +26,31 @@ namespace PhpOffice\PhpWord\Style; class Shadow extends AbstractStyle { /** - * Color + * Color. * * @var string */ private $color; /** - * Offset; Format: 3pt,3pt + * Offset; Format: 3pt,3pt. * * @var string */ private $offset; /** - * Create a new instance + * Create a new instance. * * @param array $style */ - public function __construct($style = array()) + public function __construct($style = []) { $this->setStyleByArray($style); } /** - * Get color + * Get color. * * @return string */ @@ -60,9 +60,10 @@ class Shadow extends AbstractStyle } /** - * Set color + * Set color. * * @param string $value + * * @return self */ public function setColor($value = null) @@ -73,7 +74,7 @@ class Shadow extends AbstractStyle } /** - * Get offset + * Get offset. * * @return string */ @@ -83,9 +84,10 @@ class Shadow extends AbstractStyle } /** - * Set offset + * Set offset. * * @param string $value + * * @return self */ public function setOffset($value = null) diff --git a/PhpOffice/PhpWord/Style/Shape.php b/PhpOffice/PhpWord/Style/Shape.php old mode 100755 new mode 100644 index 0c3f817..7dd62a1 --- a/PhpOffice/PhpWord/Style/Shape.php +++ b/PhpOffice/PhpWord/Style/Shape.php @@ -11,22 +11,23 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Style; /** - * Shape style + * Shape style. * * @since 0.12.0 + * * @todo Skew http://www.schemacentral.com/sc/ooxml/t-o_CT_Skew.html */ class Shape extends AbstractStyle { /** - * Points + * Points. * * - Arc: startAngle endAngle; 0 = top center, moving clockwise * - Curve: from-x1,from-y1 to-x2,to-y2 control1-x,control1-y control2-x,control2-y @@ -39,61 +40,61 @@ class Shape extends AbstractStyle private $points; /** - * Roundness measure of corners; 0 = straightest (rectangular); 1 = roundest (circle/oval) + * Roundness measure of corners; 0 = straightest (rectangular); 1 = roundest (circle/oval). * * Only for rect * - * @var int|float + * @var float|int */ private $roundness; /** - * Frame + * Frame. * * @var \PhpOffice\PhpWord\Style\Frame */ private $frame; /** - * Fill + * Fill. * * @var \PhpOffice\PhpWord\Style\Fill */ private $fill; /** - * Outline + * Outline. * * @var \PhpOffice\PhpWord\Style\Outline */ private $outline; /** - * Shadow + * Shadow. * * @var \PhpOffice\PhpWord\Style\Shadow */ private $shadow; /** - * 3D extrusion + * 3D extrusion. * * @var \PhpOffice\PhpWord\Style\Extrusion */ private $extrusion; /** - * Create a new instance + * Create a new instance. * * @param array $style */ - public function __construct($style = array()) + public function __construct($style = []) { $this->setStyleByArray($style); } /** - * Get points + * Get points. * * @return string */ @@ -103,9 +104,10 @@ class Shape extends AbstractStyle } /** - * Set points + * Set points. * * @param string $value + * * @return self */ public function setPoints($value = null) @@ -116,9 +118,9 @@ class Shape extends AbstractStyle } /** - * Get roundness + * Get roundness. * - * @return int|float + * @return float|int */ public function getRoundness() { @@ -126,9 +128,10 @@ class Shape extends AbstractStyle } /** - * Set roundness + * Set roundness. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setRoundness($value = null) @@ -139,7 +142,7 @@ class Shape extends AbstractStyle } /** - * Get frame + * Get frame. * * @return \PhpOffice\PhpWord\Style\Frame */ @@ -149,9 +152,10 @@ class Shape extends AbstractStyle } /** - * Set frame + * Set frame. * * @param mixed $value + * * @return self */ public function setFrame($value = null) @@ -162,7 +166,7 @@ class Shape extends AbstractStyle } /** - * Get fill + * Get fill. * * @return \PhpOffice\PhpWord\Style\Fill */ @@ -172,9 +176,10 @@ class Shape extends AbstractStyle } /** - * Set fill + * Set fill. * * @param mixed $value + * * @return self */ public function setFill($value = null) @@ -185,7 +190,7 @@ class Shape extends AbstractStyle } /** - * Get outline + * Get outline. * * @return \PhpOffice\PhpWord\Style\Outline */ @@ -195,9 +200,10 @@ class Shape extends AbstractStyle } /** - * Set outline + * Set outline. * * @param mixed $value + * * @return self */ public function setOutline($value = null) @@ -208,7 +214,7 @@ class Shape extends AbstractStyle } /** - * Get shadow + * Get shadow. * * @return \PhpOffice\PhpWord\Style\Shadow */ @@ -218,9 +224,10 @@ class Shape extends AbstractStyle } /** - * Set shadow + * Set shadow. * * @param mixed $value + * * @return self */ public function setShadow($value = null) @@ -231,7 +238,7 @@ class Shape extends AbstractStyle } /** - * Get 3D extrusion + * Get 3D extrusion. * * @return \PhpOffice\PhpWord\Style\Extrusion */ @@ -241,9 +248,10 @@ class Shape extends AbstractStyle } /** - * Set 3D extrusion + * Set 3D extrusion. * * @param mixed $value + * * @return self */ public function setExtrusion($value = null) diff --git a/PhpOffice/PhpWord/Style/Spacing.php b/PhpOffice/PhpWord/Style/Spacing.php old mode 100755 new mode 100644 index 9bfb228..7807065 --- a/PhpOffice/PhpWord/Style/Spacing.php +++ b/PhpOffice/PhpWord/Style/Spacing.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -20,7 +20,7 @@ namespace PhpOffice\PhpWord\Style; use PhpOffice\PhpWord\SimpleType\LineSpacingRule; /** - * Spacing between lines and above/below paragraph style + * Spacing between lines and above/below paragraph style. * * @see http://www.datypic.com/sc/ooxml/t-w_CT_Spacing.html * @since 0.10.0 @@ -28,47 +28,47 @@ use PhpOffice\PhpWord\SimpleType\LineSpacingRule; class Spacing extends AbstractStyle { /** - * Spacing above paragraph (twip) + * Spacing above paragraph (twip). * - * @var int|float + * @var float|int */ private $before; /** - * Spacing below paragraph (twip) + * Spacing below paragraph (twip). * - * @var int|float + * @var float|int */ private $after; /** - * Spacing between lines in paragraph (twip) + * Spacing between lines in paragraph (twip). * - * @var int|float + * @var float|int */ private $line; /** - * Type of spacing between lines + * Type of spacing between lines. * * @var string */ private $lineRule = LineSpacingRule::AUTO; /** - * Create a new instance + * Create a new instance. * * @param array $style */ - public function __construct($style = array()) + public function __construct($style = []) { $this->setStyleByArray($style); } /** - * Get before + * Get before. * - * @return int|float + * @return float|int */ public function getBefore() { @@ -76,9 +76,10 @@ class Spacing extends AbstractStyle } /** - * Set before + * Set before. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setBefore($value = null) @@ -89,9 +90,9 @@ class Spacing extends AbstractStyle } /** - * Get after + * Get after. * - * @return int|float + * @return float|int */ public function getAfter() { @@ -99,9 +100,10 @@ class Spacing extends AbstractStyle } /** - * Set after + * Set after. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setAfter($value = null) @@ -112,9 +114,9 @@ class Spacing extends AbstractStyle } /** - * Get line + * Get line. * - * @return int|float + * @return float|int */ public function getLine() { @@ -122,9 +124,10 @@ class Spacing extends AbstractStyle } /** - * Set distance + * Set distance. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setLine($value = null) @@ -135,7 +138,7 @@ class Spacing extends AbstractStyle } /** - * Get line rule + * Get line rule. * * @return string */ @@ -145,9 +148,10 @@ class Spacing extends AbstractStyle } /** - * Set line rule + * Set line rule. * * @param string $value + * * @return self */ public function setLineRule($value = null) @@ -157,31 +161,4 @@ class Spacing extends AbstractStyle return $this; } - - /** - * Get line rule - * - * @return string - * @deprecated Use getLineRule() instead - * @codeCoverageIgnore - */ - public function getRule() - { - return $this->lineRule; - } - - /** - * Set line rule - * - * @param string $value - * @return self - * @deprecated Use setLineRule() instead - * @codeCoverageIgnore - */ - public function setRule($value = null) - { - $this->lineRule = $value; - - return $this; - } } diff --git a/PhpOffice/PhpWord/Style/TOC.php b/PhpOffice/PhpWord/Style/TOC.php old mode 100755 new mode 100644 index 2efd54a..bd0d906 --- a/PhpOffice/PhpWord/Style/TOC.php +++ b/PhpOffice/PhpWord/Style/TOC.php @@ -11,38 +11,26 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Style; /** - * TOC style + * TOC style. */ class TOC extends Tab { /** - * Tab leader types for backward compatibility + * Indent. * - * @deprecated 0.11.0 - * - * @const string - */ - const TABLEADER_DOT = self::TAB_LEADER_DOT; - const TABLEADER_UNDERSCORE = self::TAB_LEADER_UNDERSCORE; - const TABLEADER_LINE = self::TAB_LEADER_HYPHEN; - const TABLEADER_NONE = self::TAB_LEADER_NONE; - - /** - * Indent - * - * @var int|float (twip) + * @var float|int (twip) */ private $indent = 200; /** - * Create a new TOC Style + * Create a new TOC Style. */ public function __construct() { @@ -50,9 +38,9 @@ class TOC extends Tab } /** - * Get Tab Position + * Get Tab Position. * - * @return int|float + * @return float|int */ public function getTabPos() { @@ -60,9 +48,10 @@ class TOC extends Tab } /** - * Set Tab Position + * Set Tab Position. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setTabPos($value) @@ -71,7 +60,7 @@ class TOC extends Tab } /** - * Get Tab Leader + * Get Tab Leader. * * @return string */ @@ -81,9 +70,10 @@ class TOC extends Tab } /** - * Set Tab Leader + * Set Tab Leader. * * @param string $value + * * @return self */ public function setTabLeader($value = self::TAB_LEADER_DOT) @@ -92,9 +82,9 @@ class TOC extends Tab } /** - * Get Indent + * Get Indent. * - * @return int|float + * @return float|int */ public function getIndent() { @@ -102,9 +92,10 @@ class TOC extends Tab } /** - * Set Indent + * Set Indent. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setIndent($value) diff --git a/PhpOffice/PhpWord/Style/Tab.php b/PhpOffice/PhpWord/Style/Tab.php old mode 100755 new mode 100644 index d3cf5bd..02158fd --- a/PhpOffice/PhpWord/Style/Tab.php +++ b/PhpOffice/PhpWord/Style/Tab.php @@ -11,19 +11,19 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Style; /** - * Tab style + * Tab style. */ class Tab extends AbstractStyle { /** - * Tab stop types + * Tab stop types. * * @const string */ @@ -36,7 +36,7 @@ class Tab extends AbstractStyle const TAB_STOP_NUM = 'num'; /** - * Tab leader types + * Tab leader types. * * @const string */ @@ -48,23 +48,23 @@ class Tab extends AbstractStyle const TAB_LEADER_MIDDLEDOT = 'middleDot'; /** - * Tab stop type + * Tab stop type. * * @var string */ private $type = self::TAB_STOP_CLEAR; /** - * Tab leader character + * Tab leader character. * * @var string */ private $leader = self::TAB_LEADER_NONE; /** - * Tab stop position (twip) + * Tab stop position (twip). * - * @var int|float + * @var float|int */ private $position = 0; @@ -79,14 +79,14 @@ class Tab extends AbstractStyle */ public function __construct($type = null, $position = 0, $leader = null) { - $stopTypes = array( + $stopTypes = [ self::TAB_STOP_CLEAR, self::TAB_STOP_LEFT, self::TAB_STOP_CENTER, self::TAB_STOP_RIGHT, self::TAB_STOP_DECIMAL, self::TAB_STOP_BAR, self::TAB_STOP_NUM, - ); - $leaderTypes = array( + ]; + $leaderTypes = [ self::TAB_LEADER_NONE, self::TAB_LEADER_DOT, self::TAB_LEADER_HYPHEN, self::TAB_LEADER_UNDERSCORE, self::TAB_LEADER_HEAVY, self::TAB_LEADER_MIDDLEDOT, - ); + ]; $this->type = $this->setEnumVal($type, $stopTypes, $this->type); $this->position = $this->setNumericVal($position, $this->position); @@ -94,7 +94,7 @@ class Tab extends AbstractStyle } /** - * Get stop type + * Get stop type. * * @return string */ @@ -104,25 +104,26 @@ class Tab extends AbstractStyle } /** - * Set stop type + * Set stop type. * * @param string $value + * * @return self */ public function setType($value) { - $enum = array( + $enum = [ self::TAB_STOP_CLEAR, self::TAB_STOP_LEFT, self::TAB_STOP_CENTER, self::TAB_STOP_RIGHT, self::TAB_STOP_DECIMAL, self::TAB_STOP_BAR, self::TAB_STOP_NUM, - ); + ]; $this->type = $this->setEnumVal($value, $enum, $this->type); return $this; } /** - * Get leader + * Get leader. * * @return string */ @@ -132,26 +133,27 @@ class Tab extends AbstractStyle } /** - * Set leader + * Set leader. * * @param string $value + * * @return self */ public function setLeader($value) { - $enum = array( + $enum = [ self::TAB_LEADER_NONE, self::TAB_LEADER_DOT, self::TAB_LEADER_HYPHEN, self::TAB_LEADER_UNDERSCORE, self::TAB_LEADER_HEAVY, self::TAB_LEADER_MIDDLEDOT, - ); + ]; $this->leader = $this->setEnumVal($value, $enum, $this->leader); return $this; } /** - * Get position + * Get position. * - * @return int|float + * @return float|int */ public function getPosition() { @@ -159,9 +161,10 @@ class Tab extends AbstractStyle } /** - * Set position + * Set position. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setPosition($value) diff --git a/PhpOffice/PhpWord/Style/Table.php b/PhpOffice/PhpWord/Style/Table.php old mode 100755 new mode 100644 index f777ac6..b267c75 --- a/PhpOffice/PhpWord/Style/Table.php +++ b/PhpOffice/PhpWord/Style/Table.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -24,28 +24,15 @@ use PhpOffice\PhpWord\SimpleType\TblWidth; class Table extends Border { - /** - * @deprecated Use \PhpOffice\PhpWord\SimpleType\TblWidth::AUTO instead - */ - const WIDTH_AUTO = 'auto'; // Automatically determined width - /** - * @deprecated Use \PhpOffice\PhpWord\SimpleType\TblWidth::PERCENT instead - */ - const WIDTH_PERCENT = 'pct'; // Width in fiftieths (1/50) of a percent (1% = 50 unit) - /** - * @deprecated Use \PhpOffice\PhpWord\SimpleType\TblWidth::TWIP instead - */ - const WIDTH_TWIP = 'dxa'; // Width in twentieths (1/20) of a point (twip) - //values for http://www.datypic.com/sc/ooxml/t-w_ST_TblLayoutType.html /** - * AutoFit Table Layout + * AutoFit Table Layout. * * @var string */ const LAYOUT_AUTO = 'autofit'; /** - * Fixed Width Table Layout + * Fixed Width Table Layout. * * @var string */ @@ -59,70 +46,70 @@ class Table extends Border private $isFirstRow = false; /** - * Style for first row + * Style for first row. * * @var \PhpOffice\PhpWord\Style\Table */ private $firstRowStyle; /** - * Cell margin top + * Cell margin top. * * @var int */ private $cellMarginTop; /** - * Cell margin left + * Cell margin left. * * @var int */ private $cellMarginLeft; /** - * Cell margin right + * Cell margin right. * * @var int */ private $cellMarginRight; /** - * Cell margin bottom + * Cell margin bottom. * * @var int */ private $cellMarginBottom; /** - * Border size inside horizontal + * Border size inside horizontal. * * @var int */ private $borderInsideHSize; /** - * Border color inside horizontal + * Border color inside horizontal. * * @var string */ private $borderInsideHColor; /** - * Border size inside vertical + * Border size inside vertical. * * @var int */ private $borderInsideVSize; /** - * Border color inside vertical + * Border color inside vertical. * * @var string */ private $borderInsideVColor; /** - * Shading + * Shading. * * @var \PhpOffice\PhpWord\Style\Shading */ @@ -134,7 +121,7 @@ class Table extends Border private $alignment = ''; /** - * @var int|float Width value + * @var float|int Width value */ private $width = 0; @@ -144,9 +131,9 @@ class Table extends Border private $unit = TblWidth::AUTO; /** - * @var int|float cell spacing value + * @var float|int cell spacing value */ - protected $cellSpacing = null; + protected $cellSpacing; /** * @var string Table Layout @@ -154,32 +141,33 @@ class Table extends Border private $layout = self::LAYOUT_AUTO; /** - * Position + * Position. * * @var \PhpOffice\PhpWord\Style\TablePosition */ private $position; - /** @var TblWidthComplexType|null */ + /** @var null|TblWidthComplexType */ private $indent; /** - * The width of each column, computed based on the max cell width of each column + * The width of each column, computed based on the max cell width of each column. * * @var int[] */ private $columnWidths; /** - * Visually Right to Left Table + * Visually Right to Left Table. * * @see http://www.datypic.com/sc/ooxml/e-w_bidiVisual-1.html + * * @var bool */ private $bidiVisual = false; /** - * Create new table style + * Create new table style. * * @param mixed $tableStyle * @param mixed $firstRowStyle @@ -202,7 +190,7 @@ class Table extends Border /** * @param float|int $cellSpacing */ - public function setCellSpacing($cellSpacing = null) + public function setCellSpacing($cellSpacing = null): void { $this->cellSpacing = $cellSpacing; } @@ -216,7 +204,7 @@ class Table extends Border } /** - * Set first row + * Set first row. * * @return \PhpOffice\PhpWord\Style\Table */ @@ -226,7 +214,7 @@ class Table extends Border } /** - * Get background + * Get background. * * @return string */ @@ -240,39 +228,41 @@ class Table extends Border } /** - * Set background + * Set background. * * @param string $value + * * @return self */ public function setBgColor($value = null) { - $this->setShading(array('fill' => $value)); + $this->setShading(['fill' => $value]); return $this; } /** - * Get TLRBHV Border Size + * Get TLRBHV Border Size. * * @return int[] */ public function getBorderSize() { - return array( + return [ $this->getBorderTopSize(), $this->getBorderLeftSize(), $this->getBorderRightSize(), $this->getBorderBottomSize(), $this->getBorderInsideHSize(), $this->getBorderInsideVSize(), - ); + ]; } /** - * Set TLRBHV Border Size + * Set TLRBHV Border Size. * * @param int $value Border size in eighths of a point (1/8 point) + * * @return self */ public function setBorderSize($value = null) @@ -288,26 +278,27 @@ class Table extends Border } /** - * Get TLRBHV Border Color + * Get TLRBHV Border Color. * * @return string[] */ public function getBorderColor() { - return array( + return [ $this->getBorderTopColor(), $this->getBorderLeftColor(), $this->getBorderRightColor(), $this->getBorderBottomColor(), $this->getBorderInsideHColor(), $this->getBorderInsideVColor(), - ); + ]; } /** - * Set TLRBHV Border Color + * Set TLRBHV Border Color. * * @param string $value + * * @return self */ public function setBorderColor($value = null) @@ -323,7 +314,7 @@ class Table extends Border } /** - * Get border size inside horizontal + * Get border size inside horizontal. * * @return int */ @@ -333,9 +324,10 @@ class Table extends Border } /** - * Set border size inside horizontal + * Set border size inside horizontal. * * @param int $value + * * @return self */ public function setBorderInsideHSize($value = null) @@ -344,7 +336,7 @@ class Table extends Border } /** - * Get border color inside horizontal + * Get border color inside horizontal. * * @return string */ @@ -354,9 +346,10 @@ class Table extends Border } /** - * Set border color inside horizontal + * Set border color inside horizontal. * * @param string $value + * * @return self */ public function setBorderInsideHColor($value = null) @@ -365,7 +358,7 @@ class Table extends Border } /** - * Get border size inside vertical + * Get border size inside vertical. * * @return int */ @@ -375,9 +368,10 @@ class Table extends Border } /** - * Set border size inside vertical + * Set border size inside vertical. * * @param int $value + * * @return self */ public function setBorderInsideVSize($value = null) @@ -386,7 +380,7 @@ class Table extends Border } /** - * Get border color inside vertical + * Get border color inside vertical. * * @return string */ @@ -396,9 +390,10 @@ class Table extends Border } /** - * Set border color inside vertical + * Set border color inside vertical. * * @param string $value + * * @return self */ public function setBorderInsideVColor($value = null) @@ -407,7 +402,7 @@ class Table extends Border } /** - * Get cell margin top + * Get cell margin top. * * @return int */ @@ -417,9 +412,10 @@ class Table extends Border } /** - * Set cell margin top + * Set cell margin top. * * @param int $value + * * @return self */ public function setCellMarginTop($value = null) @@ -428,7 +424,7 @@ class Table extends Border } /** - * Get cell margin left + * Get cell margin left. * * @return int */ @@ -438,9 +434,10 @@ class Table extends Border } /** - * Set cell margin left + * Set cell margin left. * * @param int $value + * * @return self */ public function setCellMarginLeft($value = null) @@ -449,7 +446,7 @@ class Table extends Border } /** - * Get cell margin right + * Get cell margin right. * * @return int */ @@ -459,9 +456,10 @@ class Table extends Border } /** - * Set cell margin right + * Set cell margin right. * * @param int $value + * * @return self */ public function setCellMarginRight($value = null) @@ -470,7 +468,7 @@ class Table extends Border } /** - * Get cell margin bottom + * Get cell margin bottom. * * @return int */ @@ -480,9 +478,10 @@ class Table extends Border } /** - * Set cell margin bottom + * Set cell margin bottom. * * @param int $value + * * @return self */ public function setCellMarginBottom($value = null) @@ -491,24 +490,25 @@ class Table extends Border } /** - * Get cell margin + * Get cell margin. * * @return int[] */ public function getCellMargin() { - return array( + return [ $this->cellMarginTop, $this->cellMarginLeft, $this->cellMarginRight, $this->cellMarginBottom, - ); + ]; } /** - * Set TLRB cell margin + * Set TLRB cell margin. * * @param int $value Margin in twips + * * @return self */ public function setCellMargin($value = null) @@ -522,7 +522,7 @@ class Table extends Border } /** - * Check if any of the margin is not null + * Check if any of the margin is not null. * * @return bool */ @@ -534,7 +534,7 @@ class Table extends Border } /** - * Get shading + * Get shading. * * @return \PhpOffice\PhpWord\Style\Shading */ @@ -544,9 +544,10 @@ class Table extends Border } /** - * Set shading + * Set shading. * * @param mixed $value + * * @return self */ public function setShading($value = null) @@ -583,35 +584,9 @@ class Table extends Border } /** - * @deprecated 0.13.0 Use the `getAlignment` method instead. + * Get width. * - * @return string - * - * @codeCoverageIgnore - */ - public function getAlign() - { - return $this->getAlignment(); - } - - /** - * @deprecated 0.13.0 Use the `setAlignment` method instead. - * - * @param string $value - * - * @return self - * - * @codeCoverageIgnore - */ - public function setAlign($value = null) - { - return $this->setAlignment($value); - } - - /** - * Get width - * - * @return int|float + * @return float|int */ public function getWidth() { @@ -619,9 +594,10 @@ class Table extends Border } /** - * Set width + * Set width. + * + * @param float|int $value * - * @param int|float $value * @return self */ public function setWidth($value = null) @@ -632,7 +608,7 @@ class Table extends Border } /** - * Get width unit + * Get width unit. * * @return string */ @@ -642,9 +618,10 @@ class Table extends Border } /** - * Set width unit + * Set width unit. * * @param string $value + * * @return self */ public function setUnit($value = null) @@ -656,7 +633,7 @@ class Table extends Border } /** - * Get layout + * Get layout. * * @return string */ @@ -666,27 +643,29 @@ class Table extends Border } /** - * Set layout + * Set layout. * * @param string $value + * * @return self */ public function setLayout($value = null) { - $enum = array(self::LAYOUT_AUTO, self::LAYOUT_FIXED); + $enum = [self::LAYOUT_AUTO, self::LAYOUT_FIXED]; $this->layout = $this->setEnumVal($value, $enum, $this->layout); return $this; } /** - * Get table style only property by checking if it's a firstRow + * Get table style only property by checking if it's a firstRow. * * This is necessary since firstRow style is cloned from table style but * without certain properties activated, e.g. margins * * @param string $property - * @return int|string|null + * + * @return null|int|string */ private function getTableOnlyProperty($property) { @@ -698,7 +677,7 @@ class Table extends Border } /** - * Set table style only property by checking if it's a firstRow + * Set table style only property by checking if it's a firstRow. * * This is necessary since firstRow style is cloned from table style but * without certain properties activated, e.g. margins @@ -706,6 +685,7 @@ class Table extends Border * @param string $property * @param int|string $value * @param bool $isNumeric + * * @return self */ private function setTableOnlyProperty($property, $value, $isNumeric = true) @@ -722,7 +702,7 @@ class Table extends Border } /** - * Get position + * Get position. * * @return \PhpOffice\PhpWord\Style\TablePosition */ @@ -732,9 +712,10 @@ class Table extends Border } /** - * Set position + * Set position. * * @param mixed $value + * * @return self */ public function setPosition($value = null) @@ -753,8 +734,8 @@ class Table extends Border } /** - * @param TblWidthComplexType $indent * @return self + * * @see http://www.datypic.com/sc/ooxml/e-w_tblInd-1.html */ public function setIndent(TblWidthComplexType $indent) @@ -765,7 +746,7 @@ class Table extends Border } /** - * Get the columnWidths + * Get the columnWidths. * * @return null|int[] */ @@ -775,17 +756,17 @@ class Table extends Border } /** - * The column widths + * The column widths. * * @param int[] $value */ - public function setColumnWidths(array $value = null) + public function setColumnWidths(?array $value = null): void { $this->columnWidths = $value; } /** - * Get bidiVisual + * Get bidiVisual. * * @return bool */ @@ -795,10 +776,11 @@ class Table extends Border } /** - * Set bidiVisual + * Set bidiVisual. * * @param bool $bidi * Set to true to visually present table as Right to Left + * * @return self */ public function setBidiVisual($bidi) diff --git a/PhpOffice/PhpWord/Style/TablePosition.php b/PhpOffice/PhpWord/Style/TablePosition.php old mode 100755 new mode 100644 index d4b7083..a61926b --- a/PhpOffice/PhpWord/Style/TablePosition.php +++ b/PhpOffice/PhpWord/Style/TablePosition.php @@ -11,23 +11,24 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Style; /** - * TablePosition style + * TablePosition style. * * @see http://www.datypic.com/sc/ooxml/e-w_tblpPr-1.html */ class TablePosition extends AbstractStyle { /** - * Vertical anchor constants + * Vertical anchor constants. * * @const string + * * @see http://www.datypic.com/sc/ooxml/t-w_ST_VAnchor.html */ const VANCHOR_TEXT = 'text'; // Relative to vertical text extents @@ -35,9 +36,10 @@ class TablePosition extends AbstractStyle const VANCHOR_PAGE = 'page'; // Relative to page /** - * Horizontal anchor constants + * Horizontal anchor constants. * * @const string + * * @see http://www.datypic.com/sc/ooxml/t-w_ST_HAnchor.html */ const HANCHOR_TEXT = 'text'; // Relative to text extents @@ -45,9 +47,10 @@ class TablePosition extends AbstractStyle const HANCHOR_PAGE = 'page'; // Relative to page /** - * Horizontal alignment constants + * Horizontal alignment constants. * * @const string + * * @see http://www.datypic.com/sc/ooxml/t-w_ST_XAlign.html */ const XALIGN_LEFT = 'left'; // Left aligned horizontally @@ -57,9 +60,10 @@ class TablePosition extends AbstractStyle const XALIGN_OUTSIDE = 'outside'; // Outside /** - * Vertical alignment constants + * Vertical alignment constants. * * @const string + * * @see http://www.datypic.com/sc/ooxml/t-w_ST_YAlign.html */ const YALIGN_INLINE = 'inline'; // In line with text @@ -70,91 +74,95 @@ class TablePosition extends AbstractStyle const YALIGN_OUTSIDE = 'outside'; // Centered vertically /** - * Distance from left of table to text + * Distance from left of table to text. * * @var int */ private $leftFromText; /** - * Distance from right of table to text + * Distance from right of table to text. * * @var int */ private $rightFromText; /** - * Distance from top of table to text + * Distance from top of table to text. * * @var int */ private $topFromText; /** - * Distance from bottom of table to text + * Distance from bottom of table to text. * * @var int */ private $bottomFromText; /** - * Table vertical anchor + * Table vertical anchor. * * @var string + * * @see http://www.datypic.com/sc/ooxml/t-w_ST_VAnchor.html */ private $vertAnchor; /** - * Table horizontal anchor + * Table horizontal anchor. * * @var string + * * @see http://www.datypic.com/sc/ooxml/t-w_ST_HAnchor.html */ private $horzAnchor; /** - * Relative horizontal alignment from anchor + * Relative horizontal alignment from anchor. * * @var string + * * @see http://www.datypic.com/sc/ooxml/t-w_ST_XAlign.html */ private $tblpXSpec; /** - * Absolute horizontal distance from anchor + * Absolute horizontal distance from anchor. * * @var int */ private $tblpX; /** - * Relative vertical alignment from anchor + * Relative vertical alignment from anchor. * * @var string + * * @see http://www.datypic.com/sc/ooxml/t-w_ST_YAlign.html */ private $tblpYSpec; /** - * Absolute vertical distance from anchor + * Absolute vertical distance from anchor. * * @var int */ private $tblpY; /** - * Create a new instance + * Create a new instance. * * @param array $style */ - public function __construct($style = array()) + public function __construct($style = []) { $this->setStyleByArray($style); } /** - * Get distance from left of table to text + * Get distance from left of table to text. * * @return int */ @@ -164,9 +172,10 @@ class TablePosition extends AbstractStyle } /** - * Set distance from left of table to text + * Set distance from left of table to text. * * @param int $value + * * @return self */ public function setLeftFromText($value = null) @@ -177,7 +186,7 @@ class TablePosition extends AbstractStyle } /** - * Get distance from right of table to text + * Get distance from right of table to text. * * @return int */ @@ -187,9 +196,10 @@ class TablePosition extends AbstractStyle } /** - * Set distance from right of table to text + * Set distance from right of table to text. * * @param int $value + * * @return self */ public function setRightFromText($value = null) @@ -200,7 +210,7 @@ class TablePosition extends AbstractStyle } /** - * Get distance from top of table to text + * Get distance from top of table to text. * * @return int */ @@ -210,9 +220,10 @@ class TablePosition extends AbstractStyle } /** - * Set distance from top of table to text + * Set distance from top of table to text. * * @param int $value + * * @return self */ public function setTopFromText($value = null) @@ -223,7 +234,7 @@ class TablePosition extends AbstractStyle } /** - * Get distance from bottom of table to text + * Get distance from bottom of table to text. * * @return int */ @@ -233,9 +244,10 @@ class TablePosition extends AbstractStyle } /** - * Set distance from bottom of table to text + * Set distance from bottom of table to text. * * @param int $value + * * @return self */ public function setBottomFromText($value = null) @@ -246,7 +258,7 @@ class TablePosition extends AbstractStyle } /** - * Get table vertical anchor + * Get table vertical anchor. * * @return string */ @@ -256,25 +268,26 @@ class TablePosition extends AbstractStyle } /** - * Set table vertical anchor + * Set table vertical anchor. * * @param string $value + * * @return self */ public function setVertAnchor($value = null) { - $enum = array( - self::VANCHOR_TEXT, - self::VANCHOR_MARGIN, - self::VANCHOR_PAGE, - ); + $enum = [ + self::VANCHOR_TEXT, + self::VANCHOR_MARGIN, + self::VANCHOR_PAGE, + ]; $this->vertAnchor = $this->setEnumVal($value, $enum, $this->vertAnchor); return $this; } /** - * Get table horizontal anchor + * Get table horizontal anchor. * * @return string */ @@ -284,25 +297,26 @@ class TablePosition extends AbstractStyle } /** - * Set table horizontal anchor + * Set table horizontal anchor. * * @param string $value + * * @return self */ public function setHorzAnchor($value = null) { - $enum = array( - self::HANCHOR_TEXT, - self::HANCHOR_MARGIN, - self::HANCHOR_PAGE, - ); + $enum = [ + self::HANCHOR_TEXT, + self::HANCHOR_MARGIN, + self::HANCHOR_PAGE, + ]; $this->horzAnchor = $this->setEnumVal($value, $enum, $this->horzAnchor); return $this; } /** - * Get relative horizontal alignment from anchor + * Get relative horizontal alignment from anchor. * * @return string */ @@ -312,27 +326,28 @@ class TablePosition extends AbstractStyle } /** - * Set relative horizontal alignment from anchor + * Set relative horizontal alignment from anchor. * * @param string $value + * * @return self */ public function setTblpXSpec($value = null) { - $enum = array( + $enum = [ self::XALIGN_LEFT, self::XALIGN_CENTER, self::XALIGN_RIGHT, self::XALIGN_INSIDE, self::XALIGN_OUTSIDE, - ); + ]; $this->tblpXSpec = $this->setEnumVal($value, $enum, $this->tblpXSpec); return $this; } /** - * Get absolute horizontal distance from anchor + * Get absolute horizontal distance from anchor. * * @return int */ @@ -342,9 +357,10 @@ class TablePosition extends AbstractStyle } /** - * Set absolute horizontal distance from anchor + * Set absolute horizontal distance from anchor. * * @param int $value + * * @return self */ public function setTblpX($value = null) @@ -355,7 +371,7 @@ class TablePosition extends AbstractStyle } /** - * Get relative vertical alignment from anchor + * Get relative vertical alignment from anchor. * * @return string */ @@ -365,28 +381,29 @@ class TablePosition extends AbstractStyle } /** - * Set relative vertical alignment from anchor + * Set relative vertical alignment from anchor. * * @param string $value + * * @return self */ public function setTblpYSpec($value = null) { - $enum = array( + $enum = [ self::YALIGN_INLINE, self::YALIGN_TOP, self::YALIGN_CENTER, self::YALIGN_BOTTOM, self::YALIGN_INSIDE, self::YALIGN_OUTSIDE, - ); + ]; $this->tblpYSpec = $this->setEnumVal($value, $enum, $this->tblpYSpec); return $this; } /** - * Get absolute vertical distance from anchor + * Get absolute vertical distance from anchor. * * @return int */ @@ -396,9 +413,10 @@ class TablePosition extends AbstractStyle } /** - * Set absolute vertical distance from anchor + * Set absolute vertical distance from anchor. * * @param int $value + * * @return self */ public function setTblpY($value = null) diff --git a/PhpOffice/PhpWord/Style/TextBox.php b/PhpOffice/PhpWord/Style/TextBox.php old mode 100755 new mode 100644 index e9c0f0c..e43e6fa --- a/PhpOffice/PhpWord/Style/TextBox.php +++ b/PhpOffice/PhpWord/Style/TextBox.php @@ -11,56 +11,56 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Style; /** - * TextBox style + * TextBox style. * * @since 0.11.0 */ class TextBox extends Image { /** - * margin top + * margin top. * * @var int */ - private $innerMarginTop = null; + private $innerMarginTop; /** - * margin left + * margin left. * * @var int */ - private $innerMarginLeft = null; + private $innerMarginLeft; /** - * margin right + * margin right. * * @var int */ - private $innerMarginRight = null; + private $innerMarginRight; /** - * Cell margin bottom + * Cell margin bottom. * * @var int */ - private $innerMarginBottom = null; + private $innerMarginBottom; /** - * border size + * border size. * * @var int */ - private $borderSize = null; + private $borderSize; /** - * border color + * border color. * * @var string */ @@ -71,13 +71,13 @@ class TextBox extends Image * * @param int $value */ - public function setInnerMarginTop($value = null) + public function setInnerMarginTop($value = null): void { $this->innerMarginTop = $value; } /** - * Get margin top + * Get margin top. * * @return int */ @@ -91,13 +91,13 @@ class TextBox extends Image * * @param int $value */ - public function setInnerMarginLeft($value = null) + public function setInnerMarginLeft($value = null): void { $this->innerMarginLeft = $value; } /** - * Get margin left + * Get margin left. * * @return int */ @@ -111,13 +111,13 @@ class TextBox extends Image * * @param int $value */ - public function setInnerMarginRight($value = null) + public function setInnerMarginRight($value = null): void { $this->innerMarginRight = $value; } /** - * Get margin right + * Get margin right. * * @return int */ @@ -131,13 +131,13 @@ class TextBox extends Image * * @param int $value */ - public function setInnerMarginBottom($value = null) + public function setInnerMarginBottom($value = null): void { $this->innerMarginBottom = $value; } /** - * Get margin bottom + * Get margin bottom. * * @return int */ @@ -151,7 +151,7 @@ class TextBox extends Image * * @param int $value Margin in twips */ - public function setInnerMargin($value = null) + public function setInnerMargin($value = null): void { $this->setInnerMarginTop($value); $this->setInnerMarginLeft($value); @@ -160,13 +160,13 @@ class TextBox extends Image } /** - * Get cell margin + * Get cell margin. * * @return int[] */ public function getInnerMargin() { - return array($this->innerMarginLeft, $this->innerMarginTop, $this->innerMarginRight, $this->innerMarginBottom); + return [$this->innerMarginLeft, $this->innerMarginTop, $this->innerMarginRight, $this->innerMarginBottom]; } /** @@ -179,7 +179,7 @@ class TextBox extends Image $hasInnerMargins = false; $margins = $this->getInnerMargin(); $numMargins = count($margins); - for ($i = 0; $i < $numMargins; $i++) { + for ($i = 0; $i < $numMargins; ++$i) { if ($margins[$i] !== null) { $hasInnerMargins = true; } @@ -193,13 +193,13 @@ class TextBox extends Image * * @param int $value Size in points */ - public function setBorderSize($value = null) + public function setBorderSize($value = null): void { $this->borderSize = $value; } /** - * Get border size + * Get border size. * * @return int */ @@ -213,13 +213,13 @@ class TextBox extends Image * * @param string $value */ - public function setBorderColor($value = null) + public function setBorderColor($value = null): void { $this->borderColor = $value; } /** - * Get border color + * Get border color. * * @return string */ diff --git a/PhpOffice/PhpWord/Template.php b/PhpOffice/PhpWord/Template.php deleted file mode 100755 index c42696f..0000000 --- a/PhpOffice/PhpWord/Template.php +++ /dev/null @@ -1,27 +0,0 @@ -zipClass->locateName($this->getHeaderName($index))) { $this->tempDocumentHeaders[$index] = $this->readPartWithRels($this->getHeaderName($index)); - $index++; + ++$index; } $index = 1; while (false !== $this->zipClass->locateName($this->getFooterName($index))) { $this->tempDocumentFooters[$index] = $this->readPartWithRels($this->getFooterName($index)); - $index++; + ++$index; } $this->tempDocumentMainPart = $this->readPartWithRels($this->getMainPartName()); @@ -132,7 +136,7 @@ class TemplateProcessor } /** - * Expose zip class + * Expose zip class. * * To replace an image: $templateProcessor->zip()->AddFromString("word/media/image1.jpg", file_get_contents($file));
      * To read a file: $templateProcessor->zip()->getFromName("word/media/image1.jpg"); @@ -162,16 +166,16 @@ class TemplateProcessor /** * @param string $xml - * @param \XSLTProcessor $xsltProcessor - * - * @throws \PhpOffice\PhpWord\Exception\Exception + * @param XSLTProcessor $xsltProcessor * * @return string */ protected function transformSingleXml($xml, $xsltProcessor) { - $orignalLibEntityLoader = libxml_disable_entity_loader(true); - $domDocument = new \DOMDocument(); + if (\PHP_VERSION_ID < 80000) { + $orignalLibEntityLoader = libxml_disable_entity_loader(true); + } + $domDocument = new DOMDocument(); if (false === $domDocument->loadXML($xml)) { throw new Exception('Could not load the given XML document.'); } @@ -180,14 +184,16 @@ class TemplateProcessor if (false === $transformedXml) { throw new Exception('Could not transform the given XML document.'); } - libxml_disable_entity_loader($orignalLibEntityLoader); + if (\PHP_VERSION_ID < 80000) { + libxml_disable_entity_loader($orignalLibEntityLoader); + } return $transformedXml; } /** * @param mixed $xml - * @param \XSLTProcessor $xsltProcessor + * @param XSLTProcessor $xsltProcessor * * @return mixed */ @@ -211,15 +217,13 @@ class TemplateProcessor * Note: since the method doesn't make any guess on logic of the provided XSL style sheet, * make sure that output is correctly escaped. Otherwise you may get broken document. * - * @param \DOMDocument $xslDomDocument + * @param DOMDocument $xslDomDocument * @param array $xslOptions * @param string $xslOptionsUri - * - * @throws \PhpOffice\PhpWord\Exception\Exception */ - public function applyXslStyleSheet($xslDomDocument, $xslOptions = array(), $xslOptionsUri = '') + public function applyXslStyleSheet($xslDomDocument, $xslOptions = [], $xslOptionsUri = ''): void { - $xsltProcessor = new \XSLTProcessor(); + $xsltProcessor = new XSLTProcessor(); $xsltProcessor->importStylesheet($xslDomDocument); if (false === $xsltProcessor->setParameter($xslOptionsUri, $xslOptions)) { @@ -238,8 +242,8 @@ class TemplateProcessor */ protected static function ensureMacroCompleted($macro) { - if (substr($macro, 0, 2) !== '${' && substr($macro, -1) !== '}') { - $macro = '${' . $macro . '}'; + if (substr($macro, 0, 2) !== self::$macroOpeningChars && substr($macro, -1) !== self::$macroClosingChars) { + $macro = self::$macroOpeningChars . $macro . self::$macroClosingChars; } return $macro; @@ -252,18 +256,18 @@ class TemplateProcessor */ protected static function ensureUtf8Encoded($subject) { - if (!Text::isUTF8($subject)) { + if (!Text::isUTF8($subject) && null !== $subject) { $subject = utf8_encode($subject); } - return $subject; + return (null !== $subject) ? $subject : ''; } /** * @param string $search * @param \PhpOffice\PhpWord\Element\AbstractElement $complexType */ - public function setComplexValue($search, \PhpOffice\PhpWord\Element\AbstractElement $complexType) + public function setComplexValue($search, Element\AbstractElement $complexType): void { $elementName = substr(get_class($complexType), strrpos(get_class($complexType), '\\') + 1); $objectClass = 'PhpOffice\\PhpWord\\Writer\\Word2007\\Element\\' . $elementName; @@ -274,6 +278,11 @@ class TemplateProcessor $elementWriter->write(); $where = $this->findContainingXmlBlockForMacro($search, 'w:r'); + + if ($where === false) { + return; + } + $block = $this->getSlice($where['start'], $where['end']); $textParts = $this->splitTextIntoTexts($block); $this->replaceXmlBlock($search, $textParts, 'w:r'); @@ -286,7 +295,7 @@ class TemplateProcessor * @param string $search * @param \PhpOffice\PhpWord\Element\AbstractElement $complexType */ - public function setComplexBlock($search, \PhpOffice\PhpWord\Element\AbstractElement $complexType) + public function setComplexBlock($search, Element\AbstractElement $complexType): void { $elementName = substr(get_class($complexType), strrpos(get_class($complexType), '\\') + 1); $objectClass = 'PhpOffice\\PhpWord\\Writer\\Word2007\\Element\\' . $elementName; @@ -304,7 +313,7 @@ class TemplateProcessor * @param mixed $replace * @param int $limit */ - public function setValue($search, $replace, $limit = self::MAXIMUM_REPLACEMENTS_DEFAULT) + public function setValue($search, $replace, $limit = self::MAXIMUM_REPLACEMENTS_DEFAULT): void { if (is_array($search)) { foreach ($search as &$item) { @@ -336,44 +345,84 @@ class TemplateProcessor /** * Set values from a one-dimensional array of "variable => value"-pairs. - * - * @param array $values */ - public function setValues(array $values) + public function setValues(array $values): void { foreach ($values as $macro => $replace) { $this->setValue($macro, $replace); } } + /** + * @param string $search + */ + public function setChart($search, Element\AbstractElement $chart): void + { + $elementName = substr(get_class($chart), strrpos(get_class($chart), '\\') + 1); + $objectClass = 'PhpOffice\\PhpWord\\Writer\\Word2007\\Element\\' . $elementName; + + // Get the next relation id + $rId = $this->getNextRelationsIndex($this->getMainPartName()); + $chart->setRelationId($rId); + + // Define the chart filename + $filename = "charts/chart{$rId}.xml"; + + // Get the part writer + $writerPart = new \PhpOffice\PhpWord\Writer\Word2007\Part\Chart(); + $writerPart->setElement($chart); + + // ContentTypes.xml + $this->zipClass->addFromString("word/{$filename}", $writerPart->write()); + + // add chart to content type + $xmlRelationsType = ""; + $this->tempDocumentContentTypes = str_replace('', $xmlRelationsType, $this->tempDocumentContentTypes) . ''; + + // Add the chart to relations + $xmlChartRelation = ""; + $this->tempDocumentRelations[$this->getMainPartName()] = str_replace('', $xmlChartRelation, $this->tempDocumentRelations[$this->getMainPartName()]) . ''; + + // Write the chart + $xmlWriter = new XMLWriter(); + $elementWriter = new $objectClass($xmlWriter, $chart, true); + $elementWriter->write(); + + // Place it in the template + $this->replaceXmlBlock($search, '' . $xmlWriter->getData() . '', 'w:p'); + } + private function getImageArgs($varNameWithArgs) { $varElements = explode(':', $varNameWithArgs); array_shift($varElements); // first element is name of variable => remove it - $varInlineArgs = array(); + $varInlineArgs = []; // size format documentation: https://msdn.microsoft.com/en-us/library/documentformat.openxml.vml.shape%28v=office.14%29.aspx?f=255&MSPPError=-2147217396 foreach ($varElements as $argIdx => $varArg) { if (strpos($varArg, '=')) { // arg=value - list($argName, $argValue) = explode('=', $varArg, 2); + [$argName, $argValue] = explode('=', $varArg, 2); $argName = strtolower($argName); if ($argName == 'size') { - list($varInlineArgs['width'], $varInlineArgs['height']) = explode('x', $argValue, 2); + [$varInlineArgs['width'], $varInlineArgs['height']] = explode('x', $argValue, 2); } else { $varInlineArgs[strtolower($argName)] = $argValue; } } elseif (preg_match('/^([0-9]*[a-z%]{0,2}|auto)x([0-9]*[a-z%]{0,2}|auto)$/i', $varArg)) { // 60x40 - list($varInlineArgs['width'], $varInlineArgs['height']) = explode('x', $varArg, 2); + [$varInlineArgs['width'], $varInlineArgs['height']] = explode('x', $varArg, 2); } else { // :60:40:f switch ($argIdx) { case 0: $varInlineArgs['width'] = $varArg; + break; case 1: $varInlineArgs['height'] = $varArg; + break; case 2: $varInlineArgs['ratio'] = $varArg; + break; } } @@ -385,13 +434,13 @@ class TemplateProcessor private function chooseImageDimension($baseValue, $inlineValue, $defaultValue) { $value = $baseValue; - if (is_null($value) && isset($inlineValue)) { + if (null === $value && isset($inlineValue)) { $value = $inlineValue; } - if (!preg_match('/^([0-9]*(cm|mm|in|pt|pc|px|%|em|ex|)|auto)$/i', $value)) { + if (!preg_match('/^([0-9]*(cm|mm|in|pt|pc|px|%|em|ex|)|auto)$/i', $value ?? '')) { $value = null; } - if (is_null($value)) { + if (null === $value) { $value = $defaultValue; } if (is_numeric($value)) { @@ -401,7 +450,7 @@ class TemplateProcessor return $value; } - private function fixImageWidthHeightRatio(&$width, &$height, $actualWidth, $actualHeight) + private function fixImageWidthHeightRatio(&$width, &$height, $actualWidth, $actualHeight): void { $imageRatio = $actualWidth / $actualHeight; @@ -411,20 +460,20 @@ class TemplateProcessor } elseif ($width === '') { // defined width is empty $heightFloat = (float) $height; $widthFloat = $heightFloat * $imageRatio; - $matches = array(); - preg_match("/\d([a-z%]+)$/", $height, $matches); + $matches = []; + preg_match('/\\d([a-z%]+)$/', $height, $matches); $width = $widthFloat . $matches[1]; } elseif ($height === '') { // defined height is empty $widthFloat = (float) $width; $heightFloat = $widthFloat / $imageRatio; - $matches = array(); - preg_match("/\d([a-z%]+)$/", $width, $matches); + $matches = []; + preg_match('/\\d([a-z%]+)$/', $width, $matches); $height = $heightFloat . $matches[1]; } else { // we have defined size, but we need also check it aspect ratio - $widthMatches = array(); - preg_match("/\d([a-z%]+)$/", $width, $widthMatches); - $heightMatches = array(); - preg_match("/\d([a-z%]+)$/", $height, $heightMatches); + $widthMatches = []; + preg_match('/\\d([a-z%]+)$/', $width, $widthMatches); + $heightMatches = []; + preg_match('/\\d([a-z%]+)$/', $height, $heightMatches); // try to fix only if dimensions are same if ($widthMatches[1] == $heightMatches[1]) { $dimention = $widthMatches[1]; @@ -447,6 +496,13 @@ class TemplateProcessor $width = null; $height = null; $ratio = null; + + // a closure can be passed as replacement value which after resolving, can contain the replacement info for the image + // use case: only when a image if found, the replacement tags can be generated + if (is_callable($replaceImage)) { + $replaceImage = $replaceImage(); + } + if (is_array($replaceImage) && isset($replaceImage['path'])) { $imgPath = $replaceImage['path']; if (isset($replaceImage['width'])) { @@ -462,46 +518,46 @@ class TemplateProcessor $imgPath = $replaceImage; } - $width = $this->chooseImageDimension($width, isset($varInlineArgs['width']) ? $varInlineArgs['width'] : null, 115); - $height = $this->chooseImageDimension($height, isset($varInlineArgs['height']) ? $varInlineArgs['height'] : null, 70); + $width = $this->chooseImageDimension($width, $varInlineArgs['width'] ?? null, 115); + $height = $this->chooseImageDimension($height, $varInlineArgs['height'] ?? null, 70); $imageData = @getimagesize($imgPath); if (!is_array($imageData)) { throw new Exception(sprintf('Invalid image: %s', $imgPath)); } - list($actualWidth, $actualHeight, $imageType) = $imageData; + [$actualWidth, $actualHeight, $imageType] = $imageData; // fix aspect ratio (by default) - if (is_null($ratio) && isset($varInlineArgs['ratio'])) { + if (null === $ratio && isset($varInlineArgs['ratio'])) { $ratio = $varInlineArgs['ratio']; } - if (is_null($ratio) || !in_array(strtolower($ratio), array('', '-', 'f', 'false'))) { + if (null === $ratio || !in_array(strtolower($ratio), ['', '-', 'f', 'false'])) { $this->fixImageWidthHeightRatio($width, $height, $actualWidth, $actualHeight); } - $imageAttrs = array( - 'src' => $imgPath, - 'mime' => image_type_to_mime_type($imageType), - 'width' => $width, + $imageAttrs = [ + 'src' => $imgPath, + 'mime' => image_type_to_mime_type($imageType), + 'width' => $width, 'height' => $height, - ); + ]; return $imageAttrs; } - private function addImageToRelations($partFileName, $rid, $imgPath, $imageMimeType) + private function addImageToRelations($partFileName, $rid, $imgPath, $imageMimeType): void { // define templates $typeTpl = ''; $relationTpl = ''; $newRelationsTpl = '' . "\n" . ''; $newRelationsTypeTpl = ''; - $extTransform = array( + $extTransform = [ 'image/jpeg' => 'jpeg', - 'image/png' => 'png', - 'image/bmp' => 'bmp', - 'image/gif' => 'gif', - ); + 'image/png' => 'png', + 'image/bmp' => 'bmp', + 'image/gif' => 'gif', + ]; // get image embed name if (isset($this->tempDocumentNewImages[$imgPath])) { @@ -520,11 +576,11 @@ class TemplateProcessor $this->tempDocumentNewImages[$imgPath] = $imgName; // setup type for image - $xmlImageType = str_replace(array('{IMG}', '{EXT}'), array($imgName, $imgExt), $typeTpl); + $xmlImageType = str_replace(['{IMG}', '{EXT}'], [$imgName, $imgExt], $typeTpl); $this->tempDocumentContentTypes = str_replace('', $xmlImageType, $this->tempDocumentContentTypes) . ''; } - $xmlImageRelation = str_replace(array('{RID}', '{IMG}'), array($rid, $imgName), $relationTpl); + $xmlImageRelation = str_replace(['{RID}', '{IMG}'], [$rid, $imgName], $relationTpl); if (!isset($this->tempDocumentRelations[$partFileName])) { // create new relations file @@ -543,29 +599,29 @@ class TemplateProcessor * @param mixed $replace Path to image, or array("path" => xx, "width" => yy, "height" => zz) * @param int $limit */ - public function setImageValue($search, $replace, $limit = self::MAXIMUM_REPLACEMENTS_DEFAULT) + public function setImageValue($search, $replace, $limit = self::MAXIMUM_REPLACEMENTS_DEFAULT): void { // prepare $search_replace if (!is_array($search)) { - $search = array($search); + $search = [$search]; } - $replacesList = array(); + $replacesList = []; if (!is_array($replace) || isset($replace['path'])) { $replacesList[] = $replace; } else { $replacesList = array_values($replace); } - $searchReplace = array(); + $searchReplace = []; foreach ($search as $searchIdx => $searchString) { - $searchReplace[$searchString] = isset($replacesList[$searchIdx]) ? $replacesList[$searchIdx] : $replacesList[0]; + $searchReplace[$searchString] = $replacesList[$searchIdx] ?? $replacesList[0]; } // collect document parts - $searchParts = array( - $this->getMainPartName() => &$this->tempDocumentMainPart, - ); + $searchParts = [ + $this->getMainPartName() => &$this->tempDocumentMainPart, + ]; foreach (array_keys($this->tempDocumentHeaders) as $headerIndex) { $searchParts[$this->getHeaderName($headerIndex)] = &$this->tempDocumentHeaders[$headerIndex]; } @@ -575,8 +631,9 @@ class TemplateProcessor // define templates // result can be verified via "Open XML SDK 2.5 Productivity Tool" (http://www.microsoft.com/en-us/download/details.aspx?id=30425) - $imgTpl = ''; + $imgTpl = ''; + $i = 0; foreach ($searchParts as $partFileName => &$partContent) { $partVariables = $this->getVariablesForPart($partContent); @@ -596,19 +653,23 @@ class TemplateProcessor // replace preparations $this->addImageToRelations($partFileName, $rid, $imgPath, $preparedImageAttrs['mime']); - $xmlImage = str_replace(array('{RID}', '{WIDTH}', '{HEIGHT}'), array($rid, $preparedImageAttrs['width'], $preparedImageAttrs['height']), $imgTpl); + $xmlImage = str_replace(['{RID}', '{WIDTH}', '{HEIGHT}'], [$rid, $preparedImageAttrs['width'], $preparedImageAttrs['height']], $imgTpl); // replace variable $varNameWithArgsFixed = static::ensureMacroCompleted($varNameWithArgs); - $matches = array(); + $matches = []; if (preg_match('/(<[^<]+>)([^<]*)(' . preg_quote($varNameWithArgsFixed) . ')([^>]*)(<[^>]+>)/Uu', $partContent, $matches)) { $wholeTag = $matches[0]; array_shift($matches); - list($openTag, $prefix, , $postfix, $closeTag) = $matches; + [$openTag, $prefix, , $postfix, $closeTag] = $matches; $replaceXml = $openTag . $prefix . $closeTag . $xmlImage . $openTag . $postfix . $closeTag; // replace on each iteration, because in one tag we can have 2+ inline variables => before proceed next variable we need to change $partContent $partContent = $this->setValueForPart($wholeTag, $replaceXml, $partContent, $limit); } + + if (++$i >= $limit) { + break; + } } } } @@ -655,10 +716,8 @@ class TemplateProcessor * * @param string $search * @param int $numberOfClones - * - * @throws \PhpOffice\PhpWord\Exception\Exception */ - public function cloneRow($search, $numberOfClones) + public function cloneRow($search, $numberOfClones): void { $search = static::ensureMacroCompleted($search); @@ -687,7 +746,8 @@ class TemplateProcessor // If tmpXmlRow doesn't contain continue, this row is no longer part of the spanned row. $tmpXmlRow = $this->getSlice($extraRowStart, $extraRowEnd); if (!preg_match('##', $tmpXmlRow) && - !preg_match('##', $tmpXmlRow)) { + !preg_match('##', $tmpXmlRow) + ) { break; } // This row was a spanned row, update $rowEnd and search for the next row. @@ -697,19 +757,83 @@ class TemplateProcessor } $result = $this->getSlice(0, $rowStart); - $result .= implode($this->indexClonedVariables($numberOfClones, $xmlRow)); + $result .= implode('', $this->indexClonedVariables($numberOfClones, $xmlRow)); $result .= $this->getSlice($rowEnd); $this->tempDocumentMainPart = $result; } + /** + * Delete a table row in a template document. + */ + public function deleteRow(string $search): void + { + if ('${' !== substr($search, 0, 2) && '}' !== substr($search, -1)) { + $search = '${' . $search . '}'; + } + + $tagPos = strpos($this->tempDocumentMainPart, $search); + if (!$tagPos) { + throw new Exception(sprintf('Can not delete row %s, template variable not found or variable contains markup.', $search)); + } + + $tableStart = $this->findTableStart($tagPos); + $tableEnd = $this->findTableEnd($tagPos); + $xmlTable = $this->getSlice($tableStart, $tableEnd); + + if (substr_count($xmlTable, 'tempDocumentMainPart = $this->getSlice(0, $tableStart) . $this->getSlice($tableEnd); + + return; + } + + $rowStart = $this->findRowStart($tagPos); + $rowEnd = $this->findRowEnd($tagPos); + $xmlRow = $this->getSlice($rowStart, $rowEnd); + + $this->tempDocumentMainPart = $this->getSlice(0, $rowStart) . $this->getSlice($rowEnd); + + // Check if there's a cell spanning multiple rows. + if (preg_match('##', $xmlRow)) { + $extraRowStart = $rowStart; + while (true) { + $extraRowStart = $this->findRowStart($extraRowStart + 1); + $extraRowEnd = $this->findRowEnd($extraRowStart + 1); + + // If extraRowEnd is lower then 7, there was no next row found. + if ($extraRowEnd < 7) { + break; + } + + // If tmpXmlRow doesn't contain continue, this row is no longer part of the spanned row. + $tmpXmlRow = $this->getSlice($extraRowStart, $extraRowEnd); + if (!preg_match('##', $tmpXmlRow) && + !preg_match('##', $tmpXmlRow) + ) { + break; + } + + $tableStart = $this->findTableStart($extraRowEnd + 1); + $tableEnd = $this->findTableEnd($extraRowEnd + 1); + $xmlTable = $this->getSlice($tableStart, $tableEnd); + if (substr_count($xmlTable, 'tempDocumentMainPart = $this->getSlice(0, $tableStart) . $this->getSlice($tableEnd); + + return; + } + + $this->tempDocumentMainPart = $this->getSlice(0, $extraRowStart) . $this->getSlice($extraRowEnd); + } + } + } + /** * Clones a table row and populates it's values from a two-dimensional array in a template document. * * @param string $search * @param array $values */ - public function cloneRowAndSetValues($search, $values) + public function cloneRowAndSetValues($search, $values): void { $this->cloneRow($search, count($values)); @@ -730,14 +854,18 @@ class TemplateProcessor * @param bool $indexVariables If true, any variables inside the block will be indexed (postfixed with #1, #2, ...) * @param array $variableReplacements Array containing replacements for macros found inside the block to clone * - * @return string|null + * @return null|string */ public function cloneBlock($blockname, $clones = 1, $replace = true, $indexVariables = false, $variableReplacements = null) { $xmlBlock = null; - $matches = array(); + $matches = []; + $escapedMacroOpeningChars = self::$macroOpeningChars; + $escapedMacroClosingChars = self::$macroClosingChars; preg_match( - '/(<\?xml.*)(\${' . $blockname . '}<\/w:.*?p>)(.*)()/is', + //'/(.*((?s)))(.*)((?s))/is', + '/(.*((?s)))(.*)((?s))/is', + //'/(.*((?s)))(.*)((?s))/is', $this->tempDocumentMainPart, $matches ); @@ -749,8 +877,8 @@ class TemplateProcessor } elseif ($variableReplacements !== null && is_array($variableReplacements)) { $cloned = $this->replaceClonedVariables($variableReplacements, $xmlBlock); } else { - $cloned = array(); - for ($i = 1; $i <= $clones; $i++) { + $cloned = []; + for ($i = 1; $i <= $clones; ++$i) { $cloned[] = $xmlBlock; } } @@ -773,11 +901,13 @@ class TemplateProcessor * @param string $blockname * @param string $replacement */ - public function replaceBlock($blockname, $replacement) + public function replaceBlock($blockname, $replacement): void { - $matches = array(); + $matches = []; + $escapedMacroOpeningChars = preg_quote(self::$macroOpeningChars); + $escapedMacroClosingChars = preg_quote(self::$macroClosingChars); preg_match( - '/(<\?xml.*)(\${' . $blockname . '}<\/w:.*?p>)(.*)()/is', + '/(<\?xml.*)(' . $escapedMacroOpeningChars . $blockname . $escapedMacroClosingChars . '<\/w:.*?p>)(.*)()/is', $this->tempDocumentMainPart, $matches ); @@ -796,20 +926,20 @@ class TemplateProcessor * * @param string $blockname */ - public function deleteBlock($blockname) + public function deleteBlock($blockname): void { $this->replaceBlock($blockname, ''); } /** - * Automatically Recalculate Fields on Open + * Automatically Recalculate Fields on Open. * * @param bool $update */ - public function setUpdateFields($update = true) + public function setUpdateFields($update = true): void { $string = $update ? 'true' : 'false'; - $matches = array(); + $matches = []; if (preg_match('//', $this->tempDocumentSettingsPart, $matches)) { $this->tempDocumentSettingsPart = str_replace($matches[0], '', $this->tempDocumentSettingsPart); } else { @@ -820,8 +950,6 @@ class TemplateProcessor /** * Saves the result document. * - * @throws \PhpOffice\PhpWord\Exception\Exception - * * @return string */ public function save() @@ -851,7 +979,7 @@ class TemplateProcessor * @param string $fileName * @param string $xml */ - protected function savePartWithRels($fileName, $xml) + protected function savePartWithRels($fileName, $xml): void { $this->zipClass->addFromString($fileName, $xml); if (isset($this->tempDocumentRelations[$fileName])) { @@ -867,7 +995,7 @@ class TemplateProcessor * * @param string $fileName */ - public function saveAs($fileName) + public function saveAs($fileName): void { $tempFileName = $this->save(); @@ -895,8 +1023,12 @@ class TemplateProcessor */ protected function fixBrokenMacros($documentPart) { + $brokenMacroOpeningChars = substr(self::$macroOpeningChars, 0, 1); + $endMacroOpeningChars = substr(self::$macroOpeningChars, 1); + $macroClosingChars = self::$macroClosingChars; + return preg_replace_callback( - '/\$(?:\{|[^{$]*\>\{)[^}$]*\}/U', + '/\\' . $brokenMacroOpeningChars . '(?:\\' . $endMacroOpeningChars . '|[^{$]*\>\{)[^' . $macroClosingChars . '$]*\}/U', function ($match) { return strip_tags($match[0]); }, @@ -909,7 +1041,7 @@ class TemplateProcessor * * @param mixed $search * @param mixed $replace - * @param string $documentPartXML + * @param array|string $documentPartXML * @param int $limit * * @return string @@ -934,8 +1066,11 @@ class TemplateProcessor */ protected function getVariablesForPart($documentPartXML) { - $matches = array(); - preg_match_all('/\$\{(.*?)}/i', $documentPartXML, $matches); + $matches = []; + $escapedMacroOpeningChars = preg_quote(self::$macroOpeningChars); + $escapedMacroClosingChars = preg_quote(self::$macroClosingChars); + + preg_match_all("/$escapedMacroOpeningChars(.*?)$escapedMacroClosingChars/i", $documentPartXML, $matches); return $matches[1]; } @@ -963,14 +1098,14 @@ class TemplateProcessor $pattern = '~PartName="\/(word\/document.*?\.xml)" ContentType="application\/vnd\.openxmlformats-officedocument\.wordprocessingml\.document\.main\+xml"~'; - $matches = array(); + $matches = []; preg_match($pattern, $contentTypes, $matches); return array_key_exists(1, $matches) ? $matches[1] : 'word/document.xml'; } /** - * The name of the file containing the Settings part + * The name of the file containing the Settings part. * * @return string */ @@ -1006,7 +1141,12 @@ class TemplateProcessor protected function getNextRelationsIndex($documentPartName) { if (isset($this->tempDocumentRelations[$documentPartName])) { - return substr_count($this->tempDocumentRelations[$documentPartName], 'tempDocumentRelations[$documentPartName], 'tempDocumentRelations[$documentPartName], 'Id="rId' . $candidate . '"') !== false) { + ++$candidate; + } + + return $candidate; } return 1; @@ -1020,13 +1160,44 @@ class TemplateProcessor return '[Content_Types].xml'; } + /** + * Find the start position of the nearest table before $offset. + */ + private function findTableStart(int $offset): int + { + $rowStart = strrpos( + $this->tempDocumentMainPart, + 'tempDocumentMainPart) - $offset) * -1) + ); + + if (!$rowStart) { + $rowStart = strrpos( + $this->tempDocumentMainPart, + '', + ((strlen($this->tempDocumentMainPart) - $offset) * -1) + ); + } + if (!$rowStart) { + throw new Exception('Can not find the start position of the table.'); + } + + return $rowStart; + } + + /** + * Find the end position of the nearest table row after $offset. + */ + private function findTableEnd(int $offset): int + { + return strpos($this->tempDocumentMainPart, '', $offset) + 7; + } + /** * Find the start position of the nearest table row before $offset. * * @param int $offset * - * @throws \PhpOffice\PhpWord\Exception\Exception - * * @return int */ protected function findRowStart($offset) @@ -1074,7 +1245,7 @@ class TemplateProcessor /** * Replaces variable names in cloned - * rows/blocks with indexed names + * rows/blocks with indexed names. * * @param int $count * @param string $xmlBlock @@ -1083,16 +1254,19 @@ class TemplateProcessor */ protected function indexClonedVariables($count, $xmlBlock) { - $results = array(); - for ($i = 1; $i <= $count; $i++) { - $results[] = preg_replace('/\$\{(.*?)\}/', '\${\\1#' . $i . '}', $xmlBlock); + $results = []; + $escapedMacroOpeningChars = preg_quote(self::$macroOpeningChars); + $escapedMacroClosingChars = preg_quote(self::$macroClosingChars); + + for ($i = 1; $i <= $count; ++$i) { + $results[] = preg_replace("/$escapedMacroOpeningChars([^:]*?)(:.*?)?$escapedMacroClosingChars/", self::$macroOpeningChars . '\1#' . $i . '\2' . self::$macroClosingChars, $xmlBlock); } return $results; } /** - * Raplaces variables with values from array, array keys are the variable names + * Raplaces variables with values from array, array keys are the variable names. * * @param array $variableReplacements * @param string $xmlBlock @@ -1101,7 +1275,7 @@ class TemplateProcessor */ protected function replaceClonedVariables($variableReplacements, $xmlBlock) { - $results = array(); + $results = []; foreach ($variableReplacements as $replacementArray) { $localXmlBlock = $xmlBlock; foreach ($replacementArray as $search => $replacement) { @@ -1114,14 +1288,15 @@ class TemplateProcessor } /** - * Replace an XML block surrounding a macro with a new block + * Replace an XML block surrounding a macro with a new block. * * @param string $macro Name of macro * @param string $block New block content * @param string $blockType XML tag type of block + * * @return \PhpOffice\PhpWord\TemplateProcessor Fluent interface */ - protected function replaceXmlBlock($macro, $block, $blockType = 'w:p') + public function replaceXmlBlock($macro, $block, $blockType = 'w:p') { $where = $this->findContainingXmlBlockForMacro($macro, $blockType); if (is_array($where)) { @@ -1133,12 +1308,13 @@ class TemplateProcessor /** * Find start and end of XML block containing the given macro - * e.g. ...${macro}... + * e.g. ...${macro}.... * * Note that only the first instance of the macro will be found * * @param string $macro Name of macro * @param string $blockType XML tag for block + * * @return bool|int[] FALSE if not found, otherwise array with start and end */ protected function findContainingXmlBlockForMacro($macro, $blockType = 'w:p') @@ -1157,11 +1333,11 @@ class TemplateProcessor return false; } - return array('start' => $start, 'end' => $end); + return ['start' => $start, 'end' => $end]; } /** - * Find the position of (the start of) a macro + * Find the position of (the start of) a macro. * * Returns -1 if not found, otherwise position of opening $ * @@ -1169,6 +1345,7 @@ class TemplateProcessor * * @param string $search Macro name * @param int $offset Offset from which to start searching + * * @return int -1 if macro not found */ protected function findMacro($search, $offset = 0) @@ -1180,10 +1357,11 @@ class TemplateProcessor } /** - * Find the start position of the nearest XML block start before $offset + * Find the start position of the nearest XML block start before $offset. * * @param int $offset Search position * @param string $blockType XML Block tag + * * @return int -1 if block start not found */ protected function findXmlBlockStart($offset, $blockType) @@ -1201,10 +1379,11 @@ class TemplateProcessor } /** - * Find the nearest block end position after $offset + * Find the nearest block end position after $offset. * * @param int $offset Search position * @param string $blockType XML Block tag + * * @return int -1 if block end not found */ protected function findXmlBlockEnd($offset, $blockType) @@ -1216,9 +1395,10 @@ class TemplateProcessor } /** - * Splits a w:r/w:t into a list of w:r where each ${macro} is in a separate w:r + * Splits a w:r/w:t into a list of w:r where each ${macro} is in a separate w:r. * * @param string $text + * * @return string */ protected function splitTextIntoTexts($text) @@ -1226,7 +1406,7 @@ class TemplateProcessor if (!$this->textNeedsSplitting($text)) { return $text; } - $matches = array(); + $matches = []; if (preg_match('/()/i', $text, $matches)) { $extractedStyle = $matches[0]; } else { @@ -1234,19 +1414,39 @@ class TemplateProcessor } $unformattedText = preg_replace('/>\s+<', $text); - $result = str_replace(array('${', '}'), array('' . $extractedStyle . '${', '}' . $extractedStyle . ''), $unformattedText); + $result = str_replace([self::$macroOpeningChars, self::$macroClosingChars], ['' . $extractedStyle . '' . self::$macroOpeningChars, self::$macroClosingChars . '' . $extractedStyle . ''], $unformattedText); - return str_replace(array('' . $extractedStyle . '', '', ''), array('', '', ''), $result); + return str_replace(['' . $extractedStyle . '', '', ''], ['', '', ''], $result); } /** - * Returns true if string contains a macro that is not in it's own w:r + * Returns true if string contains a macro that is not in it's own w:r. * * @param string $text + * * @return bool */ protected function textNeedsSplitting($text) { - return preg_match('/[^>]\${|}[^<]/i', $text) == 1; + $escapedMacroOpeningChars = preg_quote(self::$macroOpeningChars); + $escapedMacroClosingChars = preg_quote(self::$macroClosingChars); + + return 1 === preg_match('/[^>]' . $escapedMacroOpeningChars . '|' . $escapedMacroClosingChars . '[^<]/i', $text); + } + + public function setMacroOpeningChars(string $macroOpeningChars): void + { + self::$macroOpeningChars = $macroOpeningChars; + } + + public function setMacroClosingChars(string $macroClosingChars): void + { + self::$macroClosingChars = $macroClosingChars; + } + + public function setMacroChars(string $macroOpeningChars, string $macroClosingChars): void + { + self::$macroOpeningChars = $macroOpeningChars; + self::$macroClosingChars = $macroClosingChars; } } diff --git a/PhpOffice/PhpWord/Writer/AbstractWriter.php b/PhpOffice/PhpWord/Writer/AbstractWriter.php old mode 100755 new mode 100644 index 2c1ad29..3131dba --- a/PhpOffice/PhpWord/Writer/AbstractWriter.php +++ b/PhpOffice/PhpWord/Writer/AbstractWriter.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -24,96 +24,97 @@ use PhpOffice\PhpWord\Settings; use PhpOffice\PhpWord\Shared\ZipArchive; /** - * Abstract writer class + * Abstract writer class. * * @since 0.10.0 */ abstract class AbstractWriter implements WriterInterface { /** - * PHPWord object + * PHPWord object. * * @var \PhpOffice\PhpWord\PhpWord */ - protected $phpWord = null; + protected $phpWord; /** - * Part name and file name pairs + * Part name and file name pairs. * * @var array */ - protected $parts = array(); + protected $parts = []; /** - * Individual writers + * Individual writers. * * @var array */ - protected $writerParts = array(); + protected $writerParts = []; /** - * Paths to store media files + * Paths to store media files. * * @var array */ - protected $mediaPaths = array('image' => '', 'object' => ''); + protected $mediaPaths = ['image' => '', 'object' => '']; /** - * Use disk caching + * Use disk caching. * * @var bool */ private $useDiskCaching = false; /** - * Disk caching directory + * Disk caching directory. * * @var string */ private $diskCachingDirectory = './'; /** - * Temporary directory + * Temporary directory. * * @var string */ private $tempDir = ''; /** - * Original file name + * Original file name. * * @var string */ private $originalFilename; /** - * Temporary file name + * Temporary file name. * * @var string */ private $tempFilename; /** - * Get PhpWord object + * Get PhpWord object. * - * @throws \PhpOffice\PhpWord\Exception\Exception * @return \PhpOffice\PhpWord\PhpWord */ public function getPhpWord() { - if (!is_null($this->phpWord)) { + if (null !== $this->phpWord) { return $this->phpWord; } + throw new Exception('No PhpWord assigned.'); } /** - * Set PhpWord object + * Set PhpWord object. * * @param \PhpOffice\PhpWord\PhpWord + * * @return self */ - public function setPhpWord(PhpWord $phpWord = null) + public function setPhpWord(?PhpWord $phpWord = null) { $this->phpWord = $phpWord; @@ -121,9 +122,10 @@ abstract class AbstractWriter implements WriterInterface } /** - * Get writer part + * Get writer part. * * @param string $partName Writer part name + * * @return mixed */ public function getWriterPart($partName = '') @@ -136,7 +138,7 @@ abstract class AbstractWriter implements WriterInterface } /** - * Get use disk caching status + * Get use disk caching status. * * @return bool */ @@ -146,19 +148,18 @@ abstract class AbstractWriter implements WriterInterface } /** - * Set use disk caching status + * Set use disk caching status. * * @param bool $value * @param string $directory * - * @throws \PhpOffice\PhpWord\Exception\Exception * @return self */ public function setUseDiskCaching($value = false, $directory = null) { $this->useDiskCaching = $value; - if (!is_null($directory)) { + if (null !== $directory) { if (is_dir($directory)) { $this->diskCachingDirectory = $directory; } else { @@ -170,7 +171,7 @@ abstract class AbstractWriter implements WriterInterface } /** - * Get disk caching directory + * Get disk caching directory. * * @return string */ @@ -180,7 +181,7 @@ abstract class AbstractWriter implements WriterInterface } /** - * Get temporary directory + * Get temporary directory. * * @return string */ @@ -190,9 +191,10 @@ abstract class AbstractWriter implements WriterInterface } /** - * Set temporary directory + * Set temporary directory. * * @param string $value + * * @return self */ public function setTempDir($value) @@ -206,11 +208,12 @@ abstract class AbstractWriter implements WriterInterface } /** - * Get temporary file name + * Get temporary file name. * * If $filename is php://output or php://stdout, make it a temporary file * * @param string $filename + * * @return string */ protected function getTempFile($filename) @@ -233,10 +236,8 @@ abstract class AbstractWriter implements WriterInterface /** * Cleanup temporary file. - * - * @throws \PhpOffice\PhpWord\Exception\CopyFileException */ - protected function cleanupTempFile() + protected function cleanupTempFile(): void { if ($this->originalFilename != $this->tempFilename) { // @codeCoverageIgnoreStart @@ -254,7 +255,7 @@ abstract class AbstractWriter implements WriterInterface /** * Clear temporary directory. */ - protected function clearTempDir() + protected function clearTempDir(): void { if (is_dir($this->tempDir)) { $this->deleteDir($this->tempDir); @@ -262,12 +263,10 @@ abstract class AbstractWriter implements WriterInterface } /** - * Get ZipArchive object + * Get ZipArchive object. * * @param string $filename * - * @throws \Exception - * * @return \PhpOffice\PhpWord\Shared\ZipArchive */ protected function getZipArchive($filename) @@ -293,20 +292,18 @@ abstract class AbstractWriter implements WriterInterface } /** - * Open file for writing + * Open file for writing. * * @since 0.11.0 * * @param string $filename * - * @throws \Exception - * * @return resource */ protected function openFile($filename) { $filename = $this->getTempFile($filename); - $fileHandle = fopen($filename, 'w'); + $fileHandle = fopen($filename, 'wb'); // @codeCoverageIgnoreStart // Can't find any test case. Uncomment when found. if ($fileHandle === false) { @@ -325,7 +322,7 @@ abstract class AbstractWriter implements WriterInterface * @param resource $fileHandle * @param string $content */ - protected function writeFile($fileHandle, $content) + protected function writeFile($fileHandle, $content): void { fwrite($fileHandle, $content); fclose($fileHandle); @@ -335,10 +332,9 @@ abstract class AbstractWriter implements WriterInterface /** * Add files to package. * - * @param \PhpOffice\PhpWord\Shared\ZipArchive $zip * @param mixed $elements */ - protected function addFilesToPackage(ZipArchive $zip, $elements) + protected function addFilesToPackage(ZipArchive $zip, $elements): void { foreach ($elements as $element) { $type = $element['type']; // image|object|link @@ -377,13 +373,13 @@ abstract class AbstractWriter implements WriterInterface * @param string $source * @param string $target */ - protected function addFileToPackage($zipPackage, $source, $target) + protected function addFileToPackage($zipPackage, $source, $target): void { $isArchive = strpos($source, 'zip://') !== false; $actualSource = null; if ($isArchive) { $source = substr($source, 6); - list($zipFilename, $imageFilename) = explode('#', $source); + [$zipFilename, $imageFilename] = explode('#', $source); $zip = new ZipArchive(); if ($zip->open($zipFilename) !== false) { @@ -397,7 +393,7 @@ abstract class AbstractWriter implements WriterInterface $actualSource = $source; } - if (!is_null($actualSource)) { + if (null !== $actualSource) { $zipPackage->addFile($actualSource, $target); } } @@ -407,7 +403,7 @@ abstract class AbstractWriter implements WriterInterface * * @param string $dir */ - private function deleteDir($dir) + private function deleteDir($dir): void { foreach (scandir($dir) as $file) { if ($file === '.' || $file === '..') { @@ -421,16 +417,4 @@ abstract class AbstractWriter implements WriterInterface rmdir($dir); } - - /** - * Get use disk caching status - * - * @deprecated 0.10.0 - * - * @codeCoverageIgnore - */ - public function getUseDiskCaching() - { - return $this->isUseDiskCaching(); - } } diff --git a/PhpOffice/PhpWord/Writer/HTML.php b/PhpOffice/PhpWord/Writer/HTML.php old mode 100755 new mode 100644 index 7f55b9d..ea0d654 --- a/PhpOffice/PhpWord/Writer/HTML.php +++ b/PhpOffice/PhpWord/Writer/HTML.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -20,9 +20,10 @@ namespace PhpOffice\PhpWord\Writer; use PhpOffice\PhpWord\PhpWord; /** - * HTML writer + * HTML writer. * * Not supported: PreserveText, PageBreak, Object + * * @since 0.10.0 */ class HTML extends AbstractWriter implements WriterInterface @@ -35,20 +36,20 @@ class HTML extends AbstractWriter implements WriterInterface protected $isPdf = false; /** - * Footnotes and endnotes collection + * Footnotes and endnotes collection. * * @var array */ - protected $notes = array(); + protected $notes = []; /** - * Create new instance + * Create new instance. */ - public function __construct(PhpWord $phpWord = null) + public function __construct(?PhpWord $phpWord = null) { $this->setPhpWord($phpWord); - $this->parts = array('Head', 'Body'); + $this->parts = ['Head', 'Body']; foreach ($this->parts as $partName) { $partClass = 'PhpOffice\\PhpWord\\Writer\\HTML\\Part\\' . $partName; if (class_exists($partClass)) { @@ -64,18 +65,17 @@ class HTML extends AbstractWriter implements WriterInterface * Save PhpWord to file. * * @param string $filename - * - * @throws \PhpOffice\PhpWord\Exception\Exception */ - public function save($filename = null) + public function save($filename = null): void { $this->writeFile($this->openFile($filename), $this->getContent()); } /** - * Get content + * Get content. * * @return string + * * @since 0.11.0 */ public function getContent() @@ -93,7 +93,7 @@ class HTML extends AbstractWriter implements WriterInterface } /** - * Get is PDF + * Get is PDF. * * @return bool */ @@ -103,7 +103,7 @@ class HTML extends AbstractWriter implements WriterInterface } /** - * Get notes + * Get notes. * * @return array */ @@ -118,22 +118,8 @@ class HTML extends AbstractWriter implements WriterInterface * @param int $noteId * @param string $noteMark */ - public function addNote($noteId, $noteMark) + public function addNote($noteId, $noteMark): void { $this->notes[$noteId] = $noteMark; } - - /** - * Write document - * - * @deprecated 0.11.0 - * - * @return string - * - * @codeCoverageIgnore - */ - public function writeDocument() - { - return $this->getContent(); - } } diff --git a/PhpOffice/PhpWord/Writer/HTML/Element/AbstractElement.php b/PhpOffice/PhpWord/Writer/HTML/Element/AbstractElement.php old mode 100755 new mode 100644 index dc5ccfa..f5b0e91 --- a/PhpOffice/PhpWord/Writer/HTML/Element/AbstractElement.php +++ b/PhpOffice/PhpWord/Writer/HTML/Element/AbstractElement.php @@ -11,59 +11,57 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Writer\HTML\Element; +use Laminas\Escaper\Escaper; use PhpOffice\PhpWord\Element\AbstractElement as Element; use PhpOffice\PhpWord\Writer\AbstractWriter; -use Zend\Escaper\Escaper; /** - * Abstract HTML element writer + * Abstract HTML element writer. * * @since 0.11.0 */ abstract class AbstractElement { /** - * Parent writer + * Parent writer. * * @var \PhpOffice\PhpWord\Writer\AbstractWriter */ protected $parentWriter; /** - * Element + * Element. * * @var \PhpOffice\PhpWord\Element\AbstractElement */ protected $element; /** - * Without paragraph + * Without paragraph. * * @var bool */ protected $withoutP = false; /** - * @var \Zend\Escaper\Escaper|\PhpOffice\PhpWord\Escaper\AbstractEscaper + * @var \Laminas\Escaper\Escaper|\PhpOffice\PhpWord\Escaper\AbstractEscaper */ protected $escaper; /** - * Write element + * Write element. */ abstract public function write(); /** - * Create new instance + * Create new instance. * - * @param \PhpOffice\PhpWord\Writer\AbstractWriter $parentWriter - * @param \PhpOffice\PhpWord\Element\AbstractElement $element * @param bool $withoutP */ public function __construct(AbstractWriter $parentWriter, Element $element, $withoutP = false) @@ -79,7 +77,7 @@ abstract class AbstractElement * * @param bool $value */ - public function setWithoutP($value) + public function setWithoutP($value): void { $this->withoutP = $value; } diff --git a/PhpOffice/PhpWord/Writer/HTML/Element/Bookmark.php b/PhpOffice/PhpWord/Writer/HTML/Element/Bookmark.php old mode 100755 new mode 100644 index 082bd76..521a73d --- a/PhpOffice/PhpWord/Writer/HTML/Element/Bookmark.php +++ b/PhpOffice/PhpWord/Writer/HTML/Element/Bookmark.php @@ -11,21 +11,21 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Writer\HTML\Element; /** - * Bookmark element HTML writer + * Bookmark element HTML writer. * * @since 0.15.0 */ class Bookmark extends Text { /** - * Write bookmark + * Write bookmark. * * @return string */ diff --git a/PhpOffice/PhpWord/Writer/HTML/Element/Container.php b/PhpOffice/PhpWord/Writer/HTML/Element/Container.php old mode 100755 new mode 100644 index 006b588..7909e73 --- a/PhpOffice/PhpWord/Writer/HTML/Element/Container.php +++ b/PhpOffice/PhpWord/Writer/HTML/Element/Container.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -20,21 +20,21 @@ namespace PhpOffice\PhpWord\Writer\HTML\Element; use PhpOffice\PhpWord\Element\AbstractContainer as ContainerElement; /** - * Container element HTML writer + * Container element HTML writer. * * @since 0.11.0 */ class Container extends AbstractElement { /** - * Namespace; Can't use __NAMESPACE__ in inherited class (RTF) + * Namespace; Can't use __NAMESPACE__ in inherited class (RTF). * * @var string */ protected $namespace = 'PhpOffice\\PhpWord\\Writer\\HTML\\Element'; /** - * Write container + * Write container. * * @return string */ @@ -45,7 +45,7 @@ class Container extends AbstractElement return ''; } $containerClass = substr(get_class($container), strrpos(get_class($container), '\\') + 1); - $withoutP = in_array($containerClass, array('TextRun', 'Footnote', 'Endnote')) ? true : false; + $withoutP = in_array($containerClass, ['TextRun', 'Footnote', 'Endnote']) ? true : false; $content = ''; $elements = $container->getElements(); diff --git a/PhpOffice/PhpWord/Writer/HTML/Element/Endnote.php b/PhpOffice/PhpWord/Writer/HTML/Element/Endnote.php old mode 100755 new mode 100644 index 2252dc3..1c35e8f --- a/PhpOffice/PhpWord/Writer/HTML/Element/Endnote.php +++ b/PhpOffice/PhpWord/Writer/HTML/Element/Endnote.php @@ -11,21 +11,21 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Writer\HTML\Element; /** - * Endnote element HTML writer + * Endnote element HTML writer. * * @since 0.10.0 */ class Endnote extends Footnote { /** - * Note type + * Note type. * * @var string */ diff --git a/PhpOffice/PhpWord/Writer/HTML/Element/Footnote.php b/PhpOffice/PhpWord/Writer/HTML/Element/Footnote.php old mode 100755 new mode 100644 index ed14db1..0cb2ca5 --- a/PhpOffice/PhpWord/Writer/HTML/Element/Footnote.php +++ b/PhpOffice/PhpWord/Writer/HTML/Element/Footnote.php @@ -11,28 +11,28 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Writer\HTML\Element; /** - * Footnote element HTML writer + * Footnote element HTML writer. * * @since 0.10.0 */ class Footnote extends AbstractElement { /** - * Note type footnote|endnote + * Note type footnote|endnote. * * @var string */ protected $noteType = 'footnote'; /** - * Write footnote/endnote marks; The actual content is written in parent writer (HTML) + * Write footnote/endnote marks; The actual content is written in parent writer (HTML). * * @return string */ diff --git a/PhpOffice/PhpWord/Writer/HTML/Element/Image.php b/PhpOffice/PhpWord/Writer/HTML/Element/Image.php old mode 100755 new mode 100644 index 7c22a16..40e864e --- a/PhpOffice/PhpWord/Writer/HTML/Element/Image.php +++ b/PhpOffice/PhpWord/Writer/HTML/Element/Image.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -21,14 +21,14 @@ use PhpOffice\PhpWord\Element\Image as ImageElement; use PhpOffice\PhpWord\Writer\HTML\Style\Image as ImageStyleWriter; /** - * Image element HTML writer + * Image element HTML writer. * * @since 0.10.0 */ class Image extends Text { /** - * Write image + * Write image. * * @return string */ diff --git a/PhpOffice/PhpWord/Writer/HTML/Element/Link.php b/PhpOffice/PhpWord/Writer/HTML/Element/Link.php old mode 100755 new mode 100644 index f6dae5c..7d302c1 --- a/PhpOffice/PhpWord/Writer/HTML/Element/Link.php +++ b/PhpOffice/PhpWord/Writer/HTML/Element/Link.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -20,14 +20,14 @@ namespace PhpOffice\PhpWord\Writer\HTML\Element; use PhpOffice\PhpWord\Settings; /** - * Link element HTML writer + * Link element HTML writer. * * @since 0.10.0 */ class Link extends Text { /** - * Write link + * Write link. * * @return string */ diff --git a/PhpOffice/PhpWord/Writer/HTML/Element/ListItem.php b/PhpOffice/PhpWord/Writer/HTML/Element/ListItem.php old mode 100755 new mode 100644 index 384b3ef..d047986 --- a/PhpOffice/PhpWord/Writer/HTML/Element/ListItem.php +++ b/PhpOffice/PhpWord/Writer/HTML/Element/ListItem.php @@ -11,7 +11,7 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ @@ -20,14 +20,14 @@ namespace PhpOffice\PhpWord\Writer\HTML\Element; use PhpOffice\PhpWord\Settings; /** - * ListItem element HTML writer + * ListItem element HTML writer. * * @since 0.10.0 */ class ListItem extends AbstractElement { /** - * Write list item + * Write list item. * * @return string */ diff --git a/PhpOffice/PhpWord/Writer/HTML/Element/ListItemRun.php b/PhpOffice/PhpWord/Writer/HTML/Element/ListItemRun.php old mode 100755 new mode 100644 index a4d7e46..5bbe23f --- a/PhpOffice/PhpWord/Writer/HTML/Element/ListItemRun.php +++ b/PhpOffice/PhpWord/Writer/HTML/Element/ListItemRun.php @@ -11,21 +11,21 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Writer\HTML\Element; /** - * ListItem element HTML writer + * ListItem element HTML writer. * * @since 0.10.0 */ class ListItemRun extends TextRun { /** - * Write list item + * Write list item. * * @return string */ diff --git a/PhpOffice/PhpWord/Writer/HTML/Element/PageBreak.php b/PhpOffice/PhpWord/Writer/HTML/Element/PageBreak.php old mode 100755 new mode 100644 index f9998e3..762426b --- a/PhpOffice/PhpWord/Writer/HTML/Element/PageBreak.php +++ b/PhpOffice/PhpWord/Writer/HTML/Element/PageBreak.php @@ -11,21 +11,21 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Writer\HTML\Element; /** - * PageBreak element HTML writer + * PageBreak element HTML writer. * * @since 0.10.0 */ class PageBreak extends TextBreak { /** - * Write page break + * Write page break. * * @since 0.12.0 * diff --git a/PhpOffice/PhpWord/Writer/HTML/Element/Table.php b/PhpOffice/PhpWord/Writer/HTML/Element/Table.php old mode 100755 new mode 100644 index a6f1479..b1a2ee9 --- a/PhpOffice/PhpWord/Writer/HTML/Element/Table.php +++ b/PhpOffice/PhpWord/Writer/HTML/Element/Table.php @@ -11,21 +11,21 @@ * contributors, visit https://github.com/PHPOffice/PHPWord/contributors. * * @see https://github.com/PHPOffice/PHPWord - * @copyright 2010-2018 PHPWord contributors + * * @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3 */ namespace PhpOffice\PhpWord\Writer\HTML\Element; /** - * Table element HTML writer + * Table element HTML writer. * * @since 0.10.0 */ class Table extends AbstractElement { /** - * Write table + * Write table. * * @return string */ @@ -41,17 +41,18 @@ class Table extends AbstractElement if ($rowCount > 0) { $content .= 'element->getStyle()) . '>' . PHP_EOL; - for ($i = 0; $i < $rowCount; $i++) { - /** @var $row \PhpOffice\PhpWord\Element\Row Type hint */ + for ($i = 0; $i < $rowCount; ++$i) { + /** @var \PhpOffice\PhpWord\Element\Row $row Type hint */ $rowStyle = $rows[$i]->getStyle(); // $height = $row->getHeight(); $tblHeader = $rowStyle->isTblHeader(); $content .= '